@open-mercato/ai-assistant 0.4.2-canary-c02407ff85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1090 -0
- package/README.md +607 -0
- package/build.mjs +92 -0
- package/dist/di.js +8 -0
- package/dist/di.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandFooter.js +80 -0
- package/dist/frontend/components/CommandPalette/CommandFooter.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandHeader.js +53 -0
- package/dist/frontend/components/CommandPalette/CommandHeader.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandInput.js +29 -0
- package/dist/frontend/components/CommandPalette/CommandInput.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandItem.js +92 -0
- package/dist/frontend/components/CommandPalette/CommandItem.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandPalette.js +244 -0
- package/dist/frontend/components/CommandPalette/CommandPalette.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js +42 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js.map +7 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js +18 -0
- package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js.map +7 -0
- package/dist/frontend/components/CommandPalette/DebugPanel.js +215 -0
- package/dist/frontend/components/CommandPalette/DebugPanel.js.map +7 -0
- package/dist/frontend/components/CommandPalette/MessageBubble.js +64 -0
- package/dist/frontend/components/CommandPalette/MessageBubble.js.map +7 -0
- package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js +91 -0
- package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js.map +7 -0
- package/dist/frontend/components/CommandPalette/ToolCallDisplay.js +47 -0
- package/dist/frontend/components/CommandPalette/ToolCallDisplay.js.map +7 -0
- package/dist/frontend/components/CommandPalette/ToolChatPage.js +74 -0
- package/dist/frontend/components/CommandPalette/ToolChatPage.js.map +7 -0
- package/dist/frontend/components/CommandPalette/index.js +28 -0
- package/dist/frontend/components/CommandPalette/index.js.map +7 -0
- package/dist/frontend/constants.js +41 -0
- package/dist/frontend/constants.js.map +7 -0
- package/dist/frontend/hooks/index.js +13 -0
- package/dist/frontend/hooks/index.js.map +7 -0
- package/dist/frontend/hooks/useCommandPalette.js +1094 -0
- package/dist/frontend/hooks/useCommandPalette.js.map +7 -0
- package/dist/frontend/hooks/useMcpTools.js +66 -0
- package/dist/frontend/hooks/useMcpTools.js.map +7 -0
- package/dist/frontend/hooks/usePageContext.js +48 -0
- package/dist/frontend/hooks/usePageContext.js.map +7 -0
- package/dist/frontend/hooks/useRecentActions.js +56 -0
- package/dist/frontend/hooks/useRecentActions.js.map +7 -0
- package/dist/frontend/hooks/useRecentTools.js +55 -0
- package/dist/frontend/hooks/useRecentTools.js.map +7 -0
- package/dist/frontend/index.js +35 -0
- package/dist/frontend/index.js.map +7 -0
- package/dist/frontend/types.js +1 -0
- package/dist/frontend/types.js.map +7 -0
- package/dist/frontend/utils/index.js +7 -0
- package/dist/frontend/utils/index.js.map +7 -0
- package/dist/frontend/utils/toolMatcher.js +95 -0
- package/dist/frontend/utils/toolMatcher.js.map +7 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/ai_assistant/acl.js +14 -0
- package/dist/modules/ai_assistant/acl.js.map +7 -0
- package/dist/modules/ai_assistant/api/chat/route.js +152 -0
- package/dist/modules/ai_assistant/api/chat/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/health/route.js +27 -0
- package/dist/modules/ai_assistant/api/health/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/route/route.js +123 -0
- package/dist/modules/ai_assistant/api/route/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/settings/route.js +60 -0
- package/dist/modules/ai_assistant/api/settings/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/tools/execute/route.js +58 -0
- package/dist/modules/ai_assistant/api/tools/execute/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/tools/route.js +48 -0
- package/dist/modules/ai_assistant/api/tools/route.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js +28 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/cli.js +192 -0
- package/dist/modules/ai_assistant/cli.js.map +7 -0
- package/dist/modules/ai_assistant/di.js +11 -0
- package/dist/modules/ai_assistant/di.js.map +7 -0
- package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js +257 -0
- package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/index.js +13 -0
- package/dist/modules/ai_assistant/index.js.map +7 -0
- package/dist/modules/ai_assistant/lib/ai-sdk.js +13 -0
- package/dist/modules/ai_assistant/lib/ai-sdk.js.map +7 -0
- package/dist/modules/ai_assistant/lib/api-discovery-tools.js +249 -0
- package/dist/modules/ai_assistant/lib/api-discovery-tools.js.map +7 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js +177 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js +210 -0
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js.map +7 -0
- package/dist/modules/ai_assistant/lib/auth.js +87 -0
- package/dist/modules/ai_assistant/lib/auth.js.map +7 -0
- package/dist/modules/ai_assistant/lib/chat-config.js +117 -0
- package/dist/modules/ai_assistant/lib/chat-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/client-factory.js +60 -0
- package/dist/modules/ai_assistant/lib/client-factory.js.map +7 -0
- package/dist/modules/ai_assistant/lib/http-server.js +367 -0
- package/dist/modules/ai_assistant/lib/http-server.js.map +7 -0
- package/dist/modules/ai_assistant/lib/in-process-client.js +126 -0
- package/dist/modules/ai_assistant/lib/in-process-client.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-client.js +146 -0
- package/dist/modules/ai_assistant/lib/mcp-client.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js +283 -0
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-server-config.js +160 -0
- package/dist/modules/ai_assistant/lib/mcp-server-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-server.js +156 -0
- package/dist/modules/ai_assistant/lib/mcp-server.js.map +7 -0
- package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js +44 -0
- package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js.map +7 -0
- package/dist/modules/ai_assistant/lib/opencode-client.js +247 -0
- package/dist/modules/ai_assistant/lib/opencode-client.js.map +7 -0
- package/dist/modules/ai_assistant/lib/opencode-handlers.js +398 -0
- package/dist/modules/ai_assistant/lib/opencode-handlers.js.map +7 -0
- package/dist/modules/ai_assistant/lib/schema-utils.js +94 -0
- package/dist/modules/ai_assistant/lib/schema-utils.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-executor.js +55 -0
- package/dist/modules/ai_assistant/lib/tool-executor.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-index-config.js +125 -0
- package/dist/modules/ai_assistant/lib/tool-index-config.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-loader.js +88 -0
- package/dist/modules/ai_assistant/lib/tool-loader.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-registry.js +65 -0
- package/dist/modules/ai_assistant/lib/tool-registry.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-search.js +192 -0
- package/dist/modules/ai_assistant/lib/tool-search.js.map +7 -0
- package/dist/modules/ai_assistant/lib/types.js +1 -0
- package/dist/modules/ai_assistant/lib/types.js.map +7 -0
- package/package.json +108 -0
- package/src/di.ts +11 -0
- package/src/frontend/components/CommandPalette/CommandFooter.tsx +113 -0
- package/src/frontend/components/CommandPalette/CommandHeader.tsx +76 -0
- package/src/frontend/components/CommandPalette/CommandInput.tsx +50 -0
- package/src/frontend/components/CommandPalette/CommandItem.tsx +111 -0
- package/src/frontend/components/CommandPalette/CommandPalette.tsx +276 -0
- package/src/frontend/components/CommandPalette/CommandPaletteProvider.tsx +60 -0
- package/src/frontend/components/CommandPalette/CommandPaletteWrapper.tsx +21 -0
- package/src/frontend/components/CommandPalette/DebugPanel.tsx +257 -0
- package/src/frontend/components/CommandPalette/MessageBubble.tsx +73 -0
- package/src/frontend/components/CommandPalette/ToolCallConfirmation.tsx +130 -0
- package/src/frontend/components/CommandPalette/ToolCallDisplay.tsx +57 -0
- package/src/frontend/components/CommandPalette/ToolChatPage.tsx +125 -0
- package/src/frontend/components/CommandPalette/index.ts +14 -0
- package/src/frontend/constants.ts +35 -0
- package/src/frontend/hooks/index.ts +5 -0
- package/src/frontend/hooks/useCommandPalette.ts +1389 -0
- package/src/frontend/hooks/useMcpTools.ts +73 -0
- package/src/frontend/hooks/usePageContext.ts +61 -0
- package/src/frontend/hooks/useRecentActions.ts +64 -0
- package/src/frontend/hooks/useRecentTools.ts +69 -0
- package/src/frontend/index.ts +39 -0
- package/src/frontend/types.ts +260 -0
- package/src/frontend/utils/index.ts +1 -0
- package/src/frontend/utils/toolMatcher.ts +127 -0
- package/src/index.ts +92 -0
- package/src/modules/ai_assistant/acl.ts +10 -0
- package/src/modules/ai_assistant/api/chat/route.ts +213 -0
- package/src/modules/ai_assistant/api/health/route.ts +30 -0
- package/src/modules/ai_assistant/api/route/route.ts +149 -0
- package/src/modules/ai_assistant/api/settings/route.ts +73 -0
- package/src/modules/ai_assistant/api/tools/execute/route.ts +71 -0
- package/src/modules/ai_assistant/api/tools/route.ts +57 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/page.meta.ts +26 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/page.tsx +12 -0
- package/src/modules/ai_assistant/cli.ts +233 -0
- package/src/modules/ai_assistant/di.ts +9 -0
- package/src/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.tsx +418 -0
- package/src/modules/ai_assistant/index.ts +11 -0
- package/src/modules/ai_assistant/lib/ai-sdk.ts +5 -0
- package/src/modules/ai_assistant/lib/api-discovery-tools.ts +334 -0
- package/src/modules/ai_assistant/lib/api-endpoint-index-config.ts +243 -0
- package/src/modules/ai_assistant/lib/api-endpoint-index.ts +381 -0
- package/src/modules/ai_assistant/lib/auth.ts +185 -0
- package/src/modules/ai_assistant/lib/chat-config.ts +152 -0
- package/src/modules/ai_assistant/lib/client-factory.ts +130 -0
- package/src/modules/ai_assistant/lib/http-server.ts +498 -0
- package/src/modules/ai_assistant/lib/in-process-client.ts +205 -0
- package/src/modules/ai_assistant/lib/mcp-client.ts +221 -0
- package/src/modules/ai_assistant/lib/mcp-dev-server.ts +373 -0
- package/src/modules/ai_assistant/lib/mcp-server-config.ts +287 -0
- package/src/modules/ai_assistant/lib/mcp-server.ts +214 -0
- package/src/modules/ai_assistant/lib/mcp-tool-adapter.ts +76 -0
- package/src/modules/ai_assistant/lib/opencode-client.ts +426 -0
- package/src/modules/ai_assistant/lib/opencode-handlers.ts +676 -0
- package/src/modules/ai_assistant/lib/schema-utils.ts +142 -0
- package/src/modules/ai_assistant/lib/tool-executor.ts +71 -0
- package/src/modules/ai_assistant/lib/tool-index-config.ts +178 -0
- package/src/modules/ai_assistant/lib/tool-loader.ts +149 -0
- package/src/modules/ai_assistant/lib/tool-registry.ts +114 -0
- package/src/modules/ai_assistant/lib/tool-search.ts +308 -0
- package/src/modules/ai_assistant/lib/types.ts +147 -0
- package/test-schema.ts +37 -0
- package/tsconfig.json +10 -0
- package/watch.mjs +6 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { createPortal } from 'react-dom'
|
|
5
|
+
import { Command } from 'cmdk'
|
|
6
|
+
import { Loader2, Send } from 'lucide-react'
|
|
7
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
8
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
9
|
+
import { Dialog, DialogContent, DialogTitle } from '@open-mercato/ui/primitives/dialog'
|
|
10
|
+
import { VisuallyHidden } from '@radix-ui/react-visually-hidden'
|
|
11
|
+
import { useCommandPaletteContext } from './CommandPaletteProvider'
|
|
12
|
+
import { CommandInput } from './CommandInput'
|
|
13
|
+
import { CommandHeader } from './CommandHeader'
|
|
14
|
+
import { CommandFooter } from './CommandFooter'
|
|
15
|
+
import { ToolChatPage } from './ToolChatPage'
|
|
16
|
+
import { DebugPanel } from './DebugPanel'
|
|
17
|
+
|
|
18
|
+
// Idle state - shown when palette is open but no query submitted
|
|
19
|
+
function IdleState() {
|
|
20
|
+
return (
|
|
21
|
+
<div className="py-8 px-4 text-center text-muted-foreground">
|
|
22
|
+
<p className="mb-2">Ask me anything or describe what you want to do.</p>
|
|
23
|
+
<p className="text-sm">Examples:</p>
|
|
24
|
+
<ul className="text-sm mt-2 space-y-1">
|
|
25
|
+
<li>"Search for customers in New York"</li>
|
|
26
|
+
<li>"Create a new product"</li>
|
|
27
|
+
<li>"Show me recent orders"</li>
|
|
28
|
+
</ul>
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Routing indicator - shown while fast model analyzes intent
|
|
34
|
+
function RoutingIndicator() {
|
|
35
|
+
return (
|
|
36
|
+
<div className="py-8 flex items-center justify-center gap-2">
|
|
37
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
38
|
+
<span className="text-sm text-muted-foreground">Analyzing request...</span>
|
|
39
|
+
</div>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function CommandPalette() {
|
|
44
|
+
const {
|
|
45
|
+
state,
|
|
46
|
+
isThinking,
|
|
47
|
+
isSessionAuthorized,
|
|
48
|
+
messages,
|
|
49
|
+
pendingToolCalls,
|
|
50
|
+
selectedTool,
|
|
51
|
+
close,
|
|
52
|
+
setInputValue,
|
|
53
|
+
handleSubmit,
|
|
54
|
+
reset,
|
|
55
|
+
sendAgenticMessage,
|
|
56
|
+
approveToolCall,
|
|
57
|
+
rejectToolCall,
|
|
58
|
+
debugEvents,
|
|
59
|
+
showDebug,
|
|
60
|
+
setShowDebug,
|
|
61
|
+
clearDebugEvents,
|
|
62
|
+
pendingQuestion,
|
|
63
|
+
answerQuestion,
|
|
64
|
+
} = useCommandPaletteContext()
|
|
65
|
+
|
|
66
|
+
const {
|
|
67
|
+
isOpen,
|
|
68
|
+
phase,
|
|
69
|
+
inputValue,
|
|
70
|
+
isLoading,
|
|
71
|
+
isStreaming,
|
|
72
|
+
connectionStatus,
|
|
73
|
+
} = state
|
|
74
|
+
|
|
75
|
+
const [localInput, setLocalInput] = React.useState('')
|
|
76
|
+
const [chatInput, setChatInput] = React.useState('')
|
|
77
|
+
const chatInputRef = React.useRef<HTMLInputElement>(null)
|
|
78
|
+
|
|
79
|
+
// Reset local input when phase changes to idle
|
|
80
|
+
React.useEffect(() => {
|
|
81
|
+
if (phase === 'idle') {
|
|
82
|
+
setLocalInput('')
|
|
83
|
+
setChatInput('')
|
|
84
|
+
}
|
|
85
|
+
}, [phase])
|
|
86
|
+
|
|
87
|
+
// Focus chat input when entering chatting phase
|
|
88
|
+
React.useEffect(() => {
|
|
89
|
+
if (phase === 'chatting' || phase === 'confirming' || phase === 'executing') {
|
|
90
|
+
// Small delay to ensure DOM is ready
|
|
91
|
+
setTimeout(() => chatInputRef.current?.focus(), 50)
|
|
92
|
+
}
|
|
93
|
+
}, [phase])
|
|
94
|
+
|
|
95
|
+
const handleOpenChange = (open: boolean) => {
|
|
96
|
+
if (!open) {
|
|
97
|
+
close()
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const handleInputSubmit = async () => {
|
|
102
|
+
const query = localInput.trim()
|
|
103
|
+
if (!query) return
|
|
104
|
+
setLocalInput('')
|
|
105
|
+
await handleSubmit(query)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
109
|
+
if (e.key === 'Enter' && phase === 'idle' && localInput.trim()) {
|
|
110
|
+
e.preventDefault()
|
|
111
|
+
handleInputSubmit()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const handleChatSubmit = async (e: React.FormEvent) => {
|
|
116
|
+
e.preventDefault()
|
|
117
|
+
if (!chatInput.trim() || isStreaming) return
|
|
118
|
+
|
|
119
|
+
const content = chatInput
|
|
120
|
+
setChatInput('')
|
|
121
|
+
await sendAgenticMessage(content)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const handleChatKeyDown = (e: React.KeyboardEvent) => {
|
|
125
|
+
// Prevent escape from bubbling to close the palette
|
|
126
|
+
if (e.key === 'Escape') {
|
|
127
|
+
e.stopPropagation()
|
|
128
|
+
}
|
|
129
|
+
// Submit on Enter (not Shift+Enter for multiline)
|
|
130
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
131
|
+
e.preventDefault()
|
|
132
|
+
if (chatInput.trim() && !isStreaming) {
|
|
133
|
+
const content = chatInput
|
|
134
|
+
setChatInput('')
|
|
135
|
+
sendAgenticMessage(content)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<>
|
|
142
|
+
{/* Custom blur overlay when debug mode is on (since modal=false removes it) */}
|
|
143
|
+
{isOpen && showDebug && (
|
|
144
|
+
<div className="fixed inset-0 z-40 bg-black/50 backdrop-blur-sm pointer-events-none" />
|
|
145
|
+
)}
|
|
146
|
+
<Dialog open={isOpen} onOpenChange={handleOpenChange} modal={!showDebug}>
|
|
147
|
+
<DialogContent
|
|
148
|
+
className={cn(
|
|
149
|
+
'fixed left-1/2 top-[10vh] z-50 -translate-x-1/2',
|
|
150
|
+
'w-full max-w-2xl p-0',
|
|
151
|
+
'rounded-xl border bg-background shadow-2xl',
|
|
152
|
+
'flex flex-col'
|
|
153
|
+
)}
|
|
154
|
+
style={{ maxHeight: 500, overflow: 'hidden' }}
|
|
155
|
+
onKeyDown={handleKeyDown}
|
|
156
|
+
onPointerDownOutside={(e) => {
|
|
157
|
+
// Prevent closing on outside click when debug mode is on
|
|
158
|
+
if (showDebug) {
|
|
159
|
+
e.preventDefault()
|
|
160
|
+
}
|
|
161
|
+
}}
|
|
162
|
+
onInteractOutside={(e) => {
|
|
163
|
+
// Prevent closing on outside interaction when debug mode is on
|
|
164
|
+
if (showDebug) {
|
|
165
|
+
e.preventDefault()
|
|
166
|
+
}
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
{/* Visually hidden title for accessibility */}
|
|
170
|
+
<VisuallyHidden>
|
|
171
|
+
<DialogTitle>AI Command Palette</DialogTitle>
|
|
172
|
+
</VisuallyHidden>
|
|
173
|
+
<Command className="flex flex-col flex-1 min-h-0" shouldFilter={false}>
|
|
174
|
+
{/* Header - shows phase/tool info */}
|
|
175
|
+
<CommandHeader
|
|
176
|
+
phase={phase}
|
|
177
|
+
selectedTool={selectedTool}
|
|
178
|
+
onBack={reset}
|
|
179
|
+
/>
|
|
180
|
+
|
|
181
|
+
{/* Input - shown in idle phase */}
|
|
182
|
+
{phase === 'idle' && (
|
|
183
|
+
<CommandInput
|
|
184
|
+
value={localInput}
|
|
185
|
+
onValueChange={setLocalInput}
|
|
186
|
+
mode="commands"
|
|
187
|
+
isLoading={isLoading}
|
|
188
|
+
placeholder="Ask me anything or describe what you want to do..."
|
|
189
|
+
/>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
{/* Content area */}
|
|
193
|
+
<div className="flex-1 min-h-0 overflow-y-auto">
|
|
194
|
+
{phase === 'idle' && !localInput && <IdleState />}
|
|
195
|
+
|
|
196
|
+
{phase === 'routing' && <RoutingIndicator />}
|
|
197
|
+
|
|
198
|
+
{(phase === 'chatting' || phase === 'confirming' || phase === 'executing') && (
|
|
199
|
+
<ToolChatPage
|
|
200
|
+
tool={selectedTool}
|
|
201
|
+
messages={messages}
|
|
202
|
+
pendingToolCalls={pendingToolCalls}
|
|
203
|
+
isStreaming={isStreaming}
|
|
204
|
+
isThinking={isThinking}
|
|
205
|
+
onApproveToolCall={approveToolCall}
|
|
206
|
+
onRejectToolCall={rejectToolCall}
|
|
207
|
+
pendingQuestion={pendingQuestion}
|
|
208
|
+
onAnswerQuestion={answerQuestion}
|
|
209
|
+
/>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
{/* Chat input - shown in chatting phases */}
|
|
214
|
+
{(phase === 'chatting' || phase === 'confirming' || phase === 'executing') && (
|
|
215
|
+
<form onSubmit={handleChatSubmit} className="shrink-0 border-t p-3">
|
|
216
|
+
<div className="flex items-center gap-2">
|
|
217
|
+
<input
|
|
218
|
+
ref={chatInputRef}
|
|
219
|
+
type="text"
|
|
220
|
+
value={chatInput}
|
|
221
|
+
onChange={(e) => setChatInput(e.target.value)}
|
|
222
|
+
onKeyDown={handleChatKeyDown}
|
|
223
|
+
placeholder="Describe what you want to do..."
|
|
224
|
+
className={cn(
|
|
225
|
+
'flex-1 bg-muted rounded-lg px-4 py-2 text-sm outline-none',
|
|
226
|
+
'focus:ring-2 focus:ring-ring',
|
|
227
|
+
'disabled:opacity-50'
|
|
228
|
+
)}
|
|
229
|
+
disabled={isStreaming}
|
|
230
|
+
/>
|
|
231
|
+
<Button
|
|
232
|
+
type="submit"
|
|
233
|
+
size="icon"
|
|
234
|
+
disabled={!chatInput.trim() || isStreaming}
|
|
235
|
+
>
|
|
236
|
+
{isStreaming ? (
|
|
237
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
238
|
+
) : (
|
|
239
|
+
<Send className="h-4 w-4" />
|
|
240
|
+
)}
|
|
241
|
+
</Button>
|
|
242
|
+
</div>
|
|
243
|
+
</form>
|
|
244
|
+
)}
|
|
245
|
+
|
|
246
|
+
{/* Footer with connection status and keyboard hints */}
|
|
247
|
+
<CommandFooter
|
|
248
|
+
phase={phase}
|
|
249
|
+
connectionStatus={connectionStatus}
|
|
250
|
+
isSessionAuthorized={isSessionAuthorized}
|
|
251
|
+
showDebug={showDebug}
|
|
252
|
+
onToggleDebug={() => setShowDebug(!showDebug)}
|
|
253
|
+
/>
|
|
254
|
+
</Command>
|
|
255
|
+
</DialogContent>
|
|
256
|
+
</Dialog>
|
|
257
|
+
|
|
258
|
+
{/* Debug panel - rendered via portal outside the dialog DOM tree */}
|
|
259
|
+
{isOpen && showDebug && typeof document !== 'undefined' && createPortal(
|
|
260
|
+
<div
|
|
261
|
+
data-debug-panel
|
|
262
|
+
className="fixed z-[9999] bg-gray-900 rounded-xl border border-gray-700 shadow-2xl flex flex-col overflow-hidden"
|
|
263
|
+
style={{ top: '80px', right: '20px', width: '400px', minWidth: '400px', maxWidth: '400px', maxHeight: 'calc(100vh - 100px)' }}
|
|
264
|
+
>
|
|
265
|
+
<DebugPanel
|
|
266
|
+
events={debugEvents}
|
|
267
|
+
onClear={clearDebugEvents}
|
|
268
|
+
isOpen={true}
|
|
269
|
+
onToggle={() => setShowDebug(false)}
|
|
270
|
+
/>
|
|
271
|
+
</div>,
|
|
272
|
+
document.body
|
|
273
|
+
)}
|
|
274
|
+
</>
|
|
275
|
+
)
|
|
276
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { createContext, useContext, useState, useEffect } from 'react'
|
|
5
|
+
import type { PageContext, SelectedEntity } from '../../types'
|
|
6
|
+
import { usePageContext } from '../../hooks/usePageContext'
|
|
7
|
+
import { useCommandPalette } from '../../hooks/useCommandPalette'
|
|
8
|
+
|
|
9
|
+
interface CommandPaletteProviderProps {
|
|
10
|
+
children: React.ReactNode
|
|
11
|
+
tenantId: string
|
|
12
|
+
organizationId: string | null
|
|
13
|
+
disableKeyboardShortcut?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type CommandPaletteContextValue = ReturnType<typeof useCommandPalette>
|
|
17
|
+
|
|
18
|
+
const CommandPaletteContext = createContext<CommandPaletteContextValue | null>(null)
|
|
19
|
+
|
|
20
|
+
export function CommandPaletteProvider({
|
|
21
|
+
children,
|
|
22
|
+
tenantId,
|
|
23
|
+
organizationId,
|
|
24
|
+
disableKeyboardShortcut = true,
|
|
25
|
+
}: CommandPaletteProviderProps) {
|
|
26
|
+
const pageContext = usePageContext({ tenantId, organizationId })
|
|
27
|
+
const [selectedEntities, setSelectedEntities] = useState<SelectedEntity[]>([])
|
|
28
|
+
|
|
29
|
+
// Listen for DataTable selection events
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const handleSelectionChange = (event: CustomEvent<SelectedEntity[]>) => {
|
|
32
|
+
setSelectedEntities(event.detail || [])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
window.addEventListener('om:selection-change', handleSelectionChange as EventListener)
|
|
36
|
+
return () => {
|
|
37
|
+
window.removeEventListener('om:selection-change', handleSelectionChange as EventListener)
|
|
38
|
+
}
|
|
39
|
+
}, [])
|
|
40
|
+
|
|
41
|
+
const commandPalette = useCommandPalette({
|
|
42
|
+
pageContext,
|
|
43
|
+
selectedEntities,
|
|
44
|
+
disableKeyboardShortcut,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<CommandPaletteContext.Provider value={commandPalette}>
|
|
49
|
+
{children}
|
|
50
|
+
</CommandPaletteContext.Provider>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function useCommandPaletteContext(): CommandPaletteContextValue {
|
|
55
|
+
const context = useContext(CommandPaletteContext)
|
|
56
|
+
if (!context) {
|
|
57
|
+
throw new Error('useCommandPaletteContext must be used within CommandPaletteProvider')
|
|
58
|
+
}
|
|
59
|
+
return context
|
|
60
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { CommandPaletteProvider } from './CommandPaletteProvider'
|
|
5
|
+
import { CommandPalette } from './CommandPalette'
|
|
6
|
+
|
|
7
|
+
interface CommandPaletteWrapperProps {
|
|
8
|
+
tenantId: string | null
|
|
9
|
+
organizationId: string | null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function CommandPaletteWrapper({ tenantId, organizationId }: CommandPaletteWrapperProps) {
|
|
13
|
+
return (
|
|
14
|
+
<CommandPaletteProvider
|
|
15
|
+
tenantId={tenantId ?? ''}
|
|
16
|
+
organizationId={organizationId}
|
|
17
|
+
>
|
|
18
|
+
<CommandPalette />
|
|
19
|
+
</CommandPaletteProvider>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Code, X, Trash2 } from 'lucide-react'
|
|
5
|
+
import { JsonView } from 'react-json-view-lite'
|
|
6
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
7
|
+
|
|
8
|
+
export interface DebugEvent {
|
|
9
|
+
id: string
|
|
10
|
+
timestamp: Date
|
|
11
|
+
type: 'thinking' | 'tool-call' | 'tool-result' | 'text' | 'error' | 'done' | 'message' | 'connection' | 'metadata' | 'debug' | 'question' | 'session-authorized'
|
|
12
|
+
data: unknown
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface DebugPanelProps {
|
|
16
|
+
events: DebugEvent[]
|
|
17
|
+
onClear: () => void
|
|
18
|
+
isOpen: boolean
|
|
19
|
+
onToggle: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function DebugPanel({ events, onClear, isOpen, onToggle }: DebugPanelProps) {
|
|
23
|
+
const scrollRef = React.useRef<HTMLDivElement>(null)
|
|
24
|
+
|
|
25
|
+
// Filter out text events - only show tool calls, results, errors, etc.
|
|
26
|
+
const filteredEvents = React.useMemo(
|
|
27
|
+
() => events.filter((e) => e.type !== 'text'),
|
|
28
|
+
[events]
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
if (scrollRef.current) {
|
|
33
|
+
scrollRef.current.scrollTop = scrollRef.current.scrollHeight
|
|
34
|
+
}
|
|
35
|
+
}, [filteredEvents])
|
|
36
|
+
|
|
37
|
+
if (!isOpen) {
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="flex flex-col h-full min-h-0">
|
|
43
|
+
<div className="flex-shrink-0 flex items-center justify-between px-3 py-2 border-b border-gray-700">
|
|
44
|
+
<span className="text-xs font-medium text-gray-300">Debug Events ({filteredEvents.length})</span>
|
|
45
|
+
<div className="flex gap-2">
|
|
46
|
+
<button
|
|
47
|
+
onClick={onClear}
|
|
48
|
+
className="text-gray-400 hover:text-gray-200"
|
|
49
|
+
title="Clear events"
|
|
50
|
+
>
|
|
51
|
+
<Trash2 className="w-3 h-3" />
|
|
52
|
+
</button>
|
|
53
|
+
<button
|
|
54
|
+
onClick={onToggle}
|
|
55
|
+
className="text-gray-400 hover:text-gray-200"
|
|
56
|
+
title="Close debug panel"
|
|
57
|
+
>
|
|
58
|
+
<X className="w-3 h-3" />
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div ref={scrollRef} className="flex-1 min-h-0 overflow-y-auto p-2 space-y-1">
|
|
63
|
+
{filteredEvents.map((event) => (
|
|
64
|
+
<DebugEventRow key={event.id} event={event} />
|
|
65
|
+
))}
|
|
66
|
+
{filteredEvents.length === 0 && (
|
|
67
|
+
<p className="text-xs text-gray-500 text-center py-4">No events yet</p>
|
|
68
|
+
)}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Recursively parse stringified JSON fields
|
|
75
|
+
function parseNestedJson(data: unknown): unknown {
|
|
76
|
+
if (typeof data === 'string') {
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(data)
|
|
79
|
+
return parseNestedJson(parsed)
|
|
80
|
+
} catch {
|
|
81
|
+
return data
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(data)) {
|
|
85
|
+
return data.map(parseNestedJson)
|
|
86
|
+
}
|
|
87
|
+
if (data && typeof data === 'object') {
|
|
88
|
+
const result: Record<string, unknown> = {}
|
|
89
|
+
for (const [key, value] of Object.entries(data)) {
|
|
90
|
+
result[key] = parseNestedJson(value)
|
|
91
|
+
}
|
|
92
|
+
return result
|
|
93
|
+
}
|
|
94
|
+
return data
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Custom dark styles for better readability (no external CSS needed)
|
|
98
|
+
const customDarkStyles: Record<string, string> = {
|
|
99
|
+
container: 'bg-transparent text-[11px] leading-relaxed font-mono',
|
|
100
|
+
basicChildStyle: 'pl-4 ml-0',
|
|
101
|
+
label: 'text-purple-400 mr-1',
|
|
102
|
+
nullValue: 'text-gray-500 italic',
|
|
103
|
+
undefinedValue: 'text-gray-500 italic',
|
|
104
|
+
stringValue: 'text-green-400',
|
|
105
|
+
booleanValue: 'text-yellow-400',
|
|
106
|
+
numberValue: 'text-blue-400',
|
|
107
|
+
otherValue: 'text-gray-300',
|
|
108
|
+
punctuation: 'text-gray-500',
|
|
109
|
+
collapseIcon: 'text-gray-400 cursor-pointer select-none mr-1',
|
|
110
|
+
expandIcon: 'text-gray-400 cursor-pointer select-none mr-1',
|
|
111
|
+
collapsedContent: 'text-gray-500',
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function DebugEventRow({ event }: { event: DebugEvent }) {
|
|
115
|
+
const [expanded, setExpanded] = React.useState(false)
|
|
116
|
+
const typeColors: Record<string, string> = {
|
|
117
|
+
'thinking': 'text-orange-400',
|
|
118
|
+
'tool-call': 'text-blue-400',
|
|
119
|
+
'tool-result': 'text-green-400',
|
|
120
|
+
'text': 'text-gray-300',
|
|
121
|
+
'error': 'text-red-400',
|
|
122
|
+
'done': 'text-purple-400',
|
|
123
|
+
'message': 'text-yellow-400',
|
|
124
|
+
'connection': 'text-cyan-400',
|
|
125
|
+
'metadata': 'text-teal-400',
|
|
126
|
+
'debug': 'text-gray-500',
|
|
127
|
+
'question': 'text-amber-400',
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const formatTime = (date: Date) => {
|
|
131
|
+
return date.toLocaleTimeString('en-US', {
|
|
132
|
+
hour12: false,
|
|
133
|
+
hour: '2-digit',
|
|
134
|
+
minute: '2-digit',
|
|
135
|
+
second: '2-digit',
|
|
136
|
+
}) + '.' + String(date.getMilliseconds()).padStart(3, '0')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Parse nested JSON strings for better display
|
|
140
|
+
const parsedData = React.useMemo(() => parseNestedJson(event.data), [event.data])
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<div className="text-xs font-mono">
|
|
144
|
+
<button
|
|
145
|
+
onClick={() => setExpanded(!expanded)}
|
|
146
|
+
className="w-full text-left flex items-center gap-2 hover:bg-gray-800 rounded px-1 py-0.5"
|
|
147
|
+
>
|
|
148
|
+
<span className="text-gray-500 flex-shrink-0">
|
|
149
|
+
{formatTime(event.timestamp)}
|
|
150
|
+
</span>
|
|
151
|
+
<span className={cn('font-medium flex-shrink-0', typeColors[event.type] || 'text-gray-400')}>
|
|
152
|
+
{event.type}
|
|
153
|
+
</span>
|
|
154
|
+
<span className="text-gray-400 truncate flex-1">
|
|
155
|
+
{getEventPreview(event)}
|
|
156
|
+
</span>
|
|
157
|
+
<span className="text-gray-600 flex-shrink-0">
|
|
158
|
+
{expanded ? '▼' : '▶'}
|
|
159
|
+
</span>
|
|
160
|
+
</button>
|
|
161
|
+
{expanded && (
|
|
162
|
+
<div className="bg-gray-800 rounded p-2 mt-1 overflow-x-auto max-h-[300px] overflow-y-auto">
|
|
163
|
+
<JsonView
|
|
164
|
+
data={parsedData as object}
|
|
165
|
+
style={customDarkStyles}
|
|
166
|
+
shouldExpandNode={(level) => level < 2}
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function getEventPreview(event: DebugEvent): string {
|
|
175
|
+
const data = event.data as Record<string, unknown>
|
|
176
|
+
|
|
177
|
+
if (event.type === 'thinking') {
|
|
178
|
+
return 'Agent is processing...'
|
|
179
|
+
}
|
|
180
|
+
if (event.type === 'tool-call') {
|
|
181
|
+
const toolName = data?.toolName as string || 'unknown'
|
|
182
|
+
const args = data?.args
|
|
183
|
+
const argsPreview = args ? JSON.stringify(args).substring(0, 40) : ''
|
|
184
|
+
return `${toolName}(${argsPreview}${argsPreview.length >= 40 ? '...' : ''})`
|
|
185
|
+
}
|
|
186
|
+
if (event.type === 'tool-result') {
|
|
187
|
+
const result = data?.result
|
|
188
|
+
const resultPreview = typeof result === 'string'
|
|
189
|
+
? result.substring(0, 50)
|
|
190
|
+
: JSON.stringify(result)?.substring(0, 50)
|
|
191
|
+
return `→ ${resultPreview}${resultPreview && resultPreview.length >= 50 ? '...' : ''}`
|
|
192
|
+
}
|
|
193
|
+
if (event.type === 'text') {
|
|
194
|
+
const content = data?.content as string || ''
|
|
195
|
+
return content.substring(0, 50) + (content.length > 50 ? '...' : '')
|
|
196
|
+
}
|
|
197
|
+
if (event.type === 'error') {
|
|
198
|
+
return data?.error as string || 'Unknown error'
|
|
199
|
+
}
|
|
200
|
+
if (event.type === 'message') {
|
|
201
|
+
const role = data?.role as string || ''
|
|
202
|
+
const content = data?.content as string || ''
|
|
203
|
+
return `[${role}] ${content.substring(0, 40)}...`
|
|
204
|
+
}
|
|
205
|
+
if (event.type === 'connection') {
|
|
206
|
+
return data?.status as string || ''
|
|
207
|
+
}
|
|
208
|
+
if (event.type === 'metadata') {
|
|
209
|
+
const model = data?.model as string || ''
|
|
210
|
+
const tokens = data?.tokens as { input?: number; output?: number } | undefined
|
|
211
|
+
const durationMs = data?.durationMs as number | undefined
|
|
212
|
+
const parts: string[] = []
|
|
213
|
+
if (model) parts.push(model)
|
|
214
|
+
if (tokens) parts.push(`${tokens.input || 0}→${tokens.output || 0} tokens`)
|
|
215
|
+
if (durationMs) parts.push(`${(durationMs / 1000).toFixed(1)}s`)
|
|
216
|
+
return parts.join(' | ') || 'No metadata'
|
|
217
|
+
}
|
|
218
|
+
if (event.type === 'debug') {
|
|
219
|
+
const partType = data?.partType as string || 'unknown'
|
|
220
|
+
return `Unknown part: ${partType}`
|
|
221
|
+
}
|
|
222
|
+
if (event.type === 'question') {
|
|
223
|
+
// Use the enriched questionText field if available (added by frontend)
|
|
224
|
+
const questionText = (data?.questionText as string)
|
|
225
|
+
|| (data?.question as { questions?: Array<{ question: string }> })?.questions?.[0]?.question
|
|
226
|
+
|| (data?.header as string)
|
|
227
|
+
|| 'Confirmation required'
|
|
228
|
+
return questionText.substring(0, 50) + (questionText.length > 50 ? '...' : '')
|
|
229
|
+
}
|
|
230
|
+
return ''
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Export a toggle button component for use in footer
|
|
234
|
+
export function DebugToggleButton({
|
|
235
|
+
isOpen,
|
|
236
|
+
onToggle
|
|
237
|
+
}: {
|
|
238
|
+
isOpen: boolean
|
|
239
|
+
onToggle: () => void
|
|
240
|
+
}) {
|
|
241
|
+
return (
|
|
242
|
+
<button
|
|
243
|
+
type="button"
|
|
244
|
+
onClick={onToggle}
|
|
245
|
+
className={cn(
|
|
246
|
+
'flex items-center gap-1 text-xs transition-colors',
|
|
247
|
+
isOpen
|
|
248
|
+
? 'text-blue-500 hover:text-blue-400'
|
|
249
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
250
|
+
)}
|
|
251
|
+
title="Toggle debug panel"
|
|
252
|
+
>
|
|
253
|
+
<Code className="w-3 h-3" />
|
|
254
|
+
<span>{isOpen ? 'Hide Debug' : 'Debug'}</span>
|
|
255
|
+
</button>
|
|
256
|
+
)
|
|
257
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { User, Bot } from 'lucide-react'
|
|
5
|
+
import Markdown from 'react-markdown'
|
|
6
|
+
import remarkGfm from 'remark-gfm'
|
|
7
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
8
|
+
import type { ChatMessage } from '../../types'
|
|
9
|
+
|
|
10
|
+
interface MessageBubbleProps {
|
|
11
|
+
message: ChatMessage
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function MessageBubble({ message }: MessageBubbleProps) {
|
|
15
|
+
const isUser = message.role === 'user'
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
className={cn(
|
|
20
|
+
'flex gap-3 py-3',
|
|
21
|
+
isUser ? 'flex-row-reverse' : 'flex-row'
|
|
22
|
+
)}
|
|
23
|
+
>
|
|
24
|
+
<div
|
|
25
|
+
className={cn(
|
|
26
|
+
'flex items-center justify-center w-8 h-8 rounded-full shrink-0',
|
|
27
|
+
isUser ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'
|
|
28
|
+
)}
|
|
29
|
+
>
|
|
30
|
+
{isUser ? <User className="h-4 w-4" /> : <Bot className="h-4 w-4" />}
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div
|
|
34
|
+
className={cn(
|
|
35
|
+
'flex-1 min-w-0 px-4 py-2 rounded-lg text-sm',
|
|
36
|
+
isUser
|
|
37
|
+
? 'bg-primary text-primary-foreground ml-12'
|
|
38
|
+
: 'bg-muted text-foreground mr-12'
|
|
39
|
+
)}
|
|
40
|
+
>
|
|
41
|
+
{isUser ? (
|
|
42
|
+
<div className="whitespace-pre-wrap break-words">{message.content}</div>
|
|
43
|
+
) : (
|
|
44
|
+
<div className={cn(
|
|
45
|
+
'prose prose-sm dark:prose-invert max-w-none break-words',
|
|
46
|
+
// Reset margins for first/last children
|
|
47
|
+
'[&>*:first-child]:mt-0 [&>*:last-child]:mb-0',
|
|
48
|
+
// Paragraph spacing - also handle plain text with whitespace-pre-line
|
|
49
|
+
'[&_p]:my-2 [&_p]:leading-relaxed [&_p]:whitespace-pre-line',
|
|
50
|
+
// List styling
|
|
51
|
+
'[&_ul]:my-2 [&_ul]:pl-4 [&_ol]:my-2 [&_ol]:pl-4',
|
|
52
|
+
'[&_li]:my-0.5 [&_li]:leading-relaxed',
|
|
53
|
+
// Headers
|
|
54
|
+
'[&_h1]:text-base [&_h1]:font-semibold [&_h1]:mt-3 [&_h1]:mb-2',
|
|
55
|
+
'[&_h2]:text-sm [&_h2]:font-semibold [&_h2]:mt-3 [&_h2]:mb-1',
|
|
56
|
+
'[&_h3]:text-sm [&_h3]:font-medium [&_h3]:mt-2 [&_h3]:mb-1',
|
|
57
|
+
// Code blocks
|
|
58
|
+
'[&_pre]:bg-background/50 [&_pre]:rounded [&_pre]:p-2 [&_pre]:my-2 [&_pre]:overflow-x-auto',
|
|
59
|
+
'[&_code]:bg-background/50 [&_code]:px-1 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-xs',
|
|
60
|
+
// Strong/emphasis
|
|
61
|
+
'[&_strong]:font-semibold [&_em]:italic',
|
|
62
|
+
// Blockquotes
|
|
63
|
+
'[&_blockquote]:border-l-2 [&_blockquote]:border-muted-foreground/30 [&_blockquote]:pl-3 [&_blockquote]:italic'
|
|
64
|
+
)}>
|
|
65
|
+
<Markdown remarkPlugins={[remarkGfm]}>
|
|
66
|
+
{message.content}
|
|
67
|
+
</Markdown>
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|