From 268e5cf5eaa308933fb5f63e2341f5eae3e6d7df Mon Sep 17 00:00:00 2001 From: Pabloader Date: Mon, 23 Mar 2026 09:24:16 +0000 Subject: [PATCH] Think parsing & styles --- .../assets/chat-sidebar.module.css | 52 +++++++++++++++- src/games/storywriter/assets/style.css | 5 +- .../storywriter/components/chat-sidebar.tsx | 62 +++++++++++++++++-- .../storywriter/components/settings-modal.tsx | 18 +++--- src/games/storywriter/components/sidebar.tsx | 10 ++- src/games/storywriter/contexts/state.tsx | 26 +++++++- src/games/storywriter/utils/highlight.ts | 43 +++++++++---- src/games/storywriter/utils/llm.ts | 17 +++-- src/games/storywriter/utils/prompt.ts | 5 +- src/games/storywriter/utils/tools.ts | 36 ++++++++--- 10 files changed, 223 insertions(+), 51 deletions(-) diff --git a/src/games/storywriter/assets/chat-sidebar.module.css b/src/games/storywriter/assets/chat-sidebar.module.css index 1b2a71c..841e09e 100644 --- a/src/games/storywriter/assets/chat-sidebar.module.css +++ b/src/games/storywriter/assets/chat-sidebar.module.css @@ -43,6 +43,12 @@ background: var(--bg-hover); } +.message[data-role="tool"] .content { + font-size: 12px; + font-style: italic; + opacity: 0.5; +} + .role { font-size: 11px; font-weight: bold; @@ -50,6 +56,17 @@ text-transform: uppercase; } +.reasoningContent { + font-size: 12px; + font-style: italic; + opacity: 0.5; + border-left: 2px solid currentColor; + padding-left: 0.5em; + margin-bottom: 0.5em; + white-space: pre-wrap; + word-wrap: break-word; +} + .content { font-size: 13px; color: var(--text); @@ -57,6 +74,22 @@ word-wrap: break-word; } +.toolCalls { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-top: 4px; +} + +.toolBadge { + font-size: 11px; + padding: 2px 8px; + background: var(--bg-active); + color: var(--text-dim); + border-radius: var(--radius); + font-weight: 500; +} + .loading { color: var(--text-muted); font-style: italic; @@ -83,6 +116,21 @@ border-top: 1px solid var(--border); } +.toggleContainer { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: var(--text); + cursor: pointer; + user-select: none; + + input[type="checkbox"] { + cursor: pointer; + accent-color: var(--accent); + } +} + .input { width: 100%; padding: 8px; @@ -128,10 +176,10 @@ border-radius: 4px; cursor: pointer; align-self: center; - margin-top: 8px; + margin: 8px 0 28px; &:hover { color: var(--text); border-color: var(--text-muted); } -} +} \ No newline at end of file diff --git a/src/games/storywriter/assets/style.css b/src/games/storywriter/assets/style.css index 2e641b4..ba18ba0 100644 --- a/src/games/storywriter/assets/style.css +++ b/src/games/storywriter/assets/style.css @@ -18,9 +18,10 @@ --radius: 4px; --transition: 0.15s ease; - --textColor: #DCDCD2; + --textColor: #DCDCD2; --italicColor: #AFAFAF; - --quoteColor: #D4E5FF; + --quoteColor: #D4E5FF; + --codeBg: #49483e; } * { diff --git a/src/games/storywriter/components/chat-sidebar.tsx b/src/games/storywriter/components/chat-sidebar.tsx index b651623..b853b0b 100644 --- a/src/games/storywriter/components/chat-sidebar.tsx +++ b/src/games/storywriter/components/chat-sidebar.tsx @@ -1,3 +1,4 @@ +import { useInputState } from "@common/hooks/useInputState"; import { Sidebar } from "./sidebar"; import { useAppState, type ChatMessage } from "../contexts/state"; import styles from '../assets/chat-sidebar.module.css'; @@ -10,9 +11,11 @@ import clsx from "clsx"; export const ChatSidebar = () => { const appState = useAppState(); - const { currentStory, dispatch, connection, model } = appState; - const [input, setInput] = useState(''); + const { currentStory, dispatch, connection, model, enableThinking } = appState; + const [input, setInput] = useInputState(''); const [isLoading, setIsLoading] = useState(false); + const [isCollapsed, setCollapsed] = useState(false); + const [error, setError] = useState(null); const messagesRef = useRef(null); const abortControllerRef = useRef(new AbortController()); @@ -26,6 +29,12 @@ export const ChatSidebar = () => { } }, [currentStory?.chatMessages.length]); + useEffect(() => { + if (messagesRef.current) { + messagesRef.current.scrollTop = messagesRef.current.scrollHeight; + } + }, [isCollapsed]); + useEffect(() => { return () => { abortControllerRef.current?.abort(); @@ -50,7 +59,8 @@ export const ChatSidebar = () => { message: { id: assistantMessageId, role: 'assistant', - content: 'Generating...', + content: '', + reasoning_content: 'Generating...', }, }); @@ -64,6 +74,7 @@ export const ChatSidebar = () => { try { let accumulatedContent = ''; + let accumulatedReasoning = ''; let tool_calls: LLM.ToolCall[] | undefined; for await (const chunk of LLM.generateStream(connection, request)) { @@ -76,6 +87,12 @@ export const ChatSidebar = () => { const content = delta?.content; if (content) { accumulatedContent += content; + } + const reasoningContent = delta?.reasoning_content; + if (reasoningContent) { + accumulatedReasoning += reasoningContent; + } + if (content || reasoningContent) { dispatch({ type: 'ADD_CHAT_MESSAGE', storyId: currentStory.id, @@ -83,6 +100,7 @@ export const ChatSidebar = () => { id: assistantMessageId, role: 'assistant', content: accumulatedContent, + reasoning_content: accumulatedReasoning, tool_calls, }, }); @@ -95,6 +113,7 @@ export const ChatSidebar = () => { id: assistantMessageId, role: 'assistant', content: accumulatedContent, + reasoning_content: accumulatedReasoning, tool_calls, }; dispatch({ @@ -171,7 +190,7 @@ export const ChatSidebar = () => { const isDisabled = !currentStory || !connection || !model || isLoading; return ( - +
{!currentStory ? (
@@ -190,10 +209,27 @@ export const ChatSidebar = () => { {currentStory.chatMessages.map((message) => (
{message.role}
+ + {message.role === 'assistant' && message.reasoning_content && ( +
+ {message.reasoning_content} +
+ )} +
+ + {message.role === 'assistant' && message.tool_calls && ( +
+ {message.tool_calls.map((tool) => ( + + {tool.function.name} + + ))} +
+ )}
))} {error && ( @@ -209,10 +245,24 @@ export const ChatSidebar = () => { )} {currentStory && (
+ {model?.support_thinking && + + }