From 9ff81c0306201b422aa49a663a02cb18d26cba3f Mon Sep 17 00:00:00 2001 From: lyzno1 Date: Wed, 10 Sep 2025 22:27:49 +0800 Subject: [PATCH 01/11] fix: update cookies() usage for Next.js 15 compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Make getLocaleOnServer async and await cookies() - Update LocaleLayout to be async component - Fix react-tooltip compatibility with React 19 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- app/layout.tsx | 4 ++-- i18n/server.ts | 7 ++++--- package.json | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 7092434..c5bc12e 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,12 +3,12 @@ import { getLocaleOnServer } from '@/i18n/server' import './styles/globals.css' import './styles/markdown.scss' -const LocaleLayout = ({ +const LocaleLayout = async ({ children, }: { children: React.ReactNode }) => { - const locale = getLocaleOnServer() + const locale = await getLocaleOnServer() return ( diff --git a/i18n/server.ts b/i18n/server.ts index 5b97004..f043b98 100644 --- a/i18n/server.ts +++ b/i18n/server.ts @@ -6,19 +6,20 @@ import { match } from '@formatjs/intl-localematcher' import type { Locale } from '.' import { i18n } from '.' -export const getLocaleOnServer = (): Locale => { +export const getLocaleOnServer = async (): Promise => { // @ts-expect-error locales are readonly const locales: string[] = i18n.locales let languages: string[] | undefined // get locale from cookie - const localeCookie = cookies().get('locale') + const localeCookie = (await cookies()).get('locale') languages = localeCookie?.value ? [localeCookie.value] : [] if (!languages.length) { // Negotiator expects plain object so we need to transform headers const negotiatorHeaders: Record = {} - headers().forEach((value, key) => (negotiatorHeaders[key] = value)) + const headersList = await headers() + headersList.forEach((value, key) => (negotiatorHeaders[key] = value)) // Use negotiator and intl-localematcher to get best locale languages = new Negotiator({ headers: negotiatorHeaders }).languages() } diff --git a/package.json b/package.json index cf945aa..32898a1 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "react-i18next": "^12.2.0", "react-markdown": "^8.0.6", "react-syntax-highlighter": "^15.5.0", - "react-tooltip": "~5.8.3", + "react-tooltip": "~5.29.1", "rehype-katex": "^7.0.1", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.0", From ba95a431b3bf02dddc065cf9829169a67923ee3d Mon Sep 17 00:00:00 2001 From: lyzno1 Date: Wed, 10 Sep 2025 22:41:14 +0800 Subject: [PATCH 02/11] fix: assistant answer pb --- app/components/base/streamdown-markdown.tsx | 18 ++++++++++++++++++ app/components/index.tsx | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 app/components/base/streamdown-markdown.tsx diff --git a/app/components/base/streamdown-markdown.tsx b/app/components/base/streamdown-markdown.tsx new file mode 100644 index 0000000..4020e23 --- /dev/null +++ b/app/components/base/streamdown-markdown.tsx @@ -0,0 +1,18 @@ +'use client' +import { Streamdown } from 'streamdown' +import 'katex/dist/katex.min.css' + +interface StreamdownMarkdownProps { + content: string + className?: string +} + +export function StreamdownMarkdown({ content, className = '' }: StreamdownMarkdownProps) { + return ( +
+ {content} +
+ ) +} + +export default StreamdownMarkdown diff --git a/app/components/index.tsx b/app/components/index.tsx index 788a01b..cafcc5f 100644 --- a/app/components/index.tsx +++ b/app/components/index.tsx @@ -703,7 +703,7 @@ const Main: FC = () => { { hasSetInputs && ( -
+
Date: Wed, 10 Sep 2025 22:47:59 +0800 Subject: [PATCH 03/11] feat: question use streamdown --- app/components/chat/question/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/chat/question/index.tsx b/app/components/chat/question/index.tsx index 75b5391..b085f32 100644 --- a/app/components/chat/question/index.tsx +++ b/app/components/chat/question/index.tsx @@ -4,7 +4,7 @@ import React from 'react' import type { IChatItem } from '../type' import s from '../style.module.css' -import { Markdown } from '@/app/components/base/markdown' +import StreamdownMarkdown from '@/app/components/base/streamdown-markdown' import ImageGallery from '@/app/components/base/image-gallery' type IQuestionProps = Pick & { @@ -23,7 +23,7 @@ const Question: FC = ({ id, content, useCurrentUserAvatar, imgSr {imgSrcs && imgSrcs.length > 0 && ( )} - +
From 2d426902bd40b7d97d0a61e22e53ffbf23006f7d Mon Sep 17 00:00:00 2001 From: lyzno1 Date: Wed, 10 Sep 2025 22:54:22 +0800 Subject: [PATCH 04/11] fix: max answer bubble width --- app/components/chat/answer/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/chat/answer/index.tsx b/app/components/chat/answer/index.tsx index 2bce5b9..5137d79 100644 --- a/app/components/chat/answer/index.tsx +++ b/app/components/chat/answer/index.tsx @@ -178,7 +178,7 @@ const Answer: FC = ({ } -
+
{workflowProcess && ( From 672ad29e5dcc7c7d55a43247867b9ffc438c1143 Mon Sep 17 00:00:00 2001 From: lyzno1 Date: Wed, 10 Sep 2025 22:58:00 +0800 Subject: [PATCH 05/11] Integrate Streamdown and optimize chat layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace react-markdown with Streamdown for better streaming support - Fix input box positioning with proper sidebar offset - Optimize scrolling behavior with main container handling scroll - Add max-width constraint for assistant messages - Ensure proper spacing to prevent input box overlap 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- app/components/chat/index.tsx | 2 +- app/components/index.tsx | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/components/chat/index.tsx b/app/components/chat/index.tsx index fc67d85..9ded7bb 100644 --- a/app/components/chat/index.tsx +++ b/app/components/chat/index.tsx @@ -174,7 +174,7 @@ const Chat: FC = ({
{ !isHideSendInput && ( -
+
{ visionConfig?.enabled && ( diff --git a/app/components/index.tsx b/app/components/index.tsx index cafcc5f..8f610f2 100644 --- a/app/components/index.tsx +++ b/app/components/index.tsx @@ -703,18 +703,16 @@ const Main: FC = () => { { hasSetInputs && ( -
-
- -
+
+
) }
From c907432782c3f3f744f43f2f209155e97fd55f45 Mon Sep 17 00:00:00 2001 From: lyzno1 Date: Wed, 10 Sep 2025 23:44:18 +0800 Subject: [PATCH 06/11] fix: send button ui --- app/components/chat/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/chat/index.tsx b/app/components/chat/index.tsx index 9ded7bb..a959686 100644 --- a/app/components/chat/index.tsx +++ b/app/components/chat/index.tsx @@ -221,8 +221,8 @@ const Chat: FC = ({ onKeyDown={handleKeyDown} autoSize /> -
-
{query.trim().length}
+
+
{query.trim().length}
Date: Wed, 10 Sep 2025 23:45:34 +0800 Subject: [PATCH 07/11] Fix auto-scroll for page-level scrolling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace container scrollTop logic with scrollIntoView API to support page-level scroll bars instead of internal container scrolling. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- app/components/index.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/components/index.tsx b/app/components/index.tsx index 8f610f2..b582476 100644 --- a/app/components/index.tsx +++ b/app/components/index.tsx @@ -179,9 +179,15 @@ const Main: FC = () => { const [chatList, setChatList, getChatList] = useGetState([]) const chatListDomRef = useRef(null) useEffect(() => { - // scroll to bottom - if (chatListDomRef.current) - chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight + // scroll to bottom with page-level scrolling + if (chatListDomRef.current) { + setTimeout(() => { + chatListDomRef.current?.scrollIntoView({ + behavior: 'auto', + block: 'end', + }) + }, 50) + } }, [chatList, currConversationId]) // user can not edit inputs if user had send message const canEditInputs = !chatList.some(item => item.isAnswer === false) && isNewConversation From 532aba026a89d6bc14293c274feb9c0fd5c6eb23 Mon Sep 17 00:00:00 2001 From: lyzno1 Date: Wed, 10 Sep 2025 23:56:22 +0800 Subject: [PATCH 08/11] fix: filter empty URLs in image gallery to prevent browser reload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add validation to filter out empty and whitespace-only image URLs - Return null when no valid images exist - Prevents console errors and browser reload issues from empty img src 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- app/components/base/image-gallery/index.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/components/base/image-gallery/index.tsx b/app/components/base/image-gallery/index.tsx index bd46c34..0d078e2 100644 --- a/app/components/base/image-gallery/index.tsx +++ b/app/components/base/image-gallery/index.tsx @@ -32,12 +32,15 @@ const ImageGallery: FC = ({ }) => { const [imagePreviewUrl, setImagePreviewUrl] = useState('') - const imgNum = srcs.length + const validSrcs = srcs.filter(src => src && src.trim() !== '') + const imgNum = validSrcs.length const imgStyle = getWidthStyle(imgNum) + + if (imgNum === 0) { return null } + return (
- {/* TODO: support preview */} - {srcs.map((src, index) => ( + {validSrcs.map((src, index) => ( Date: Thu, 11 Sep 2025 12:39:35 +0800 Subject: [PATCH 09/11] fix: await params in dynamic route for Next.js 15 compatibility --- app/api/conversations/[conversationId]/name/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/conversations/[conversationId]/name/route.ts b/app/api/conversations/[conversationId]/name/route.ts index d9dd327..1bb0068 100644 --- a/app/api/conversations/[conversationId]/name/route.ts +++ b/app/api/conversations/[conversationId]/name/route.ts @@ -3,14 +3,14 @@ import { NextResponse } from 'next/server' import { client, getInfo } from '@/app/api/utils/common' export async function POST(request: NextRequest, { params }: { - params: { conversationId: string } + params: Promise<{ conversationId: string }> }) { const body = await request.json() const { auto_generate, name, } = body - const { conversationId } = params + const { conversationId } = await params const { user } = getInfo(request) // auto generate name From 18a4229464e495170c20588d4c0865b5216c1862 Mon Sep 17 00:00:00 2001 From: Eric-Guo Date: Mon, 15 Sep 2025 10:02:43 +0800 Subject: [PATCH 10/11] =?UTF-8?q?=E2=9C=A8Error:=20Route=20"/api/messages/?= =?UTF-8?q?[messageId]/feedbacks"=20used=20`params.messageId`.=20`params`?= =?UTF-8?q?=20should=20be=20awaited=20before=20using=20its=20properties.?= =?UTF-8?q?=20Learn=20more:=20https://nextjs.org/docs/messages/sync-dynami?= =?UTF-8?q?c-apis=20=20=20=20=20at=20POST=20(app/api/messages/[messageId]/?= =?UTF-8?q?feedbacks/route.ts:12:11)=20=20=2010=20|=20=20=20=20=20rating,?= =?UTF-8?q?=20=20=2011=20|=20=20=20}=20=3D=20body=20>=2012=20|=20=20=20con?= =?UTF-8?q?st=20{=20messageId=20}=20=3D=20params=20=20=20=20=20=20|=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20^=20=20=2013=20|=20=20=20const=20{?= =?UTF-8?q?=20user=20}=20=3D=20getInfo(request)=20=20=2014=20|=20=20=20con?= =?UTF-8?q?st=20{=20data=20}=20=3D=20await=20client.messageFeedback(messag?= =?UTF-8?q?eId,=20rating,=20user)=20=20=2015=20|=20=20=20return=20NextResp?= =?UTF-8?q?onse.json(data)=20=20POST=20/api/messages/36fd2d18-909b-4bb9-b4?= =?UTF-8?q?6d-6e7f72a705e4/feedbacks=20200=20in=20557ms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/messages/[messageId]/feedbacks/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/messages/[messageId]/feedbacks/route.ts b/app/api/messages/[messageId]/feedbacks/route.ts index c701e15..5e18e38 100644 --- a/app/api/messages/[messageId]/feedbacks/route.ts +++ b/app/api/messages/[messageId]/feedbacks/route.ts @@ -3,13 +3,13 @@ import { NextResponse } from 'next/server' import { client, getInfo } from '@/app/api/utils/common' export async function POST(request: NextRequest, { params }: { - params: { messageId: string } + params: Promise<{ messageId: string }> }) { const body = await request.json() const { rating, } = body - const { messageId } = params + const { messageId } = await params const { user } = getInfo(request) const { data } = await client.messageFeedback(messageId, rating, user) return NextResponse.json(data) From 34bb34f89021a1df459cdf9c3487dcf8b727f352 Mon Sep 17 00:00:00 2001 From: Eric-Guo Date: Sat, 13 Sep 2025 17:12:13 +0800 Subject: [PATCH 11/11] =?UTF-8?q?=E2=9C=A8[Error]=20Maximum=20update=20dep?= =?UTF-8?q?th=20exceeded.=20This=20can=20happen=20when=20a=20component=20c?= =?UTF-8?q?alls=20setState=20inside=20useEffect,=20but=20useEffect=20eithe?= =?UTF-8?q?r=20doesn't=20have=20a=20dependency=20array,=20or=20one=20of=20?= =?UTF-8?q?the=20dependencies=20changes=20on=20every=20render.=20=09error?= =?UTF-8?q?=20(intercept-console-error.js:57)=20=09getRootForUpdatedFiber?= =?UTF-8?q?=20(react-dom-client.development.js:3899)=20=09dispatchSetState?= =?UTF-8?q?Internal=20(react-dom-client.development.js:8249)=20=09dispatch?= =?UTF-8?q?SetState=20(react-dom-client.development.js:8209)=20=09(anonymo?= =?UTF-8?q?us=20function)=20(react-tooltip.min.mjs:18:14416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/base/tooltip/index.tsx | 46 +++++++++++++++++---------- app/components/welcome/index.tsx | 1 - 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/app/components/base/tooltip/index.tsx b/app/components/base/tooltip/index.tsx index 610f17b..e047a65 100644 --- a/app/components/base/tooltip/index.tsx +++ b/app/components/base/tooltip/index.tsx @@ -1,9 +1,8 @@ 'use client' import classNames from 'classnames' import type { FC } from 'react' -import React from 'react' -import { Tooltip as ReactTooltip } from 'react-tooltip' // fixed version to 5.8.3 https://github.com/ReactTooltip/react-tooltip/issues/972 -import 'react-tooltip/dist/react-tooltip.css' +import React, { useState } from 'react' +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' type TooltipProps = { selector: string @@ -15,6 +14,10 @@ type TooltipProps = { children: React.ReactNode } +const arrow = ( + +) + const Tooltip: FC = ({ selector, content, @@ -24,22 +27,31 @@ const Tooltip: FC = ({ className, clickable, }) => { + const [open, setOpen] = useState(false) + const triggerMethod = clickable ? 'click' : 'hover' + return ( -
- {React.cloneElement(children as React.ReactElement, { - 'data-tooltip-id': selector, - }) - } - + triggerMethod === 'click' && setOpen(v => !v)} + onMouseEnter={() => triggerMethod === 'hover' && setOpen(true)} + onMouseLeave={() => triggerMethod === 'hover' && setOpen(false)} > - {htmlContent && htmlContent} - -
+ {children} + + +
+ {htmlContent ?? content} + {arrow} +
+
+ ) } diff --git a/app/components/welcome/index.tsx b/app/components/welcome/index.tsx index b795261..9925259 100644 --- a/app/components/welcome/index.tsx +++ b/app/components/welcome/index.tsx @@ -37,7 +37,6 @@ const Welcome: FC = ({ savedInputs, onInputsChange, }) => { - console.log(promptConfig) const { t } = useTranslation() const hasVar = promptConfig.prompt_variables.length > 0 const [isFold, setIsFold] = useState(true)