Skip to content

Commit a17cea5

Browse files
committed
feat(deepresearch): fix conflict
2 parents 2e3b3fd + cdfaffe commit a17cea5

File tree

4 files changed

+201
-32
lines changed

4 files changed

+201
-32
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* API 响应基础类型定义
3+
* 对应 Java 类:ApiResponse<T>
4+
*/
5+
export interface ApiResponse<T = any> {
6+
/** 响应状态码 */
7+
code: number;
8+
/** 响应状态 */
9+
status: string;
10+
/** 响应消息 */
11+
message: string;
12+
/** 响应数据 */
13+
data: T;
14+
}
15+
16+
/**
17+
* 创建成功响应的工具函数
18+
* @param data 响应数据
19+
* @returns 成功的 API 响应
20+
*/
21+
export function createSuccessResponse<T>(data: T): ApiResponse<T> {
22+
return {
23+
code: 200,
24+
status: 'success',
25+
message: '',
26+
data
27+
};
28+
}
29+
30+
/**
31+
* 创建错误响应的工具函数
32+
* @param message 错误消息
33+
* @param data 可选的错误数据
34+
* @returns 错误的 API 响应
35+
*/
36+
export function createErrorResponse<T = null>(message: string, data: T = null as T): ApiResponse<T> {
37+
return {
38+
code: 500,
39+
status: 'error',
40+
message,
41+
data
42+
};
43+
}
44+
45+
/**
46+
* 类型守卫:检查响应是否为成功响应
47+
* @param response API 响应
48+
* @returns 是否为成功响应
49+
*/
50+
export function isSuccessResponse<T>(response: ApiResponse<T>): boolean {
51+
return response.code === 200 && response.status === 'success';
52+
}
53+
54+
/**
55+
* 类型守卫:检查响应是否为错误响应
56+
* @param response API 响应
57+
* @returns 是否为错误响应
58+
*/
59+
export function isErrorResponse<T>(response: ApiResponse<T>): boolean {
60+
return response.code !== 200 || response.status === 'error';
61+
}

spring-ai-alibaba-deepresearch/ui-vue3/src/types/node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export interface NormalNode {
2525
/** 节点名称 */
2626
nodeName: string
2727
/** 节点内容 */
28-
content: string | object ,
28+
content: string | any ,
2929
graphId: GraphId
3030
displayTitle: string,
3131
siteInformation: SiteInformation[]

spring-ai-alibaba-deepresearch/ui-vue3/src/utils/request.ts

Lines changed: 104 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import type {
2222
InternalAxiosRequestConfig,
2323
} from 'axios'
2424
import axios from 'axios'
25-
import { message } from 'ant-design-vue';
25+
import { message } from 'ant-design-vue'
26+
import type { ApiResponse } from '@/types/base'
27+
import { isSuccessResponse } from '@/types/base'
2628

2729
const service: AxiosInstance = axios.create({
2830
baseURL: import.meta.env.VITE_BASE_URL || '',
@@ -53,23 +55,112 @@ request.use(
5355
}
5456
)
5557

58+
/**
59+
* 响应拦截器封装
60+
* 处理 ApiResponse 类型的响应数据
61+
*/
5662
response.use(
57-
response => {
58-
if (
59-
response.status === 200 &&
60-
(response.data.code === 200 || response.data.status === 'success')
61-
) {
62-
return Promise.resolve(response.data)
63+
(response: AxiosResponse<ApiResponse>) => {
64+
const apiResponse: ApiResponse = response.data
65+
66+
// 使用类型守卫检查响应是否成功
67+
if (response.status === 200 && isSuccessResponse(apiResponse)) {
68+
// 返回原始响应对象,保持 axios 响应结构
69+
return response
70+
}
71+
72+
// 显示错误消息
73+
if (apiResponse.message) {
74+
message.error(apiResponse.message)
6375
}
64-
message.error(response.data.message)
65-
return Promise.reject(response.data)
76+
return Promise.reject(apiResponse)
6677
},
6778
error => {
68-
if (error) {
69-
console.error('error', error)
79+
console.error('Request error:', error)
80+
81+
// 处理网络错误或其他错误
82+
if (error.response?.data) {
83+
const errorResponse: ApiResponse = error.response.data
84+
if (errorResponse.message) {
85+
message.error(errorResponse.message)
86+
}
87+
return Promise.reject(errorResponse)
7088
}
71-
message.error(error.response.data.message)
72-
return Promise.reject(error.response.data)
89+
90+
// 处理网络错误等情况
91+
const networkError: ApiResponse = {
92+
code: 500,
93+
status: 'error',
94+
message: error.message || '网络请求失败',
95+
data: null
96+
}
97+
message.error(networkError.message)
98+
return Promise.reject(networkError)
7399
}
74100
)
101+
/**
102+
* 封装的请求函数,返回 ApiResponse 数据部分
103+
* @param config axios 请求配置
104+
* @returns Promise<T> 直接返回 ApiResponse.data 的类型
105+
*/
106+
export async function apiRequest<T = any>(config: Partial<InternalAxiosRequestConfig>): Promise<T> {
107+
try {
108+
const response = await service(config as InternalAxiosRequestConfig)
109+
const apiResponse: ApiResponse<T> = response.data
110+
111+
if (isSuccessResponse(apiResponse)) {
112+
return apiResponse.data
113+
}
114+
115+
throw apiResponse
116+
} catch (error: any) {
117+
throw error
118+
}
119+
}
120+
121+
/**
122+
* GET 请求封装
123+
*/
124+
export function get<T = any>(url: string, params?: any): Promise<T> {
125+
return apiRequest<T>({
126+
method: 'GET',
127+
url,
128+
params
129+
})
130+
}
131+
132+
/**
133+
* POST 请求封装
134+
*/
135+
export function post<T = any>(url: string, data?: any): Promise<T> {
136+
return apiRequest<T>({
137+
method: 'POST',
138+
url,
139+
data
140+
})
141+
}
142+
143+
/**
144+
* PUT 请求封装
145+
*/
146+
export function put<T = any>(url: string, data?: any): Promise<T> {
147+
return apiRequest<T>({
148+
method: 'PUT',
149+
url,
150+
data
151+
})
152+
}
153+
154+
/**
155+
* DELETE 请求封装
156+
*/
157+
export function del<T = any>(url: string, params?: any): Promise<T> {
158+
return apiRequest<T>({
159+
method: 'DELETE',
160+
url,
161+
params
162+
})
163+
}
164+
165+
// 导出原始 axios 实例,用于特殊需求
75166
export default service

spring-ai-alibaba-deepresearch/ui-vue3/src/views/chat/index.vue

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<div class="welcome">
3333
<span class="gradient-text">{{ $t('welcome') }}, {{ username }}</span>
3434
</div>
35-
</Flex>
35+
</Flex>
3636
<div class="sender-wrapper">
3737
<sender
3838
class-name="sender"
@@ -71,7 +71,7 @@
7171
v-if="senderLoading"
7272
type="default"
7373
style="display: block"
74-
:disabled="true"
74+
@click="stopHandle"
7575
>
7676
<template #icon>
7777
<Spin size="small" />
@@ -137,9 +137,8 @@ import { useRoute, useRouter } from 'vue-router'
137137
import { useConfigStore } from '@/store/ConfigStore'
138138
import { parseJsonTextStrict } from '@/utils/jsonParser';
139139
import type { NormalNode, SiteInformation } from '@/types/node';
140-
import type { UploadFile } from '@/types/upload';
141140
import type { MessageState } from '@/types/message';
142-
import service from '@/utils/request'
141+
import service, { post } from '@/utils/request'
143142
144143
const router = useRouter()
145144
const route = useRoute()
@@ -150,7 +149,6 @@ if (!convId) {
150149
const { key } = conversationStore.newOne()
151150
router.push(`/chat/${key}`)
152151
}
153-
const uploadFileList = ref<UploadFile[]>([])
154152
const { useToken } = theme
155153
const { token } = useToken()
156154
const username = useAuthStore().token
@@ -308,6 +306,17 @@ const submitHandle = (nextContent: any) => {
308306
conversationStore.updateTitle(convId, nextContent)
309307
}
310308
309+
const stopHandle = async () => {
310+
// 发送上传请求
311+
await post<string>('/chat/stop', {
312+
session_id: convId,
313+
thread_id: current.threadId,
314+
})
315+
316+
message.success('停止成功')
317+
318+
}
319+
311320
// 开始研究
312321
function startDeepResearch() {
313322
messageStore.nextAIType()
@@ -538,24 +547,32 @@ function parseLoadingMessage(msg: string): any{
538547
539548
return buildPendingNodeThoughtChain(jsonArray)
540549
}
541-
550+
function findNode(jsonArray: NormalNode[], nodeName: string): NormalNode | undefined {
551+
return jsonArray.filter((item) => item.nodeName === nodeName)[0]
552+
}
542553
// 解析 status = success 的消息
543554
function parseSuccessMessage(msg: string) {
544555
// 解析完整数据
545556
const jsonArray: NormalNode[] = parseJsonTextStrict(msg)
546557
// 闲聊模式
547-
if(jsonArray.filter((item) => item.nodeName === 'coordinator').length > 0) {
548-
const coordinatorNode = jsonArray.filter((item) => item.nodeName === 'coordinator')[0];
549-
if(coordinatorNode && !coordinatorNode.content) {
550-
return (jsonArray.filter((item) => item.nodeName === '__END__')[0].content as any).output
558+
const coordinatorNode = findNode(jsonArray, 'coordinator')
559+
if(coordinatorNode && !coordinatorNode.content) {
560+
const endNode = findNode(jsonArray, '__END__')
561+
return endNode?.content.output
551562
}
563+
// 用户终止
564+
const endNode = findNode(jsonArray, '__END__')
565+
if(endNode && endNode.content.reason === '用户终止') {
566+
current.deepResearchDetail = false
567+
return endNode.content.reason
552568
}
553-
// 人类中断模式
554-
if(jsonArray.filter((item) => item.nodeName === '__END__').length === 0) {
569+
// 需要用用户反馈
570+
if(!endNode) {
555571
return buildStartDSThoughtChain(jsonArray)
556572
}
557573
// 人类恢复模式 或者 直接 end 模式
558-
if(jsonArray.filter((item) => item.nodeName === 'human_feedback').length > 0 || jsonArray.filter((item) => item.nodeName === '__END__').length) {
574+
const humanFeedbackNode = findNode(jsonArray, 'human_feedback')
575+
if(humanFeedbackNode || endNode) {
559576
return buildEndDSThoughtChain(jsonArray)
560577
}
561578
}
@@ -649,21 +666,21 @@ function beforeUpload(file: File) {
649666
message.error('只支持 PDF、TXT、DOC、DOCX、MD 格式的文件')
650667
return false
651668
}
652-
669+
653670
// 检查文件大小(限制为 10MB)
654671
const maxSize = 10 * 1024 * 1024
655672
if (file.size > maxSize) {
656673
message.error('文件大小不能超过 10MB')
657674
return false
658675
}
659-
676+
660677
return true
661678
}
662679
663680
// 处理文件上传
664681
async function handleFileUpload(options: any) {
665682
const file = options.file as File
666-
683+
667684
try {
668685
// 构建表单数据
669686
const formData = new FormData()
@@ -696,7 +713,7 @@ async function handleFileUpload(options: any) {
696713
const headerNode = computed(() => {
697714
const filesList = messageStore.uploadedFiles?.[convId] || []
698715
return (
699-
716+
700717
<Sender.Header title="上传文件" open={headerOpen.value} onOpenChange={v => headerOpen.value = v}>
701718
<Attachments items={filesList} overflow="scrollX" onChange={handleFileChange} beforeUpload={beforeUpload} customRequest={handleFileUpload} placeholder={(type) => type === 'drop'
702719
? {
@@ -708,7 +725,7 @@ const headerNode = computed(() => {
708725
description: '支持上传PDF、TXT、DOC、DOCX、MD等文件',
709726
}}/>
710727
</Sender.Header>
711-
728+
712729
)});
713730
714731
const scrollContainer = ref<Element | any>(null)

0 commit comments

Comments
 (0)