|
| 1 | +import { generateText } from "@xsai/generate-text" |
1 | 2 | import type { StreamTextChunkResult, StreamTextResult } from "@xsai/stream-text"
|
2 | 3 | import { useCallback, useLayoutEffect, useState } from "react"
|
| 4 | +import { isMobile } from "react-device-detect" |
3 | 5 | import { toast } from "react-toastify"
|
4 | 6 |
|
5 | 7 | import { Storage } from "@plasmohq/storage"
|
@@ -214,141 +216,193 @@ export const useAiSummary = (
|
214 | 216 | processedPrompt = processTemplate(customPrompt, scrapedData)
|
215 | 217 | }
|
216 | 218 | debugLog("processedPrompt", processedPrompt)
|
217 |
| - const result = await generateSummary(processedPrompt) |
218 |
| - if (result) { |
219 |
| - debugLog("generateSummaryText result获取成功", result) |
220 |
| - debugLog("streamText返回值结构:", { |
221 |
| - textStream: typeof result.textStream, |
222 |
| - stepStream: typeof result.stepStream, |
223 |
| - chunkStream: typeof result.chunkStream, |
224 |
| - hasTextStream: !!result.textStream, |
225 |
| - hasStepStream: !!result.stepStream, |
226 |
| - hasChunkStream: !!result.chunkStream |
| 219 | + |
| 220 | + // 根据是否为移动端选择不同的生成方式 |
| 221 | + if (isMobile) { |
| 222 | + // 移动端使用非流式生成 |
| 223 | + // TODO: 移动端流式输出好像有问题,等后续 debug,先打补丁 |
| 224 | + debugLog("检测到移动设备,使用直接生成方式") |
| 225 | + |
| 226 | + // 获取AI配置信息 |
| 227 | + const config = await getAiConfig() |
| 228 | + |
| 229 | + // 调用generateText直接生成文本 |
| 230 | + const { text, usage } = await generateText({ |
| 231 | + apiKey: apiKey, |
| 232 | + baseURL: config.baseURL || "https://api.openai.com/v1/", |
| 233 | + messages: [ |
| 234 | + { |
| 235 | + content: config.systemPrompt || "你是一个有用的助手", |
| 236 | + role: "system" |
| 237 | + }, |
| 238 | + { |
| 239 | + content: processedPrompt + "\n\n内容: " + content, |
| 240 | + role: "user" |
| 241 | + } |
| 242 | + ], |
| 243 | + model: config.model || "gpt-3.5-turbo" |
227 | 244 | })
|
228 | 245 |
|
229 |
| - // 保存结果对象 |
230 |
| - setResult(result) |
231 |
| - setUsage(null) // 初始化usage |
| 246 | + // 设置生成的文本 |
| 247 | + fullText = text |
| 248 | + setSummary(text) |
| 249 | + setStreamingText(text) |
| 250 | + onSummaryGenerated?.(text) |
| 251 | + |
| 252 | + // 设置token使用情况 |
| 253 | + if (usage) { |
| 254 | + const newUsage = { |
| 255 | + total_tokens: usage.total_tokens, |
| 256 | + prompt_tokens: usage.prompt_tokens, |
| 257 | + completion_tokens: usage.completion_tokens |
| 258 | + } |
| 259 | + setUsage(newUsage) |
| 260 | + currentUsage = newUsage |
| 261 | + } |
232 | 262 |
|
233 |
| - // 使用流式接收文本和处理 usage |
234 |
| - try { |
235 |
| - // 处理 usage 流 |
236 |
| - const chunkStream = |
237 |
| - result.chunkStream as unknown as AsyncIterable<StreamTextChunkResult> |
238 |
| - // TODO: 流这块还得完善,现在只是能跑! |
239 |
| - // 单独启动一个异步任务处理 chunkStream |
240 |
| - const processChunkStream = async () => { |
241 |
| - debugLog("开始处理 chunkStream 获取 usage...") |
| 263 | + // 保存到历史记录 |
| 264 | + await saveToHistory(text, currentUsage) |
| 265 | + savedToHistory = true |
| 266 | + setError(null) |
| 267 | + } else { |
| 268 | + // 桌面端使用流式生成 |
| 269 | + const result = await generateSummary(processedPrompt) |
| 270 | + if (result) { |
| 271 | + debugLog("generateSummaryText result获取成功", result) |
| 272 | + debugLog("streamText返回值结构:", { |
| 273 | + textStream: typeof result.textStream, |
| 274 | + stepStream: typeof result.stepStream, |
| 275 | + chunkStream: typeof result.chunkStream, |
| 276 | + hasTextStream: !!result.textStream, |
| 277 | + hasStepStream: !!result.stepStream, |
| 278 | + hasChunkStream: !!result.chunkStream |
| 279 | + }) |
242 | 280 |
|
243 |
| - try { |
244 |
| - for await (const chunk of chunkStream) { |
245 |
| - // 如果 chunk 中有 usage 信息,则更新 usage 状态 |
246 |
| - if (chunk.usage) { |
247 |
| - debugLog("接收到 usage 信息:", chunk.usage) |
248 |
| - const newUsage = { |
249 |
| - total_tokens: chunk.usage.total_tokens, |
250 |
| - prompt_tokens: chunk.usage.prompt_tokens, |
251 |
| - completion_tokens: chunk.usage.completion_tokens |
| 281 | + // 保存结果对象 |
| 282 | + setResult(result) |
| 283 | + setUsage(null) // 初始化usage |
| 284 | + |
| 285 | + // 使用流式接收文本和处理 usage |
| 286 | + try { |
| 287 | + // 处理 usage 流 |
| 288 | + const chunkStream = |
| 289 | + result.chunkStream as unknown as AsyncIterable<StreamTextChunkResult> |
| 290 | + // TODO: 流这块还得完善,现在只是能跑! |
| 291 | + // 单独启动一个异步任务处理 chunkStream |
| 292 | + const processChunkStream = async () => { |
| 293 | + debugLog("开始处理 chunkStream 获取 usage...") |
| 294 | + |
| 295 | + try { |
| 296 | + for await (const chunk of chunkStream) { |
| 297 | + // 如果 chunk 中有 usage 信息,则更新 usage 状态 |
| 298 | + if (chunk.usage) { |
| 299 | + debugLog("接收到 usage 信息:", chunk.usage) |
| 300 | + const newUsage = { |
| 301 | + total_tokens: chunk.usage.total_tokens, |
| 302 | + prompt_tokens: chunk.usage.prompt_tokens, |
| 303 | + completion_tokens: chunk.usage.completion_tokens |
| 304 | + } |
| 305 | + setUsage(newUsage) |
| 306 | + currentUsage = newUsage |
252 | 307 | }
|
253 |
| - setUsage(newUsage) |
254 |
| - currentUsage = newUsage |
255 | 308 | }
|
| 309 | + debugLog("chunkStream 处理完成") |
| 310 | + } catch (chunkError) { |
| 311 | + console.error("chunkStream 处理出错:", chunkError) |
| 312 | + debugLog("chunkStream 处理详细错误:", { |
| 313 | + name: chunkError.name, |
| 314 | + message: chunkError.message, |
| 315 | + stack: chunkError.stack |
| 316 | + }) |
256 | 317 | }
|
257 |
| - debugLog("chunkStream 处理完成") |
258 |
| - } catch (chunkError) { |
259 |
| - console.error("chunkStream 处理出错:", chunkError) |
260 |
| - debugLog("chunkStream 处理详细错误:", { |
261 |
| - name: chunkError.name, |
262 |
| - message: chunkError.message, |
263 |
| - stack: chunkError.stack |
264 |
| - }) |
265 | 318 | }
|
266 |
| - } |
267 | 319 |
|
268 |
| - // 启动异步处理,但不等待它完成 |
269 |
| - processChunkStream() |
| 320 | + // 启动异步处理,但不等待它完成 |
| 321 | + processChunkStream() |
270 | 322 |
|
271 |
| - // 处理文本流 |
272 |
| - const textStream = |
273 |
| - result.textStream as unknown as AsyncIterable<string> |
| 323 | + // 处理文本流 |
| 324 | + const textStream = |
| 325 | + result.textStream as unknown as AsyncIterable<string> |
274 | 326 |
|
275 |
| - // 处理文本流 |
276 |
| - debugLog("开始处理textStream...") |
277 |
| - let chunkCount = 0 |
| 327 | + // 处理文本流 |
| 328 | + debugLog("开始处理textStream...") |
| 329 | + let chunkCount = 0 |
278 | 330 |
|
279 |
| - try { |
280 |
| - for await (const textPart of textStream) { |
281 |
| - chunkCount++ |
282 |
| - // 每接收 20 个文本块打印一次日志,避免日志过多 |
283 |
| - if (chunkCount % 20 === 0 || chunkCount <= 2) { |
284 |
| - debugLog(`接收到第${chunkCount}个文本块:`, { |
285 |
| - length: textPart.length, |
286 |
| - preview: |
287 |
| - textPart.slice(0, 20) + (textPart.length > 20 ? "..." : "") |
288 |
| - }) |
| 331 | + try { |
| 332 | + for await (const textPart of textStream) { |
| 333 | + chunkCount++ |
| 334 | + // 每接收 20 个文本块打印一次日志,避免日志过多 |
| 335 | + if (chunkCount % 20 === 0 || chunkCount <= 2) { |
| 336 | + debugLog(`接收到第${chunkCount}个文本块:`, { |
| 337 | + length: textPart.length, |
| 338 | + preview: |
| 339 | + textPart.slice(0, 20) + |
| 340 | + (textPart.length > 20 ? "..." : "") |
| 341 | + }) |
| 342 | + } |
| 343 | + |
| 344 | + fullText += textPart |
| 345 | + setStreamingText((prev) => prev + textPart) |
289 | 346 | }
|
290 | 347 |
|
291 |
| - fullText += textPart |
292 |
| - setStreamingText((prev) => prev + textPart) |
| 348 | + debugLog( |
| 349 | + `textStream 处理完成,共接收${chunkCount}个文本块,最终文本长度:${fullText?.length}` |
| 350 | + ) |
| 351 | + } catch (textStreamError) { |
| 352 | + console.error("textStream 处理出错:", textStreamError) |
| 353 | + debugLog("textStream 处理详细错误:", { |
| 354 | + name: textStreamError.name, |
| 355 | + message: textStreamError.message, |
| 356 | + stack: textStreamError.stack |
| 357 | + }) |
| 358 | + // 即使textStream出错,但如果我们已经收集了一些文本,我们也应该使用它 |
| 359 | + debugLog( |
| 360 | + "尽管 textStream 出错,仍将使用已收集的文本,长度:", |
| 361 | + fullText.length |
| 362 | + ) |
293 | 363 | }
|
294 | 364 |
|
295 |
| - debugLog( |
296 |
| - `textStream 处理完成,共接收${chunkCount}个文本块,最终文本长度:${fullText?.length}` |
297 |
| - ) |
298 |
| - } catch (textStreamError) { |
299 |
| - console.error("textStream 处理出错:", textStreamError) |
300 |
| - debugLog("textStream 处理详细错误:", { |
301 |
| - name: textStreamError.name, |
302 |
| - message: textStreamError.message, |
303 |
| - stack: textStreamError.stack |
304 |
| - }) |
305 |
| - // 即使textStream出错,但如果我们已经收集了一些文本,我们也应该使用它 |
306 |
| - debugLog( |
307 |
| - "尽管 textStream 出错,仍将使用已收集的文本,长度:", |
308 |
| - fullText.length |
309 |
| - ) |
310 |
| - } |
311 |
| - |
312 |
| - // 无论流处理是否成功,都使用收集的文本 |
313 |
| - if (fullText) { |
314 |
| - // 流处理完成后使用收集的完整文本 |
315 |
| - setSummary(fullText) |
316 |
| - onSummaryGenerated?.(fullText) |
317 |
| - debugLog("处理完成,最终文本长度:", fullText.length) |
318 |
| - |
319 |
| - // 获取当前usage状态用于保存历史记录 |
320 |
| - const usageForSave = currentUsage || usage || null |
321 |
| - |
322 |
| - debugLog("准备保存历史记录,当前状态:", { |
323 |
| - fullTextLength: fullText?.length || 0, |
324 |
| - hasFullText: !!fullText, |
325 |
| - hasScrapedData: !!scrapedData, |
326 |
| - hasUrl: !!scrapedData?.url, |
327 |
| - usage: usageForSave |
328 |
| - }) |
| 365 | + // 无论流处理是否成功,都使用收集的文本 |
| 366 | + if (fullText) { |
| 367 | + // 流处理完成后使用收集的完整文本 |
| 368 | + setSummary(fullText) |
| 369 | + onSummaryGenerated?.(fullText) |
| 370 | + debugLog("处理完成,最终文本长度:", fullText.length) |
| 371 | + |
| 372 | + // 获取当前usage状态用于保存历史记录 |
| 373 | + const usageForSave = currentUsage || usage || null |
| 374 | + |
| 375 | + debugLog("准备保存历史记录,当前状态:", { |
| 376 | + fullTextLength: fullText?.length || 0, |
| 377 | + hasFullText: !!fullText, |
| 378 | + hasScrapedData: !!scrapedData, |
| 379 | + hasUrl: !!scrapedData?.url, |
| 380 | + usage: usageForSave |
| 381 | + }) |
329 | 382 |
|
330 |
| - // 保存到历史记录 |
331 |
| - try { |
332 |
| - await saveToHistory(fullText, usageForSave) |
333 |
| - savedToHistory = true |
334 |
| - debugLog("成功保存到历史记录") |
335 |
| - } catch (saveError) { |
336 |
| - console.error("保存历史记录失败:", saveError) |
337 |
| - debugLog("保存历史记录失败:", saveError) |
| 383 | + // 保存到历史记录 |
| 384 | + try { |
| 385 | + await saveToHistory(fullText, usageForSave) |
| 386 | + savedToHistory = true |
| 387 | + debugLog("成功保存到历史记录") |
| 388 | + } catch (saveError) { |
| 389 | + console.error("保存历史记录失败:", saveError) |
| 390 | + debugLog("保存历史记录失败:", saveError) |
| 391 | + } |
338 | 392 | }
|
| 393 | + } catch (streamError) { |
| 394 | + console.error("流处理出错:", streamError) |
| 395 | + debugLog("流处理详细错误:", { |
| 396 | + name: streamError.name, |
| 397 | + message: streamError.message, |
| 398 | + stack: streamError.stack |
| 399 | + }) |
339 | 400 | }
|
340 |
| - } catch (streamError) { |
341 |
| - console.error("流处理出错:", streamError) |
342 |
| - debugLog("流处理详细错误:", { |
343 |
| - name: streamError.name, |
344 |
| - message: streamError.message, |
345 |
| - stack: streamError.stack |
346 |
| - }) |
347 |
| - } |
348 | 401 |
|
349 |
| - setError(null) |
350 |
| - } else { |
351 |
| - throw new Error("生成摘要失败") |
| 402 | + setError(null) |
| 403 | + } else { |
| 404 | + throw new Error("生成摘要失败") |
| 405 | + } |
352 | 406 | }
|
353 | 407 | } catch (error) {
|
354 | 408 | setError(error.message || "未知错误")
|
|
0 commit comments