* 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.
214 lines
4.9 KiB
TypeScript
Vendored
214 lines
4.9 KiB
TypeScript
Vendored
/*
|
|
Copyright (C) 2023-2026 QuantumNous
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
For commercial licensing, please contact support@quantumnous.com
|
|
*/
|
|
import type { ReactNode } from 'react'
|
|
import type {
|
|
CodeBlockNode,
|
|
DefinitionItemNode,
|
|
DefinitionListNode,
|
|
HeadingNode,
|
|
ListNode,
|
|
MathBlockNode,
|
|
MathInlineNode,
|
|
} from 'stream-markdown-parser'
|
|
|
|
import {
|
|
CodeBlock,
|
|
CodeBlockCopyButton,
|
|
} from '@/components/ai-elements/code-block'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
import { getNodeKey } from './response-content'
|
|
import type { BlockRendererOptions } from './response-types'
|
|
|
|
const headingClasses = {
|
|
1: 'mt-6 mb-3 text-xl font-semibold tracking-normal',
|
|
2: 'mt-6 mb-3 text-lg font-semibold tracking-normal',
|
|
3: 'mt-5 mb-2 text-base font-semibold tracking-normal',
|
|
4: 'mt-5 mb-2 text-sm font-semibold tracking-normal',
|
|
5: 'text-muted-foreground mt-4 mb-2 text-sm font-semibold tracking-normal',
|
|
6: 'text-muted-foreground mt-4 mb-2 text-xs font-semibold tracking-normal uppercase',
|
|
} satisfies Record<1 | 2 | 3 | 4 | 5 | 6, string>
|
|
|
|
export function renderHeading(
|
|
node: HeadingNode,
|
|
key: string,
|
|
options: BlockRendererOptions
|
|
): ReactNode {
|
|
const headingLevel = Math.min(Math.max(node.level, 1), 6) as
|
|
| 1
|
|
| 2
|
|
| 3
|
|
| 4
|
|
| 5
|
|
| 6
|
|
const className = headingClasses[headingLevel]
|
|
const children = options.renderChildren(node.children)
|
|
|
|
if (headingLevel === 1) {
|
|
return (
|
|
<h1 className={className} key={key}>
|
|
{children}
|
|
</h1>
|
|
)
|
|
}
|
|
|
|
if (headingLevel === 2) {
|
|
return (
|
|
<h2 className={className} key={key}>
|
|
{children}
|
|
</h2>
|
|
)
|
|
}
|
|
|
|
if (headingLevel === 3) {
|
|
return (
|
|
<h3 className={className} key={key}>
|
|
{children}
|
|
</h3>
|
|
)
|
|
}
|
|
|
|
if (headingLevel === 4) {
|
|
return (
|
|
<h4 className={className} key={key}>
|
|
{children}
|
|
</h4>
|
|
)
|
|
}
|
|
|
|
if (headingLevel === 5) {
|
|
return (
|
|
<h5 className={className} key={key}>
|
|
{children}
|
|
</h5>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<h6 className={className} key={key}>
|
|
{children}
|
|
</h6>
|
|
)
|
|
}
|
|
|
|
export function renderList(
|
|
node: ListNode,
|
|
key: string,
|
|
options: BlockRendererOptions
|
|
): ReactNode {
|
|
const className = cn(
|
|
'my-3 list-outside space-y-1.5 pl-5',
|
|
node.ordered ? 'list-decimal' : 'list-disc'
|
|
)
|
|
const items = node.items.map((item, index) => (
|
|
<li
|
|
className='marker:text-muted-foreground pl-1 leading-7'
|
|
key={getNodeKey(item, index)}
|
|
>
|
|
{options.renderChildren(item.children)}
|
|
</li>
|
|
))
|
|
|
|
if (node.ordered) {
|
|
return (
|
|
<ol className={className} key={key} start={node.start}>
|
|
{items}
|
|
</ol>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<ul className={className} key={key}>
|
|
{items}
|
|
</ul>
|
|
)
|
|
}
|
|
|
|
export function renderCodeBlock(node: CodeBlockNode, key: string): ReactNode {
|
|
const language = node.language || 'plaintext'
|
|
const lineCount = node.code.split('\n').length
|
|
|
|
return (
|
|
<CodeBlock
|
|
collapsedLines={14}
|
|
code={node.code}
|
|
defaultCollapsed={lineCount > 14}
|
|
key={key}
|
|
language={language}
|
|
maxExpandedLines={44}
|
|
showLineNumbers
|
|
showToolbar
|
|
title={language}
|
|
>
|
|
<CodeBlockCopyButton />
|
|
</CodeBlock>
|
|
)
|
|
}
|
|
|
|
export function renderDefinitionList(
|
|
node: DefinitionListNode,
|
|
key: string,
|
|
options: BlockRendererOptions
|
|
): ReactNode {
|
|
return (
|
|
<dl className='my-4 space-y-3' key={key}>
|
|
{node.items.map((item, index) =>
|
|
renderDefinitionItem(item, index, options)
|
|
)}
|
|
</dl>
|
|
)
|
|
}
|
|
|
|
function renderDefinitionItem(
|
|
node: DefinitionItemNode,
|
|
index: number,
|
|
options: BlockRendererOptions
|
|
): ReactNode {
|
|
return (
|
|
<div key={`definition-${index}`}>
|
|
<dt className='font-semibold'>{options.renderChildren(node.term)}</dt>
|
|
<dd className='text-muted-foreground mt-1 pl-4'>
|
|
{options.renderChildren(node.definition)}
|
|
</dd>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function renderMathBlock(node: MathBlockNode, key: string): ReactNode {
|
|
return (
|
|
<pre
|
|
className='border-border bg-muted/40 my-4 overflow-x-auto rounded-lg border p-4 font-mono text-sm'
|
|
key={key}
|
|
>
|
|
{node.content}
|
|
</pre>
|
|
)
|
|
}
|
|
|
|
export function renderMathInline(node: MathInlineNode, key: string): ReactNode {
|
|
return (
|
|
<code
|
|
className='bg-muted/70 text-foreground rounded px-1 py-0.5 font-mono text-[0.9em]'
|
|
key={key}
|
|
>
|
|
{node.content}
|
|
</code>
|
|
)
|
|
}
|