From 5ee1632489f74571424809c2a83e4d9326cabb9c Mon Sep 17 00:00:00 2001 From: Li Xin Date: Tue, 22 Apr 2025 11:02:18 +0800 Subject: [PATCH] feat: extract parseJSON() --- web/src/app/_components/message-list-view.tsx | 8 ++++---- .../_components/research-activities-block.tsx | 4 ++-- web/src/core/store/store.ts | 8 +++++--- web/src/core/utils/index.ts | 2 ++ web/src/core/utils/json.ts | 17 +++++++++++++++++ 5 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 web/src/core/utils/json.ts diff --git a/web/src/app/_components/message-list-view.tsx b/web/src/app/_components/message-list-view.tsx index 0937ade..79344e0 100644 --- a/web/src/app/_components/message-list-view.tsx +++ b/web/src/app/_components/message-list-view.tsx @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MIT import { LoadingOutlined } from "@ant-design/icons"; -import { parse } from "best-effort-json-parser"; import { motion } from "framer-motion"; import { Download, Headphones } from "lucide-react"; import { useCallback, useMemo, useState } from "react"; @@ -23,6 +22,7 @@ import { useResearchTitle, useStore, } from "~/core/store"; +import { parseJSON } from "~/core/utils"; import { cn } from "~/lib/utils"; import { LoadingAnimation } from "./loading-animation"; @@ -281,7 +281,7 @@ function PlanCard({ thought?: string; steps?: { title?: string; description?: string }[]; }>(() => { - return parse(message.content ?? ""); + return parseJSON(message.content ?? "", {}); }, [message.content]); const handleAccept = useCallback(async () => { await sendMessage( @@ -326,7 +326,7 @@ function PlanCard({ {!message.isStreaming && interruptMessage?.options?.length && ( { - return parse(message.content ?? ""); + return JSON.parse(message.content ?? ""); }, [message.content]); const title = useMemo(() => data?.title, [data]); const audioUrl = useMemo(() => data?.audioUrl, [data]); diff --git a/web/src/app/_components/research-activities-block.tsx b/web/src/app/_components/research-activities-block.tsx index dc2dc30..67a3c71 100644 --- a/web/src/app/_components/research-activities-block.tsx +++ b/web/src/app/_components/research-activities-block.tsx @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MIT import { PythonOutlined } from "@ant-design/icons"; -import { parse } from "best-effort-json-parser"; import { motion } from "framer-motion"; import { LRUCache } from "lru-cache"; import { BookOpenText, Search } from "lucide-react"; @@ -13,6 +12,7 @@ import { docco } from "react-syntax-highlighter/dist/esm/styles/hljs"; import { Skeleton } from "~/components/ui/skeleton"; import type { ToolCallRuntime } from "~/core/messages"; import { useMessage, useStore } from "~/core/store"; +import { parseJSON } from "~/core/utils"; import { cn } from "~/lib/utils"; import { FavIcon } from "./fav-icon"; @@ -112,7 +112,7 @@ function WebSearchToolCall({ toolCall }: { toolCall: ToolCallRuntime }) { const searchResults = useMemo(() => { let results: SearchResult[] | undefined = undefined; try { - results = toolCall.result ? parse(toolCall.result) : undefined; + results = toolCall.result ? parseJSON(toolCall.result, []) : undefined; } catch { results = undefined; } diff --git a/web/src/core/store/store.ts b/web/src/core/store/store.ts index d075eb9..a0fbc45 100644 --- a/web/src/core/store/store.ts +++ b/web/src/core/store/store.ts @@ -1,7 +1,6 @@ // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates // SPDX-License-Identifier: MIT -import { parse } from "best-effort-json-parser"; import { nanoid } from "nanoid"; import { create } from "zustand"; @@ -9,6 +8,7 @@ import { chatStream } from "../api"; import { generatePodcast } from "../api/podcast"; import type { Message } from "../messages"; import { mergeMessage } from "../messages"; +import { parseJSON } from "../utils"; const THREAD_ID = nanoid(); @@ -230,7 +230,7 @@ export async function listenToPodcast(researchId: string) { const reportMessageId = useStore.getState().researchReportIds.get(researchId); if (planMessageId && reportMessageId) { const planMessage = getMessage(planMessageId)!; - const title = (JSON.parse(planMessage.content) as { title: string }).title; + const title = parseJSON(planMessage.content, { title: "Untitled" }).title; const reportMessage = getMessage(reportMessageId); if (reportMessage?.content) { appendMessage({ @@ -269,7 +269,9 @@ export function useResearchTitle(researchId: string) { const planMessage = useMessage( useStore.getState().researchPlanIds.get(researchId), ); - return planMessage ? parse(planMessage.content).title : undefined; + return planMessage + ? parseJSON(planMessage.content, { title: "" }).title + : undefined; } export function useMessage(messageId: string | null | undefined) { diff --git a/web/src/core/utils/index.ts b/web/src/core/utils/index.ts index c1575b0..7ae9efd 100644 --- a/web/src/core/utils/index.ts +++ b/web/src/core/utils/index.ts @@ -2,3 +2,5 @@ // SPDX-License-Identifier: MIT export * from "./time"; +export * from "./json"; +export * from "./deep-clone"; diff --git a/web/src/core/utils/json.ts b/web/src/core/utils/json.ts new file mode 100644 index 0000000..35e00b0 --- /dev/null +++ b/web/src/core/utils/json.ts @@ -0,0 +1,17 @@ +import { parse } from "best-effort-json-parser"; + +export function parseJSON(json: string | null | undefined, fallback: T) { + if (!json) { + return fallback; + } + try { + const raw = json + .trim() + .replace(/^```json\s*/, "") + .replace(/^```\s*/, "") + .replace(/\s*```$/, ""); + return parse(raw) as T; + } catch { + return fallback; + } +}