Skip to content

feat(playground): improve Playground chat experience and Markdown rendering - #5217

Merged
Calcium-Ion merged 68 commits into
mainfrom
perf/playground
Jun 27, 2026
Merged

feat(playground): improve Playground chat experience and Markdown rendering#5217
Calcium-Ion merged 68 commits into
mainfrom
perf/playground

Conversation

@QuentinHsu

@QuentinHsu QuentinHsu commented May 31, 2026

Copy link
Copy Markdown
Collaborator

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)

概述

  • 这组改动围绕 Playground 聊天体验展开,覆盖对话管理、输入区、消息操作、错误恢复、流式处理和 Markdown 渲染。
  • 同时拆分了原本集中的 Playground 逻辑,降低组件复杂度,便于后续维护。

改动说明

  • 新增清空聊天历史、空状态提示、建议入口和错误恢复操作。
  • 拆分 Playground 的输入控件、输入工具、消息内容、消息编辑器、选项加载和会话状态逻辑。
  • 统一 AI 回复的 Markdown 渲染,覆盖标题、列表、任务列表、表格、图片、代码块、引用和 HTML details 折叠内容。
  • 优化代码块折叠、复制、下载、行号和最大高度表现。
  • 改善移动端消息操作、输入控件布局和刷新初期滚动边界。
  • 补充多语言文案,并调整相关样式。

使用方式

  • 打开 Playground 后可以直接使用新的空状态建议、清空聊天历史和错误恢复入口。
  • AI 回复中的 Markdown、代码块、表格、图片和折叠内容会按统一聊天样式渲染。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

(请在此粘贴截图、关键日志或测试报告,以证明变更生效)

CleanShot 2026-06-25 at 21 43 27

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Upgraded code blocks with collapsible previews, toolbar/title & filename support, localized copy tooltips, and one-click downloading.
    • Improved AI response rendering for rich markdown (tables, lists, images with placeholders, footnotes, details/alerts, and math).
    • Refreshed playground UI with clearer message actions, tool controls, and “clear chat history” support, plus starter-prompt model testing.
  • Bug Fixes
    • Improved conversation scrolling/overflow behavior.
    • More reliable cancellation for streaming and non-streaming generation.
  • Localization
    • Added new translated strings for playground actions, status text, and accessibility labels.

- extract conversation actions from the page component to keep message flow logic reusable.
- unify streaming and non-streaming generation state, including abort support for non-stream requests.
- simplify message rendering and payload construction while localizing Playground prompts.
- wrap saved Playground state with a storage version while still reading legacy values.

- validate config, parameter toggles, and messages before restoring them from localStorage.

- cap stored chat history to the latest messages to avoid oversized or stale state.
- route chat rendering, copy actions, and error display through shared message helpers.

- reuse the current-version update helper for non-streaming assistant responses.

- keep message version details behind utility functions to reduce future model churn.
- move Playground storage validation schemas into a dedicated module.

- keep storage read and write logic focused on migration, trimming, and persistence.

- preserve the existing storage envelope and validation behavior.
@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Playground chat and request handling were split into helper hooks and composed controls, with new cancellation and localized messages. AI response markdown now parses into typed nodes and renders through specialized components, with CodeMirror-based code blocks and updated layout support.

Changes

Playground Conversation and Chat Refactor

Layer / File(s) Summary
State, storage, and option loading
web/default/src/features/playground/lib/*, web/default/src/features/playground/hooks/use-playground-state.ts, web/default/src/features/playground/hooks/use-playground-options.ts, web/default/src/features/playground/api.ts, controller/user.go, controller/model_list_test.go
Message validation, versioned storage, initial state helpers, input control helpers, model/group fallback helpers, grouped model fetching, and option loading hooks are updated together.
Request flow and conversation actions
web/default/src/features/playground/hooks/index.ts, web/default/src/features/playground/hooks/use-chat-handler.ts, web/default/src/features/playground/hooks/use-stream-request.ts, web/default/src/features/playground/hooks/use-playground-conversation.ts
Abortable chat completion requests, streaming updates, conversation message lifecycle handlers, and the hooks barrel re-export are updated.
Playground UI composition and locales
web/default/src/features/playground/components/*, web/default/src/features/playground/index.tsx, web/default/src/components/model-group-selector.tsx, web/default/src/i18n/locales/*.json
Message actions, chat rendering, input composition, model-group selection, top-level wiring, helper barrels, and playground translations are updated.

Response Rendering and Messaging UI

Layer / File(s) Summary
Markdown contracts and normalization
web/default/src/components/ai-elements/response-content.ts, web/default/src/components/ai-elements/response-node-guards.ts, web/default/src/components/ai-elements/response-types.ts
Response types, node guards, and markdown content helpers define parsed node contracts, normalize fenced examples and custom tags, and split parsed nodes into body and footnotes.
Code block rendering and support
web/default/src/components/ai-elements/code-block.tsx, web/default/package.json, web/default/src/styles/index.css, web/default/rsbuild.config.ts
CodeMirror-based code blocks replace the previous HTML flow, and dependency, style, and build files add parser and token styling support.
Markdown node renderers
web/default/src/components/ai-elements/response-renderer-*.tsx
Specialized block, inline, alert, details, footnote, image, table, and code block renderers map parsed markdown nodes to React output.
Renderer dispatch and response component
web/default/src/components/ai-elements/response-renderer.tsx, web/default/src/components/ai-elements/response.tsx
The dispatcher maps parsed nodes to the specialized renderers, and the response component parses markdown into renderable nodes before rendering body content and footnotes.
Conversation, reasoning, and sources layout
web/default/src/components/ai-elements/conversation.tsx, web/default/src/components/ai-elements/reasoning.tsx, web/default/src/components/ai-elements/sources.tsx
Conversation, reasoning, and sources wrappers adjust overflow, spacing, and translated labels.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested reviewers

  • creamlike1024
  • seefs001

Poem

🐰 I hopped through chats and markdown streams,
with code blocks lit like little beams.
The playground hums, the bunnies cheer,
and footnotes land both crisp and clear.
Hop, hop—new labels bloom this year!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes to Playground chat UX and Markdown rendering.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/playground

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
web/default/src/components/ai-elements/response.tsx (1)

449-490: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix memo comparator for Response to account for className, components, and forwarded Streamdown props (web/default/src/components/ai-elements/response.tsx, ~449-490)

Response’s render depends on className, merges components overrides, and spreads ...props into <Streamdown>, but the memo comparator only checks children, so updates to overrides/styling/other Streamdown props can be skipped and leave stale output.

Suggested fix
 export const Response = memo(
   ({ className, children, components, ...props }: ResponseProps) => {
...
-  },
-  (prevProps, nextProps) => prevProps.children === nextProps.children
+  }
 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/components/ai-elements/response.tsx` around lines 449 - 490,
The memo comparator for the Response component only compares children but must
also consider className, components and any forwarded Streamdown props to avoid
stale renders; update the second argument to memo (the equality function used by
Response) to return false when prevProps.className !== nextProps.className, when
prevProps.components !== nextProps.components (reference/shallow compare), or
when any other keys in the rest/spread props differ (shallow-compare
prevProps.props vs nextProps.props or iterate keys in prevProps and nextProps to
compare values), otherwise return true — locate the memo call around the
Response declaration and replace the current (prevProps, nextProps) =>
prevProps.children === nextProps.children comparator with one that checks
children, className, components, and a shallow equality of the rest props
forwarded to Streamdown.
web/default/src/features/playground/hooks/use-stream-request.ts (1)

57-67: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Suppress callbacks from closed or superseded SSE sources.

Intentional closes never mark the stream as terminal before shutting down the source. If sse.js emits a late error or readystatechange while a stopped/replaced stream is closing, handleError still runs and can flip a user-cancelled or newer request into an error state.

Suggested fix
       const source = new SSE(API_ENDPOINTS.CHAT_COMPLETIONS, {
         headers: getCommonHeaders(),
         method: 'POST',
         payload: JSON.stringify(payload),
       })
@@
       const handleError = (errorMessage: string, errorCode?: string) => {
+        if (sseSourceRef.current !== source) {
+          return
+        }
         if (!isStreamCompleteRef.current) {
           onError(errorMessage, errorCode)
           closeActiveStream(source)
         }
       }
 
       source.addEventListener('message', (e: MessageEvent) => {
+        if (sseSourceRef.current !== source) return
         if (isStreamDoneMessage(e.data)) {
           isStreamCompleteRef.current = true
           closeActiveStream(source)
           onComplete()
           return
@@
       source.addEventListener('error', (e: Event & { data?: string }) => {
+        if (sseSourceRef.current !== source) return
         // Only handle errors if stream didn't complete normally
         if (!isStreamClosedReadyState(source.readyState)) {
@@
         'readystatechange',
         (e: Event & { readyState?: number }) => {
+          if (sseSourceRef.current !== source) return
           const errorMessage = getStreamReadyStateError(e.readyState, source)
@@
   const stopStream = useCallback(() => {
+    isStreamCompleteRef.current = true
     closeActiveStream()
   }, [closeActiveStream])

Also applies to: 69-74, 97-115, 130-132

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/hooks/use-stream-request.ts` around lines
57 - 67, When attaching event handlers to the new SSE instance, guard each
handler (handleError, handleMessage, readystatechange handler, etc.) so they
ignore events from closed or superseded sources: capture the local "source"
variable when you create the SSE and at the top of each callback return early
unless sseSourceRef.current === source (and optionally ensure
!isStreamCompleteRef.current). Apply the same early-return guard to every SSE
callback registration site (the blocks around the new SSE creation and the other
places noted) so late events from an old/closed source do not flip state for a
newer or intentionally cancelled stream.
🧹 Nitpick comments (14)
web/default/src/styles/index.css (1)

36-42: 💤 Low value

LGTM!

The light-mode Shiki styling correctly mirrors the existing dark-mode pattern and completes the dual-theme implementation.

Optional improvement: Update the comment on line 34 to reflect that both themes are now styled:

📝 Suggested comment update
-/* Shiki dual themes: token colors follow dark theme (pre background stays `bg-background` on the block) */
+/* Shiki dual themes: token colors follow light/dark theme via CSS variables (pre background stays `bg-background` on the block) */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/styles/index.css` around lines 36 - 42, Update the nearby
comment that mentions Shiki styling to state that both light and dark themes are
now styled; locate the CSS rule block for `.shiki span` and revise the comment
that precedes it (the comment referencing Shiki/dark-mode) so it clearly
indicates dual-theme support (light + dark) instead of only dark-mode.
web/default/src/features/playground/components/message-actions.tsx (1)

69-78: ⚡ Quick win

Avoid destructuring component props here.

This component is already carrying a fairly wide surface, and destructuring at the signature makes it harder to trace which values are actually consumed. Please accept props: MessageActionsProps and read props.message, props.onCopy, etc. As per coding guidelines "Do not destructure component props; use props.xxx directly instead for clarity".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/components/message-actions.tsx` around
lines 69 - 78, The MessageActions component currently destructures props in its
parameter list (function MessageActions({ message, onCopy, onRegenerate, onEdit,
onDelete, isGenerating = false, alwaysVisible = false, className = '' }:
MessageActionsProps)); change the signature to accept a single props parameter
(props: MessageActionsProps) and update all internal usages to reference
props.message, props.onCopy, props.onRegenerate, props.onEdit, props.onDelete,
props.isGenerating, props.alwaysVisible, and props.className (keeping the same
default behavior by applying defaults when reading the properties if needed) so
the component follows the "do not destructure props" guideline.
web/default/src/features/playground/components/message-error.tsx (1)

63-68: ⚡ Quick win

Use routed/link navigation instead of window.open for the settings CTA.

For an internal route, window.open(..., '_blank') sidesteps the app's navigation layer, and the new tab is opened without noopener,noreferrer. Prefer a Link/anchor-based CTA with the proper rel attributes, or useNavigate if this should stay in-app.

As per coding guidelines, web/default/**/*.{ts,tsx}: use useNavigate or Link for navigation while maintaining type safety; avoid direct manipulation of browser navigation APIs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/components/message-error.tsx` around
lines 63 - 68, Replace the direct window.open call in the MessageError
component’s Button onClick (which currently uses MODEL_PRICING_SETTINGS_PATH)
with the app routing API: either wrap the Button with a react-router Link (or
render an anchor with rel="noopener noreferrer") pointing to
MODEL_PRICING_SETTINGS_PATH, or use useNavigate from react-router-dom and call
navigate(MODEL_PRICING_SETTINGS_PATH) on click to preserve the app navigation
layer and type safety; update imports to pull in Link or useNavigate and remove
window.open usage.
web/default/src/features/playground/components/playground-suggestions.tsx (2)

50-52: ⚡ Quick win

Use props.xxx here instead of destructuring the component props.

That keeps this component aligned with the TSX convention used for playground components.

As per coding guidelines, "web/default/**/*.tsx: Do not destructure component props; use props.xxx directly instead for clarity".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/components/playground-suggestions.tsx`
around lines 50 - 52, The component PlaygroundSuggestions currently destructures
props in its signature; update it to accept a single props parameter and
reference properties as props.onSelect (i.e., change the function signature from
"PlaygroundSuggestions({ onSelect }: PlaygroundSuggestionsProps)" to accept
"props: PlaygroundSuggestionsProps" and replace internal uses of onSelect with
props.onSelect) so it follows the project's TSX convention for playground
components.

41-68: ⚡ Quick win

Move the suggestion icon colors out of inline styles.

The fixed hex colors bypass the Tailwind/theme pipeline, so they won't adapt cleanly to dark mode or shared design tokens. Prefer semantic classes or CSS variables resolved through cn().

As per coding guidelines, "web/default/**/*.{ts,tsx,css}: Use Tailwind utility classes as the primary styling approach; merge dynamic class names using cn(); avoid inline styles for dynamic scenarios" and "web/default/**/*.{tsx,css}: use CSS variables and dark: prefix for theming and dark mode".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/components/playground-suggestions.tsx`
around lines 41 - 68, The suggestions array currently stores hex colors and the
PlaygroundSuggestions component applies them via inline style on the Icon (see
suggestions and the Icon render in PlaygroundSuggestions), which bypasses
Tailwind/dark-mode theming; instead change the suggestions entries to provide a
semantic token or class name (e.g., colorClass or cssVarName) and update the
Icon render to set color via a resolved class using cn() or via a CSS variable
referenced in a Tailwind-friendly class (e.g., style={{
['--suggestion-icon-color']: 'var(--token)' }} paired with a class that uses
text-[color:var(--suggestion-icon-color)] and dark: variants). Update
getSuggestionDisplayState usage if needed to combine classes with cn() so no
inline hex styles remain and theming flows through Tailwind/CSS variables.
web/default/src/features/playground/components/playground-input-controls.tsx (1)

42-55: ⚡ Quick win

Keep component props on props instead of destructuring them here.

This file is under the TSX rule that prefers props.xxx, and destructuring this many values makes the helper closures harder to scan.

As per coding guidelines, "web/default/**/*.tsx: Do not destructure component props; use props.xxx directly instead for clarity".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/components/playground-input-controls.tsx`
around lines 42 - 55, The component PlaygroundInputControls currently
destructures its props in the function signature; change it to accept a single
parameter (props: PlaygroundInputControlsProps) and update all references inside
the component (e.g., disabled, groups, groupValue, isGenerating, isModelLoading,
models, modelValue, onGroupChange, onModelChange, onStop, text, tools) to use
props.xxx so helper closures and inner functions read props.disabled,
props.onStop, etc., rather than relying on the destructured variables; ensure
the exported function name PlaygroundInputControls remains unchanged and update
any default value handling (like isModelLoading) to reference props where
needed.
web/default/src/features/playground/lib/suggestion-utils.ts (1)

19-35: ⚡ Quick win

Drive the mobile-hidden state from metadata, not the "More" label.

This logic is brittle because a copy/key change breaks the behavior. Add an explicit flag on the suggestion definition instead of matching the English text here.

As per coding guidelines, "web/default/**/*.{tsx,ts}: All user-facing text content must support i18n using the t() function from useTranslation() in React components".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/lib/suggestion-utils.ts` around lines 19
- 35, The function getSuggestionDisplayState currently infers mobile-hidden
behavior by comparing text to MORE_SUGGESTION_TEXT which is brittle and violates
i18n rules; change the suggestion data shape to include an explicit boolean
(e.g., isMobileHidden) on the suggestion object, update
getSuggestionDisplayState to accept that flag instead of the text (replace the
parameter text: string with something like suggestion: { text: string;
isMobileHidden?: boolean } or accept the flag separately), use isMobileHidden to
choose between MOBILE_HIDDEN_SUGGESTION_CLASS_NAME and SUGGESTION_CLASS_NAME,
remove reliance on MORE_SUGGESTION_TEXT, and ensure that any user-facing label
continues to be localized via t() in the React component that renders the
suggestion rather than in this utility.
web/default/src/features/playground/components/playground-input-tools.tsx (1)

46-50: ⚡ Quick win

Prefer props.xxx over destructuring the component props.

This component is small enough that keeping a single props object reads more clearly and aligns with the repo convention.

As per coding guidelines, "web/default/**/*.tsx: Do not destructure component props; use props.xxx directly instead for clarity".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/components/playground-input-tools.tsx`
around lines 46 - 50, Change the component signature from destructured params to
a single props object: replace "export function PlaygroundInputTools({ disabled,
hasMessages = false, onClearMessages, }: PlaygroundInputToolsProps)" with
"export function PlaygroundInputTools(props: PlaygroundInputToolsProps)". Then
update all internal references to use props.disabled, props.hasMessages (use a
fallback like "props.hasMessages ?? false" where needed) and
props.onClearMessages instead of the destructured names; ensure any default
behavior for hasMessages is preserved via the nullish-coalescing fallback and
adjust prop type usage accordingly.
web/default/src/features/playground/components/playground-input.tsx (1)

49-63: ⚡ Quick win

Keep the component props on props instead of destructuring them.

This signature is large enough that the repo's props.xxx convention would be easier to follow here.

As per coding guidelines, "web/default/**/*.tsx: Do not destructure component props; use props.xxx directly instead for clarity".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/components/playground-input.tsx` around
lines 49 - 63, The PlaygroundInput component currently destructures its props in
the function signature; change it to accept a single parameter (props:
PlaygroundInputProps) and update all internal references from destructured names
(onSubmit, onStop, disabled, isGenerating, models, modelValue, onModelChange,
isModelLoading, groups, groupValue, onGroupChange, hasMessages, onClearMessages)
to use props.xxx (e.g., props.onSubmit, props.isModelLoading). Ensure any
default value (isModelLoading = false) is handled via the props
type/defaultProps or by using a fallback inside the function (e.g., const
isModelLoading = props.isModelLoading ?? false) and keep the component name
PlaygroundInput unchanged.
web/default/src/features/playground/hooks/use-playground-options.ts (1)

70-90: ⚡ Quick win

Use the shared server-error handler here.

These effects bypass the repo’s unified error flow by calling toast.error(...) directly, so playground option failures won’t go through the same HTTP-status-specific handling as the rest of the app. Please route these query errors through handleServerError instead of formatting/toasting them locally.

As per coding guidelines, "Centrally handle server errors using handleServerError; integrate into React Query global configuration and request interceptors; provide appropriate prompts based on HTTP status codes with i18n text".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/hooks/use-playground-options.ts` around
lines 70 - 90, Replace the direct toast.error calls inside the two useEffect
blocks in use-playground-options.ts with the shared server error handler: call
handleServerError(modelsError, { defaultMessage: t('Failed to load playground
models') }) in the models effect and handleServerError(groupsError, {
defaultMessage: t('Failed to load playground groups') }) in the groups effect
(instead of getOptionLoadErrorMessage/toast.error). Ensure you import
handleServerError at the top and pass the original error object and the i18n
default message so the central HTTP-status-specific handling runs.
web/default/src/features/playground/components/playground-message-editor.tsx (1)

37-45: ⚡ Quick win

Keep editor props on a single props object.

This component is another TSX guideline mismatch: please avoid destructuring props in the signature and read them through props.xxx instead.

As per coding guidelines, "Do not destructure component props; use props.xxx directly instead for clarity".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/components/playground-message-editor.tsx`
around lines 37 - 45, The component PlaygroundMessageEditor currently
destructures props in the function signature; change it to accept a single
parameter (props: PlaygroundMessageEditorProps) and update all internal
references to use props.editText, props.message, props.onCancelEdit,
props.onEditTextChange, props.onSaveEdit, props.onSaveEditAndSubmit, and
props.originalText instead of the destructured variables so the component
follows the "use props.xxx" guideline.
web/default/src/features/playground/components/message-error-actions.tsx (1)

30-35: ⚡ Quick win

Keep component props on props instead of destructuring them.

The new component signature breaks the repo’s TSX convention. Please accept a single props object and read props.disabled, props.onRetry, etc. inside the component.

As per coding guidelines, "Do not destructure component props; use props.xxx directly instead for clarity".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/components/message-error-actions.tsx`
around lines 30 - 35, The MessageErrorActions component currently destructures
props in its parameter list; change its signature to accept a single props
object (e.g., function MessageErrorActions(props: MessageErrorActionsProps)) and
update all internal uses to reference props.disabled, props.onDelete,
props.onEditPrompt, props.onRetry (and any other props) instead of the
destructured variables, keeping the component's behavior unchanged.
web/default/src/features/playground/components/playground-chat.tsx (1)

53-65: ⚡ Quick win

Use props.xxx instead of destructuring the component props.

Please keep the PlaygroundChat parameter as a single props object to match the repository TSX convention.

As per coding guidelines, "Do not destructure component props; use props.xxx directly instead for clarity".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/components/playground-chat.tsx` around
lines 53 - 65, The function currently destructures component props in the
PlaygroundChat signature; change it to accept a single props object
(PlaygroundChat(props: PlaygroundChatProps)) and update all internal usages to
reference props.xxx instead of the destructured names (e.g. props.messages,
props.onCopyMessage, props.onRegenerateMessage, props.onEditMessage,
props.onDeleteMessage, props.onSelectPrompt, props.isGenerating,
props.editingKey, props.onSaveEdit, props.onCancelEdit,
props.onSaveEditAndSubmit). Ensure the isGenerating default behavior is
preserved by using props.isGenerating ?? false (or an equivalent fallback) where
previously the default parameter was used.
web/default/src/features/playground/components/playground-empty-state.tsx (1)

40-42: ⚡ Quick win

Follow the TSX props convention here as well.

Please keep the component parameter as props and access props.onSelectPrompt inside the body instead of destructuring it in the signature.

As per coding guidelines, "Do not destructure component props; use props.xxx directly instead for clarity".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/components/playground-empty-state.tsx`
around lines 40 - 42, The PlaygroundEmptyState component currently destructures
props in its signature; change the function to accept a single parameter named
props (function PlaygroundEmptyState(props: PlaygroundEmptyStateProps)) and
update all internal usages to reference props.onSelectPrompt instead of using
the destructured onSelectPrompt variable so the component follows the TSX props
convention; ensure the exported function name PlaygroundEmptyState and the prop
type PlaygroundEmptyStateProps remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/default/src/components/ai-elements/code-block.tsx`:
- Line 75: The global highlightCache currently never evicts entries (const
highlightCache), causing unbounded memory growth; replace it with a bounded
cache (e.g., implement a small LRU wrapper around Map with a configurable
maxEntries like 100) so that on cache.get you move the key to the "most recently
used" position and on cache.set you evict the least-recently-used entry when
size > maxEntries; apply the same bounded-LRU change to the other cache usage
noted in the file (the map instance referenced around lines 152-181) so both
caches have a fixed cap and proper eviction semantics.

In `@web/default/src/components/ai-elements/response.tsx`:
- Around line 69-82: The MarkdownImage component currently hardcodes the
fallback string; import useTranslation and call const { t } = useTranslation()
inside MarkdownImage and replace 'Image not available' with
t('imageNotAvailable' or similar key) (also add the missing useTranslation
import). Separately, the Response component’s React.memo comparator only
compares children which causes stale renders when other props like className or
components change; either remove the custom comparator to use default shallow
prop comparison or update the comparator to compare all relevant props (e.g.,
className, components, and any other props used) so changes trigger rerenders.

In `@web/default/src/features/playground/components/message-actions.tsx`:
- Around line 174-175: The MoreHorizontal icon and the inline shortcut icons are
decorative and should be hidden from assistive tech; update the JSX to add
aria-hidden="true" to the MoreHorizontal component (the menu trigger icon) and
to each shortcut icon element rendered inside the menu items (the
shortcut/keyboard icon components) so screen readers won't announce redundant
content while preserving the existing accessible name and visible text.

In `@web/default/src/features/playground/components/message-error-actions.tsx`:
- Around line 42-80: The three decorative icons rendered inside the buttons
(RefreshCw, Edit, Trash2) are currently announced by screen readers; update the
JSX in message-error-actions.tsx where the onRetry, onEditPrompt, and onDelete
Buttons are rendered to mark these icons as decorative by adding
aria-hidden="true" to the RefreshCw, Edit, and Trash2 elements so they are
ignored by assistive tech while keeping the visible labels.

In `@web/default/src/features/playground/components/playground-chat.tsx`:
- Around line 69-76: The effect currently re-seeds the edit buffer whenever the
messages array changes, wiping unsaved edits; change the useEffect so it only
runs when entering/exiting edit mode by removing messages from the dependency
array (use [editingKey] only). Keep the existing guard (if (!editingKey)
return), call getEditingMessageContent(messages, editingKey) inside the effect,
and continue to setEditText(content) and setOriginalText(content) so the editor
is only initialized when editingKey changes.

In `@web/default/src/features/playground/components/playground-empty-state.tsx`:
- Around line 68-76: The Icon inside the starter prompt Button is decorative and
should be hidden from screen readers; update the Icon JSX in the Button (the
Icon component rendered within the Button that maps over prompt) to include
aria-hidden="true" so only the visible prompt text (span with {prompt}) is
announced; keep the Button, onSelectPrompt, and prompt usage unchanged.

In `@web/default/src/features/playground/components/playground-input.tsx`:
- Line 114: PlaygroundSuggestions currently invokes onSubmit directly which
bypasses the component's disabled/generating guards; instead create and pass a
wrapper handler (e.g., handleSuggestionSelect) to PlaygroundSuggestions'
onSelect that performs the same checks as your form submit flow (the same guards
used by onSubmit/handleSubmit or the isDisabled/isGenerating checks around the
textarea/send button) and only calls onSubmit when the guard allows it. Locate
PlaygroundSuggestions, the onSelect prop, and the existing onSubmit/handleSubmit
logic and ensure the suggestion selection path routes through that guarded
function rather than calling onSubmit directly.

In
`@web/default/src/features/playground/components/playground-message-editor.tsx`:
- Around line 107-151: The four SVG icon components (Send, Check, RotateCcw, X)
used inside the PlaygroundMessageEditor buttons are decorative because each
button already renders a text label; update those icon elements to include
aria-hidden="true" so assistive tech ignores them. Locate the icons within the
button blocks that call onSaveEditAndSubmit/onSaveEdit (Send, Check), the reset
button that calls onEditTextChange (RotateCcw), and the cancel button that calls
handleCancel (X) and add aria-hidden="true" to each icon element.
- Around line 71-88: The keyboard handler handleKeyDown should ignore shortcuts
while IME composition is active; add a guard at the start of the function that
checks event.nativeEvent.isComposing (cast to any if necessary) and returns
early if true so Escape and Ctrl/Cmd+Enter are not processed during composition.
Keep the rest of the logic unchanged (preserve the existing prevents, canSave
check, and the onSaveEdit/onSaveEditAndSubmit branches).

In `@web/default/src/features/playground/hooks/use-chat-handler.ts`:
- Around line 184-195: The stopGeneration handler currently finalizes any
pending assistant placeholder by calling completeAssistantMessage even when it
has no content; change it so that after stopStream/abort you only finalize the
last assistant message if it actually contains content or reasoning. In the
onMessageUpdate/updateLastAssistantMessage callback check the pending message
(isAssistantMessagePending) and if it has non-empty content or reasoning tokens
then call completeAssistantMessage; otherwise mark it as interrupted (matching
sanitizeMessagesOnLoad behavior) or leave it as-not-complete instead of
converting to COMPLETE. Ensure you reference stopGeneration, abortControllerRef,
onMessageUpdate, updateLastAssistantMessage, isAssistantMessagePending and
completeAssistantMessage when making the change.
- Around line 127-165: The current non-streaming request can overwrite newer
results because only the finally block checks requestIdRef; fix by capturing the
local requestId (already done) and guard all state-updating branches so they
only apply if requestIdRef.current === requestId: before calling
onMessageUpdate/updateLastAssistantMessage/applyChatCompletionResponse and
before calling handleStreamError (both in the normal flow and in the catch),
check that the request is still the latest; use the existing requestIdRef,
abortControllerRef, onMessageUpdate, updateLastAssistantMessage,
applyChatCompletionResponse, parseRequestErrorDetails and handleStreamError
symbols to locate and wrap those calls with the requestId check so superseded
responses are ignored.

In `@web/default/src/features/playground/lib/conversation-message-utils.ts`:
- Around line 101-114: The current branch that returns early when shouldSubmit
is false leaves assistant replies after the edited user message, causing an
inconsistent transcript; change the behavior in the check inside the function
handling updatedMessages/messageIndex/shouldSubmit so that when shouldSubmit ===
false and the message at updatedMessages[messageIndex].from ===
MESSAGE_ROLES.USER, you truncate any downstream turns (slice messages to
updatedMessages.slice(0, messageIndex + 1)) and return shouldSend: false
(instead of preserving subsequent assistant replies); keep references to
createLoadingAssistantMessage only in the shouldSend true path so no loading
assistant is appended when not submitting.

In `@web/default/src/features/playground/lib/input-tool-utils.ts`:
- Around line 38-53: getAttachmentActionNotice currently returns the internal
action id as the description; update it to look up the matching entry in
ATTACHMENT_ACTIONS (match by action) and return a user-facing, localizable
string instead of the raw slug by importing and using t from 'i18next' (import {
t } from 'i18next') to translate the entry's label or label key; modify
getAttachmentActionNotice(action: string) to find ATTACHMENT_ACTIONS.find(a =>
a.action === action) and call t(found.label) (or t(found.labelKey) if you change
labels to keys), falling back to a generic translated notice when not found.

In `@web/default/src/features/playground/lib/message-content-utils.ts`:
- Around line 57-64: The visibility check currently uses raw versionContent
length which can be non-empty even when parsed visible text is empty; update
shouldShowMessageContent to derive displayContent via
parseThinkTags(versionContent).visibleContent and use displayContent.length > 0
in the return expression (instead of versionContent.length > 0), and apply the
same change to the other occurrence that calls shouldShowMessageContent (the
similar logic around the alternate check referenced in the comment) so both
places base visibility on parseThinkTags(...).visibleContent.

In `@web/default/src/features/playground/lib/message-error-utils.ts`:
- Around line 26-27: Replace the hardcoded user-facing fallback string by using
i18next translation: import { t } from 'i18next' and change
FALLBACK_ERROR_CONTENT to call t with an appropriate key (e.g.,
t('errors.unknown')) or expose a getFallbackErrorContent() that returns
t('errors.unknown') so translations are applied at runtime; keep
MODEL_PRICE_ERROR_CODE unchanged and ensure the chosen translation key is used
wherever FALLBACK_ERROR_CONTENT was referenced.

In `@web/default/src/features/playground/lib/message-reasoning-utils.ts`:
- Around line 30-69: The parser parseThinkTags currently removes any literal
"<think>" occurrences from assistant replies; update the logic so parsing only
runs when the message explicitly contains reasoning metadata (e.g., as checked
by getMessageContentState) or tighten parseThinkTags to only strip tags that
match the exact protocol shape you emit (for example anchored tags or tags with
a special prefix/suffix or surrounding whitespace/newline pattern), preserving
literal or in-code instances; modify getMessageContentState to gate calling
parseThinkTags based on that metadata or change parseThinkTags to validate the
tag context (e.g., ensure tags are not inside backticks/code blocks and match
the protocol pattern) and only then remove them while leaving other text
untouched.

In `@web/default/src/features/playground/lib/playground-option-utils.ts`:
- Around line 49-53: getOptionLoadErrorMessage currently returns error.message
even when it's an empty string, causing callers to lose the fallbackMessage;
update getOptionLoadErrorMessage to check error instanceof Error and that
error.message is non-empty (e.g., truthy after trimming) and only then return
error.message, otherwise return the fallbackMessage so empty Error.message
values do not override the fallback.

In `@web/default/src/features/playground/lib/request-error-utils.ts`:
- Around line 21-31: RequestErrorLike currently models response.data.error.code
but not response.data.error.message, causing server-provided messages to be
lost; update the RequestErrorLike type to include response.data.error.message
and then change the error-parsing logic (the code that reads from
RequestErrorLike in this module) to prefer response.data.error.message first,
then response.data.message, and finally fall back to the generic wrapper message
so non-streaming failures like { error: { code, message } } surface the
actionable server message.

In `@web/default/src/features/playground/lib/storage.ts`:
- Around line 42-49: unwrapStoredValue currently unpacks any {version, data}
envelope without validating the version, allowing incompatible persisted state
to pass through; modify unwrapStoredValue to compare the envelope's version
against STORAGE_VERSION (the same constant used by writeStoredValue) and return
null (or undefined) when they differ so callers know the stored value is
unusable; reference the StoredEnvelope shape and the unwrapStoredValue function
and ensure each caller/loader treats a null return as "no usable stored value"
before calling schema.parse(...) so migrations or resets can occur.

---

Outside diff comments:
In `@web/default/src/components/ai-elements/response.tsx`:
- Around line 449-490: The memo comparator for the Response component only
compares children but must also consider className, components and any forwarded
Streamdown props to avoid stale renders; update the second argument to memo (the
equality function used by Response) to return false when prevProps.className !==
nextProps.className, when prevProps.components !== nextProps.components
(reference/shallow compare), or when any other keys in the rest/spread props
differ (shallow-compare prevProps.props vs nextProps.props or iterate keys in
prevProps and nextProps to compare values), otherwise return true — locate the
memo call around the Response declaration and replace the current (prevProps,
nextProps) => prevProps.children === nextProps.children comparator with one that
checks children, className, components, and a shallow equality of the rest props
forwarded to Streamdown.

In `@web/default/src/features/playground/hooks/use-stream-request.ts`:
- Around line 57-67: When attaching event handlers to the new SSE instance,
guard each handler (handleError, handleMessage, readystatechange handler, etc.)
so they ignore events from closed or superseded sources: capture the local
"source" variable when you create the SSE and at the top of each callback return
early unless sseSourceRef.current === source (and optionally ensure
!isStreamCompleteRef.current). Apply the same early-return guard to every SSE
callback registration site (the blocks around the new SSE creation and the other
places noted) so late events from an old/closed source do not flip state for a
newer or intentionally cancelled stream.

---

Nitpick comments:
In `@web/default/src/features/playground/components/message-actions.tsx`:
- Around line 69-78: The MessageActions component currently destructures props
in its parameter list (function MessageActions({ message, onCopy, onRegenerate,
onEdit, onDelete, isGenerating = false, alwaysVisible = false, className = '' }:
MessageActionsProps)); change the signature to accept a single props parameter
(props: MessageActionsProps) and update all internal usages to reference
props.message, props.onCopy, props.onRegenerate, props.onEdit, props.onDelete,
props.isGenerating, props.alwaysVisible, and props.className (keeping the same
default behavior by applying defaults when reading the properties if needed) so
the component follows the "do not destructure props" guideline.

In `@web/default/src/features/playground/components/message-error-actions.tsx`:
- Around line 30-35: The MessageErrorActions component currently destructures
props in its parameter list; change its signature to accept a single props
object (e.g., function MessageErrorActions(props: MessageErrorActionsProps)) and
update all internal uses to reference props.disabled, props.onDelete,
props.onEditPrompt, props.onRetry (and any other props) instead of the
destructured variables, keeping the component's behavior unchanged.

In `@web/default/src/features/playground/components/message-error.tsx`:
- Around line 63-68: Replace the direct window.open call in the MessageError
component’s Button onClick (which currently uses MODEL_PRICING_SETTINGS_PATH)
with the app routing API: either wrap the Button with a react-router Link (or
render an anchor with rel="noopener noreferrer") pointing to
MODEL_PRICING_SETTINGS_PATH, or use useNavigate from react-router-dom and call
navigate(MODEL_PRICING_SETTINGS_PATH) on click to preserve the app navigation
layer and type safety; update imports to pull in Link or useNavigate and remove
window.open usage.

In `@web/default/src/features/playground/components/playground-chat.tsx`:
- Around line 53-65: The function currently destructures component props in the
PlaygroundChat signature; change it to accept a single props object
(PlaygroundChat(props: PlaygroundChatProps)) and update all internal usages to
reference props.xxx instead of the destructured names (e.g. props.messages,
props.onCopyMessage, props.onRegenerateMessage, props.onEditMessage,
props.onDeleteMessage, props.onSelectPrompt, props.isGenerating,
props.editingKey, props.onSaveEdit, props.onCancelEdit,
props.onSaveEditAndSubmit). Ensure the isGenerating default behavior is
preserved by using props.isGenerating ?? false (or an equivalent fallback) where
previously the default parameter was used.

In `@web/default/src/features/playground/components/playground-empty-state.tsx`:
- Around line 40-42: The PlaygroundEmptyState component currently destructures
props in its signature; change the function to accept a single parameter named
props (function PlaygroundEmptyState(props: PlaygroundEmptyStateProps)) and
update all internal usages to reference props.onSelectPrompt instead of using
the destructured onSelectPrompt variable so the component follows the TSX props
convention; ensure the exported function name PlaygroundEmptyState and the prop
type PlaygroundEmptyStateProps remain unchanged.

In
`@web/default/src/features/playground/components/playground-input-controls.tsx`:
- Around line 42-55: The component PlaygroundInputControls currently
destructures its props in the function signature; change it to accept a single
parameter (props: PlaygroundInputControlsProps) and update all references inside
the component (e.g., disabled, groups, groupValue, isGenerating, isModelLoading,
models, modelValue, onGroupChange, onModelChange, onStop, text, tools) to use
props.xxx so helper closures and inner functions read props.disabled,
props.onStop, etc., rather than relying on the destructured variables; ensure
the exported function name PlaygroundInputControls remains unchanged and update
any default value handling (like isModelLoading) to reference props where
needed.

In `@web/default/src/features/playground/components/playground-input-tools.tsx`:
- Around line 46-50: Change the component signature from destructured params to
a single props object: replace "export function PlaygroundInputTools({ disabled,
hasMessages = false, onClearMessages, }: PlaygroundInputToolsProps)" with
"export function PlaygroundInputTools(props: PlaygroundInputToolsProps)". Then
update all internal references to use props.disabled, props.hasMessages (use a
fallback like "props.hasMessages ?? false" where needed) and
props.onClearMessages instead of the destructured names; ensure any default
behavior for hasMessages is preserved via the nullish-coalescing fallback and
adjust prop type usage accordingly.

In `@web/default/src/features/playground/components/playground-input.tsx`:
- Around line 49-63: The PlaygroundInput component currently destructures its
props in the function signature; change it to accept a single parameter (props:
PlaygroundInputProps) and update all internal references from destructured names
(onSubmit, onStop, disabled, isGenerating, models, modelValue, onModelChange,
isModelLoading, groups, groupValue, onGroupChange, hasMessages, onClearMessages)
to use props.xxx (e.g., props.onSubmit, props.isModelLoading). Ensure any
default value (isModelLoading = false) is handled via the props
type/defaultProps or by using a fallback inside the function (e.g., const
isModelLoading = props.isModelLoading ?? false) and keep the component name
PlaygroundInput unchanged.

In
`@web/default/src/features/playground/components/playground-message-editor.tsx`:
- Around line 37-45: The component PlaygroundMessageEditor currently
destructures props in the function signature; change it to accept a single
parameter (props: PlaygroundMessageEditorProps) and update all internal
references to use props.editText, props.message, props.onCancelEdit,
props.onEditTextChange, props.onSaveEdit, props.onSaveEditAndSubmit, and
props.originalText instead of the destructured variables so the component
follows the "use props.xxx" guideline.

In `@web/default/src/features/playground/components/playground-suggestions.tsx`:
- Around line 50-52: The component PlaygroundSuggestions currently destructures
props in its signature; update it to accept a single props parameter and
reference properties as props.onSelect (i.e., change the function signature from
"PlaygroundSuggestions({ onSelect }: PlaygroundSuggestionsProps)" to accept
"props: PlaygroundSuggestionsProps" and replace internal uses of onSelect with
props.onSelect) so it follows the project's TSX convention for playground
components.
- Around line 41-68: The suggestions array currently stores hex colors and the
PlaygroundSuggestions component applies them via inline style on the Icon (see
suggestions and the Icon render in PlaygroundSuggestions), which bypasses
Tailwind/dark-mode theming; instead change the suggestions entries to provide a
semantic token or class name (e.g., colorClass or cssVarName) and update the
Icon render to set color via a resolved class using cn() or via a CSS variable
referenced in a Tailwind-friendly class (e.g., style={{
['--suggestion-icon-color']: 'var(--token)' }} paired with a class that uses
text-[color:var(--suggestion-icon-color)] and dark: variants). Update
getSuggestionDisplayState usage if needed to combine classes with cn() so no
inline hex styles remain and theming flows through Tailwind/CSS variables.

In `@web/default/src/features/playground/hooks/use-playground-options.ts`:
- Around line 70-90: Replace the direct toast.error calls inside the two
useEffect blocks in use-playground-options.ts with the shared server error
handler: call handleServerError(modelsError, { defaultMessage: t('Failed to load
playground models') }) in the models effect and handleServerError(groupsError, {
defaultMessage: t('Failed to load playground groups') }) in the groups effect
(instead of getOptionLoadErrorMessage/toast.error). Ensure you import
handleServerError at the top and pass the original error object and the i18n
default message so the central HTTP-status-specific handling runs.

In `@web/default/src/features/playground/lib/suggestion-utils.ts`:
- Around line 19-35: The function getSuggestionDisplayState currently infers
mobile-hidden behavior by comparing text to MORE_SUGGESTION_TEXT which is
brittle and violates i18n rules; change the suggestion data shape to include an
explicit boolean (e.g., isMobileHidden) on the suggestion object, update
getSuggestionDisplayState to accept that flag instead of the text (replace the
parameter text: string with something like suggestion: { text: string;
isMobileHidden?: boolean } or accept the flag separately), use isMobileHidden to
choose between MOBILE_HIDDEN_SUGGESTION_CLASS_NAME and SUGGESTION_CLASS_NAME,
remove reliance on MORE_SUGGESTION_TEXT, and ensure that any user-facing label
continues to be localized via t() in the React component that renders the
suggestion rather than in this utility.

In `@web/default/src/styles/index.css`:
- Around line 36-42: Update the nearby comment that mentions Shiki styling to
state that both light and dark themes are now styled; locate the CSS rule block
for `.shiki span` and revise the comment that precedes it (the comment
referencing Shiki/dark-mode) so it clearly indicates dual-theme support (light +
dark) instead of only dark-mode.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 641487d7-1335-4799-8e57-5a09c32317a4

📥 Commits

Reviewing files that changed from the base of the PR and between 0c7aceb and f53b557.

📒 Files selected for processing (53)
  • web/default/src/components/ai-elements/code-block.tsx
  • web/default/src/components/ai-elements/conversation.tsx
  • web/default/src/components/ai-elements/reasoning.tsx
  • web/default/src/components/ai-elements/response.tsx
  • web/default/src/components/ai-elements/sources.tsx
  • web/default/src/features/playground/api.ts
  • web/default/src/features/playground/components/message-actions.tsx
  • web/default/src/features/playground/components/message-error-actions.tsx
  • web/default/src/features/playground/components/message-error.tsx
  • web/default/src/features/playground/components/playground-chat.tsx
  • web/default/src/features/playground/components/playground-empty-state.tsx
  • web/default/src/features/playground/components/playground-input-controls.tsx
  • web/default/src/features/playground/components/playground-input-tools.tsx
  • web/default/src/features/playground/components/playground-input.tsx
  • web/default/src/features/playground/components/playground-message-content.tsx
  • web/default/src/features/playground/components/playground-message-editor.tsx
  • web/default/src/features/playground/components/playground-suggestions.tsx
  • web/default/src/features/playground/hooks/index.ts
  • web/default/src/features/playground/hooks/use-chat-handler.ts
  • web/default/src/features/playground/hooks/use-playground-conversation.ts
  • web/default/src/features/playground/hooks/use-playground-options.ts
  • web/default/src/features/playground/hooks/use-playground-state.ts
  • web/default/src/features/playground/hooks/use-stream-request.ts
  • web/default/src/features/playground/index.tsx
  • web/default/src/features/playground/lib/conversation-message-utils.ts
  • web/default/src/features/playground/lib/index.ts
  • web/default/src/features/playground/lib/input-control-utils.ts
  • web/default/src/features/playground/lib/input-tool-utils.ts
  • web/default/src/features/playground/lib/message-action-utils.ts
  • web/default/src/features/playground/lib/message-content-utils.ts
  • web/default/src/features/playground/lib/message-editor-utils.ts
  • web/default/src/features/playground/lib/message-error-utils.ts
  • web/default/src/features/playground/lib/message-reasoning-utils.ts
  • web/default/src/features/playground/lib/message-streaming-utils.ts
  • web/default/src/features/playground/lib/message-styles.ts
  • web/default/src/features/playground/lib/message-update-utils.ts
  • web/default/src/features/playground/lib/message-utils.ts
  • web/default/src/features/playground/lib/payload-builder.ts
  • web/default/src/features/playground/lib/playground-option-utils.ts
  • web/default/src/features/playground/lib/playground-state-utils.ts
  • web/default/src/features/playground/lib/request-error-utils.ts
  • web/default/src/features/playground/lib/storage-schema.ts
  • web/default/src/features/playground/lib/storage.ts
  • web/default/src/features/playground/lib/stream-utils.ts
  • web/default/src/features/playground/lib/suggestion-utils.ts
  • web/default/src/features/usage-logs/components/usage-logs-mobile-card.tsx
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/styles/index.css

Comment thread web/default/src/components/ai-elements/code-block.tsx Outdated
Comment thread web/default/src/components/ai-elements/response.tsx Outdated
Comment on lines +174 to +175
<MoreHorizontal className='size-4' />
<span className='sr-only'>{t('Open menu')}</span>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Hide these decorative icons from assistive tech.

The trigger already exposes an accessible name, and each menu item already renders visible text. Mark the MoreHorizontal and shortcut icons as decorative so screen readers don't announce redundant content.

Proposed fix
-            <MoreHorizontal className='size-4' />
+            <MoreHorizontal aria-hidden='true' className='size-4' />
@@
-                    <Icon className='size-4' />
+                    <Icon aria-hidden='true' className='size-4' />
As per coding guidelines "add `aria-hidden=\"true\"` to decorative icons".

Also applies to: 190-191

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/components/message-actions.tsx` around
lines 174 - 175, The MoreHorizontal icon and the inline shortcut icons are
decorative and should be hidden from assistive tech; update the JSX to add
aria-hidden="true" to the MoreHorizontal component (the menu trigger icon) and
to each shortcut icon element rendered inside the menu items (the
shortcut/keyboard icon components) so screen readers won't announce redundant
content while preserving the existing accessible name and visible text.

Comment thread web/default/src/features/playground/components/playground-chat.tsx Outdated
Comment on lines +26 to +27
const MODEL_PRICE_ERROR_CODE = 'model_price_error'
const FALLBACK_ERROR_CONTENT = 'An unknown error occurred'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the fallback error copy.

FALLBACK_ERROR_CONTENT is user-facing text, so this will bypass translations whenever the message has no content.

Suggested fix
+import { t } from 'i18next'
+
 const MODEL_PRICE_ERROR_CODE = 'model_price_error'
-const FALLBACK_ERROR_CONTENT = 'An unknown error occurred'
+const FALLBACK_ERROR_CONTENT = t('playground.errors.unknown')

As per coding guidelines, web/default/**/*.ts: use import { t } from 'i18next' for translations in non-React environments.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/lib/message-error-utils.ts` around lines
26 - 27, Replace the hardcoded user-facing fallback string by using i18next
translation: import { t } from 'i18next' and change FALLBACK_ERROR_CONTENT to
call t with an appropriate key (e.g., t('errors.unknown')) or expose a
getFallbackErrorContent() that returns t('errors.unknown') so translations are
applied at runtime; keep MODEL_PRICE_ERROR_CODE unchanged and ensure the chosen
translation key is used wherever FALLBACK_ERROR_CONTENT was referenced.

Comment on lines +30 to +69
export function parseThinkTags(content: string): ParsedThinkTags {
if (!content.includes('<think>')) {
return { visibleContent: content, reasoning: '', hasUnclosedTag: false }
}

const visibleParts: string[] = []
const reasoningParts: string[] = []
let currentPos = 0
let hasUnclosedTag = false

while (true) {
const openPos = content.indexOf('<think>', currentPos)

if (openPos === -1) {
if (currentPos < content.length) {
visibleParts.push(content.substring(currentPos))
}
break
}

if (openPos > currentPos) {
visibleParts.push(content.substring(currentPos, openPos))
}

const closePos = content.indexOf('</think>', openPos + 7)

if (closePos === -1) {
reasoningParts.push(content.substring(openPos + 7))
hasUnclosedTag = true
break
}

reasoningParts.push(content.substring(openPos + 7, closePos))
currentPos = closePos + 8
}

return {
visibleContent: visibleParts.join('').trim(),
reasoning: reasoningParts.join('\n\n').trim(),
hasUnclosedTag,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't treat every literal <think> as hidden reasoning.

getMessageContentState() runs every assistant reply through parseThinkTags() (web/default/src/features/playground/lib/message-content-utils.ts:62-64), so a normal response that documents <think> literally or includes it in a code sample will lose part of its visible output here. Please gate this parsing behind reasoning-specific metadata, or tighten the parser so it only strips the protocol shape you actually emit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/lib/message-reasoning-utils.ts` around
lines 30 - 69, The parser parseThinkTags currently removes any literal "<think>"
occurrences from assistant replies; update the logic so parsing only runs when
the message explicitly contains reasoning metadata (e.g., as checked by
getMessageContentState) or tighten parseThinkTags to only strip tags that match
the exact protocol shape you emit (for example anchored tags or tags with a
special prefix/suffix or surrounding whitespace/newline pattern), preserving
literal or in-code instances; modify getMessageContentState to gate calling
parseThinkTags based on that metadata or change parseThinkTags to validate the
tag context (e.g., ensure tags are not inside backticks/code blocks and match
the protocol pattern) and only then remove them while leaving other text
untouched.

Comment on lines +49 to +53
export function getOptionLoadErrorMessage(
error: unknown,
fallbackMessage: string
): string {
return error instanceof Error ? error.message : fallbackMessage

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preserve the fallback when Error.message is empty.

Some thrown Error objects carry an empty message. In that case this returns '' and the caller loses the fallback text entirely.

Suggested fix
 export function getOptionLoadErrorMessage(
   error: unknown,
   fallbackMessage: string
 ): string {
-  return error instanceof Error ? error.message : fallbackMessage
+  return error instanceof Error && error.message.trim().length > 0
+    ? error.message
+    : fallbackMessage
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/lib/playground-option-utils.ts` around
lines 49 - 53, getOptionLoadErrorMessage currently returns error.message even
when it's an empty string, causing callers to lose the fallbackMessage; update
getOptionLoadErrorMessage to check error instanceof Error and that error.message
is non-empty (e.g., truthy after trimming) and only then return error.message,
otherwise return the fallbackMessage so empty Error.message values do not
override the fallback.

Comment on lines +21 to +31
type RequestErrorLike = {
message?: string
response?: {
data?: {
error?: {
code?: string
}
message?: string
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preserve nested backend error messages.

This parser reads response.data.error.code but never response.data.error.message, so non-streaming failures shaped like { error: { code, message } } lose the actionable server message and fall back to a generic wrapper message.

Suggested fix
 type RequestErrorLike = {
   message?: string
   response?: {
     data?: {
       error?: {
         code?: string
+        message?: string
       }
       message?: string
     }
   }
 }
@@
   return {
     errorCode: requestError?.response?.data?.error?.code || undefined,
     errorMessage:
+      requestError?.response?.data?.error?.message ||
       requestError?.response?.data?.message ||
       requestError?.message ||
       ERROR_MESSAGES.API_REQUEST_ERROR,
   }
 }

Also applies to: 41-46

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/playground/lib/request-error-utils.ts` around lines
21 - 31, RequestErrorLike currently models response.data.error.code but not
response.data.error.message, causing server-provided messages to be lost; update
the RequestErrorLike type to include response.data.error.message and then change
the error-parsing logic (the code that reads from RequestErrorLike in this
module) to prefer response.data.error.message first, then response.data.message,
and finally fall back to the generic wrapper message so non-streaming failures
like { error: { code, message } } surface the actionable server message.

Comment thread web/default/src/features/playground/lib/storage.ts Outdated
QuentinHsu added 24 commits June 7, 2026 07:22
- move model and group queries into a dedicated hook so the page component stays focused on layout wiring.
- preserve existing fallback selection and error toast behavior while reusing the hook through the playground barrel export.
- move static prompt suggestion rendering into a focused component so the input stays centered on compose controls.
- preserve translated suggestion submission behavior while isolating icon metadata from the input form.
- move attachment and search controls into a dedicated component so the prompt input stays focused on compose state.
- keep existing development toast behavior and disabled handling while centralizing tool metadata.
- move model, group, send, and stop controls into a focused component so the input only manages compose state.
- preserve existing disabled states and generation button behavior while isolating control rendering.
- move sources, reasoning, loading, error, and response rendering into a dedicated message content component.
- keep the chat list focused on message iteration, edit state, and action wiring without changing display behavior.
- move inline message editing controls into a dedicated editor component so the chat list stays focused on rendering flow.
- preserve save, save-and-submit, cancel, and disabled-state behavior for edited messages.
- move SSE error payload parsing into a reusable stream utility so the request hook stays focused on lifecycle handling.
- preserve existing error message, error code, and fallback behavior for raw or empty stream errors.
- move non-stream request error extraction into a shared utility so the chat handler stays focused on request flow.
- preserve the existing response message, error code, and fallback priority for failed chat completions.
- move reasoning and content chunk application into a message utility so the chat handler only wires stream events.
- preserve error-state skipping, reasoning accumulation, and content streaming behavior for assistant messages.
- move think tag parsing into a dedicated playground message utility.
- export the parser through the shared playground lib barrel for consistent imports.
- move stream chunk application and message finalization into a dedicated utility.
- keep stored message sanitization with the streaming lifecycle helpers.
- move assistant message update helpers into a focused playground utility.
- keep error-state message updates separate from core message construction helpers.
- move non-streaming choice application into the message streaming utilities.
- keep the chat handler focused on request orchestration and message updates.
- add a helper for finalizing assistant messages with complete status.
- reuse the helper in stream completion and stop-generation paths.
- move SSE delta parsing into a shared stream utility.
- keep the stream request hook focused on lifecycle handling and update dispatch.
- move SSE ready-state status handling into stream utilities.
- keep weak source status typing outside the stream request hook.
- move send, regenerate, and edit message list construction into focused utilities.
- keep the conversation hook focused on edit state and update dispatch.
- move playground initial state loading into focused utility helpers.
- centralize message state updater resolution outside the React state hook.
- move model and group fallback selection into focused playground utilities.
- keep the options hook focused on query results, toasts, and config updates.
- move message action state derivation into focused utilities.

- keep the action component focused on guarded handlers and rendering.
- move submit, stop, and selector state derivation into a pure helper.

- keep input controls focused on rendering model selectors and action buttons.
- move source, reasoning, loader, and body visibility checks into a pure helper.

- use a discriminated state shape so rendered reasoning content stays type-safe.
- move save eligibility and submit visibility checks into a pure helper.

- keep the editor component focused on textarea and button rendering.
- move error kind, fallback content, and admin visibility checks into a pure helper.

- centralize the model pricing settings path used by the error action.
QuentinHsu added 14 commits June 7, 2026 07:22
- collapse long playground code blocks after a short preview instead of waiting for very large snippets
- cap expanded code blocks so long responses scroll inside the code block
- keep generic code block usage unconstrained unless a caller opts in
- add a toolbar action that is enabled only when saved playground messages exist.
- confirm destructive clears before removing browser-stored conversation state.
- add localized strings for the action, dialog, and completion toast.
- refine assistant and user message surfaces so chat content matches the app UI.
- normalize markdown typography, tables, images, lists, blockquotes, and details rendering.
- add indentation cues for collapsible reasoning and source sections.
# Conflicts:
#	web/default/src/components/ai-elements/code-block.tsx
- replace Streamdown with stream-markdown-parser for project-owned markdown rendering and styling.
- split response rendering into focused block, inline, table, alert, details, and footnote modules.
- pass message final state into response parsing so streaming content can be parsed incrementally.
- translate reasoning status, message actions, playground errors, and response renderer fallbacks across supported locales.
- keep reasoning duration numeric and tighten the collapsible layout to prevent trigger jitter.
- register dynamic keys so i18n sync keeps runtime labels covered.
- move chat, input, and message components into focused subdirectories to make the UI structure easier to scan.
- split playground helpers into input, message, streaming, storage, options, state, and suggestions modules.
- update barrel exports and imports so existing feature entry points continue to work.
- defer saved conversation loading so route entry no longer blocks on localStorage parsing and markdown rendering.
- limit initial history rendering and skip expensive markdown parsing for oversized responses.
- normalize corrupted streaming snapshots and cumulative chunks to keep saved playground history bounded.
- add message timing metadata and layout alignment groundwork without introducing live timers.
- show regenerate actions on user messages with saved content.
- truncate following conversation state before starting a fresh assistant response.
- add a per-message source toggle for assistant responses.
- render raw response content with the existing code block viewer.
- localize the new source and preview action labels.
- replace Shiki HTML rendering with a read-only CodeMirror view for code blocks and raw responses.
- reuse the same CodeMirror frame for message editing so source and edit modes stay visually aligned.
- add lightweight CodeMirror dependencies while keeping language support scoped to Markdown.
# Conflicts:
#	web/default/src/i18n/locales/en.json
#	web/default/src/i18n/locales/fr.json
#	web/default/src/i18n/locales/ja.json
#	web/default/src/i18n/locales/ru.json
#	web/default/src/i18n/locales/vi.json
#	web/default/src/i18n/locales/zh.json
- combine model and group selection into one compact picker for faster context switching.
- switch playground action buttons to icon-first controls with tooltips to reduce toolbar width.
- refresh input footer styling and submit states so active and destructive actions are clearer.
- bump dompurify lockfile entry to keep the frontend dependency current.
- query user models by the selected playground group instead of reusing the cross-group model union.
- clear unavailable model selections and block sending when the active group has no models.
- align model selector and error action controls with the existing playground interaction style.
- remove the prompt suggestion row below the playground input to reduce visual noise.
- delete the now-unused suggestion component and display helper.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/default/src/features/playground/components/input/playground-input-tools.tsx (1)

63-65: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Translate the attachment toast description as well.

notice.description is shown to users but isn’t passed through t(), so localized UIs can show mixed-language toasts.

Suggested fix
   toast.info(t(notice.title), {
-    description: notice.description,
+    description: t(notice.description),
   })

As per coding guidelines, “Frontend UI text must support i18n with i18next/react-i18next.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/default/src/features/playground/components/input/playground-input-tools.tsx`
around lines 63 - 65, The attachment toast in playground-input-tools is only
translating the title, leaving notice.description untranslated and causing
mixed-language UI. Update the toast.info call to pass the description through
t() as well, using the existing i18next/react-i18next pattern already used in
this component, so both the title and description are localized consistently.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@web/default/src/features/playground/components/input/playground-input-tools.tsx`:
- Around line 63-65: The attachment toast in playground-input-tools is only
translating the title, leaving notice.description untranslated and causing
mixed-language UI. Update the toast.info call to pass the description through
t() as well, using the existing i18next/react-i18next pattern already used in
this component, so both the title and description are localized consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b80c49fd-5821-4de5-ba4e-76bed4def233

📥 Commits

Reviewing files that changed from the base of the PR and between 05cf08a and 342650d.

⛔ Files ignored due to path filters (1)
  • web/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • controller/model_list_test.go
  • controller/user.go
  • web/default/src/components/model-group-selector.tsx
  • web/default/src/features/playground/api.ts
  • web/default/src/features/playground/components/input/playground-input-controls.tsx
  • web/default/src/features/playground/components/input/playground-input-tools.tsx
  • web/default/src/features/playground/components/input/playground-input.tsx
  • web/default/src/features/playground/components/message/message-error-actions.tsx
  • web/default/src/features/playground/hooks/use-playground-options.ts
  • web/default/src/features/playground/lib/index.ts
  • web/default/src/features/playground/lib/input/input-control-utils.ts
  • web/default/src/features/playground/lib/options/playground-option-utils.ts
💤 Files with no reviewable changes (1)
  • web/default/src/features/playground/lib/index.ts
✅ Files skipped from review due to trivial changes (1)
  • web/default/src/features/playground/lib/input/input-control-utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/default/src/features/playground/hooks/use-playground-options.ts

- use fixed icon slots around the reasoning label so the left content stays still when toggling.
- limit the open state animation to the chevron rotation for a smoother collapse interaction.
- use the collapsible panel height animation for vertical reasoning reveals.
- sync inner content opacity and position with the panel state.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/default/src/components/ai-elements/reasoning.tsx (1)

193-210: 🩺 Stability & Availability | 🟡 Minor

Add keepMounted={true} to CollapsibleContent to enable exit transitions.

The @base-ui/react/collapsible Panel component is unmounted from the DOM by default when closed. Since ReasoningContent relies on CSS transitions (opacity, translate) based on the data-closed state, the element disappears instantly upon closing without keepMounted, causing the animation to skip.

Update web/default/src/components/ui/collapsible.tsx or pass the prop to ReasoningContent:

function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {
  return (
    <CollapsiblePrimitive.Panel
      data-slot='collapsible-content'
      keepMounted={true}
      {...props}
    />
  )
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/components/ai-elements/reasoning.tsx` around lines 193 - 210,
The closing animation in ReasoningContent is skipped because
CollapsibleContent/CollapsiblePrimitive.Panel unmounts immediately when closed.
Update the CollapsibleContent wrapper in collapsible.tsx to pass
keepMounted={true} to CollapsiblePrimitive.Panel, or ensure ReasoningContent
receives that prop through CollapsibleContent, so the data-closed CSS
transitions can run before the element is removed.
🧹 Nitpick comments (1)
web/default/src/components/ai-elements/reasoning.tsx (1)

160-179: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Hide decorative icons from assistive tech.

BrainIcon and ChevronDownIcon are purely decorative (the trigger's meaning is already conveyed by the adjacent text), but they're exposed to screen readers. Add aria-hidden so they don't add noise to the accessible name.

♿ Proposed fix
             <span className='grid size-3.5 place-items-center'>
-              <BrainIcon className='size-3.5' />
+              <BrainIcon className='size-3.5' aria-hidden='true' />
             </span>
             <span className='min-w-0 truncate leading-none'>
               {isStreaming ? (
                 <Shimmer duration={1}>{t('Thinking...')}</Shimmer>
               ) : (
                 thinkingText
               )}
             </span>
             <span className='grid size-3.5 place-items-center'>
               <ChevronDownIcon
+                aria-hidden='true'
                 className={cn(
                   'size-3.5 transition-transform duration-200 ease-out',
                   isOpen ? 'rotate-180' : 'rotate-0'
                 )}
               />
             </span>

As per coding guidelines: "ensure decorative icons are hidden from assistive tech."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/components/ai-elements/reasoning.tsx` around lines 160 - 179,
The decorative icons in the reasoning trigger are being exposed to assistive
tech, so update the `BrainIcon` and `ChevronDownIcon` usages inside `Reasoning`
to hide them from screen readers by adding `aria-hidden` (and keep the
accessible name coming from the adjacent text only). Locate the icons in the
fallback trigger content rendered by `reasoning.tsx` and apply the fix without
changing the visible UI or the `isStreaming`/`isOpen` behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@web/default/src/components/ai-elements/reasoning.tsx`:
- Around line 193-210: The closing animation in ReasoningContent is skipped
because CollapsibleContent/CollapsiblePrimitive.Panel unmounts immediately when
closed. Update the CollapsibleContent wrapper in collapsible.tsx to pass
keepMounted={true} to CollapsiblePrimitive.Panel, or ensure ReasoningContent
receives that prop through CollapsibleContent, so the data-closed CSS
transitions can run before the element is removed.

---

Nitpick comments:
In `@web/default/src/components/ai-elements/reasoning.tsx`:
- Around line 160-179: The decorative icons in the reasoning trigger are being
exposed to assistive tech, so update the `BrainIcon` and `ChevronDownIcon`
usages inside `Reasoning` to hide them from screen readers by adding
`aria-hidden` (and keep the accessible name coming from the adjacent text only).
Locate the icons in the fallback trigger content rendered by `reasoning.tsx` and
apply the fix without changing the visible UI or the `isStreaming`/`isOpen`
behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7b8435e9-49d8-4fcc-9409-3e74e3f2e17e

📥 Commits

Reviewing files that changed from the base of the PR and between 342650d and 110a3c3.

📒 Files selected for processing (1)
  • web/default/src/components/ai-elements/reasoning.tsx

@Calcium-Ion
Calcium-Ion merged commit 966af88 into main Jun 27, 2026
1 check passed
shudonglin added a commit to rayward-external/new-api that referenced this pull request Jun 27, 2026
* chore: avoid duplicate shadcn skill exposure

* fix: support SMTP STARTTLS mode and NTLM auth (QuantumNous#5426)

* fix: support SMTP STARTTLS mode and NTLM auth

Add explicit SMTP STARTTLS configuration for 587-style connections and keep SSL/TLS as the implicit TLS mode.

Prefer PLAIN when advertised, keep LOGIN compatibility, and add NTLM as a fallback for Exchange SMTP servers that require it after STARTTLS.

* fix: respect explicit SMTP encryption mode

* fix: preserve SMTP TLS compatibility

* fix: preserve SMTP PLAIN auth TLS guard

* chore(deps): bump github.com/ClickHouse/ch-go from 0.58.2 to 0.65.0 (QuantumNous#5664)

Bumps [github.com/ClickHouse/ch-go](https://github.com/ClickHouse/ch-go) from 0.58.2 to 0.65.0.
- [Release notes](https://github.com/ClickHouse/ch-go/releases)
- [Commits](ClickHouse/ch-go@v0.58.2...v0.65.0)

---
updated-dependencies:
- dependency-name: github.com/ClickHouse/ch-go
  dependency-version: 0.65.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore: update agent skills and project config

- add vercel-react-best-practices skill (SKILL.md + full-guide.md)
- slim CLAUDE.md to import shared AGENTS.md conventions
- promote go-ntlmssp to a direct dependency in go.mod

* fix: date-fns-tz classic theme build error (QuantumNous#5676)

* chore(deps): update clickhouse-go and orb dependencies

* feat: add system task runner (QuantumNous#5680)

* feat: add system instance info panel (QuantumNous#5716)

* feat: add system instance reporting

* feat: show system instance resources

* fix: update translations for heartbeat messages in Russian and Vietnamese

* fix(web): replace default markdown renderer and expand syntax support (QuantumNous#5689)

* fix(markdown): render default markdown with marked

- switch default frontend markdown rendering from react-markdown/remark-gfm to marked to avoid old WebKit parse failures from lookbehind regex literals
- sanitize marked HTML output with DOMPurify and preserve external link target and rel behavior
- remove default direct dependencies on react-markdown, remark-gfm, and rehype-raw while leaving classic unchanged

* fix(markdown): expand default markdown rendering support

- render default markdown with marked extensions for KaTeX formulas, page breaks, and common emoji shortcodes.
- sanitize KaTeX output with an explicit DOMPurify allowlist while preserving external link behavior.
- avoid overriding marked text rendering so lists and inline parsing keep their internal parser context.

* fix(markdown): render diagram code blocks in default UI

- add sanitized SVG rendering for flow and sequence diagram code blocks.
- size flow nodes from their labels and route edges from node anchors to prevent clipping.
- style diagram nodes, arrows, labels, and notes with theme-aware classes.

* fix(web): sync channel card selection state (QuantumNous#5700)

* fix(web): hide wallet entry in profile dropdown when wallet module disabled (QuantumNous#5708)

The profile dropdown rendered the wallet item unconditionally, so it
still showed after an admin disabled the personal/topup (wallet) sidebar
module. Reuse the sidebar module visibility check so the dropdown honours
the same toggle as the sidebar.

Fixes QuantumNous#5696

* feat(system-settings): add user token limit configuration section (QuantumNous#5678)

* feat: add channel async polling delay toggle

Fixes QuantumNous#5717
Fixes QuantumNous#4244

* fix: add token limit save label translations

* feat: enhance i18n-translate skill

* feat: add date-fns and date-fns-tz dependencies

* feat: add date-fns and date-fns-tz paths to build configuration

* chore(deps): bump dompurify from 3.4.5 to 3.4.11 in /web/default (QuantumNous#5718)

Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.5 to 3.4.11.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](cure53/DOMPurify@3.4.5...3.4.11)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.4.11
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(ci): install classic workspace dependencies for releases (QuantumNous#5719)

* fix: use neutral drawing task labels

* perf(web): streamline table actions and destructive dialogs (QuantumNous#5645)

* perf(data-table): autosize action columns

- exclude actions columns from shared table width calculations so action cells size to their content.
- remove fixed size and w-* width overrides from feature action columns to preserve content-based layout.

* perf(data-table): streamline row action controls

- expose common edit and status actions directly while moving secondary actions into overflow menus.
- add shared row action menu helpers so static and table rows use consistent action controls.
- let action columns size to their content instead of relying on fixed widths.

* fix(web): localize destructive dialog copy

- route delete, reset, and batch update confirmation text through i18n.
- add locale entries for affected channel, model, system settings, and user dialogs.

* perf(web): unify destructive dialog actions

- align delete and cleanup confirmation buttons with the shared destructive variant.
- replace custom destructive color overrides with semantic button variants.
- clean up lint errors in touched dialog files before committing.

* fix(web): add user action success translations

- add localized success messages for user delete, status, and role changes.
- keep user management toast copy available across all frontend locales.

* fix(data-table): prevent mobile badge clipping

- expose badge cell slots so mobile card styles can target nested badge wrappers.
- reset badge margins in card rows to keep provider icons fully visible on small screens.

* fix: add Waffo goods info and webhook SDK update (QuantumNous#5704)

* fix: add Waffo goods info and webhook SDK update

* chore: remove Waffo test code from PR

* fix(model-pricing): refresh tiered expression editor when switching models (QuantumNous#5752)

Switching models in the pricing editor kept the previous model's tiers and prices in the expression panel: TieredPricingEditor seeds its internal visual/raw state only on mount, and the initRef guard never re-ran on prop changes, so only the model name updated.

Bump a reload token in the same effect that seeds billingExpr and use it as the editor's key, so a freshly loaded model remounts the editor and re-parses its expression. The token changes in lockstep with billingExpr, and user edits (which only touch state) do not trigger it.

Closes QuantumNous#5750

* chore(deps): sync bun.lock for dompurify 3.4.11 (QuantumNous#5738)

* fix(theme): 切换前端主题后重置到首页,避免路由 404 (QuantumNous#5612)

* fix(theme): 切换前端主题后重置到首页,避免路由 404
经典前端与新版前端的路由路径不同,切换主题后停留在原路径会导致 404:
- 经典前端切换到新版前端时跳转首页,不再原地刷新当前路径
- 新版前端保存时若前端主题发生变化,保存成功后跳转首页

Fixes QuantumNous#4947

* fix: 更新前端切换提示信息,修正页面跳转逻辑

* fix(task): attribute async task usage log to the initiating node (QuantumNous#5684)

Async task usage logs (LogQuotaData node dimension) were recorded
under whichever node happened to poll the task to completion, not the
node that submitted it. For token/adaptor-billed video tasks the
pre-deduction is often 0, so the entire quota landed on the last
polling node.

Snapshot common.NodeName into TaskPrivateData at submit time and use
it when writing the settlement consume log; fall back to the current
node when empty so existing tasks stay compatible.

* chore: update i18n skill

* feat: better admin permissions (QuantumNous#5755)

* feat: add casbin admin permissions

* feat: improve audit logging to associate logs with actual operators and target users

* feat: enhance admin permissions and UI interactions for sensitive actions

* Refactor authz RBAC and tighten channel permissions

* Split channel authz field policy

* Address channel authz review findings

* fix: adapt ClickHouse log LIKE filters

* feat(playground): improve Playground chat experience and Markdown rendering (QuantumNous#5217)

* refactor(playground): streamline chat request state

- extract conversation actions from the page component to keep message flow logic reusable.
- unify streaming and non-streaming generation state, including abort support for non-stream requests.
- simplify message rendering and payload construction while localizing Playground prompts.

* fix(playground): validate persisted chat state

- wrap saved Playground state with a storage version while still reading legacy values.

- validate config, parameter toggles, and messages before restoring them from localStorage.

- cap stored chat history to the latest messages to avoid oversized or stale state.

* refactor(playground): centralize message content access

- route chat rendering, copy actions, and error display through shared message helpers.

- reuse the current-version update helper for non-streaming assistant responses.

- keep message version details behind utility functions to reduce future model churn.

* refactor(playground): split storage schemas

- move Playground storage validation schemas into a dedicated module.

- keep storage read and write logic focused on migration, trimming, and persistence.

- preserve the existing storage envelope and validation behavior.

* refactor(playground): extract options loading hook

- move model and group queries into a dedicated hook so the page component stays focused on layout wiring.
- preserve existing fallback selection and error toast behavior while reusing the hook through the playground barrel export.

* refactor(playground): extract prompt suggestions

- move static prompt suggestion rendering into a focused component so the input stays centered on compose controls.
- preserve translated suggestion submission behavior while isolating icon metadata from the input form.

* refactor(playground): extract input tools

- move attachment and search controls into a dedicated component so the prompt input stays focused on compose state.
- keep existing development toast behavior and disabled handling while centralizing tool metadata.

* refactor(playground): extract input controls

- move model, group, send, and stop controls into a focused component so the input only manages compose state.
- preserve existing disabled states and generation button behavior while isolating control rendering.

* refactor(playground): extract message content display

- move sources, reasoning, loading, error, and response rendering into a dedicated message content component.
- keep the chat list focused on message iteration, edit state, and action wiring without changing display behavior.

* refactor(playground): extract message editor

- move inline message editing controls into a dedicated editor component so the chat list stays focused on rendering flow.
- preserve save, save-and-submit, cancel, and disabled-state behavior for edited messages.

* refactor(playground): extract stream error parsing

- move SSE error payload parsing into a reusable stream utility so the request hook stays focused on lifecycle handling.
- preserve existing error message, error code, and fallback behavior for raw or empty stream errors.

* refactor(playground): extract request error parsing

- move non-stream request error extraction into a shared utility so the chat handler stays focused on request flow.
- preserve the existing response message, error code, and fallback priority for failed chat completions.

* refactor(playground): extract streaming chunk updates

- move reasoning and content chunk application into a message utility so the chat handler only wires stream events.
- preserve error-state skipping, reasoning accumulation, and content streaming behavior for assistant messages.

* refactor(playground): extract message reasoning parser

- move think tag parsing into a dedicated playground message utility.
- export the parser through the shared playground lib barrel for consistent imports.

* refactor(playground): extract message streaming utilities

- move stream chunk application and message finalization into a dedicated utility.
- keep stored message sanitization with the streaming lifecycle helpers.

* refactor(playground): extract message update utilities

- move assistant message update helpers into a focused playground utility.
- keep error-state message updates separate from core message construction helpers.

* refactor(playground): extract completion choice handling

- move non-streaming choice application into the message streaming utilities.
- keep the chat handler focused on request orchestration and message updates.

* refactor(playground): centralize assistant completion state

- add a helper for finalizing assistant messages with complete status.
- reuse the helper in stream completion and stop-generation paths.

* refactor(playground): extract stream message parsing

- move SSE delta parsing into a shared stream utility.
- keep the stream request hook focused on lifecycle handling and update dispatch.

* refactor(playground): extract stream ready state checks

- move SSE ready-state status handling into stream utilities.
- keep weak source status typing outside the stream request hook.

* refactor(playground): extract conversation message helpers

- move send, regenerate, and edit message list construction into focused utilities.
- keep the conversation hook focused on edit state and update dispatch.

* refactor(playground): extract state initialization helpers

- move playground initial state loading into focused utility helpers.
- centralize message state updater resolution outside the React state hook.

* refactor(playground): extract option fallback helpers

- move model and group fallback selection into focused playground utilities.
- keep the options hook focused on query results, toasts, and config updates.

* refactor(playground): extract message action helpers

- move message action state derivation into focused utilities.

- keep the action component focused on guarded handlers and rendering.

* refactor(playground): extract input control state

- move submit, stop, and selector state derivation into a pure helper.

- keep input controls focused on rendering model selectors and action buttons.

* refactor(playground): extract message content state

- move source, reasoning, loader, and body visibility checks into a pure helper.

- use a discriminated state shape so rendered reasoning content stays type-safe.

* refactor(playground): extract message editor state

- move save eligibility and submit visibility checks into a pure helper.

- keep the editor component focused on textarea and button rendering.

* refactor(playground): extract message error state

- move error kind, fallback content, and admin visibility checks into a pure helper.

- centralize the model pricing settings path used by the error action.

* refactor(playground): extract chat render state

- move editing content lookup and per-message render flags into conversation helpers.

- keep the chat component focused on mapping messages to editor and content views.

* refactor(playground): extract suggestion display state

- move suggestion class selection into a pure helper.

- keep the suggestions component focused on translation and rendering.

* refactor(playground): extract assistant message state checks

- move final and pending assistant status checks into streaming utilities.

- keep the chat handler focused on request lifecycle updates.

* refactor(playground): extract input tool state

- move attachment action metadata and development notices into input tool utilities.

- keep the input tools component focused on menu and button rendering.

* refactor(playground): extract stream protocol checks

- move SSE done-message and closed-ready-state checks into stream utilities.

- keep the stream request hook focused on event handling flow.

* refactor(playground): extract message removal helper

- move delete-message filtering into conversation message utilities.

- keep the conversation hook focused on action orchestration.

* refactor(playground): extract option error messages

- move option load error message selection into playground option utilities
- keep the options hook focused on query effects and fallback updates

* refactor(playground): extract input submit text helper

- move prompt submit text validation into input control utilities
- let the input component submit only when a concrete text value is available

* refactor(playground): centralize error message checks

- add a shared helper for identifying error messages
- remove direct status string checks from message content rendering

* refactor(playground): extract message content display checks

- move loader and content visibility decisions into local helper functions
- keep message content state assembly focused on composing render state

* refactor(playground): replace raw message role checks

- use shared message role constants in conversation edit handling
- avoid raw assistant role literals when validating API messages

* refactor(playground): extract non-stream response handling

- move chat completion response choice handling into message streaming utilities
- keep the chat handler focused on request lifecycle and error routing

* refactor(playground): centralize stream cleanup

- reuse one stream cleanup path for completion, errors, startup failures, and manual stops
- preserve the current-source guard when closing SSE streams

* refactor(playground): extract pending assistant check

- centralize pending assistant message detection in streaming utilities
- reuse the helper when sanitizing stored playground messages

* perf(playground): improve mobile input controls

- split mobile input controls into selector and action rows
- keep the desktop input footer compact while reducing mobile control crowding

* perf(playground): add starter empty state

- show starter prompts in the empty playground chat area
- wire empty-state prompt selection into the existing send flow
- add localized copy for the new empty state

* perf(playground): improve mobile message actions

- collapse mobile message actions into a touch-friendly dropdown menu
- keep the desktop hover action strip unchanged for pointer workflows
- share one action list between desktop buttons and the mobile menu

* perf(playground): add error recovery actions

- show retry, edit, and delete actions inside error message alerts
- route edit recovery to the previous user prompt when available
- keep recovery controls touch-friendly on mobile layouts

* perf(playground): refine message editing experience

- present message edits in a focused bordered editor panel
- add unsaved-change state, reset, and cancel confirmation flows
- improve mobile touch targets and keyboard shortcuts for editing

* perf(playground): improve markdown code blocks

- render fenced markdown code with syntax highlighting, line numbers, and fallback plain text
- add copy, download, and collapse controls for playground AI responses
- tighten code block layout and theme token styles for responsive markdown rendering

* fix(playground): constrain markdown code block height

- collapse long playground code blocks after a short preview instead of waiting for very large snippets
- cap expanded code blocks so long responses scroll inside the code block
- keep generic code block usage unconstrained unless a caller opts in

* feat(playground): add chat history clearing

- add a toolbar action that is enabled only when saved playground messages exist.
- confirm destructive clears before removing browser-stored conversation state.
- add localized strings for the action, dialog, and completion toast.

* perf(playground): improve chat markdown rendering

- refine assistant and user message surfaces so chat content matches the app UI.
- normalize markdown typography, tables, images, lists, blockquotes, and details rendering.
- add indentation cues for collapsible reasoning and source sections.

* style: format code block component

* style: format playground frontend files

* feat(playground): render markdown with stream parser

- replace Streamdown with stream-markdown-parser for project-owned markdown rendering and styling.
- split response rendering into focused block, inline, table, alert, details, and footnote modules.
- pass message final state into response parsing so streaming content can be parsed incrementally.

* fix(playground): localize reasoning and chat feedback

- translate reasoning status, message actions, playground errors, and response renderer fallbacks across supported locales.
- keep reasoning duration numeric and tighten the collapsible layout to prevent trigger jitter.
- register dynamic keys so i18n sync keeps runtime labels covered.

* refactor(playground): group files by functional area

- move chat, input, and message components into focused subdirectories to make the UI structure easier to scan.
- split playground helpers into input, message, streaming, storage, options, state, and suggestions modules.
- update barrel exports and imports so existing feature entry points continue to work.

* fix(playground): prevent history replay from freezing page

- defer saved conversation loading so route entry no longer blocks on localStorage parsing and markdown rendering.
- limit initial history rendering and skip expensive markdown parsing for oversized responses.
- normalize corrupted streaming snapshots and cumulative chunks to keep saved playground history bounded.
- add message timing metadata and layout alignment groundwork without introducing live timers.

* feat(playground): allow regenerating from user messages

- show regenerate actions on user messages with saved content.
- truncate following conversation state before starting a fresh assistant response.

* feat(playground): add raw response source view

- add a per-message source toggle for assistant responses.
- render raw response content with the existing code block viewer.
- localize the new source and preview action labels.

* feat(playground): render code with unified editor

- replace Shiki HTML rendering with a read-only CodeMirror view for code blocks and raw responses.
- reuse the same CodeMirror frame for message editing so source and edit modes stay visually aligned.
- add lightweight CodeMirror dependencies while keeping language support scoped to Markdown.

* perf(playground): streamline chat input controls

- combine model and group selection into one compact picker for faster context switching.
- switch playground action buttons to icon-first controls with tooltips to reduce toolbar width.
- refresh input footer styling and submit states so active and destructive actions are clearer.
- bump dompurify lockfile entry to keep the frontend dependency current.

* fix(playground): filter models by selected group

- query user models by the selected playground group instead of reusing the cross-group model union.
- clear unavailable model selections and block sending when the active group has no models.
- align model selector and error action controls with the existing playground interaction style.

* perf(playground): remove input suggestion chips

- remove the prompt suggestion row below the playground input to reduce visual noise.
- delete the now-unused suggestion component and display helper.

* perf(playground): stabilize reasoning trigger layout

- use fixed icon slots around the reasoning label so the left content stays still when toggling.
- limit the open state animation to the chevron rotation for a smoother collapse interaction.

* perf(playground): smooth reasoning expansion

- use the collapsible panel height animation for vertical reasoning reveals.
- sync inner content opacity and position with the panel state.

* fix(auth): align password validation copy (QuantumNous#5759)

* fix(i18n): add missing frontend translations

- add missing locale entries for API key loading, channel model empty states, auth, playground, and model configuration copy.
- correct inaccurate Russian and Vietnamese model empty-state translations to avoid fallback or misleading copy.

* fix(auth): align password validation copy

- remove the login password length gate so existing shorter passwords are not blocked before reaching the server.
- reuse distinct minimum-length and 8-20 character messages based on the actual validation rule.
- drop unused duplicate password locale keys and align the user creation placeholder with the 8-20 character constraint.

* fix(i18n): add auth validation message translations

- cover schema-driven auth form errors that are translated through FormMessage.
- keep password, username, confirmation, and OTP validation messages available in every locale.

* fix(web): render custom HTML and Markdown content consistently (QuantumNous#5760)

* fix(markdown): render announcement markdown consistently

- support soft line breaks for announcement markdown without changing the default parser behavior.
- add explicit markdown element styles so lists, tables, code blocks, and quotes render correctly when typography styles are unavailable.
- apply the announcement markdown mode in both the popover and detail dialog for consistent display.

* refactor(markdown): simplify fallback markdown styles

- remove duplicate typography utility classes now covered by explicit markdown element fallbacks.
- keep the markdown renderer behavior unchanged while reducing class noise.
- modernize small helper expressions to satisfy targeted lint checks.

* fix(content): render custom HTML consistently

- add shared rich content rendering so custom HTML and Markdown use the same path across public pages and announcements.
- reuse common URL and HTML detection instead of duplicating content format checks per page.
- keep custom home content inside the standard public layout while preserving full-page iframe rendering for external URLs.

* fix(security): pin patched frontend transitive dependencies

* fix(web): secure rich content rendering

* fix(web): harden iframe sandboxing

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: CaIon <i@caion.me>
Co-authored-by: Benson Yan <fuxin04@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Seefs <40468931+seefs001@users.noreply.github.com>
Co-authored-by: QuentinHsu <xuquentinyang@gmail.com>
Co-authored-by: yyhhyyyyyy <yyhhyyyyyy8@gmail.com>
Co-authored-by: feitianbubu <feitianbubu@qq.com>
Co-authored-by: RedwindA <128586631+RedwindA@users.noreply.github.com>
Co-authored-by: zhongyuanzhao-alt <zhongyuan.zhao@waffo.com>
Co-authored-by: peakchao <zhangzhichaolove@vip.qq.com>
@Calcium-Ion
Calcium-Ion deleted the perf/playground branch June 28, 2026 05:42
ruanhangjian pushed a commit to ruanhangjian/new-api that referenced this pull request Jul 11, 2026
…dering (QuantumNous#5217)

* refactor(playground): streamline chat request state

- extract conversation actions from the page component to keep message flow logic reusable.
- unify streaming and non-streaming generation state, including abort support for non-stream requests.
- simplify message rendering and payload construction while localizing Playground prompts.

* fix(playground): validate persisted chat state

- wrap saved Playground state with a storage version while still reading legacy values.

- validate config, parameter toggles, and messages before restoring them from localStorage.

- cap stored chat history to the latest messages to avoid oversized or stale state.

* refactor(playground): centralize message content access

- route chat rendering, copy actions, and error display through shared message helpers.

- reuse the current-version update helper for non-streaming assistant responses.

- keep message version details behind utility functions to reduce future model churn.

* refactor(playground): split storage schemas

- move Playground storage validation schemas into a dedicated module.

- keep storage read and write logic focused on migration, trimming, and persistence.

- preserve the existing storage envelope and validation behavior.

* refactor(playground): extract options loading hook

- move model and group queries into a dedicated hook so the page component stays focused on layout wiring.
- preserve existing fallback selection and error toast behavior while reusing the hook through the playground barrel export.

* refactor(playground): extract prompt suggestions

- move static prompt suggestion rendering into a focused component so the input stays centered on compose controls.
- preserve translated suggestion submission behavior while isolating icon metadata from the input form.

* refactor(playground): extract input tools

- move attachment and search controls into a dedicated component so the prompt input stays focused on compose state.
- keep existing development toast behavior and disabled handling while centralizing tool metadata.

* refactor(playground): extract input controls

- move model, group, send, and stop controls into a focused component so the input only manages compose state.
- preserve existing disabled states and generation button behavior while isolating control rendering.

* refactor(playground): extract message content display

- move sources, reasoning, loading, error, and response rendering into a dedicated message content component.
- keep the chat list focused on message iteration, edit state, and action wiring without changing display behavior.

* refactor(playground): extract message editor

- move inline message editing controls into a dedicated editor component so the chat list stays focused on rendering flow.
- preserve save, save-and-submit, cancel, and disabled-state behavior for edited messages.

* refactor(playground): extract stream error parsing

- move SSE error payload parsing into a reusable stream utility so the request hook stays focused on lifecycle handling.
- preserve existing error message, error code, and fallback behavior for raw or empty stream errors.

* refactor(playground): extract request error parsing

- move non-stream request error extraction into a shared utility so the chat handler stays focused on request flow.
- preserve the existing response message, error code, and fallback priority for failed chat completions.

* refactor(playground): extract streaming chunk updates

- move reasoning and content chunk application into a message utility so the chat handler only wires stream events.
- preserve error-state skipping, reasoning accumulation, and content streaming behavior for assistant messages.

* refactor(playground): extract message reasoning parser

- move think tag parsing into a dedicated playground message utility.
- export the parser through the shared playground lib barrel for consistent imports.

* refactor(playground): extract message streaming utilities

- move stream chunk application and message finalization into a dedicated utility.
- keep stored message sanitization with the streaming lifecycle helpers.

* refactor(playground): extract message update utilities

- move assistant message update helpers into a focused playground utility.
- keep error-state message updates separate from core message construction helpers.

* refactor(playground): extract completion choice handling

- move non-streaming choice application into the message streaming utilities.
- keep the chat handler focused on request orchestration and message updates.

* refactor(playground): centralize assistant completion state

- add a helper for finalizing assistant messages with complete status.
- reuse the helper in stream completion and stop-generation paths.

* refactor(playground): extract stream message parsing

- move SSE delta parsing into a shared stream utility.
- keep the stream request hook focused on lifecycle handling and update dispatch.

* refactor(playground): extract stream ready state checks

- move SSE ready-state status handling into stream utilities.
- keep weak source status typing outside the stream request hook.

* refactor(playground): extract conversation message helpers

- move send, regenerate, and edit message list construction into focused utilities.
- keep the conversation hook focused on edit state and update dispatch.

* refactor(playground): extract state initialization helpers

- move playground initial state loading into focused utility helpers.
- centralize message state updater resolution outside the React state hook.

* refactor(playground): extract option fallback helpers

- move model and group fallback selection into focused playground utilities.
- keep the options hook focused on query results, toasts, and config updates.

* refactor(playground): extract message action helpers

- move message action state derivation into focused utilities.

- keep the action component focused on guarded handlers and rendering.

* refactor(playground): extract input control state

- move submit, stop, and selector state derivation into a pure helper.

- keep input controls focused on rendering model selectors and action buttons.

* refactor(playground): extract message content state

- move source, reasoning, loader, and body visibility checks into a pure helper.

- use a discriminated state shape so rendered reasoning content stays type-safe.

* refactor(playground): extract message editor state

- move save eligibility and submit visibility checks into a pure helper.

- keep the editor component focused on textarea and button rendering.

* refactor(playground): extract message error state

- move error kind, fallback content, and admin visibility checks into a pure helper.

- centralize the model pricing settings path used by the error action.

* refactor(playground): extract chat render state

- move editing content lookup and per-message render flags into conversation helpers.

- keep the chat component focused on mapping messages to editor and content views.

* refactor(playground): extract suggestion display state

- move suggestion class selection into a pure helper.

- keep the suggestions component focused on translation and rendering.

* refactor(playground): extract assistant message state checks

- move final and pending assistant status checks into streaming utilities.

- keep the chat handler focused on request lifecycle updates.

* refactor(playground): extract input tool state

- move attachment action metadata and development notices into input tool utilities.

- keep the input tools component focused on menu and button rendering.

* refactor(playground): extract stream protocol checks

- move SSE done-message and closed-ready-state checks into stream utilities.

- keep the stream request hook focused on event handling flow.

* refactor(playground): extract message removal helper

- move delete-message filtering into conversation message utilities.

- keep the conversation hook focused on action orchestration.

* refactor(playground): extract option error messages

- move option load error message selection into playground option utilities
- keep the options hook focused on query effects and fallback updates

* refactor(playground): extract input submit text helper

- move prompt submit text validation into input control utilities
- let the input component submit only when a concrete text value is available

* refactor(playground): centralize error message checks

- add a shared helper for identifying error messages
- remove direct status string checks from message content rendering

* refactor(playground): extract message content display checks

- move loader and content visibility decisions into local helper functions
- keep message content state assembly focused on composing render state

* refactor(playground): replace raw message role checks

- use shared message role constants in conversation edit handling
- avoid raw assistant role literals when validating API messages

* refactor(playground): extract non-stream response handling

- move chat completion response choice handling into message streaming utilities
- keep the chat handler focused on request lifecycle and error routing

* refactor(playground): centralize stream cleanup

- reuse one stream cleanup path for completion, errors, startup failures, and manual stops
- preserve the current-source guard when closing SSE streams

* refactor(playground): extract pending assistant check

- centralize pending assistant message detection in streaming utilities
- reuse the helper when sanitizing stored playground messages

* perf(playground): improve mobile input controls

- split mobile input controls into selector and action rows
- keep the desktop input footer compact while reducing mobile control crowding

* perf(playground): add starter empty state

- show starter prompts in the empty playground chat area
- wire empty-state prompt selection into the existing send flow
- add localized copy for the new empty state

* perf(playground): improve mobile message actions

- collapse mobile message actions into a touch-friendly dropdown menu
- keep the desktop hover action strip unchanged for pointer workflows
- share one action list between desktop buttons and the mobile menu

* perf(playground): add error recovery actions

- show retry, edit, and delete actions inside error message alerts
- route edit recovery to the previous user prompt when available
- keep recovery controls touch-friendly on mobile layouts

* perf(playground): refine message editing experience

- present message edits in a focused bordered editor panel
- add unsaved-change state, reset, and cancel confirmation flows
- improve mobile touch targets and keyboard shortcuts for editing

* perf(playground): improve markdown code blocks

- render fenced markdown code with syntax highlighting, line numbers, and fallback plain text
- add copy, download, and collapse controls for playground AI responses
- tighten code block layout and theme token styles for responsive markdown rendering

* fix(playground): constrain markdown code block height

- collapse long playground code blocks after a short preview instead of waiting for very large snippets
- cap expanded code blocks so long responses scroll inside the code block
- keep generic code block usage unconstrained unless a caller opts in

* feat(playground): add chat history clearing

- add a toolbar action that is enabled only when saved playground messages exist.
- confirm destructive clears before removing browser-stored conversation state.
- add localized strings for the action, dialog, and completion toast.

* perf(playground): improve chat markdown rendering

- refine assistant and user message surfaces so chat content matches the app UI.
- normalize markdown typography, tables, images, lists, blockquotes, and details rendering.
- add indentation cues for collapsible reasoning and source sections.

* style: format code block component

* style: format playground frontend files

* feat(playground): render markdown with stream parser

- replace Streamdown with stream-markdown-parser for project-owned markdown rendering and styling.
- split response rendering into focused block, inline, table, alert, details, and footnote modules.
- pass message final state into response parsing so streaming content can be parsed incrementally.

* fix(playground): localize reasoning and chat feedback

- translate reasoning status, message actions, playground errors, and response renderer fallbacks across supported locales.
- keep reasoning duration numeric and tighten the collapsible layout to prevent trigger jitter.
- register dynamic keys so i18n sync keeps runtime labels covered.

* refactor(playground): group files by functional area

- move chat, input, and message components into focused subdirectories to make the UI structure easier to scan.
- split playground helpers into input, message, streaming, storage, options, state, and suggestions modules.
- update barrel exports and imports so existing feature entry points continue to work.

* fix(playground): prevent history replay from freezing page

- defer saved conversation loading so route entry no longer blocks on localStorage parsing and markdown rendering.
- limit initial history rendering and skip expensive markdown parsing for oversized responses.
- normalize corrupted streaming snapshots and cumulative chunks to keep saved playground history bounded.
- add message timing metadata and layout alignment groundwork without introducing live timers.

* feat(playground): allow regenerating from user messages

- show regenerate actions on user messages with saved content.
- truncate following conversation state before starting a fresh assistant response.

* feat(playground): add raw response source view

- add a per-message source toggle for assistant responses.
- render raw response content with the existing code block viewer.
- localize the new source and preview action labels.

* feat(playground): render code with unified editor

- replace Shiki HTML rendering with a read-only CodeMirror view for code blocks and raw responses.
- reuse the same CodeMirror frame for message editing so source and edit modes stay visually aligned.
- add lightweight CodeMirror dependencies while keeping language support scoped to Markdown.

* perf(playground): streamline chat input controls

- combine model and group selection into one compact picker for faster context switching.
- switch playground action buttons to icon-first controls with tooltips to reduce toolbar width.
- refresh input footer styling and submit states so active and destructive actions are clearer.
- bump dompurify lockfile entry to keep the frontend dependency current.

* fix(playground): filter models by selected group

- query user models by the selected playground group instead of reusing the cross-group model union.
- clear unavailable model selections and block sending when the active group has no models.
- align model selector and error action controls with the existing playground interaction style.

* perf(playground): remove input suggestion chips

- remove the prompt suggestion row below the playground input to reduce visual noise.
- delete the now-unused suggestion component and display helper.

* perf(playground): stabilize reasoning trigger layout

- use fixed icon slots around the reasoning label so the left content stays still when toggling.
- limit the open state animation to the chevron rotation for a smoother collapse interaction.

* perf(playground): smooth reasoning expansion

- use the collapsible panel height animation for vertical reasoning reveals.
- sync inner content opacity and position with the panel state.
zhaodechao2008 pushed a commit to zhaodechao2008/new-api that referenced this pull request Jul 27, 2026
…dering (QuantumNous#5217)

* refactor(playground): streamline chat request state

- extract conversation actions from the page component to keep message flow logic reusable.
- unify streaming and non-streaming generation state, including abort support for non-stream requests.
- simplify message rendering and payload construction while localizing Playground prompts.

* fix(playground): validate persisted chat state

- wrap saved Playground state with a storage version while still reading legacy values.

- validate config, parameter toggles, and messages before restoring them from localStorage.

- cap stored chat history to the latest messages to avoid oversized or stale state.

* refactor(playground): centralize message content access

- route chat rendering, copy actions, and error display through shared message helpers.

- reuse the current-version update helper for non-streaming assistant responses.

- keep message version details behind utility functions to reduce future model churn.

* refactor(playground): split storage schemas

- move Playground storage validation schemas into a dedicated module.

- keep storage read and write logic focused on migration, trimming, and persistence.

- preserve the existing storage envelope and validation behavior.

* refactor(playground): extract options loading hook

- move model and group queries into a dedicated hook so the page component stays focused on layout wiring.
- preserve existing fallback selection and error toast behavior while reusing the hook through the playground barrel export.

* refactor(playground): extract prompt suggestions

- move static prompt suggestion rendering into a focused component so the input stays centered on compose controls.
- preserve translated suggestion submission behavior while isolating icon metadata from the input form.

* refactor(playground): extract input tools

- move attachment and search controls into a dedicated component so the prompt input stays focused on compose state.
- keep existing development toast behavior and disabled handling while centralizing tool metadata.

* refactor(playground): extract input controls

- move model, group, send, and stop controls into a focused component so the input only manages compose state.
- preserve existing disabled states and generation button behavior while isolating control rendering.

* refactor(playground): extract message content display

- move sources, reasoning, loading, error, and response rendering into a dedicated message content component.
- keep the chat list focused on message iteration, edit state, and action wiring without changing display behavior.

* refactor(playground): extract message editor

- move inline message editing controls into a dedicated editor component so the chat list stays focused on rendering flow.
- preserve save, save-and-submit, cancel, and disabled-state behavior for edited messages.

* refactor(playground): extract stream error parsing

- move SSE error payload parsing into a reusable stream utility so the request hook stays focused on lifecycle handling.
- preserve existing error message, error code, and fallback behavior for raw or empty stream errors.

* refactor(playground): extract request error parsing

- move non-stream request error extraction into a shared utility so the chat handler stays focused on request flow.
- preserve the existing response message, error code, and fallback priority for failed chat completions.

* refactor(playground): extract streaming chunk updates

- move reasoning and content chunk application into a message utility so the chat handler only wires stream events.
- preserve error-state skipping, reasoning accumulation, and content streaming behavior for assistant messages.

* refactor(playground): extract message reasoning parser

- move think tag parsing into a dedicated playground message utility.
- export the parser through the shared playground lib barrel for consistent imports.

* refactor(playground): extract message streaming utilities

- move stream chunk application and message finalization into a dedicated utility.
- keep stored message sanitization with the streaming lifecycle helpers.

* refactor(playground): extract message update utilities

- move assistant message update helpers into a focused playground utility.
- keep error-state message updates separate from core message construction helpers.

* refactor(playground): extract completion choice handling

- move non-streaming choice application into the message streaming utilities.
- keep the chat handler focused on request orchestration and message updates.

* refactor(playground): centralize assistant completion state

- add a helper for finalizing assistant messages with complete status.
- reuse the helper in stream completion and stop-generation paths.

* refactor(playground): extract stream message parsing

- move SSE delta parsing into a shared stream utility.
- keep the stream request hook focused on lifecycle handling and update dispatch.

* refactor(playground): extract stream ready state checks

- move SSE ready-state status handling into stream utilities.
- keep weak source status typing outside the stream request hook.

* refactor(playground): extract conversation message helpers

- move send, regenerate, and edit message list construction into focused utilities.
- keep the conversation hook focused on edit state and update dispatch.

* refactor(playground): extract state initialization helpers

- move playground initial state loading into focused utility helpers.
- centralize message state updater resolution outside the React state hook.

* refactor(playground): extract option fallback helpers

- move model and group fallback selection into focused playground utilities.
- keep the options hook focused on query results, toasts, and config updates.

* refactor(playground): extract message action helpers

- move message action state derivation into focused utilities.

- keep the action component focused on guarded handlers and rendering.

* refactor(playground): extract input control state

- move submit, stop, and selector state derivation into a pure helper.

- keep input controls focused on rendering model selectors and action buttons.

* refactor(playground): extract message content state

- move source, reasoning, loader, and body visibility checks into a pure helper.

- use a discriminated state shape so rendered reasoning content stays type-safe.

* refactor(playground): extract message editor state

- move save eligibility and submit visibility checks into a pure helper.

- keep the editor component focused on textarea and button rendering.

* refactor(playground): extract message error state

- move error kind, fallback content, and admin visibility checks into a pure helper.

- centralize the model pricing settings path used by the error action.

* refactor(playground): extract chat render state

- move editing content lookup and per-message render flags into conversation helpers.

- keep the chat component focused on mapping messages to editor and content views.

* refactor(playground): extract suggestion display state

- move suggestion class selection into a pure helper.

- keep the suggestions component focused on translation and rendering.

* refactor(playground): extract assistant message state checks

- move final and pending assistant status checks into streaming utilities.

- keep the chat handler focused on request lifecycle updates.

* refactor(playground): extract input tool state

- move attachment action metadata and development notices into input tool utilities.

- keep the input tools component focused on menu and button rendering.

* refactor(playground): extract stream protocol checks

- move SSE done-message and closed-ready-state checks into stream utilities.

- keep the stream request hook focused on event handling flow.

* refactor(playground): extract message removal helper

- move delete-message filtering into conversation message utilities.

- keep the conversation hook focused on action orchestration.

* refactor(playground): extract option error messages

- move option load error message selection into playground option utilities
- keep the options hook focused on query effects and fallback updates

* refactor(playground): extract input submit text helper

- move prompt submit text validation into input control utilities
- let the input component submit only when a concrete text value is available

* refactor(playground): centralize error message checks

- add a shared helper for identifying error messages
- remove direct status string checks from message content rendering

* refactor(playground): extract message content display checks

- move loader and content visibility decisions into local helper functions
- keep message content state assembly focused on composing render state

* refactor(playground): replace raw message role checks

- use shared message role constants in conversation edit handling
- avoid raw assistant role literals when validating API messages

* refactor(playground): extract non-stream response handling

- move chat completion response choice handling into message streaming utilities
- keep the chat handler focused on request lifecycle and error routing

* refactor(playground): centralize stream cleanup

- reuse one stream cleanup path for completion, errors, startup failures, and manual stops
- preserve the current-source guard when closing SSE streams

* refactor(playground): extract pending assistant check

- centralize pending assistant message detection in streaming utilities
- reuse the helper when sanitizing stored playground messages

* perf(playground): improve mobile input controls

- split mobile input controls into selector and action rows
- keep the desktop input footer compact while reducing mobile control crowding

* perf(playground): add starter empty state

- show starter prompts in the empty playground chat area
- wire empty-state prompt selection into the existing send flow
- add localized copy for the new empty state

* perf(playground): improve mobile message actions

- collapse mobile message actions into a touch-friendly dropdown menu
- keep the desktop hover action strip unchanged for pointer workflows
- share one action list between desktop buttons and the mobile menu

* perf(playground): add error recovery actions

- show retry, edit, and delete actions inside error message alerts
- route edit recovery to the previous user prompt when available
- keep recovery controls touch-friendly on mobile layouts

* perf(playground): refine message editing experience

- present message edits in a focused bordered editor panel
- add unsaved-change state, reset, and cancel confirmation flows
- improve mobile touch targets and keyboard shortcuts for editing

* perf(playground): improve markdown code blocks

- render fenced markdown code with syntax highlighting, line numbers, and fallback plain text
- add copy, download, and collapse controls for playground AI responses
- tighten code block layout and theme token styles for responsive markdown rendering

* fix(playground): constrain markdown code block height

- collapse long playground code blocks after a short preview instead of waiting for very large snippets
- cap expanded code blocks so long responses scroll inside the code block
- keep generic code block usage unconstrained unless a caller opts in

* feat(playground): add chat history clearing

- add a toolbar action that is enabled only when saved playground messages exist.
- confirm destructive clears before removing browser-stored conversation state.
- add localized strings for the action, dialog, and completion toast.

* perf(playground): improve chat markdown rendering

- refine assistant and user message surfaces so chat content matches the app UI.
- normalize markdown typography, tables, images, lists, blockquotes, and details rendering.
- add indentation cues for collapsible reasoning and source sections.

* style: format code block component

* style: format playground frontend files

* feat(playground): render markdown with stream parser

- replace Streamdown with stream-markdown-parser for project-owned markdown rendering and styling.
- split response rendering into focused block, inline, table, alert, details, and footnote modules.
- pass message final state into response parsing so streaming content can be parsed incrementally.

* fix(playground): localize reasoning and chat feedback

- translate reasoning status, message actions, playground errors, and response renderer fallbacks across supported locales.
- keep reasoning duration numeric and tighten the collapsible layout to prevent trigger jitter.
- register dynamic keys so i18n sync keeps runtime labels covered.

* refactor(playground): group files by functional area

- move chat, input, and message components into focused subdirectories to make the UI structure easier to scan.
- split playground helpers into input, message, streaming, storage, options, state, and suggestions modules.
- update barrel exports and imports so existing feature entry points continue to work.

* fix(playground): prevent history replay from freezing page

- defer saved conversation loading so route entry no longer blocks on localStorage parsing and markdown rendering.
- limit initial history rendering and skip expensive markdown parsing for oversized responses.
- normalize corrupted streaming snapshots and cumulative chunks to keep saved playground history bounded.
- add message timing metadata and layout alignment groundwork without introducing live timers.

* feat(playground): allow regenerating from user messages

- show regenerate actions on user messages with saved content.
- truncate following conversation state before starting a fresh assistant response.

* feat(playground): add raw response source view

- add a per-message source toggle for assistant responses.
- render raw response content with the existing code block viewer.
- localize the new source and preview action labels.

* feat(playground): render code with unified editor

- replace Shiki HTML rendering with a read-only CodeMirror view for code blocks and raw responses.
- reuse the same CodeMirror frame for message editing so source and edit modes stay visually aligned.
- add lightweight CodeMirror dependencies while keeping language support scoped to Markdown.

* perf(playground): streamline chat input controls

- combine model and group selection into one compact picker for faster context switching.
- switch playground action buttons to icon-first controls with tooltips to reduce toolbar width.
- refresh input footer styling and submit states so active and destructive actions are clearer.
- bump dompurify lockfile entry to keep the frontend dependency current.

* fix(playground): filter models by selected group

- query user models by the selected playground group instead of reusing the cross-group model union.
- clear unavailable model selections and block sending when the active group has no models.
- align model selector and error action controls with the existing playground interaction style.

* perf(playground): remove input suggestion chips

- remove the prompt suggestion row below the playground input to reduce visual noise.
- delete the now-unused suggestion component and display helper.

* perf(playground): stabilize reasoning trigger layout

- use fixed icon slots around the reasoning label so the left content stays still when toggling.
- limit the open state animation to the chevron rotation for a smoother collapse interaction.

* perf(playground): smooth reasoning expansion

- use the collapsible panel height animation for vertical reasoning reveals.
- sync inner content opacity and position with the panel state.
330079598 pushed a commit to 330079598/new-api that referenced this pull request Aug 19, 2026
…dering (QuantumNous#5217)

* refactor(playground): streamline chat request state

- extract conversation actions from the page component to keep message flow logic reusable.
- unify streaming and non-streaming generation state, including abort support for non-stream requests.
- simplify message rendering and payload construction while localizing Playground prompts.

* fix(playground): validate persisted chat state

- wrap saved Playground state with a storage version while still reading legacy values.

- validate config, parameter toggles, and messages before restoring them from localStorage.

- cap stored chat history to the latest messages to avoid oversized or stale state.

* refactor(playground): centralize message content access

- route chat rendering, copy actions, and error display through shared message helpers.

- reuse the current-version update helper for non-streaming assistant responses.

- keep message version details behind utility functions to reduce future model churn.

* refactor(playground): split storage schemas

- move Playground storage validation schemas into a dedicated module.

- keep storage read and write logic focused on migration, trimming, and persistence.

- preserve the existing storage envelope and validation behavior.

* refactor(playground): extract options loading hook

- move model and group queries into a dedicated hook so the page component stays focused on layout wiring.
- preserve existing fallback selection and error toast behavior while reusing the hook through the playground barrel export.

* refactor(playground): extract prompt suggestions

- move static prompt suggestion rendering into a focused component so the input stays centered on compose controls.
- preserve translated suggestion submission behavior while isolating icon metadata from the input form.

* refactor(playground): extract input tools

- move attachment and search controls into a dedicated component so the prompt input stays focused on compose state.
- keep existing development toast behavior and disabled handling while centralizing tool metadata.

* refactor(playground): extract input controls

- move model, group, send, and stop controls into a focused component so the input only manages compose state.
- preserve existing disabled states and generation button behavior while isolating control rendering.

* refactor(playground): extract message content display

- move sources, reasoning, loading, error, and response rendering into a dedicated message content component.
- keep the chat list focused on message iteration, edit state, and action wiring without changing display behavior.

* refactor(playground): extract message editor

- move inline message editing controls into a dedicated editor component so the chat list stays focused on rendering flow.
- preserve save, save-and-submit, cancel, and disabled-state behavior for edited messages.

* refactor(playground): extract stream error parsing

- move SSE error payload parsing into a reusable stream utility so the request hook stays focused on lifecycle handling.
- preserve existing error message, error code, and fallback behavior for raw or empty stream errors.

* refactor(playground): extract request error parsing

- move non-stream request error extraction into a shared utility so the chat handler stays focused on request flow.
- preserve the existing response message, error code, and fallback priority for failed chat completions.

* refactor(playground): extract streaming chunk updates

- move reasoning and content chunk application into a message utility so the chat handler only wires stream events.
- preserve error-state skipping, reasoning accumulation, and content streaming behavior for assistant messages.

* refactor(playground): extract message reasoning parser

- move think tag parsing into a dedicated playground message utility.
- export the parser through the shared playground lib barrel for consistent imports.

* refactor(playground): extract message streaming utilities

- move stream chunk application and message finalization into a dedicated utility.
- keep stored message sanitization with the streaming lifecycle helpers.

* refactor(playground): extract message update utilities

- move assistant message update helpers into a focused playground utility.
- keep error-state message updates separate from core message construction helpers.

* refactor(playground): extract completion choice handling

- move non-streaming choice application into the message streaming utilities.
- keep the chat handler focused on request orchestration and message updates.

* refactor(playground): centralize assistant completion state

- add a helper for finalizing assistant messages with complete status.
- reuse the helper in stream completion and stop-generation paths.

* refactor(playground): extract stream message parsing

- move SSE delta parsing into a shared stream utility.
- keep the stream request hook focused on lifecycle handling and update dispatch.

* refactor(playground): extract stream ready state checks

- move SSE ready-state status handling into stream utilities.
- keep weak source status typing outside the stream request hook.

* refactor(playground): extract conversation message helpers

- move send, regenerate, and edit message list construction into focused utilities.
- keep the conversation hook focused on edit state and update dispatch.

* refactor(playground): extract state initialization helpers

- move playground initial state loading into focused utility helpers.
- centralize message state updater resolution outside the React state hook.

* refactor(playground): extract option fallback helpers

- move model and group fallback selection into focused playground utilities.
- keep the options hook focused on query results, toasts, and config updates.

* refactor(playground): extract message action helpers

- move message action state derivation into focused utilities.

- keep the action component focused on guarded handlers and rendering.

* refactor(playground): extract input control state

- move submit, stop, and selector state derivation into a pure helper.

- keep input controls focused on rendering model selectors and action buttons.

* refactor(playground): extract message content state

- move source, reasoning, loader, and body visibility checks into a pure helper.

- use a discriminated state shape so rendered reasoning content stays type-safe.

* refactor(playground): extract message editor state

- move save eligibility and submit visibility checks into a pure helper.

- keep the editor component focused on textarea and button rendering.

* refactor(playground): extract message error state

- move error kind, fallback content, and admin visibility checks into a pure helper.

- centralize the model pricing settings path used by the error action.

* refactor(playground): extract chat render state

- move editing content lookup and per-message render flags into conversation helpers.

- keep the chat component focused on mapping messages to editor and content views.

* refactor(playground): extract suggestion display state

- move suggestion class selection into a pure helper.

- keep the suggestions component focused on translation and rendering.

* refactor(playground): extract assistant message state checks

- move final and pending assistant status checks into streaming utilities.

- keep the chat handler focused on request lifecycle updates.

* refactor(playground): extract input tool state

- move attachment action metadata and development notices into input tool utilities.

- keep the input tools component focused on menu and button rendering.

* refactor(playground): extract stream protocol checks

- move SSE done-message and closed-ready-state checks into stream utilities.

- keep the stream request hook focused on event handling flow.

* refactor(playground): extract message removal helper

- move delete-message filtering into conversation message utilities.

- keep the conversation hook focused on action orchestration.

* refactor(playground): extract option error messages

- move option load error message selection into playground option utilities
- keep the options hook focused on query effects and fallback updates

* refactor(playground): extract input submit text helper

- move prompt submit text validation into input control utilities
- let the input component submit only when a concrete text value is available

* refactor(playground): centralize error message checks

- add a shared helper for identifying error messages
- remove direct status string checks from message content rendering

* refactor(playground): extract message content display checks

- move loader and content visibility decisions into local helper functions
- keep message content state assembly focused on composing render state

* refactor(playground): replace raw message role checks

- use shared message role constants in conversation edit handling
- avoid raw assistant role literals when validating API messages

* refactor(playground): extract non-stream response handling

- move chat completion response choice handling into message streaming utilities
- keep the chat handler focused on request lifecycle and error routing

* refactor(playground): centralize stream cleanup

- reuse one stream cleanup path for completion, errors, startup failures, and manual stops
- preserve the current-source guard when closing SSE streams

* refactor(playground): extract pending assistant check

- centralize pending assistant message detection in streaming utilities
- reuse the helper when sanitizing stored playground messages

* perf(playground): improve mobile input controls

- split mobile input controls into selector and action rows
- keep the desktop input footer compact while reducing mobile control crowding

* perf(playground): add starter empty state

- show starter prompts in the empty playground chat area
- wire empty-state prompt selection into the existing send flow
- add localized copy for the new empty state

* perf(playground): improve mobile message actions

- collapse mobile message actions into a touch-friendly dropdown menu
- keep the desktop hover action strip unchanged for pointer workflows
- share one action list between desktop buttons and the mobile menu

* perf(playground): add error recovery actions

- show retry, edit, and delete actions inside error message alerts
- route edit recovery to the previous user prompt when available
- keep recovery controls touch-friendly on mobile layouts

* perf(playground): refine message editing experience

- present message edits in a focused bordered editor panel
- add unsaved-change state, reset, and cancel confirmation flows
- improve mobile touch targets and keyboard shortcuts for editing

* perf(playground): improve markdown code blocks

- render fenced markdown code with syntax highlighting, line numbers, and fallback plain text
- add copy, download, and collapse controls for playground AI responses
- tighten code block layout and theme token styles for responsive markdown rendering

* fix(playground): constrain markdown code block height

- collapse long playground code blocks after a short preview instead of waiting for very large snippets
- cap expanded code blocks so long responses scroll inside the code block
- keep generic code block usage unconstrained unless a caller opts in

* feat(playground): add chat history clearing

- add a toolbar action that is enabled only when saved playground messages exist.
- confirm destructive clears before removing browser-stored conversation state.
- add localized strings for the action, dialog, and completion toast.

* perf(playground): improve chat markdown rendering

- refine assistant and user message surfaces so chat content matches the app UI.
- normalize markdown typography, tables, images, lists, blockquotes, and details rendering.
- add indentation cues for collapsible reasoning and source sections.

* style: format code block component

* style: format playground frontend files

* feat(playground): render markdown with stream parser

- replace Streamdown with stream-markdown-parser for project-owned markdown rendering and styling.
- split response rendering into focused block, inline, table, alert, details, and footnote modules.
- pass message final state into response parsing so streaming content can be parsed incrementally.

* fix(playground): localize reasoning and chat feedback

- translate reasoning status, message actions, playground errors, and response renderer fallbacks across supported locales.
- keep reasoning duration numeric and tighten the collapsible layout to prevent trigger jitter.
- register dynamic keys so i18n sync keeps runtime labels covered.

* refactor(playground): group files by functional area

- move chat, input, and message components into focused subdirectories to make the UI structure easier to scan.
- split playground helpers into input, message, streaming, storage, options, state, and suggestions modules.
- update barrel exports and imports so existing feature entry points continue to work.

* fix(playground): prevent history replay from freezing page

- defer saved conversation loading so route entry no longer blocks on localStorage parsing and markdown rendering.
- limit initial history rendering and skip expensive markdown parsing for oversized responses.
- normalize corrupted streaming snapshots and cumulative chunks to keep saved playground history bounded.
- add message timing metadata and layout alignment groundwork without introducing live timers.

* feat(playground): allow regenerating from user messages

- show regenerate actions on user messages with saved content.
- truncate following conversation state before starting a fresh assistant response.

* feat(playground): add raw response source view

- add a per-message source toggle for assistant responses.
- render raw response content with the existing code block viewer.
- localize the new source and preview action labels.

* feat(playground): render code with unified editor

- replace Shiki HTML rendering with a read-only CodeMirror view for code blocks and raw responses.
- reuse the same CodeMirror frame for message editing so source and edit modes stay visually aligned.
- add lightweight CodeMirror dependencies while keeping language support scoped to Markdown.

* perf(playground): streamline chat input controls

- combine model and group selection into one compact picker for faster context switching.
- switch playground action buttons to icon-first controls with tooltips to reduce toolbar width.
- refresh input footer styling and submit states so active and destructive actions are clearer.
- bump dompurify lockfile entry to keep the frontend dependency current.

* fix(playground): filter models by selected group

- query user models by the selected playground group instead of reusing the cross-group model union.
- clear unavailable model selections and block sending when the active group has no models.
- align model selector and error action controls with the existing playground interaction style.

* perf(playground): remove input suggestion chips

- remove the prompt suggestion row below the playground input to reduce visual noise.
- delete the now-unused suggestion component and display helper.

* perf(playground): stabilize reasoning trigger layout

- use fixed icon slots around the reasoning label so the left content stays still when toggling.
- limit the open state animation to the chevron rotation for a smoother collapse interaction.

* perf(playground): smooth reasoning expansion

- use the collapsible panel height animation for vertical reasoning reveals.
- sync inner content opacity and position with the panel state.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

游乐场切换不同分组,只需要显示当前选择分组里的模型

2 participants