feat(playground): improve Playground chat experience and Markdown rendering - #5217
Conversation
- 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.
WalkthroughPlayground 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. ChangesPlayground Conversation and Chat Refactor
Response Rendering and Messaging UI
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 winFix
memocomparator forResponseto account forclassName,components, and forwardedStreamdownprops (web/default/src/components/ai-elements/response.tsx, ~449-490)
Response’s render depends onclassName, mergescomponentsoverrides, and spreads...propsinto<Streamdown>, but thememocomparator only checkschildren, 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 winSuppress callbacks from closed or superseded SSE sources.
Intentional closes never mark the stream as terminal before shutting down the source. If
sse.jsemits a lateerrororreadystatechangewhile a stopped/replaced stream is closing,handleErrorstill 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 valueLGTM!
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 winAvoid 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: MessageActionsPropsand readprops.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 winUse routed/link navigation instead of
window.openfor the settings CTA.For an internal route,
window.open(..., '_blank')sidesteps the app's navigation layer, and the new tab is opened withoutnoopener,noreferrer. Prefer aLink/anchor-based CTA with the properrelattributes, oruseNavigateif this should stay in-app.As per coding guidelines,
web/default/**/*.{ts,tsx}: useuseNavigateorLinkfor 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 winUse
props.xxxhere 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; useprops.xxxdirectly 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 winMove 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 usingcn(); avoid inline styles for dynamic scenarios" and "web/default/**/*.{tsx,css}: use CSS variables anddark: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 winKeep component props on
propsinstead 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; useprops.xxxdirectly 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 winDrive 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 thet()function fromuseTranslation()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 winPrefer
props.xxxover destructuring the component props.This component is small enough that keeping a single
propsobject reads more clearly and aligns with the repo convention.As per coding guidelines, "
web/default/**/*.tsx: Do not destructure component props; useprops.xxxdirectly 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 winKeep the component props on
propsinstead of destructuring them.This signature is large enough that the repo's
props.xxxconvention would be easier to follow here.As per coding guidelines, "
web/default/**/*.tsx: Do not destructure component props; useprops.xxxdirectly 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 winUse 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 throughhandleServerErrorinstead 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 winKeep editor props on a single
propsobject.This component is another TSX guideline mismatch: please avoid destructuring props in the signature and read them through
props.xxxinstead.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 winKeep component props on
propsinstead of destructuring them.The new component signature breaks the repo’s TSX convention. Please accept a single
propsobject and readprops.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 winUse
props.xxxinstead of destructuring the component props.Please keep the
PlaygroundChatparameter as a singlepropsobject 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 winFollow the TSX props convention here as well.
Please keep the component parameter as
propsand accessprops.onSelectPromptinside 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
📒 Files selected for processing (53)
web/default/src/components/ai-elements/code-block.tsxweb/default/src/components/ai-elements/conversation.tsxweb/default/src/components/ai-elements/reasoning.tsxweb/default/src/components/ai-elements/response.tsxweb/default/src/components/ai-elements/sources.tsxweb/default/src/features/playground/api.tsweb/default/src/features/playground/components/message-actions.tsxweb/default/src/features/playground/components/message-error-actions.tsxweb/default/src/features/playground/components/message-error.tsxweb/default/src/features/playground/components/playground-chat.tsxweb/default/src/features/playground/components/playground-empty-state.tsxweb/default/src/features/playground/components/playground-input-controls.tsxweb/default/src/features/playground/components/playground-input-tools.tsxweb/default/src/features/playground/components/playground-input.tsxweb/default/src/features/playground/components/playground-message-content.tsxweb/default/src/features/playground/components/playground-message-editor.tsxweb/default/src/features/playground/components/playground-suggestions.tsxweb/default/src/features/playground/hooks/index.tsweb/default/src/features/playground/hooks/use-chat-handler.tsweb/default/src/features/playground/hooks/use-playground-conversation.tsweb/default/src/features/playground/hooks/use-playground-options.tsweb/default/src/features/playground/hooks/use-playground-state.tsweb/default/src/features/playground/hooks/use-stream-request.tsweb/default/src/features/playground/index.tsxweb/default/src/features/playground/lib/conversation-message-utils.tsweb/default/src/features/playground/lib/index.tsweb/default/src/features/playground/lib/input-control-utils.tsweb/default/src/features/playground/lib/input-tool-utils.tsweb/default/src/features/playground/lib/message-action-utils.tsweb/default/src/features/playground/lib/message-content-utils.tsweb/default/src/features/playground/lib/message-editor-utils.tsweb/default/src/features/playground/lib/message-error-utils.tsweb/default/src/features/playground/lib/message-reasoning-utils.tsweb/default/src/features/playground/lib/message-streaming-utils.tsweb/default/src/features/playground/lib/message-styles.tsweb/default/src/features/playground/lib/message-update-utils.tsweb/default/src/features/playground/lib/message-utils.tsweb/default/src/features/playground/lib/payload-builder.tsweb/default/src/features/playground/lib/playground-option-utils.tsweb/default/src/features/playground/lib/playground-state-utils.tsweb/default/src/features/playground/lib/request-error-utils.tsweb/default/src/features/playground/lib/storage-schema.tsweb/default/src/features/playground/lib/storage.tsweb/default/src/features/playground/lib/stream-utils.tsweb/default/src/features/playground/lib/suggestion-utils.tsweb/default/src/features/usage-logs/components/usage-logs-mobile-card.tsxweb/default/src/i18n/locales/en.jsonweb/default/src/i18n/locales/fr.jsonweb/default/src/i18n/locales/ja.jsonweb/default/src/i18n/locales/ru.jsonweb/default/src/i18n/locales/vi.jsonweb/default/src/i18n/locales/zh.jsonweb/default/src/styles/index.css
| <MoreHorizontal className='size-4' /> | ||
| <span className='sr-only'>{t('Open menu')}</span> |
There was a problem hiding this comment.
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' />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.
| const MODEL_PRICE_ERROR_CODE = 'model_price_error' | ||
| const FALLBACK_ERROR_CONTENT = 'An unknown error occurred' |
There was a problem hiding this comment.
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.
| 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, |
There was a problem hiding this comment.
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.
| export function getOptionLoadErrorMessage( | ||
| error: unknown, | ||
| fallbackMessage: string | ||
| ): string { | ||
| return error instanceof Error ? error.message : fallbackMessage |
There was a problem hiding this comment.
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.
| type RequestErrorLike = { | ||
| message?: string | ||
| response?: { | ||
| data?: { | ||
| error?: { | ||
| code?: string | ||
| } | ||
| message?: string | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
- 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.
- 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.
f53b557 to
53d7b87
Compare
# 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.
There was a problem hiding this comment.
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 winTranslate the attachment toast description as well.
notice.descriptionis shown to users but isn’t passed throught(), 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
⛔ Files ignored due to path filters (1)
web/bun.lockis excluded by!**/*.lock
📒 Files selected for processing (12)
controller/model_list_test.gocontroller/user.goweb/default/src/components/model-group-selector.tsxweb/default/src/features/playground/api.tsweb/default/src/features/playground/components/input/playground-input-controls.tsxweb/default/src/features/playground/components/input/playground-input-tools.tsxweb/default/src/features/playground/components/input/playground-input.tsxweb/default/src/features/playground/components/message/message-error-actions.tsxweb/default/src/features/playground/hooks/use-playground-options.tsweb/default/src/features/playground/lib/index.tsweb/default/src/features/playground/lib/input/input-control-utils.tsweb/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.
There was a problem hiding this comment.
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 | 🟡 MinorAdd
keepMounted={true}toCollapsibleContentto enable exit transitions.The
@base-ui/react/collapsiblePanelcomponent is unmounted from the DOM by default when closed. SinceReasoningContentrelies on CSS transitions (opacity,translate) based on thedata-closedstate, the element disappears instantly upon closing withoutkeepMounted, causing the animation to skip.Update
web/default/src/components/ui/collapsible.tsxor pass the prop toReasoningContent: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 valueHide decorative icons from assistive tech.
BrainIconandChevronDownIconare purely decorative (the trigger's meaning is already conveyed by the adjacent text), but they're exposed to screen readers. Addaria-hiddenso 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
📒 Files selected for processing (1)
web/default/src/components/ai-elements/reasoning.tsx
* 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>
…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.
…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.
…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.
Important
📝 变更描述 / Description
(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)
概述
改动说明
使用方式
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
(请在此粘贴截图、关键日志或测试报告,以证明变更生效)
Summary by CodeRabbit
Summary by CodeRabbit