diff --git a/frontend/app/aipanel/aipanelmessages.tsx b/frontend/app/aipanel/aipanelmessages.tsx index a8f72f86b..a32e3936b 100644 --- a/frontend/app/aipanel/aipanelmessages.tsx +++ b/frontend/app/aipanel/aipanelmessages.tsx @@ -18,6 +18,7 @@ export const AIPanelMessages = memo(({ messages, status, onContextMenu }: AIPane const isPanelOpen = useAtomValue(model.getPanelVisibleAtom()); const messagesEndRef = useRef(null); const messagesContainerRef = useRef(null); + const prevStatusRef = useRef(status); const scrollToBottom = () => { const container = messagesContainerRef.current; @@ -41,6 +42,19 @@ export const AIPanelMessages = memo(({ messages, status, onContextMenu }: AIPane } }, [isPanelOpen]); + useEffect(() => { + const wasStreaming = prevStatusRef.current === "streaming"; + const isNowNotStreaming = status !== "streaming"; + + if (wasStreaming && isNowNotStreaming) { + requestAnimationFrame(() => { + scrollToBottom(); + }); + } + + prevStatusRef.current = status; + }, [status]); + return (
{ + FocusManager.getInstance().refocusNode(); + }, 10); return; } } @@ -249,6 +252,9 @@ function switchBlockInDirection(direction: NavigateDirection) { const navResult = layoutModel.switchNodeFocusInDirection(direction, inWaveAI); if (navResult.atLeft) { FocusManager.getInstance().requestWaveAIFocus(); + setTimeout(() => { + FocusManager.getInstance().refocusNode(); + }, 10); return; } setTimeout(() => { diff --git a/package-lock.json b/package-lock.json index e8e603bfc..3a7484884 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "tsunami/frontend" ], "dependencies": { - "@ai-sdk/react": "^2.0.76", + "@ai-sdk/react": "^2.0.92", "@floating-ui/react": "^0.27.16", "@monaco-editor/loader": "^1.5.0", "@monaco-editor/react": "^4.7.0", @@ -29,7 +29,7 @@ "@xterm/addon-web-links": "^0.11.0", "@xterm/addon-webgl": "^0.18.0", "@xterm/xterm": "^5.5.0", - "ai": "^5.0.44", + "ai": "^5.0.92", "base64-js": "^1.5.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -272,13 +272,13 @@ "license": "MIT" }, "node_modules/@ai-sdk/gateway": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.0.tgz", - "integrity": "sha512-Gj0PuawK7NkZuyYgO/h5kDK/l6hFOjhLdTq3/Lli1FTl47iGmwhH1IZQpAL3Z09BeFYWakcwUmn02ovIm2wy9g==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.11.tgz", + "integrity": "sha512-B0Vt2Xv88Lo9rg861Oyzq/SdTmT4LiqdjkpOxpSPpNk8Ih5TXTgyDAsV/qW14N6asPdK1YI5PosFLnVzfK5LrA==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.12", + "@ai-sdk/provider-utils": "3.0.17", "@vercel/oidc": "3.0.3" }, "engines": { @@ -301,14 +301,14 @@ } }, "node_modules/@ai-sdk/provider-utils": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.12.tgz", - "integrity": "sha512-ZtbdvYxdMoria+2SlNarEk6Hlgyf+zzcznlD55EAl+7VZvJaSg2sqPvwArY7L6TfDEDJsnCq0fdhBSkYo0Xqdg==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz", + "integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.5" + "eventsource-parser": "^3.0.6" }, "engines": { "node": ">=18" @@ -318,13 +318,13 @@ } }, "node_modules/@ai-sdk/react": { - "version": "2.0.76", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.76.tgz", - "integrity": "sha512-ggAPzyaKJTqUWigpxMzI5DuC0Y3iEpDUPCgz6/6CpnKZY/iok+x5xiZhDemeaP0ILw5IQekV0kdgBR8JPgI8zQ==", + "version": "2.0.96", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.96.tgz", + "integrity": "sha512-9Jbch0zzbOMIInfsc58DpffE16b3nxDqBvW3ZA5xPpMAAt1sKSyIBngb3tvl8YOz009y8xztCcRE3/qwDytkRw==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider-utils": "3.0.12", - "ai": "5.0.76", + "@ai-sdk/provider-utils": "3.0.17", + "ai": "5.0.96", "swr": "^2.2.5", "throttleit": "2.1.0" }, @@ -12164,14 +12164,14 @@ } }, "node_modules/ai": { - "version": "5.0.76", - "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.76.tgz", - "integrity": "sha512-ZCxi1vrpyCUnDbtYrO/W8GLvyacV9689f00yshTIQ3mFFphbD7eIv40a2AOZBv3GGRA7SSRYIDnr56wcS/gyQg==", + "version": "5.0.96", + "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.96.tgz", + "integrity": "sha512-eSf9V0fKpPglCGb7gib7HoWmgpUbdlm6FAgfvY/lMNfIxKx8WWAfX0RenVqTODVq3OISKcxSs2Gsh+XzgYNm5g==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "2.0.0", + "@ai-sdk/gateway": "2.0.11", "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.12", + "@ai-sdk/provider-utils": "3.0.17", "@opentelemetry/api": "1.9.0" }, "engines": { @@ -34646,9 +34646,9 @@ "license": "MIT" }, "node_modules/zod": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", - "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "license": "MIT", "peer": true, "funding": { diff --git a/package.json b/package.json index 3a6651ddb..e49513c9c 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "vitest": "^3.0.9" }, "dependencies": { - "@ai-sdk/react": "^2.0.76", + "@ai-sdk/react": "^2.0.92", "@floating-ui/react": "^0.27.16", "@monaco-editor/loader": "^1.5.0", "@monaco-editor/react": "^4.7.0", @@ -104,7 +104,7 @@ "@xterm/addon-web-links": "^0.11.0", "@xterm/addon-webgl": "^0.18.0", "@xterm/xterm": "^5.5.0", - "ai": "^5.0.44", + "ai": "^5.0.92", "base64-js": "^1.5.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/pkg/aiusechat/openai/openai-backend.go b/pkg/aiusechat/openai/openai-backend.go index f551a7684..541c138b8 100644 --- a/pkg/aiusechat/openai/openai-backend.go +++ b/pkg/aiusechat/openai/openai-backend.go @@ -94,11 +94,14 @@ type OpenAIMessageContent struct { } func (c *OpenAIMessageContent) clean() *OpenAIMessageContent { - if c.PreviewUrl == "" { + if c.PreviewUrl == "" && (c.Type != "input_image" || c.Filename == "") { return c } rtn := *c rtn.PreviewUrl = "" + if c.Type == "input_image" { + rtn.Filename = "" + } return &rtn } diff --git a/pkg/aiusechat/openai/openai-convertmessage.go b/pkg/aiusechat/openai/openai-convertmessage.go index d2dc594a2..f0ac28eeb 100644 --- a/pkg/aiusechat/openai/openai-convertmessage.go +++ b/pkg/aiusechat/openai/openai-convertmessage.go @@ -45,6 +45,7 @@ func convertContentBlockToParts(block OpenAIMessageContent, role string) []uctyp parts = append(parts, uctypes.UIMessagePart{ Type: "data-userfile", Data: uctypes.UIMessageDataUserFile{ + FileName: block.Filename, MimeType: "image/*", PreviewUrl: block.PreviewUrl, }, @@ -317,6 +318,7 @@ func convertFileAIMessagePart(part uctypes.AIMessagePart) (*OpenAIMessageContent return &OpenAIMessageContent{ Type: "input_image", ImageUrl: imageUrl, + Filename: part.FileName, PreviewUrl: part.PreviewUrl, }, nil