+
{{ $t('modelFeedback.waiting') }}
{{ error }}
+
+
+
- {{ isLoadingFeedback ? 'Researching...' : $t('researchTopic.start') }}
+ {{ isLoadingFeedback ? $t('researchTopic.researching') : $t('researchTopic.start') }}
diff --git a/components/ResearchReport.vue b/components/ResearchReport.vue
index 4504f9f..993fb09 100644
--- a/components/ResearchReport.vue
+++ b/components/ResearchReport.vue
@@ -16,6 +16,7 @@
const error = ref('')
const loading = ref(false)
const loadingExportPdf = ref(false)
+ const reasoningContent = ref('')
const reportContent = ref('')
const reportHtml = computed(() =>
marked(reportContent.value, { silent: true, gfm: true, breaks: true }),
@@ -29,9 +30,20 @@
loading.value = true
error.value = ''
reportContent.value = ''
+ reasoningContent.value = ''
try {
- for await (const chunk of writeFinalReport(params).textStream) {
- reportContent.value += chunk
+ for await (const chunk of writeFinalReport(params).fullStream) {
+ if (chunk.type === 'reasoning') {
+ reasoningContent.value += chunk.textDelta
+ } else if (chunk.type === 'text-delta') {
+ reportContent.value += chunk.textDelta
+ } else if (chunk.type === 'error') {
+ error.value = t('researchReport.error', [
+ chunk.error instanceof Error
+ ? chunk.error.message
+ : String(chunk.error),
+ ])
+ }
}
reportContent.value += `\n\n## ${t(
'researchReport.sources',
@@ -158,21 +170,25 @@
+
{{ error }}
+
+
+
-
- {{ error }}
-
- {{
- loading
- ? $t('researchReport.generating')
- : $t('researchReport.waiting')
- }}
-
-
+
+ {{
+ loading ? $t('researchReport.generating') : $t('researchReport.waiting')
+ }}
+
diff --git a/components/Tree.vue b/components/Tree.vue
index de3ce95..2003857 100644
--- a/components/Tree.vue
+++ b/components/Tree.vue
@@ -6,11 +6,15 @@
export type TreeNode = {
id: string
- /** Label, represents the search query */
+ /** Label, represents the search query. Generated from parent node. */
label: string
+ /** The research goal of this node. Generated from parent node. */
researchGoal?: string
+ /** Reasoning content when generating queries for the next iteration. */
+ generateQueriesReasoning?: string
+ /** Reasoning content when generating learnings for this iteration. */
+ generateLearningsReasoning?: string
learnings?: string[]
- followUpQuestions?: string[]
visitedUrls?: string[]
status?: TreeNodeStatus
children: TreeNode[]
diff --git a/composables/useAiProvider.ts b/composables/useAiProvider.ts
index 31fa575..c880098 100644
--- a/composables/useAiProvider.ts
+++ b/composables/useAiProvider.ts
@@ -1,14 +1,18 @@
-import { createOpenAI } from '@ai-sdk/openai'
+import { createDeepSeek } from '@ai-sdk/deepseek'
+import { extractReasoningMiddleware, wrapLanguageModel } from 'ai'
export const useAiModel = () => {
const config = useConfigStore()
switch (config.config.ai.provider) {
case 'openai-compatible':
- const openai = createOpenAI({
+ const deepseek = createDeepSeek({
apiKey: config.config.ai.apiKey,
baseURL: config.aiApiBase,
})
- return openai(config.config.ai.model)
+ return wrapLanguageModel({
+ model: deepseek(config.config.ai.model),
+ middleware: extractReasoningMiddleware({ tagName: 'think' }),
+ })
default:
throw new Error(`Unknown AI provider: ${config.config.ai.provider}`)
}
diff --git a/i18n/en.json b/i18n/en.json
index 370302b..6c18bf8 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -1,5 +1,8 @@
{
"language": "English",
+ "modelThinking": "Thinking...",
+ "modelThinkingComplete": "Thinking complete",
+ "invalidStructuredOutput": "The model returned content which is incomplete or invalid and cannot be parsed. Try using a bigger or \"smarter\" model.",
"index": {
"projectDescription": "This is a web UI for {0} that allows AI to search online and dig deeper on its own based on specific questions, and then output a research report.\nThis project features streaming AI responses for realtime feedback, and visualization of the research process using a tree structure.\nAll API requests are directly sent from your browser. No remote data stored.",
"missingConfigTitle": "Config not set",
diff --git a/i18n/zh.json b/i18n/zh.json
index 260e1e6..2e2e7d7 100644
--- a/i18n/zh.json
+++ b/i18n/zh.json
@@ -1,5 +1,8 @@
{
"language": "中文",
+ "modelThinking": "思考中...",
+ "modelThinkingComplete": "思考完毕",
+ "invalidStructuredOutput": "模型返回的内容无效或不完整,无法解析。请尝试换一个更大或者更“聪明”的模型。",
"index": {
"projectDescription": "Deep Research 是 {0} 的可视化 UI,可以让 AI 根据特定问题联网搜索并自行深挖,并输出研究报告。\n本项目可以流式传输 AI 的回答来实时反馈,并使用树状结构可视化搜索过程。\n全部 API 请求都在浏览器本地完成。",
"missingConfigTitle": "需要配置 API",
@@ -45,7 +48,7 @@
"breadth": "研究广度 (Breadth)",
"breadthHelp": "第一次迭代中的搜索次数。后续每轮迭代的搜索次数为上一轮的一半。",
"start": "开始研究",
- "researching": "正在研究..."
+ "researching": "研究中..."
},
"modelFeedback": {
"title": "2. 模型反馈",
diff --git a/lib/deep-research.ts b/lib/deep-research.ts
index ca13c8d..e5bf36c 100644
--- a/lib/deep-research.ts
+++ b/lib/deep-research.ts
@@ -29,6 +29,7 @@ export type PartialSearchResult = DeepPartial
export type ResearchStep =
| { type: 'generating_query'; result: PartialSearchQuery; nodeId: string }
+ | { type: 'generating_query_reasoning'; delta: string; nodeId: string }
| {
type: 'generated_query'
query: string
@@ -43,6 +44,11 @@ export type ResearchStep =
result: PartialSearchResult
nodeId: string
}
+ | {
+ type: 'processing_serach_result_reasoning'
+ delta: string
+ nodeId: string
+ }
| {
type: 'processed_search_result'
query: string
@@ -110,7 +116,7 @@ export function generateSearchQueries({
'\n',
)}`
: '',
- `You MUST respond in JSON with the following schema: ${jsonSchema}`,
+ `You MUST respond in JSON matching this JSON schema: ${jsonSchema}`,
lp,
].join('\n\n')
return streamText({
@@ -160,7 +166,7 @@ function processSearchResult({
`${contents
.map((content) => `\n${content}\n`)
.join('\n')}`,
- `You MUST respond in JSON with the following schema: ${jsonSchema}`,
+ `You MUST respond in JSON matching this JSON schema: ${jsonSchema}`,
languagePrompt(language),
].join('\n\n')
@@ -234,6 +240,8 @@ export async function deepResearch({
/** Force the LLM to generate serp queries in a certain language */
searchLanguage?: string
}): Promise {
+ const { t } = useNuxtApp().$i18n
+
try {
const searchQueriesResult = generateSearchQueries({
query,
@@ -246,12 +254,12 @@ export async function deepResearch({
let searchQueries: PartialSearchQuery[] = []
- for await (const parsedQueries of parseStreamingJson(
- searchQueriesResult.textStream,
+ for await (const chunk of parseStreamingJson(
+ searchQueriesResult.fullStream,
searchQueriesTypeSchema,
(value) => !!value.queries?.length && !!value.queries[0]?.query,
)) {
- if (parsedQueries.queries) {
+ if (chunk.type === 'object' && chunk.value.queries) {
for (let i = 0; i < searchQueries.length; i++) {
onProgress({
type: 'generating_query',
@@ -259,7 +267,27 @@ export async function deepResearch({
nodeId: childNodeId(nodeId, i),
})
}
- searchQueries = parsedQueries.queries
+ searchQueries = chunk.value.queries
+ } else if (chunk.type === 'reasoning') {
+ onProgress({
+ type: 'generating_query_reasoning',
+ delta: chunk.delta,
+ nodeId,
+ })
+ } else if (chunk.type === 'error') {
+ onProgress({
+ type: 'error',
+ message: chunk.message,
+ nodeId,
+ })
+ break
+ } else if (chunk.type === 'bad-end') {
+ onProgress({
+ type: 'error',
+ message: t('invalidStructuredOutput'),
+ nodeId,
+ })
+ break
}
}
@@ -272,6 +300,7 @@ export async function deepResearch({
})
}
+ // Run in parallel and limit the concurrency
const results = await Promise.all(
searchQueries.map((searchQuery, i) =>
limit(async () => {
@@ -287,6 +316,7 @@ export async function deepResearch({
nodeId: childNodeId(nodeId, i),
})
try {
+ // Use Tavily to search the web
const result = await useTavily().search(searchQuery.query, {
maxResults: 5,
})
@@ -314,18 +344,41 @@ export async function deepResearch({
})
let searchResult: PartialSearchResult = {}
- for await (const parsedLearnings of parseStreamingJson(
- searchResultGenerator.textStream,
+ for await (const chunk of parseStreamingJson(
+ searchResultGenerator.fullStream,
searchResultTypeSchema,
(value) => !!value.learnings?.length,
)) {
- searchResult = parsedLearnings
- onProgress({
- type: 'processing_serach_result',
- result: parsedLearnings,
- query: searchQuery.query,
- nodeId: childNodeId(nodeId, i),
- })
+ const id = childNodeId(nodeId, i)
+ if (chunk.type === 'object') {
+ searchResult = chunk.value
+ onProgress({
+ type: 'processing_serach_result',
+ result: chunk.value,
+ query: searchQuery.query,
+ nodeId: id,
+ })
+ } else if (chunk.type === 'reasoning') {
+ onProgress({
+ type: 'processing_serach_result_reasoning',
+ delta: chunk.delta,
+ nodeId: id,
+ })
+ } else if (chunk.type === 'error') {
+ onProgress({
+ type: 'error',
+ message: chunk.message,
+ nodeId: id,
+ })
+ break
+ } else if (chunk.type === 'bad-end') {
+ onProgress({
+ type: 'error',
+ message: t('invalidStructuredOutput'),
+ nodeId: id,
+ })
+ break
+ }
}
console.log(
`Processed search result for ${searchQuery.query}`,
diff --git a/lib/feedback.ts b/lib/feedback.ts
index a125bee..29b9261 100644
--- a/lib/feedback.ts
+++ b/lib/feedback.ts
@@ -28,7 +28,7 @@ export function generateFeedback({
const jsonSchema = JSON.stringify(zodToJsonSchema(schema))
const prompt = [
`Given the following query from the user, ask ${numQuestions} follow up questions to clarify the research direction. Return a maximum of ${numQuestions} questions, but feel free to return less if the original query is clear: ${query}`,
- `You MUST respond in JSON with the following schema: ${jsonSchema}`,
+ `You MUST respond in JSON matching this JSON schema: ${jsonSchema}`,
languagePrompt(language),
].join('\n\n')
@@ -37,12 +37,13 @@ export function generateFeedback({
system: systemPrompt(),
prompt,
onError({ error }) {
+ console.error(`generateFeedback`, error)
throw error
},
})
return parseStreamingJson(
- stream.textStream,
+ stream.fullStream,
feedbackTypeSchema,
(value: PartialFeedback) => !!value.questions && value.questions.length > 0,
)
diff --git a/package.json b/package.json
index 0b9985e..0a5436a 100644
--- a/package.json
+++ b/package.json
@@ -12,27 +12,28 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
- "@ai-sdk/openai": "^1.1.9",
- "@ai-sdk/ui-utils": "^1.1.11",
- "@ai-sdk/vue": "^1.1.11",
+ "@ai-sdk/deepseek": "^0.1.10",
+ "@ai-sdk/openai": "^1.1.11",
+ "@ai-sdk/ui-utils": "^1.1.14",
+ "@ai-sdk/vue": "^1.1.15",
"@iconify-json/lucide": "^1.2.26",
"@nuxt/ui": "3.0.0-alpha.12",
"@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/i18n": "9.2.0",
- "@pinia/nuxt": "^0.10.0",
+ "@pinia/nuxt": "^0.10.1",
"@tailwindcss/typography": "^0.5.16",
- "@tavily/core": "^0.0.3",
- "ai": "^4.1.28",
- "js-tiktoken": "^1.0.18",
+ "@tavily/core": "^0.3.1",
+ "ai": "^4.1.38",
+ "js-tiktoken": "^1.0.19",
"jspdf": "^2.5.2",
"marked": "^15.0.7",
"nuxt": "^3.15.4",
"p-limit": "^6.2.0",
- "pinia": "^3.0.0",
+ "pinia": "^3.0.1",
"tailwindcss": "^4.0.5",
"vue": "latest",
"vue-router": "latest",
- "zod": "^3.24.1",
+ "zod": "^3.24.2",
"zod-to-json-schema": "^3.24.1"
},
"devDependencies": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 244d037..f8fb315 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,15 +8,18 @@ importers:
.:
dependencies:
+ '@ai-sdk/deepseek':
+ specifier: ^0.1.10
+ version: 0.1.10(zod@3.24.2)
'@ai-sdk/openai':
- specifier: ^1.1.9
- version: 1.1.9(zod@3.24.1)
+ specifier: ^1.1.11
+ version: 1.1.11(zod@3.24.2)
'@ai-sdk/ui-utils':
- specifier: ^1.1.11
- version: 1.1.11(zod@3.24.1)
+ specifier: ^1.1.14
+ version: 1.1.14(zod@3.24.2)
'@ai-sdk/vue':
- specifier: ^1.1.11
- version: 1.1.11(vue@3.5.13(typescript@5.7.3))(zod@3.24.1)
+ specifier: ^1.1.15
+ version: 1.1.15(vue@3.5.13(typescript@5.7.3))(zod@3.24.2)
'@iconify-json/lucide':
specifier: ^1.2.26
version: 1.2.26
@@ -30,20 +33,20 @@ importers:
specifier: 9.2.0
version: 9.2.0(@vue/compiler-dom@3.5.13)(eslint@9.20.1(jiti@2.4.2))(magicast@0.3.5)(rollup@4.34.6)(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))
'@pinia/nuxt':
- specifier: ^0.10.0
- version: 0.10.0(magicast@0.3.5)(pinia@3.0.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)))
+ specifier: ^0.10.1
+ version: 0.10.1(magicast@0.3.5)(pinia@3.0.1(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)))
'@tailwindcss/typography':
specifier: ^0.5.16
version: 0.5.16(tailwindcss@4.0.6)
'@tavily/core':
- specifier: ^0.0.3
- version: 0.0.3
+ specifier: ^0.3.1
+ version: 0.3.1
ai:
- specifier: ^4.1.28
- version: 4.1.28(react@19.0.0)(zod@3.24.1)
+ specifier: ^4.1.38
+ version: 4.1.38(react@19.0.0)(zod@3.24.2)
js-tiktoken:
- specifier: ^1.0.18
- version: 1.0.18
+ specifier: ^1.0.19
+ version: 1.0.19
jspdf:
specifier: ^2.5.2
version: 2.5.2
@@ -57,8 +60,8 @@ importers:
specifier: ^6.2.0
version: 6.2.0
pinia:
- specifier: ^3.0.0
- version: 3.0.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))
+ specifier: ^3.0.1
+ version: 3.0.1(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))
tailwindcss:
specifier: ^4.0.5
version: 4.0.6
@@ -69,11 +72,11 @@ importers:
specifier: latest
version: 4.5.0(vue@3.5.13(typescript@5.7.3))
zod:
- specifier: ^3.24.1
- version: 3.24.1
+ specifier: ^3.24.2
+ version: 3.24.2
zod-to-json-schema:
specifier: ^3.24.1
- version: 3.24.1(zod@3.24.1)
+ version: 3.24.1(zod@3.24.2)
devDependencies:
'@vueuse/core':
specifier: ^12.5.0
@@ -84,14 +87,26 @@ importers:
packages:
- '@ai-sdk/openai@1.1.9':
- resolution: {integrity: sha512-t/CpC4TLipdbgBJTMX/otzzqzCMBSPQwUOkYPGbT/jyuC86F+YO9o+LS0Ty2pGUE1kyT+B3WmJ318B16ZCg4hw==}
+ '@ai-sdk/deepseek@0.1.10':
+ resolution: {integrity: sha512-JCYFEYog365yFXXrbySAwuYfe6zHKuBPmdOIa3xl0LSE0p1GcvGkZn80lNPLLzqR8GO8S4qUZoH+HL1kqYHaYA==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
- '@ai-sdk/provider-utils@2.1.6':
- resolution: {integrity: sha512-Pfyaj0QZS22qyVn5Iz7IXcJ8nKIKlu2MeSAdKJzTwkAks7zdLaKVB+396Rqcp1bfQnxl7vaduQVMQiXUrgK8Gw==}
+ '@ai-sdk/openai-compatible@0.1.10':
+ resolution: {integrity: sha512-AR5Acly7U64wDgVdVs9AqaMHgVps/35TMUMDS4akSZjsB4TdAhKnWdMXmACzCxmbCySHMoMnKY41XgvCyFN1pQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.0.0
+
+ '@ai-sdk/openai@1.1.11':
+ resolution: {integrity: sha512-gyqjoRvycmN3OGeK2SJXwhROv2ZZuP+SXbiAOoJf0ehWkqwkQSVaHigmg6OYLznmXusVHAvYD7SRgysXoJmuog==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.0.0
+
+ '@ai-sdk/provider-utils@2.1.8':
+ resolution: {integrity: sha512-1j9niMUAFlCBdYRYJr1yoB5kwZcRFBVuBiL1hhrf0ONFNrDiJYA6F+gROOuP16NHhezMfTo60+GeeV1xprHFjg==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
@@ -103,8 +118,8 @@ packages:
resolution: {integrity: sha512-q1PJEZ0qD9rVR+8JFEd01/QM++csMT5UVwYXSN2u54BrVw/D8TZLTeg2FEfKK00DgAx0UtWd8XOhhwITP9BT5g==}
engines: {node: '>=18'}
- '@ai-sdk/react@1.1.11':
- resolution: {integrity: sha512-vfjZ7w2M+Me83HTMMrnnrmXotz39UDCMd27YQSrvt2f1YCLPloVpLhP+Y9TLZeFE/QiiRCrPYLDQm6aQJYJ9PQ==}
+ '@ai-sdk/react@1.1.14':
+ resolution: {integrity: sha512-4Y2d37l52TzOZNgH2KxXiWkJc4R7Yr+1k0VryOpZslqFHK0cclinFgWlclzF6Qn8C1pNMdhhxSEx1/N7SQZeKQ==}
engines: {node: '>=18'}
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
@@ -115,8 +130,8 @@ packages:
zod:
optional: true
- '@ai-sdk/ui-utils@1.1.11':
- resolution: {integrity: sha512-1SC9W4VZLcJtxHRv4Y0aX20EFeaEP6gUvVqoKLBBtMLOgtcZrv/F/HQRjGavGugiwlS3dsVza4X+E78fiwtlTA==}
+ '@ai-sdk/ui-utils@1.1.14':
+ resolution: {integrity: sha512-JQXcnPRnDfeH1l503s/8+SxJdmgyUKC3QvKjOpTV6Z/LyRWJZrruBoZnVB1OrL9o/WHEguC+rD+p9udv281KzQ==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
@@ -124,8 +139,8 @@ packages:
zod:
optional: true
- '@ai-sdk/vue@1.1.11':
- resolution: {integrity: sha512-Zw8x+CoJLhPAa2Esn8X0OCAtUqoDRZ9eBcuDag8g+fLbmwdHLzUjgh4mgABKmUYXBcCFmrN+SlowzM4X6yprVQ==}
+ '@ai-sdk/vue@1.1.15':
+ resolution: {integrity: sha512-HO54RTfT/MbPZd8wO+lnwXLKpu/ys6P733Ui5IqW5AbpRQ79zShLfV3+Wqu5q9rKXO42brwRHZApigC5Ex7zKQ==}
engines: {node: '>=18'}
peerDependencies:
vue: ^3.3.4
@@ -851,10 +866,10 @@ packages:
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
- '@pinia/nuxt@0.10.0':
- resolution: {integrity: sha512-crYQgsqhEnf0HbOuaLYyLR9hyWK2lYUjcCYhFV4vgo3YThrLdnRKfBLa31au1uqcmgDqoX4mEm2OS3DO8GxR7w==}
+ '@pinia/nuxt@0.10.1':
+ resolution: {integrity: sha512-xrpkKZHSmshPK6kQzboJ+TZiZ5zj73gBCI5SfiUaJkKKS9gx4B1hLEzJIjxZl0/HS5jRWrIvQ+u9ulvIRlNiow==}
peerDependencies:
- pinia: ^3.0.0
+ pinia: ^3.0.1
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
@@ -1160,8 +1175,8 @@ packages:
peerDependencies:
vue: ^2.7.0 || ^3.0.0
- '@tavily/core@0.0.3':
- resolution: {integrity: sha512-17fsQnuxgkUSpKDq0+JWvfy4JZwe/b7/HVb2dqwFKWd5SILR+86OHL5C4TjEu6gO4DT6yWuH7ZNW9s+IlG+K3Q==}
+ '@tavily/core@0.3.1':
+ resolution: {integrity: sha512-7jyvPWG4Zjst0s4v0FMLO1f/dfHqs4FnqvKm86zOGYzXxSfxHu0isbLzlwjJad0csYwF0kifdlECTuNouHfr5A==}
'@trysound/sax@0.2.0':
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
@@ -1291,8 +1306,8 @@ packages:
'@vue/devtools-api@6.6.4':
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
- '@vue/devtools-api@7.7.1':
- resolution: {integrity: sha512-Cexc8GimowoDkJ6eNelOPdYIzsu2mgNyp0scOQ3tiaYSb9iok6LOESSsJvHaI+ib3joRfqRJNLkHFjhNuWA5dg==}
+ '@vue/devtools-api@7.7.2':
+ resolution: {integrity: sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==}
'@vue/devtools-core@7.6.8':
resolution: {integrity: sha512-8X4roysTwzQ94o7IobjVcOd1aZF5iunikrMrHPI2uUdigZCi2kFTQc7ffYiFiTNaLElCpjOhCnM7bo7aK1yU7A==}
@@ -1302,11 +1317,11 @@ packages:
'@vue/devtools-kit@7.6.8':
resolution: {integrity: sha512-JhJ8M3sPU+v0P2iZBF2DkdmR9L0dnT5RXJabJqX6o8KtFs3tebdvfoXV2Dm3BFuqeECuMJIfF1aCzSt+WQ4wrw==}
- '@vue/devtools-kit@7.7.1':
- resolution: {integrity: sha512-yhZ4NPnK/tmxGtLNQxmll90jIIXdb2jAhPF76anvn5M/UkZCiLJy28bYgPIACKZ7FCosyKoaope89/RsFJll1w==}
+ '@vue/devtools-kit@7.7.2':
+ resolution: {integrity: sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==}
- '@vue/devtools-shared@7.7.1':
- resolution: {integrity: sha512-BtgF7kHq4BHG23Lezc/3W2UhK2ga7a8ohAIAGJMBr4BkxUFzhqntQtCiuL1ijo2ztWnmusymkirgqUrXoQKumA==}
+ '@vue/devtools-shared@7.7.2':
+ resolution: {integrity: sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==}
'@vue/reactivity@3.5.13':
resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==}
@@ -1416,8 +1431,8 @@ packages:
resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==}
engines: {node: '>= 14'}
- ai@4.1.28:
- resolution: {integrity: sha512-DrYyVGK6HKx8TVzwuJOhemH9phdcyMfix5d7giyMYH9tu71Cvfs/Hi4zZPs9KPGDJnZYMLJna7CLs6l5CgRW7g==}
+ ai@4.1.38:
+ resolution: {integrity: sha512-jveFmoUbAn05B0OHlbIxUyIjVdhzEIwxFP1ZJtugMLXD6800RyhuaEzFRRBUk5WquLT4Hokm9uwAjCDJ7187dw==}
engines: {node: '>=18'}
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
@@ -1586,6 +1601,14 @@ packages:
magicast:
optional: true
+ c12@2.0.2:
+ resolution: {integrity: sha512-NkvlL5CHZt9kPswJYDCUYtTaMt7JOfcpsnNncfj7sWsc13x6Wz+GiTpBtqZOojFlzyTHui8+OAfR6praV6PYaQ==}
+ peerDependencies:
+ magicast: ^0.3.5
+ peerDependenciesMeta:
+ magicast:
+ optional: true
+
cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
@@ -2516,8 +2539,8 @@ packages:
resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
engines: {node: '>=0.10.0'}
- js-tiktoken@1.0.18:
- resolution: {integrity: sha512-hFYx4xYf6URgcttcGvGuOBJhTxPYZ2R5eIesqCaNRJmYH8sNmsfTeWg4yu//7u1VD/qIUkgKJTpGom9oHXmB4g==}
+ js-tiktoken@1.0.19:
+ resolution: {integrity: sha512-XC63YQeEcS47Y53gg950xiZ4IWmkfMe4p2V9OSaBt26q+p47WHn18izuXzSclCI73B7yGqtfRsT6jcZQI0y08g==}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -3060,6 +3083,9 @@ packages:
pathe@2.0.2:
resolution: {integrity: sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==}
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
perfect-debounce@1.0.0:
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
@@ -3077,8 +3103,8 @@ packages:
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
engines: {node: '>=12'}
- pinia@3.0.0:
- resolution: {integrity: sha512-Go23UsqaeABb4OYNmpDkE9VwDnqmbbjGzWpQhi3xfNkSPO6ZP+Ttt0EMo2J4DHXW+T0l3EqRneeXdyV/oJg/Mg==}
+ pinia@3.0.1:
+ resolution: {integrity: sha512-WXglsDzztOTH6IfcJ99ltYZin2mY8XZCXujkYWVIJlBjqsP6ST7zw+Aarh63E1cDVYeyUcPCxPHzJpEOmzB6Wg==}
peerDependencies:
typescript: '>=4.4.4'
vue: ^2.7.0 || ^3.5.11
@@ -3799,6 +3825,10 @@ packages:
resolution: {integrity: sha512-y5ZYDG+j7IB45+Y6CIkWIKou4E1JFigCUw6vI+h15HdYAKmT0oQWcawnxXuwJG8srJyXhIZuWz5uXB1MQ/ARZw==}
engines: {node: '>=18.20.6'}
+ unimport@4.1.1:
+ resolution: {integrity: sha512-j9+fijH6aDd05yv1fXlyt7HSxtOWtGtrZeYTVBsSUg57Iuf+Ps2itIZjeyu7bEQ4k0WOgYhHrdW8m/pJgOpl5g==}
+ engines: {node: '>=18.12.0'}
+
universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
@@ -3819,6 +3849,10 @@ packages:
resolution: {integrity: sha512-unB2e2ogZwEoMw/X0Gq1vj2jaRKLmTh9wcSEJggESPllcrZI68uO7B8ykixbXqsSwG8r9T7qaHZudXIC/3qvhw==}
engines: {node: '>=18.12.0'}
+ unplugin-utils@0.2.4:
+ resolution: {integrity: sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==}
+ engines: {node: '>=18.12.0'}
+
unplugin-vue-components@28.0.0:
resolution: {integrity: sha512-vYe0wSyqTVhyNFIad1iiGyQGhG++tDOMgohqenMDOAooMJP9vvzCdXTqCVx20A0rCQXFNjgoRbSeDAioLPH36Q==}
engines: {node: '>=14'}
@@ -3860,6 +3894,10 @@ packages:
resolution: {integrity: sha512-Q3LU0e4zxKfRko1wMV2HmP8lB9KWislY7hxXpxd+lGx0PRInE4vhMBVEZwpdVYHvtqzhSrzuIfErsob6bQfCzw==}
engines: {node: '>=18.12.0'}
+ unplugin@2.2.0:
+ resolution: {integrity: sha512-m1ekpSwuOT5hxkJeZGRxO7gXbXT3gF26NjQ7GdVHoLoF8/nopLcd/QfPigpCy7i51oFHiRJg/CyHhj4vs2+KGw==}
+ engines: {node: '>=18.12.0'}
+
unstorage@1.14.4:
resolution: {integrity: sha512-1SYeamwuYeQJtJ/USE1x4l17LkmQBzg7deBJ+U9qOBoHo15d1cDxG4jM31zKRgF7pG0kirZy4wVMX6WL6Zoscg==}
peerDependencies:
@@ -4228,52 +4266,65 @@ packages:
peerDependencies:
zod: ^3.24.1
- zod@3.24.1:
- resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
+ zod@3.24.2:
+ resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==}
snapshots:
- '@ai-sdk/openai@1.1.9(zod@3.24.1)':
+ '@ai-sdk/deepseek@0.1.10(zod@3.24.2)':
+ dependencies:
+ '@ai-sdk/openai-compatible': 0.1.10(zod@3.24.2)
+ '@ai-sdk/provider': 1.0.7
+ '@ai-sdk/provider-utils': 2.1.8(zod@3.24.2)
+ zod: 3.24.2
+
+ '@ai-sdk/openai-compatible@0.1.10(zod@3.24.2)':
dependencies:
'@ai-sdk/provider': 1.0.7
- '@ai-sdk/provider-utils': 2.1.6(zod@3.24.1)
- zod: 3.24.1
+ '@ai-sdk/provider-utils': 2.1.8(zod@3.24.2)
+ zod: 3.24.2
- '@ai-sdk/provider-utils@2.1.6(zod@3.24.1)':
+ '@ai-sdk/openai@1.1.11(zod@3.24.2)':
+ dependencies:
+ '@ai-sdk/provider': 1.0.7
+ '@ai-sdk/provider-utils': 2.1.8(zod@3.24.2)
+ zod: 3.24.2
+
+ '@ai-sdk/provider-utils@2.1.8(zod@3.24.2)':
dependencies:
'@ai-sdk/provider': 1.0.7
eventsource-parser: 3.0.0
nanoid: 3.3.8
secure-json-parse: 2.7.0
optionalDependencies:
- zod: 3.24.1
+ zod: 3.24.2
'@ai-sdk/provider@1.0.7':
dependencies:
json-schema: 0.4.0
- '@ai-sdk/react@1.1.11(react@19.0.0)(zod@3.24.1)':
+ '@ai-sdk/react@1.1.14(react@19.0.0)(zod@3.24.2)':
dependencies:
- '@ai-sdk/provider-utils': 2.1.6(zod@3.24.1)
- '@ai-sdk/ui-utils': 1.1.11(zod@3.24.1)
+ '@ai-sdk/provider-utils': 2.1.8(zod@3.24.2)
+ '@ai-sdk/ui-utils': 1.1.14(zod@3.24.2)
swr: 2.3.2(react@19.0.0)
throttleit: 2.1.0
optionalDependencies:
react: 19.0.0
- zod: 3.24.1
+ zod: 3.24.2
- '@ai-sdk/ui-utils@1.1.11(zod@3.24.1)':
+ '@ai-sdk/ui-utils@1.1.14(zod@3.24.2)':
dependencies:
'@ai-sdk/provider': 1.0.7
- '@ai-sdk/provider-utils': 2.1.6(zod@3.24.1)
- zod-to-json-schema: 3.24.1(zod@3.24.1)
+ '@ai-sdk/provider-utils': 2.1.8(zod@3.24.2)
+ zod-to-json-schema: 3.24.1(zod@3.24.2)
optionalDependencies:
- zod: 3.24.1
+ zod: 3.24.2
- '@ai-sdk/vue@1.1.11(vue@3.5.13(typescript@5.7.3))(zod@3.24.1)':
+ '@ai-sdk/vue@1.1.15(vue@3.5.13(typescript@5.7.3))(zod@3.24.2)':
dependencies:
- '@ai-sdk/provider-utils': 2.1.6(zod@3.24.1)
- '@ai-sdk/ui-utils': 1.1.11(zod@3.24.1)
+ '@ai-sdk/provider-utils': 2.1.8(zod@3.24.2)
+ '@ai-sdk/ui-utils': 1.1.14(zod@3.24.2)
swrv: 1.1.0(vue@3.5.13(typescript@5.7.3))
optionalDependencies:
vue: 3.5.13(typescript@5.7.3)
@@ -5036,7 +5087,7 @@ snapshots:
'@nuxt/kit@3.15.4(magicast@0.3.5)':
dependencies:
- c12: 2.0.1(magicast@0.3.5)
+ c12: 2.0.2(magicast@0.3.5)
consola: 3.4.0
defu: 6.1.4
destr: 2.0.3
@@ -5047,14 +5098,14 @@ snapshots:
knitwork: 1.2.0
mlly: 1.7.4
ohash: 1.1.4
- pathe: 2.0.2
+ pathe: 2.0.3
pkg-types: 1.3.1
scule: 1.3.0
semver: 7.7.1
std-env: 3.8.0
ufo: 1.5.4
unctx: 2.4.1
- unimport: 4.1.0
+ unimport: 4.1.1
untyped: 1.5.2
transitivePeerDependencies:
- magicast
@@ -5342,10 +5393,10 @@ snapshots:
'@parcel/watcher-win32-ia32': 2.5.1
'@parcel/watcher-win32-x64': 2.5.1
- '@pinia/nuxt@0.10.0(magicast@0.3.5)(pinia@3.0.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)))':
+ '@pinia/nuxt@0.10.1(magicast@0.3.5)(pinia@3.0.1(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)))':
dependencies:
'@nuxt/kit': 3.15.4(magicast@0.3.5)
- pinia: 3.0.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))
+ pinia: 3.0.1(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))
transitivePeerDependencies:
- magicast
- supports-color
@@ -5604,10 +5655,10 @@ snapshots:
'@tanstack/virtual-core': 3.13.0
vue: 3.5.13(typescript@5.7.3)
- '@tavily/core@0.0.3':
+ '@tavily/core@0.3.1':
dependencies:
axios: 1.7.9
- js-tiktoken: 1.0.18
+ js-tiktoken: 1.0.19
transitivePeerDependencies:
- debug
@@ -5799,14 +5850,14 @@ snapshots:
'@vue/devtools-api@6.6.4': {}
- '@vue/devtools-api@7.7.1':
+ '@vue/devtools-api@7.7.2':
dependencies:
- '@vue/devtools-kit': 7.7.1
+ '@vue/devtools-kit': 7.7.2
'@vue/devtools-core@7.6.8(vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.38.1)(yaml@2.7.0))(vue@3.5.13(typescript@5.7.3))':
dependencies:
'@vue/devtools-kit': 7.6.8
- '@vue/devtools-shared': 7.7.1
+ '@vue/devtools-shared': 7.7.2
mitt: 3.0.1
nanoid: 5.0.9
pathe: 1.1.2
@@ -5817,7 +5868,7 @@ snapshots:
'@vue/devtools-kit@7.6.8':
dependencies:
- '@vue/devtools-shared': 7.7.1
+ '@vue/devtools-shared': 7.7.2
birpc: 0.2.19
hookable: 5.5.3
mitt: 3.0.1
@@ -5825,9 +5876,9 @@ snapshots:
speakingurl: 14.0.1
superjson: 2.2.2
- '@vue/devtools-kit@7.7.1':
+ '@vue/devtools-kit@7.7.2':
dependencies:
- '@vue/devtools-shared': 7.7.1
+ '@vue/devtools-shared': 7.7.2
birpc: 0.2.19
hookable: 5.5.3
mitt: 3.0.1
@@ -5835,7 +5886,7 @@ snapshots:
speakingurl: 14.0.1
superjson: 2.2.2
- '@vue/devtools-shared@7.7.1':
+ '@vue/devtools-shared@7.7.2':
dependencies:
rfdc: 1.4.1
@@ -5942,17 +5993,17 @@ snapshots:
agent-base@7.1.3: {}
- ai@4.1.28(react@19.0.0)(zod@3.24.1):
+ ai@4.1.38(react@19.0.0)(zod@3.24.2):
dependencies:
'@ai-sdk/provider': 1.0.7
- '@ai-sdk/provider-utils': 2.1.6(zod@3.24.1)
- '@ai-sdk/react': 1.1.11(react@19.0.0)(zod@3.24.1)
- '@ai-sdk/ui-utils': 1.1.11(zod@3.24.1)
+ '@ai-sdk/provider-utils': 2.1.8(zod@3.24.2)
+ '@ai-sdk/react': 1.1.14(react@19.0.0)(zod@3.24.2)
+ '@ai-sdk/ui-utils': 1.1.14(zod@3.24.2)
'@opentelemetry/api': 1.9.0
jsondiffpatch: 0.6.0
optionalDependencies:
react: 19.0.0
- zod: 3.24.1
+ zod: 3.24.2
ajv@6.12.6:
dependencies:
@@ -6126,6 +6177,23 @@ snapshots:
optionalDependencies:
magicast: 0.3.5
+ c12@2.0.2(magicast@0.3.5):
+ dependencies:
+ chokidar: 4.0.3
+ confbox: 0.1.8
+ defu: 6.1.4
+ dotenv: 16.4.7
+ giget: 1.2.4
+ jiti: 2.4.2
+ mlly: 1.7.4
+ ohash: 1.1.4
+ pathe: 2.0.3
+ perfect-debounce: 1.0.0
+ pkg-types: 1.3.1
+ rc9: 2.1.2
+ optionalDependencies:
+ magicast: 0.3.5
+
cac@6.7.14: {}
callsites@3.1.0: {}
@@ -7055,7 +7123,7 @@ snapshots:
js-levenshtein@1.1.6: {}
- js-tiktoken@1.0.18:
+ js-tiktoken@1.0.19:
dependencies:
base64-js: 1.5.1
@@ -7757,6 +7825,8 @@ snapshots:
pathe@2.0.2: {}
+ pathe@2.0.3: {}
+
perfect-debounce@1.0.0: {}
performance-now@2.1.0:
@@ -7768,9 +7838,9 @@ snapshots:
picomatch@4.0.2: {}
- pinia@3.0.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)):
+ pinia@3.0.1(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)):
dependencies:
- '@vue/devtools-api': 7.7.1
+ '@vue/devtools-api': 7.7.2
vue: 3.5.13(typescript@5.7.3)
optionalDependencies:
typescript: 5.7.3
@@ -7781,7 +7851,7 @@ snapshots:
dependencies:
confbox: 0.1.8
mlly: 1.7.4
- pathe: 2.0.2
+ pathe: 2.0.3
pluralize@8.0.0: {}
@@ -8536,6 +8606,23 @@ snapshots:
unplugin: 2.1.2
unplugin-utils: 0.2.3
+ unimport@4.1.1:
+ dependencies:
+ acorn: 8.14.0
+ escape-string-regexp: 5.0.0
+ estree-walker: 3.0.3
+ fast-glob: 3.3.3
+ local-pkg: 1.0.0
+ magic-string: 0.30.17
+ mlly: 1.7.4
+ pathe: 2.0.3
+ picomatch: 4.0.2
+ pkg-types: 1.3.1
+ scule: 1.3.0
+ strip-literal: 3.0.0
+ unplugin: 2.2.0
+ unplugin-utils: 0.2.4
+
universalify@2.0.1: {}
unplugin-auto-import@19.0.0(@nuxt/kit@3.15.4(magicast@0.3.5))(@vueuse/core@12.5.0(typescript@5.7.3))(rollup@4.34.6):
@@ -8558,6 +8645,11 @@ snapshots:
pathe: 2.0.2
picomatch: 4.0.2
+ unplugin-utils@0.2.4:
+ dependencies:
+ pathe: 2.0.3
+ picomatch: 4.0.2
+
unplugin-vue-components@28.0.0(@babel/parser@7.26.8)(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.34.6)(vue@3.5.13(typescript@5.7.3)):
dependencies:
'@antfu/utils': 0.7.10
@@ -8637,6 +8729,11 @@ snapshots:
acorn: 8.14.0
webpack-virtual-modules: 0.6.2
+ unplugin@2.2.0:
+ dependencies:
+ acorn: 8.14.0
+ webpack-virtual-modules: 0.6.2
+
unstorage@1.14.4(db0@0.2.3)(ioredis@5.5.0):
dependencies:
anymatch: 3.1.3
@@ -8941,8 +9038,8 @@ snapshots:
compress-commons: 6.0.2
readable-stream: 4.7.0
- zod-to-json-schema@3.24.1(zod@3.24.1):
+ zod-to-json-schema@3.24.1(zod@3.24.2):
dependencies:
- zod: 3.24.1
+ zod: 3.24.2
- zod@3.24.1: {}
+ zod@3.24.2: {}
diff --git a/utils/json.ts b/utils/json.ts
index 2d20251..18e6ade 100644
--- a/utils/json.ts
+++ b/utils/json.ts
@@ -1,4 +1,5 @@
import { parsePartialJson } from '@ai-sdk/ui-utils'
+import type { TextStreamPart } from 'ai'
import { z } from 'zod'
export type DeepPartial = T extends object
@@ -7,6 +8,13 @@ export type DeepPartial = T extends object
: { [P in keyof T]?: DeepPartial }
: T
+export type ParseStreamingJsonEvent =
+ | { type: 'object'; value: DeepPartial }
+ | { type: 'reasoning'; delta: string }
+ | { type: 'error'; message: string }
+ /** The call finished with invalid content that can't be parsed as JSON */
+ | { type: 'bad-end'; rawText: string }
+
export function removeJsonMarkdown(text: string) {
text = text.trim()
if (text.startsWith('```json')) {
@@ -23,32 +31,56 @@ export function removeJsonMarkdown(text: string) {
}
/**
- * 解析流式的 JSON 数据
- * @param textStream 字符串流
- * @param _schema zod schema 用于类型验证
- * @param isValid 自定义验证函数,用于判断解析出的 JSON 是否有效
- * @returns 异步生成器,yield 解析后的数据
+ * Parse streaming JSON text
+ * @param fullStream Returned by AI SDK
+ * @param _schema zod schema for type definition
+ * @param isValid Custom validation function to check if the parsed JSON is valid
*/
export async function* parseStreamingJson(
- textStream: AsyncIterable,
+ fullStream: AsyncIterable>,
_schema: T,
isValid: (value: DeepPartial>) => boolean,
-): AsyncGenerator>> {
+): AsyncGenerator>> {
let rawText = ''
let isParseSuccessful = false
- for await (const chunk of textStream) {
- rawText += chunk
- const parsed = parsePartialJson(removeJsonMarkdown(rawText))
+ for await (const chunk of fullStream) {
+ if (chunk.type === 'reasoning') {
+ yield { type: 'reasoning', delta: chunk.textDelta }
+ continue
+ }
+ if (chunk.type === 'error') {
+ yield {
+ type: 'error',
+ message:
+ chunk.error instanceof Error
+ ? chunk.error.message
+ : String(chunk.error),
+ }
+ continue
+ }
+ if (chunk.type === 'text-delta') {
+ rawText += chunk.textDelta
+ const parsed = parsePartialJson(removeJsonMarkdown(rawText))
- isParseSuccessful =
- parsed.state === 'repaired-parse' || parsed.state === 'successful-parse'
- if (isParseSuccessful && isValid(parsed.value as any)) {
- yield parsed.value as DeepPartial>
- } else {
- console.debug(`Failed to parse JSON:`, rawText)
+ isParseSuccessful =
+ parsed.state === 'repaired-parse' || parsed.state === 'successful-parse'
+ if (isParseSuccessful && isValid(parsed.value as any)) {
+ yield {
+ type: 'object',
+ value: parsed.value as DeepPartial>,
+ }
+ } else {
+ console.debug(`Failed to parse JSON: ${removeJsonMarkdown(rawText)}`)
+ }
}
}
- return { isSuccessful: isParseSuccessful }
+ // If the last chunk parses failed, return an error
+ if (!isParseSuccessful) {
+ yield {
+ type: 'bad-end',
+ rawText,
+ }
+ }
}