1
1
"use client" ;
2
+ import { ApiPath , Alibaba , ALIBABA_BASE_URL } from "@/app/constant" ;
2
3
import {
3
- ApiPath ,
4
- Alibaba ,
5
- ALIBABA_BASE_URL ,
6
- REQUEST_TIMEOUT_MS ,
7
- } from "@/app/constant" ;
8
- import { useAccessStore , useAppConfig , useChatStore } from "@/app/store" ;
9
-
4
+ useAccessStore ,
5
+ useAppConfig ,
6
+ useChatStore ,
7
+ ChatMessageTool ,
8
+ usePluginStore ,
9
+ } from "@/app/store" ;
10
+ import { streamWithThink } from "@/app/utils/chat" ;
10
11
import {
11
12
ChatOptions ,
12
13
getHeaders ,
@@ -15,14 +16,12 @@ import {
15
16
SpeechOptions ,
16
17
MultimodalContent ,
17
18
} from "../api" ;
18
- import Locale from "../../locales" ;
19
- import {
20
- EventStreamContentType ,
21
- fetchEventSource ,
22
- } from "@fortaine/fetch-event-source" ;
23
- import { prettyObject } from "@/app/utils/format" ;
24
19
import { getClientConfig } from "@/app/config/client" ;
25
- import { getMessageTextContent } from "@/app/utils" ;
20
+ import {
21
+ getMessageTextContent ,
22
+ getMessageTextContentWithoutThinking ,
23
+ getTimeoutMSByModel ,
24
+ } from "@/app/utils" ;
26
25
import { fetch } from "@/app/utils/stream" ;
27
26
28
27
export interface OpenAIListModelResponse {
@@ -92,7 +91,10 @@ export class QwenApi implements LLMApi {
92
91
async chat ( options : ChatOptions ) {
93
92
const messages = options . messages . map ( ( v ) => ( {
94
93
role : v . role ,
95
- content : getMessageTextContent ( v ) ,
94
+ content :
95
+ v . role === "assistant"
96
+ ? getMessageTextContentWithoutThinking ( v )
97
+ : getMessageTextContent ( v ) ,
96
98
} ) ) ;
97
99
98
100
const modelConfig = {
@@ -122,134 +124,118 @@ export class QwenApi implements LLMApi {
122
124
options . onController ?.( controller ) ;
123
125
124
126
try {
127
+ const headers = {
128
+ ...getHeaders ( ) ,
129
+ "X-DashScope-SSE" : shouldStream ? "enable" : "disable" ,
130
+ } ;
131
+
125
132
const chatPath = this . path ( Alibaba . ChatPath ) ;
126
133
const chatPayload = {
127
134
method : "POST" ,
128
135
body : JSON . stringify ( requestPayload ) ,
129
136
signal : controller . signal ,
130
- headers : {
131
- ...getHeaders ( ) ,
132
- "X-DashScope-SSE" : shouldStream ? "enable" : "disable" ,
133
- } ,
137
+ headers : headers ,
134
138
} ;
135
139
136
140
// make a fetch request
137
141
const requestTimeoutId = setTimeout (
138
142
( ) => controller . abort ( ) ,
139
- REQUEST_TIMEOUT_MS ,
143
+ getTimeoutMSByModel ( options . config . model ) ,
140
144
) ;
141
145
142
146
if ( shouldStream ) {
143
- let responseText = "" ;
144
- let remainText = "" ;
145
- let finished = false ;
146
- let responseRes : Response ;
147
-
148
- // animate response to make it looks smooth
149
- function animateResponseText ( ) {
150
- if ( finished || controller . signal . aborted ) {
151
- responseText += remainText ;
152
- console . log ( "[Response Animation] finished" ) ;
153
- if ( responseText ?. length === 0 ) {
154
- options . onError ?.( new Error ( "empty response from server" ) ) ;
147
+ const [ tools , funcs ] = usePluginStore
148
+ . getState ( )
149
+ . getAsTools (
150
+ useChatStore . getState ( ) . currentSession ( ) . mask ?. plugin || [ ] ,
151
+ ) ;
152
+ return streamWithThink (
153
+ chatPath ,
154
+ requestPayload ,
155
+ headers ,
156
+ tools as any ,
157
+ funcs ,
158
+ controller ,
159
+ // parseSSE
160
+ ( text : string , runTools : ChatMessageTool [ ] ) => {
161
+ // console.log("parseSSE", text, runTools);
162
+ const json = JSON . parse ( text ) ;
163
+ const choices = json . output . choices as Array < {
164
+ message : {
165
+ content : string | null ;
166
+ tool_calls : ChatMessageTool [ ] ;
167
+ reasoning_content : string | null ;
168
+ } ;
169
+ } > ;
170
+
171
+ if ( ! choices ?. length ) return { isThinking : false , content : "" } ;
172
+
173
+ const tool_calls = choices [ 0 ] ?. message ?. tool_calls ;
174
+ if ( tool_calls ?. length > 0 ) {
175
+ const index = tool_calls [ 0 ] ?. index ;
176
+ const id = tool_calls [ 0 ] ?. id ;
177
+ const args = tool_calls [ 0 ] ?. function ?. arguments ;
178
+ if ( id ) {
179
+ runTools . push ( {
180
+ id,
181
+ type : tool_calls [ 0 ] ?. type ,
182
+ function : {
183
+ name : tool_calls [ 0 ] ?. function ?. name as string ,
184
+ arguments : args ,
185
+ } ,
186
+ } ) ;
187
+ } else {
188
+ // @ts -ignore
189
+ runTools [ index ] [ "function" ] [ "arguments" ] += args ;
190
+ }
155
191
}
156
- return ;
157
- }
158
-
159
- if ( remainText . length > 0 ) {
160
- const fetchCount = Math . max ( 1 , Math . round ( remainText . length / 60 ) ) ;
161
- const fetchText = remainText . slice ( 0 , fetchCount ) ;
162
- responseText += fetchText ;
163
- remainText = remainText . slice ( fetchCount ) ;
164
- options . onUpdate ?.( responseText , fetchText ) ;
165
- }
166
-
167
- requestAnimationFrame ( animateResponseText ) ;
168
- }
169
-
170
- // start animaion
171
- animateResponseText ( ) ;
172
-
173
- const finish = ( ) => {
174
- if ( ! finished ) {
175
- finished = true ;
176
- options . onFinish ( responseText + remainText , responseRes ) ;
177
- }
178
- } ;
179
-
180
- controller . signal . onabort = finish ;
181
-
182
- fetchEventSource ( chatPath , {
183
- fetch : fetch as any ,
184
- ...chatPayload ,
185
- async onopen ( res ) {
186
- clearTimeout ( requestTimeoutId ) ;
187
- const contentType = res . headers . get ( "content-type" ) ;
188
- console . log (
189
- "[Alibaba] request response content type: " ,
190
- contentType ,
191
- ) ;
192
- responseRes = res ;
193
192
194
- if ( contentType ?. startsWith ( "text/plain" ) ) {
195
- responseText = await res . clone ( ) . text ( ) ;
196
- return finish ( ) ;
197
- }
193
+ const reasoning = choices [ 0 ] ?. message ?. reasoning_content ;
194
+ const content = choices [ 0 ] ?. message ?. content ;
198
195
196
+ // Skip if both content and reasoning_content are empty or null
199
197
if (
200
- ! res . ok ||
201
- ! res . headers
202
- . get ( "content-type" )
203
- ?. startsWith ( EventStreamContentType ) ||
204
- res . status !== 200
198
+ ( ! reasoning || reasoning . length === 0 ) &&
199
+ ( ! content || content . length === 0 )
205
200
) {
206
- const responseTexts = [ responseText ] ;
207
- let extraInfo = await res . clone ( ) . text ( ) ;
208
- try {
209
- const resJson = await res . clone ( ) . json ( ) ;
210
- extraInfo = prettyObject ( resJson ) ;
211
- } catch { }
212
-
213
- if ( res . status === 401 ) {
214
- responseTexts . push ( Locale . Error . Unauthorized ) ;
215
- }
216
-
217
- if ( extraInfo ) {
218
- responseTexts . push ( extraInfo ) ;
219
- }
220
-
221
- responseText = responseTexts . join ( "\n\n" ) ;
222
-
223
- return finish ( ) ;
201
+ return {
202
+ isThinking : false ,
203
+ content : "" ,
204
+ } ;
224
205
}
225
- } ,
226
- onmessage ( msg ) {
227
- if ( msg . data === "[DONE]" || finished ) {
228
- return finish ( ) ;
229
- }
230
- const text = msg . data ;
231
- try {
232
- const json = JSON . parse ( text ) ;
233
- const choices = json . output . choices as Array < {
234
- message : { content : string } ;
235
- } > ;
236
- const delta = choices [ 0 ] ?. message ?. content ;
237
- if ( delta ) {
238
- remainText += delta ;
239
- }
240
- } catch ( e ) {
241
- console . error ( "[Request] parse error" , text , msg ) ;
206
+
207
+ if ( reasoning && reasoning . length > 0 ) {
208
+ return {
209
+ isThinking : true ,
210
+ content : reasoning ,
211
+ } ;
212
+ } else if ( content && content . length > 0 ) {
213
+ return {
214
+ isThinking : false ,
215
+ content : content ,
216
+ } ;
242
217
}
218
+
219
+ return {
220
+ isThinking : false ,
221
+ content : "" ,
222
+ } ;
243
223
} ,
244
- onclose ( ) {
245
- finish ( ) ;
246
- } ,
247
- onerror ( e ) {
248
- options . onError ?.( e ) ;
249
- throw e ;
224
+ // processToolMessage, include tool_calls message and tool call results
225
+ (
226
+ requestPayload : RequestPayload ,
227
+ toolCallMessage : any ,
228
+ toolCallResult : any [ ] ,
229
+ ) => {
230
+ requestPayload ?. input ?. messages ?. splice (
231
+ requestPayload ?. input ?. messages ?. length ,
232
+ 0 ,
233
+ toolCallMessage ,
234
+ ...toolCallResult ,
235
+ ) ;
250
236
} ,
251
- openWhenHidden : true ,
252
- } ) ;
237
+ options ,
238
+ ) ;
253
239
} else {
254
240
const res = await fetch ( chatPath , chatPayload ) ;
255
241
clearTimeout ( requestTimeoutId ) ;
0 commit comments