添加ai请求地址

This commit is contained in:
2026-09-01 11:03:27 +08:00
parent a4c7adb317
commit 3d6525e3d4
7 changed files with 287 additions and 11 deletions
+18 -7
View File
@@ -8,6 +8,7 @@
*/
/** ai-server 统一响应 */
import { request } from '../request/ai';
interface AiResp<T = any> {
code: number;
message: string;
@@ -16,9 +17,7 @@ interface AiResp<T = any> {
export class AiError extends Error {
code: number;
data: any;
constructor(code: number, message: string, data?: any) {
super(message || `AI 服务错误(${code}`);
this.code = code;
@@ -182,12 +181,24 @@ export interface RecognizeResult {
materials_suggest: string[];
}
export const aiRecognize = (content: string, deviceTags: string[] = []) =>
aiFetch<RecognizeResult>('/open/v1/moc/change/recognize', {
request_id: nextRequestId('rec'),
content,
device_tags: deviceTags
export function aiRecognize(content: string, deviceTags: string[] = []){
return request({
url: '/open/v1/moc/change/recognize',
method: 'get',
params: {
request_id: nextRequestId('rec'),
content,
device_tags: deviceTags
}
});
}
// aiFetch<RecognizeResult>('/open/v1/moc/change/recognize', {
// request_id: nextRequestId('rec'),
// content,
// device_tags: deviceTags
// });
export interface ApplicationResult {
name: string;
+68
View File
@@ -0,0 +1,68 @@
import { request } from '../request';
/** 变更预识别查询
*
*/
export function precheckApi(id: number) {
return request({
url: `/api/changes/${id}/precheck`,
method: 'get',
});
}
/**
* 变更预识别保存
*
*/
export function precheckSaveApi(params: {
change_id: number,
title: string,
description: string,
severity: string,
probability: string,
protection: string,
compare_rows: {item: string, before_text: string, after_text: string}[]
}) {
return request({
url: '/api/changes/precheck',
method: 'post',
data: params
});
}
/** 变更申请表查询
*
*/
export function applicationApi(id: number) {
return request({
url: `/api/changes/${id}/apply-form`,
method: 'get',
});
}
/**
* 变更申请表保存
*
*/
export function applicationSaveApi(id: number, params: {
change_type: number,
change_level: number,
duration_type: number,
restore_deadline: string,
org_id: number,
urgent: number,
purposes: string[],
effect: string,
main_changes: string[],
related_changes: string[],
materials: string,
update_docs: string[],
disciplines: string[],
manage_dept: string,
plan_use_date: string,
risk_tools: string[],
}) {
return request({
url: `/api/changes/${id}/apply-form`,
method: 'post',
data: params
});
}
+124
View File
@@ -0,0 +1,124 @@
import type { AxiosResponse } from 'axios';
import { BACKEND_ERROR_CODE, createFlatRequest } from '@sa/axios';
import { useAuthStore } from '@/store/modules/auth';
import { getServiceAiURL } from '@/utils/service';
import { $t } from '@/locales';
import { getAiAuthorization, handleExpiredRequest, showErrorMsg } from './shared';
import type { RequestInstanceState } from './type';
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
const { baseURL } = getServiceAiURL(import.meta.env, isHttpProxy);
export const request = createFlatRequest(
{
baseURL,
headers: {}
},
{
defaultState: {
errMsgStack: [],
refreshTokenPromise: null
} as RequestInstanceState,
transform(response: AxiosResponse<App.Service.Response<any>>) {
return response.data.data;
},
async onRequest(config) {
const Authorization = getAiAuthorization();
Object.assign(config.headers, { Authorization });
return config;
},
isBackendSuccess(response) {
// when the backend response code is "0000"(default), it means the request is success
// to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file
return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
},
async onBackendFail(response, instance) {
const authStore = useAuthStore();
const responseCode = String(response.data.code);
function handleLogout() {
authStore.resetStore();
}
function logoutAndCleanup() {
handleLogout();
window.removeEventListener('beforeunload', handleLogout);
request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== response.data.msg);
}
// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
if (logoutCodes.includes(responseCode)) {
handleLogout();
return null;
}
// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(response.data.msg)) {
request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg];
// prevent the user from refreshing the page
window.addEventListener('beforeunload', handleLogout);
window.$dialog?.error({
title: $t('common.error'),
content: response.data.msg,
positiveText: $t('common.confirm'),
maskClosable: false,
closeOnEsc: false,
onPositiveClick() {
logoutAndCleanup();
},
onClose() {
logoutAndCleanup();
}
});
return null;
}
// when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token
// the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes`
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
if (expiredTokenCodes.includes(responseCode)) {
const success = await handleExpiredRequest(request.state);
if (success) {
const Authorization = getAuthorization();
Object.assign(response.config.headers, { Authorization });
return instance.request(response.config) as Promise<AxiosResponse>;
}
}
return null;
},
onError(error) {
// when the request is fail, you can show error message
let message = error.message;
let backendErrorCode = '';
// get backend error message and code
if (error.code === BACKEND_ERROR_CODE) {
message = error.response?.data?.message || message;
backendErrorCode = String(error.response?.data?.code || '');
}
// the error message is displayed in the modal
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
if (modalLogoutCodes.includes(backendErrorCode)) {
return;
}
// when the token is expired, refresh token and retry request, so no need to show error message
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
if (expiredTokenCodes.includes(backendErrorCode)) {
return;
}
showErrorMsg(request.state, message);
}
}
);
+1 -3
View File
@@ -12,9 +12,7 @@ const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
export const request = createFlatRequest(
{
baseURL,
headers: {
apifoxToken: 'XL299LiMEDZ0H5h3A29PxwQXdMJqWyY2'
}
headers: {}
},
{
defaultState: {
+8
View File
@@ -3,12 +3,20 @@ import { localStg } from '@/utils/storage';
import { fetchRefreshToken } from '../api';
import type { RequestInstanceState } from './type';
// 获取token
export function getAuthorization() {
const token = localStg.get('token');
const Authorization = token ? `Bearer ${token}` : null;
return Authorization;
}
// 获取ai token
export function getAiAuthorization() {
const token = localStg.get('aiToken');
const Authorization = token ? `Bearer ${token}` : null;
return Authorization;
}
/** refresh token */
async function handleRefreshToken() {
+54
View File
@@ -45,6 +45,46 @@ export function createServiceConfig(env: Env.ImportMeta) {
return config;
}
let aiUrl = '';
export function createAiServiceConfig(env: Env.ImportMeta) {
const { VITE_SERVICE_BASE_URL, VITE_OTHER_SERVICE_BASE_URL } = env;
let other = {} as Record<App.Service.OtherBaseURLKey, string>;
try {
other = json5.parse(VITE_OTHER_SERVICE_BASE_URL);
} catch {
// eslint-disable-next-line no-console
console.error('VITE_OTHER_SERVICE_BASE_URL is not a valid json5 string');
}
if(env.DEV){
aiUrl = 'http://192.168.0.230:8085/proxy-ai';
}else{
// url = window.location.origin;
aiUrl = '/api';
}
const httpConfig: App.Service.SimpleServiceConfig = {
baseURL: aiUrl,
other
};
const otherHttpKeys = Object.keys(httpConfig.other) as App.Service.OtherBaseURLKey[];
const otherConfig: App.Service.OtherServiceConfigItem[] = otherHttpKeys.map(key => {
return {
key,
baseURL: httpConfig.other[key],
proxyPattern: createProxyPattern(key)
};
});
const config: App.Service.ServiceConfig = {
baseURL: httpConfig.baseURL,
proxyPattern: createProxyPattern(),
other: otherConfig
};
return config;
}
export function getBaseUrl() {
return url;
}
@@ -69,6 +109,20 @@ export function getServiceBaseURL(env: Env.ImportMeta, isProxy: boolean) {
otherBaseURL
};
}
export function getServiceAiURL(env: Env.ImportMeta, isProxy: boolean) {
const { baseURL, other } = createAiServiceConfig(env);
const otherBaseURL = {} as Record<App.Service.OtherBaseURLKey, string>;
other.forEach(item => {
otherBaseURL[item.key] = isProxy ? item.proxyPattern : item.baseURL;
});
return {
baseURL: isProxy ? createProxyPattern() : baseURL,
otherBaseURL
};
}
/**
* Get proxy pattern of backend service base url
+14 -1
View File
@@ -695,6 +695,19 @@ const onBack = () => {
}
const currentId = ref<number>(0);
// tab 切换
const tabChange = (id: string) => {
if(form.value.name.trim() === '') {
return window.$message?.warning('请先填写变更名称');
}
if(desc.value.trim() === '') {
return window.$message?.warning('请先填写变更内容描述');
}
tab.value = id;
}
</script>
<template>
@@ -719,7 +732,7 @@ const onBack = () => {
<NInput value="MOC-2026-R01-0041" disabled class="!w-44" />
</div>
<div class="flex items-center gap-1 overflow-x-auto px-3 pt-2">
<div v-for="t in TABS" :key="t.id" @click="tab = t.id"
<div v-for="t in TABS" :key="t.id" @click="tabChange(t.id)"
class="flex items-center gap-1.5 whitespace-nowrap px-3 py-2 text-sm transition cursor-pointer border hover:bg-white/60 rounded-t-lg"
:class="tab === t.id ? 'border-b-transparent border-[var(--border-color)] bg-white text-[var(--active-color)]':'border-transparent text-slate-500'"
:style="{