feat: 开发对话功能
This commit is contained in:
@@ -1,20 +1,21 @@
|
||||
import { getAction, postAction, deleteAction } from './manage.js'
|
||||
import url, { getBaseUrl } from './url.js'
|
||||
import { getAction, postAction, deleteAction } from './manage.js';
|
||||
import url, { getBaseUrl } from './url.js';
|
||||
|
||||
// 健康检查
|
||||
export const getHealthAction = () => getAction(url.health)
|
||||
export const getHealthAction = () => getAction(url.health);
|
||||
|
||||
// 会话
|
||||
export const createSessionAction = (data) => postAction(url.session.create, data)
|
||||
export const getSessionAction = (id) => getAction(url.session.detail(id))
|
||||
export const listSessionsAction = () => getAction(url.session.list)
|
||||
export const deleteSessionAction = (id) => deleteAction(url.session.delete(id))
|
||||
export const createSessionAction = (data) => postAction(url.session.create, data);
|
||||
export const getSessionAction = (id) => getAction(url.session.detail(id));
|
||||
export const listSessionsAction = () => getAction(url.session.list);
|
||||
export const deleteSessionAction = (id) => deleteAction(url.session.delete(id));
|
||||
|
||||
// 消息
|
||||
export const sendMessageAction = (sessionId, data) => postAction(url.message.send(sessionId), data)
|
||||
export const listMessagesAction = (sessionId) => getAction(url.message.list(sessionId))
|
||||
export const sendMessageAction = (sessionId, data) => postAction(url.message.send(sessionId), data);
|
||||
export const promptAsyncAction = (sessionId, data) => postAction(url.message.promptAsync(sessionId), data);
|
||||
export const listMessagesAction = (sessionId) => getAction(url.message.list(sessionId));
|
||||
|
||||
// SSE 事件流(返回 EventSource 实例,由调用方管理生命周期)
|
||||
export function createEventSource() {
|
||||
return new EventSource(`${getBaseUrl()}${url.event}`)
|
||||
return new EventSource(`${getBaseUrl()}${url.event}`);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getBaseUrl } from './url.js'
|
||||
import axios from 'axios';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { getBaseUrl } from './url.js';
|
||||
|
||||
// baseURL 由主进程动态分配端口,通过 getBaseUrl() 运行时获取
|
||||
const axiosInstance = axios.create({
|
||||
@@ -10,131 +10,142 @@ const axiosInstance = axios.create({
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
},
|
||||
responseType: 'json',
|
||||
})
|
||||
});
|
||||
|
||||
// 每次请求前动态更新 baseURL,确保服务启动后端口变更能被感知
|
||||
axiosInstance.interceptors.request.use((config) => {
|
||||
config.baseURL = getBaseUrl();
|
||||
return config;
|
||||
});
|
||||
|
||||
// 请求拦截
|
||||
axiosInstance.interceptors.request.use((config) => {
|
||||
config.headers = config.headers || {}
|
||||
let Authorization = localStorage.getItem('Authorization')
|
||||
config.headers = config.headers || {};
|
||||
let Authorization = localStorage.getItem('Authorization');
|
||||
// 优先使用本地持久化的 Authorization 头(完整值)
|
||||
config.headers.Authorization = Authorization || ''
|
||||
config.headers.Authorization = Authorization || '';
|
||||
|
||||
if ('get' === config?.method?.toLowerCase()) {
|
||||
if (config.params) {
|
||||
config.params.timestamp = new Date().getTime()
|
||||
config.params.timestamp = new Date().getTime();
|
||||
}
|
||||
}
|
||||
// 移除敏感信息日志
|
||||
// console.log(config, 'axios request.use config')
|
||||
return config
|
||||
})
|
||||
return config;
|
||||
});
|
||||
axiosInstance.interceptors.response.use(
|
||||
(response) => {
|
||||
// 移除敏感信息日志
|
||||
// console.log(response, 'response response')
|
||||
// 若请求为二进制下载(blob),直接透传响应,交由调用方自行处理
|
||||
try {
|
||||
const isBlob = response?.config?.responseType === 'blob'
|
||||
const isBlob = response?.config?.responseType === 'blob';
|
||||
if (isBlob) {
|
||||
// 仍然尝试持久化可能返回的 Authorization
|
||||
const respHeaders = response?.headers || {}
|
||||
const newAuthorization = respHeaders['authorization'] || respHeaders['Authorization']
|
||||
const respHeaders = response?.headers || {};
|
||||
const newAuthorization = respHeaders['authorization'] || respHeaders['Authorization'];
|
||||
if (newAuthorization && typeof newAuthorization === 'string') {
|
||||
localStorage.setItem('Authorization', newAuthorization)
|
||||
localStorage.setItem('Authorization', newAuthorization);
|
||||
}
|
||||
return response
|
||||
return response;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
console.log(e);
|
||||
// 忽略检查失败
|
||||
}
|
||||
// 如果响应头里带有 Authorization,则使用 useStorage 持久化到 localStorage,
|
||||
// 以便后续请求自动携带该请求头
|
||||
try {
|
||||
const respHeaders = response?.headers || {}
|
||||
const newAuthorization = respHeaders['authorization'] || respHeaders['Authorization']
|
||||
const respHeaders = response?.headers || {};
|
||||
const newAuthorization = respHeaders['authorization'] || respHeaders['Authorization'];
|
||||
if (newAuthorization && typeof newAuthorization === 'string') {
|
||||
localStorage.setItem('Authorization', newAuthorization)
|
||||
localStorage.setItem('Authorization', newAuthorization);
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略持久化失败,避免影响主流程
|
||||
console.warn('持久化 Authorization 失败:', e)
|
||||
console.warn('持久化 Authorization 失败:', e);
|
||||
}
|
||||
// 204 No Content(如 prompt_async)直接视为成功
|
||||
if (response.status === 204) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
if (response.status === 200) {
|
||||
const res = response.data || {}
|
||||
const code = res.code
|
||||
const msg = res.message || res.msg
|
||||
const res = response.data || {};
|
||||
const code = res.code;
|
||||
const msg = res.message || res.msg;
|
||||
|
||||
// 明确的 200 成功,但需要按业务码再判断
|
||||
if (code === 0) {
|
||||
// 业务成功
|
||||
return Promise.resolve(res)
|
||||
return Promise.resolve(res);
|
||||
}
|
||||
|
||||
// 特殊业务码处理
|
||||
if (code === 401) {
|
||||
// 清除持久化的 Authorization,避免后续使用失效的头部
|
||||
localStorage.removeItem('Authorization')
|
||||
sessionStorage.removeItem('Token')
|
||||
localStorage.removeItem('Authorization');
|
||||
sessionStorage.removeItem('Token');
|
||||
// 延迟跳转,确保消息显示
|
||||
setTimeout(() => {
|
||||
window.location.href = '/#/login'
|
||||
}, 500)
|
||||
return Promise.reject(new Error('认证失败,请重新登录'))
|
||||
window.location.href = '/#/login';
|
||||
}, 500);
|
||||
return Promise.reject(new Error('认证失败,请重新登录'));
|
||||
}
|
||||
|
||||
// 其余非 0 的业务码统一拦截提示,但不在这里显示 ElMessage
|
||||
// 交由业务层使用 await-to-js 处理
|
||||
return Promise.reject(new Error(msg || '请求失败'))
|
||||
return Promise.reject(new Error(msg || '请求失败'));
|
||||
}
|
||||
|
||||
// 非 2xx 按错误分支处理(通常会进入 error 拦截器)
|
||||
return Promise.reject(new Error('请求失败'))
|
||||
return Promise.reject(new Error('请求失败'));
|
||||
},
|
||||
(error) => {
|
||||
console.error('请求错误:', error)
|
||||
console.error('请求错误:', error);
|
||||
|
||||
if (error.response) {
|
||||
// 服务器响应错误
|
||||
const status = error.response.status
|
||||
const message = error.response.data?.message || error.response.data?.msg || '请求失败'
|
||||
const status = error.response.status;
|
||||
const message = error.response.data?.message || error.response.data?.msg || '请求失败';
|
||||
|
||||
switch (status) {
|
||||
case 400:
|
||||
ElMessage.error(`无效的请求参数:${message}`)
|
||||
break
|
||||
ElMessage.error(`无效的请求参数:${message}`);
|
||||
break;
|
||||
case 401:
|
||||
// 清除持久化的 Authorization,避免后续使用失效的头部
|
||||
localStorage.removeItem('Authorization')
|
||||
ElMessage.error('未授权访问或登录已过期,请重新登录')
|
||||
break
|
||||
localStorage.removeItem('Authorization');
|
||||
ElMessage.error('未授权访问或登录已过期,请重新登录');
|
||||
break;
|
||||
case 403:
|
||||
ElMessage.error('访问被拒绝')
|
||||
break
|
||||
ElMessage.error('访问被拒绝');
|
||||
break;
|
||||
case 404:
|
||||
ElMessage.error('资源未找到')
|
||||
break
|
||||
ElMessage.error('资源未找到');
|
||||
break;
|
||||
case 500:
|
||||
ElMessage.error('服务器内部错误')
|
||||
break
|
||||
ElMessage.error('服务器内部错误');
|
||||
break;
|
||||
case 502:
|
||||
case 503:
|
||||
case 504:
|
||||
ElMessage.error('服务暂时不可用,请稍后重试')
|
||||
break
|
||||
ElMessage.error('服务暂时不可用,请稍后重试');
|
||||
break;
|
||||
default:
|
||||
ElMessage.error(`请求失败: ${message}`)
|
||||
ElMessage.error(`请求失败: ${message}`);
|
||||
}
|
||||
} else if (error.request) {
|
||||
// 网络错误
|
||||
ElMessage.error('网络连接失败,请检查网络连接')
|
||||
ElMessage.error('网络连接失败,请检查网络连接');
|
||||
} else {
|
||||
// 其他错误
|
||||
ElMessage.error('请求发送失败')
|
||||
ElMessage.error('请求发送失败');
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default axiosInstance
|
||||
export default axiosInstance;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// OpenCode 服务地址由主进程动态分配端口,通过 getBaseUrl() 获取
|
||||
export function getBaseUrl() {
|
||||
return window.__opencodeBaseUrl || 'http://127.0.0.1:4096'
|
||||
return window.__opencodeBaseUrl || 'http://127.0.0.1:4096';
|
||||
}
|
||||
|
||||
const url = {
|
||||
@@ -18,11 +18,12 @@ const url = {
|
||||
// 消息
|
||||
message: {
|
||||
send: (sessionId) => `/session/${sessionId}/message`,
|
||||
promptAsync: (sessionId) => `/session/${sessionId}/prompt_async`,
|
||||
list: (sessionId) => `/session/${sessionId}/message`,
|
||||
},
|
||||
|
||||
// SSE 事件流
|
||||
event: '/event',
|
||||
}
|
||||
};
|
||||
|
||||
export default url
|
||||
export default url;
|
||||
|
||||
Reference in New Issue
Block a user