Skip to content

Commit ec1133f

Browse files
committed
better log
1 parent 5fb7ca0 commit ec1133f

File tree

2 files changed

+107
-34
lines changed

2 files changed

+107
-34
lines changed

frontend/src/scenes/frame/panels/Chat/Chat.tsx

Lines changed: 85 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { KeyboardEvent } from 'react'
1212
import clsx from 'clsx'
1313
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso'
1414
import { Area, Panel } from '../../../../types'
15-
import { ArrowLeftIcon, ChevronLeftIcon } from '@heroicons/react/24/solid'
15+
import { ChevronLeftIcon } from '@heroicons/react/24/solid'
1616

1717
export function Chat() {
1818
const { frameId, scenes } = useValues(frameLogic)
@@ -35,12 +35,14 @@ export function Chat() {
3535
chatMessagesLoading,
3636
isCreatingChat,
3737
contextSelectionSummary,
38+
logExpanded,
3839
} = useValues(chatLogic({ frameId, sceneId: selectedSceneId }))
3940
const {
4041
setInput,
4142
submitMessage,
4243
clearChat,
4344
toggleContextItemsExpanded,
45+
toggleLogExpanded,
4446
selectChat,
4547
backToList,
4648
createChat,
@@ -53,12 +55,6 @@ export function Chat() {
5355
const scrollerElementRef = useRef<HTMLElement | null>(null)
5456
const shouldStickToBottomRef = useRef(true)
5557
const lastMessage = messages[messages.length - 1]
56-
const pendingAssistantPlaceholder =
57-
messages.length > 0 &&
58-
messages[messages.length - 1].isPlaceholder &&
59-
!messages[messages.length - 1].content &&
60-
!messages[messages.length - 1].tool
61-
const pendingThinkingIndex = pendingAssistantPlaceholder ? messages.length - 2 : null
6258
const isChatView = chatView === 'chat' && activeChatId
6359
const hasBackendApiKey = Boolean(savedSettings?.openAI?.backendApiKey?.trim())
6460
const missingBackendApiKey = !hasBackendApiKey
@@ -193,10 +189,30 @@ export function Chat() {
193189
)
194190
}
195191

196-
const renderLogLine = (line: string) => {
192+
const stripBracketSegments = (value: string) =>
193+
value
194+
.replace(/\s*\[[^\]]+\]\s*/g, ' ')
195+
.replace(/\s+/g, ' ')
196+
.trim()
197+
198+
const stripReviewIssues = (value: string) => {
199+
const match = value.match(/^(WARNING: Scene review issues:)\s*(\[.*\])$/)
200+
return match ? match[1] : value
201+
}
202+
203+
const renderLogLine = (
204+
line: string,
205+
options: { showStage?: boolean; showDetails?: boolean } = { showStage: true, showDetails: true }
206+
) => {
207+
const { showStage = true, showDetails = true } = options
208+
const lineWithReview = showDetails ? line : stripReviewIssues(line)
209+
197210
const contextMatch = line.match(/^(.*Selected \d+ context items: )(.+)$/)
198211
if (contextMatch) {
199212
const [, label, items] = contextMatch
213+
if (!showDetails) {
214+
return <span className="text-slate-100">{label.trim()}</span>
215+
}
200216
const tokens = items
201217
.split(',')
202218
.map((item) => item.trim())
@@ -240,11 +256,12 @@ export function Chat() {
240256
const [, time, stage, message] = structuredMatch
241257
const statusMatch = message.match(/^(SUCCESS|ERROR):\s+(.*)$/)
242258
const statusMessage = statusMatch ? statusMatch[2] : message
259+
const filteredStatusMessage = showDetails ? statusMessage : stripReviewIssues(statusMessage)
243260
const generatedSceneName = extractGeneratedSceneName(statusMessage)
244261
return (
245262
<div className="flex flex-wrap gap-x-2 gap-y-1">
246263
<span className="text-slate-500">{time}</span>
247-
<span className="text-sky-300">{stage}</span>
264+
{showStage ? <span className="text-sky-300">{stage}</span> : null}
248265
{statusMatch ? (
249266
<span className={clsx('font-semibold', statusMatch[1] === 'ERROR' ? 'text-red-400' : 'text-emerald-300')}>
250267
{statusMatch[1]}:
@@ -253,40 +270,82 @@ export function Chat() {
253270
{generatedSceneName ? (
254271
<span className="text-slate-100">{renderGeneratedSceneMessage(generatedSceneName)}</span>
255272
) : statusMatch ? (
256-
<span className="text-slate-100">{statusMatch[2]}</span>
273+
<span className="text-slate-100">{filteredStatusMessage}</span>
257274
) : (
258-
<span className="text-slate-100">{message}</span>
275+
<span className="text-slate-100">
276+
{showStage
277+
? filteredStatusMessage
278+
: stripBracketSegments(showDetails ? message : stripReviewIssues(message))}
279+
</span>
259280
)}
260281
</div>
261282
)
262283
}
263284

264-
const generatedSceneName = extractGeneratedSceneName(line)
285+
const sanitizedLine = showStage ? lineWithReview : stripBracketSegments(lineWithReview)
286+
const generatedSceneName = extractGeneratedSceneName(lineWithReview)
265287
if (generatedSceneName) {
266288
return <span className="text-slate-100">{renderGeneratedSceneMessage(generatedSceneName)}</span>
267289
}
268290

269-
return <span className="text-slate-100">{line}</span>
291+
return <span className="text-slate-100">{sanitizedLine}</span>
270292
}
271293

272-
const renderMessageBody = (messageContent: string, isLog: boolean) => {
273-
if (!messageContent) {
274-
return null
275-
}
294+
const renderLogMessage = (messageContent: string, messageId: string, isStreaming?: boolean) => {
295+
const lines = messageContent ? messageContent.split('\n') : []
296+
const displayLines = lines.length > 0 ? lines : ['Thinking…']
297+
const lastLine = displayLines[displayLines.length - 1] ?? ''
298+
const isExpanded = logExpanded[messageId] ?? false
299+
const canExpand = displayLines.length > 1
276300

277-
if (isLog) {
278-
const lines = messageContent.split('\n')
301+
if (!isExpanded) {
279302
return (
280-
<div className="space-y-2 font-mono text-xs">
281-
{lines.map((line, index) => (
282-
<div key={`${line}-${index}`} className="whitespace-pre-wrap break-words">
283-
{renderLogLine(line)}
303+
<button
304+
type="button"
305+
className={clsx('flex w-full items-start text-left', canExpand ? 'cursor-pointer' : 'cursor-default')}
306+
onClick={() => activeChatId && canExpand && toggleLogExpanded(activeChatId, messageId)}
307+
disabled={!canExpand}
308+
>
309+
{isStreaming ? <Spinner className="h-4 w-4 mr-2 text-slate-400" /> : <span className="h-4 w-4" />}
310+
<span className={clsx('flex-1 text-sm', isStreaming ? 'opacity-70' : '')}>
311+
{renderLogLine(lastLine, { showStage: false, showDetails: false })}
312+
</span>
313+
{isStreaming ? (
314+
<div className="w-2">
315+
<span className="ai-scene-ellipsis text-slate-400" />
284316
</div>
285-
))}
286-
</div>
317+
) : null}
318+
</button>
287319
)
288320
}
289321

322+
return (
323+
<div className="space-y-2 text-sm">
324+
{displayLines.map((line, index) => (
325+
<div key={`${line}-${index}`} className="whitespace-pre-wrap break-words">
326+
{renderLogLine(line)}
327+
</div>
328+
))}
329+
<button
330+
type="button"
331+
className="text-xs text-slate-500 hover:text-slate-300 transition"
332+
onClick={() => activeChatId && toggleLogExpanded(activeChatId, messageId)}
333+
>
334+
Hide log steps
335+
</button>
336+
</div>
337+
)
338+
}
339+
340+
const renderMessageBody = (messageContent: string, isLog: boolean, messageId: string, isStreaming?: boolean) => {
341+
if (isLog) {
342+
return renderLogMessage(messageContent, messageId, isStreaming)
343+
}
344+
345+
if (!messageContent) {
346+
return null
347+
}
348+
290349
return <div className="whitespace-pre-wrap break-words">{messageContent}</div>
291350
}
292351

@@ -398,15 +457,7 @@ export function Chat() {
398457
<span className="uppercase tracking-wide">{message.role}</span>
399458
{message.tool ? <span className="text-slate-500">tool: {message.tool}</span> : null}
400459
</div>
401-
<div>
402-
{renderMessageBody(message.content, isLog)}
403-
{pendingThinkingIndex === index && isLog && message.isStreaming ? (
404-
<div className="inline-flex items-center gap-2 text-slate-300 pt-2">
405-
<span>Thinking…</span>
406-
</div>
407-
) : null}
408-
{message.isStreaming ? <span className="ml-1 animate-pulse"></span> : null}
409-
</div>
460+
<div>{renderMessageBody(message.content, isLog, message.id, message.isStreaming)}</div>
410461
</div>
411462
</div>
412463
)

frontend/src/scenes/frame/panels/Chat/chatLogic.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export const chatLogic = kea<chatLogicType>([
8585
setActiveLogMessageId: (messageId: string | null) => ({ messageId }),
8686
setActiveLogStartTime: (timestamp: string | null) => ({ timestamp }),
8787
toggleContextItemsExpanded: (chatId: string, key: string) => ({ chatId, key }),
88+
toggleLogExpanded: (chatId: string, messageId: string) => ({ chatId, messageId }),
8889
loadChats: () => ({}),
8990
loadChatsSuccess: (chats: ChatSummary[], hasMore: boolean, nextOffset: number) => ({
9091
chats,
@@ -331,6 +332,22 @@ export const chatLogic = kea<chatLogicType>([
331332
}),
332333
},
333334
],
335+
logExpandedByChat: [
336+
{} as Record<string, Record<string, boolean>>,
337+
{
338+
toggleLogExpanded: (state, { chatId, messageId }) => ({
339+
...state,
340+
[chatId]: {
341+
...(state[chatId] ?? {}),
342+
[messageId]: !(state[chatId] ?? {})[messageId],
343+
},
344+
}),
345+
clearChat: (state, { chatId }) => ({
346+
...state,
347+
[chatId]: {},
348+
}),
349+
},
350+
],
334351
}),
335352
selectors({
336353
selectedScene: [
@@ -371,6 +388,11 @@ export const chatLogic = kea<chatLogicType>([
371388
(contextItemsExpandedByChat: Record<string, Record<string, boolean>>, activeChatId: string | null) =>
372389
activeChatId ? contextItemsExpandedByChat[activeChatId] ?? {} : {},
373390
],
391+
logExpanded: [
392+
(s: any) => [s.logExpandedByChat, s.activeChatId],
393+
(logExpandedByChat: Record<string, Record<string, boolean>>, activeChatId: string | null) =>
394+
activeChatId ? logExpandedByChat[activeChatId] ?? {} : {},
395+
],
374396
historyForRequest: [
375397
(s) => [s.messages],
376398
(messages: ChatMessage[]) =>

0 commit comments

Comments
 (0)