fluxy-bot 0.8.9 → 0.9.0
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/README.md +53 -84
- package/bin/cli.js +1717 -6
- package/components.json +18 -18
- package/package.json +98 -109
- package/postcss.config.js +3 -3
- package/scripts/install.ps1 +1 -1
- package/scripts/install.sh +1 -1
- package/scripts/postinstall.js +47 -58
- package/shared/ai.ts +118 -202
- package/shared/config.ts +37 -39
- package/shared/logger.ts +6 -14
- package/shared/paths.ts +10 -16
- package/shared/relay.ts +89 -98
- package/supervisor/backend.ts +77 -89
- package/supervisor/chat/ARCHITECTURE.md +13 -14
- package/supervisor/chat/OnboardWizard.tsx +2194 -3419
- package/supervisor/chat/fluxy-main.tsx +495 -633
- package/supervisor/chat/fluxy.html +24 -36
- package/supervisor/chat/onboard-main.tsx +8 -8
- package/supervisor/chat/onboard.html +10 -13
- package/supervisor/chat/src/components/Chat/AudioBubble.tsx +94 -108
- package/supervisor/chat/src/components/Chat/ChatView.tsx +14 -36
- package/supervisor/chat/src/components/Chat/ImageLightbox.tsx +71 -89
- package/supervisor/chat/src/components/Chat/InputBar.tsx +498 -728
- package/supervisor/chat/src/components/Chat/MessageBubble.tsx +154 -246
- package/supervisor/chat/src/components/Chat/MessageList.tsx +114 -138
- package/supervisor/chat/src/components/Chat/TypingIndicator.tsx +38 -50
- package/supervisor/chat/src/components/LoginScreen.tsx +245 -296
- package/supervisor/chat/src/hooks/useChat.ts +210 -259
- package/supervisor/chat/src/hooks/useFluxyChat.ts +232 -322
- package/supervisor/chat/src/hooks/useSpeechRecognition.ts +126 -148
- package/supervisor/chat/src/lib/auth.ts +20 -23
- package/supervisor/chat/src/lib/ws-client.ts +113 -125
- package/supervisor/chat/src/styles/globals.css +76 -85
- package/supervisor/file-saver.ts +39 -52
- package/supervisor/fluxy-agent.ts +213 -292
- package/supervisor/index.ts +895 -1263
- package/supervisor/scheduler.ts +237 -299
- package/supervisor/tunnel.ts +113 -167
- package/supervisor/vite-dev.ts +51 -61
- package/supervisor/widget.js +135 -147
- package/supervisor/worker.ts +45 -49
- package/tsconfig.json +18 -24
- package/vite.config.ts +48 -55
- package/vite.fluxy.config.ts +34 -34
- package/worker/claude-auth.ts +202 -265
- package/worker/codex-auth.ts +144 -181
- package/worker/db.ts +63 -191
- package/worker/index.ts +601 -842
- package/workspace/MYSELF.md +1 -1
- package/workspace/PULSE.json +6 -6
- package/workspace/backend/index.ts +16 -19
- package/workspace/client/index.html +47 -60
- package/workspace/client/public/manifest.json +21 -21
- package/workspace/client/public/sw.js +29 -31
- package/workspace/client/src/App.tsx +91 -136
- package/workspace/client/src/components/Dashboard/DashboardPage.tsx +68 -72
- package/workspace/client/src/components/ErrorBoundary.tsx +11 -11
- package/workspace/client/src/components/Layout/DashboardLayout.tsx +35 -35
- package/workspace/client/src/components/Layout/Footer.tsx +12 -12
- package/workspace/client/src/components/Layout/MobileNav.tsx +23 -23
- package/workspace/client/src/components/Layout/Sidebar.tsx +81 -81
- package/workspace/client/src/components/ui/avatar.tsx +85 -85
- package/workspace/client/src/components/ui/badge.tsx +40 -41
- package/workspace/client/src/components/ui/button.tsx +56 -57
- package/workspace/client/src/components/ui/card.tsx +75 -75
- package/workspace/client/src/components/ui/dialog.tsx +114 -117
- package/workspace/client/src/components/ui/dropdown-menu.tsx +191 -199
- package/workspace/client/src/components/ui/input.tsx +17 -17
- package/workspace/client/src/components/ui/scroll-area.tsx +47 -47
- package/workspace/client/src/components/ui/select.tsx +145 -151
- package/workspace/client/src/components/ui/separator.tsx +21 -21
- package/workspace/client/src/components/ui/sheet.tsx +102 -102
- package/workspace/client/src/components/ui/skeleton.tsx +10 -10
- package/workspace/client/src/components/ui/switch.tsx +26 -26
- package/workspace/client/src/components/ui/tabs.tsx +69 -69
- package/workspace/client/src/components/ui/textarea.tsx +14 -14
- package/workspace/client/src/components/ui/tooltip.tsx +37 -37
- package/workspace/client/src/lib/utils.ts +1 -1
- package/workspace/client/src/main.tsx +3 -3
- package/workspace/client/src/styles/globals.css +76 -85
- package/workspace/skills/code-reviewer/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/code-reviewer/skills/code-reviewer/SKILL.md +0 -6
- package/workspace/skills/daily-standup/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/daily-standup/skills/daily-standup/SKILL.md +0 -7
- package/workspace/skills/workspace-helper/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/workspace-helper/skills/workspace-helper/SKILL.md +0 -2
- package/bin/cli.backup.js +0 -2138
- package/cli/commands/daemon.ts +0 -42
- package/cli/commands/init.ts +0 -32
- package/cli/commands/start.ts +0 -113
- package/cli/commands/tunnel.temp.ts +0 -206
- package/cli/commands/tunnel.ts +0 -227
- package/cli/commands/update.ts +0 -163
- package/cli/core/base-adapter.ts +0 -156
- package/cli/core/cloudflared.ts +0 -113
- package/cli/core/config.ts +0 -73
- package/cli/core/os-detector.ts +0 -43
- package/cli/core/server.ts +0 -109
- package/cli/core/types.ts +0 -15
- package/cli/index.ts +0 -72
- package/cli/platforms/darwin.ts +0 -146
- package/cli/platforms/index.ts +0 -21
- package/cli/platforms/linux.ts +0 -156
- package/cli/platforms/win32.ts +0 -34
- package/cli/utils/ui.ts +0 -37
|
@@ -8,268 +8,176 @@ import AudioBubble from './AudioBubble';
|
|
|
8
8
|
import type { StoredAttachment } from '../../hooks/useChat';
|
|
9
9
|
|
|
10
10
|
interface Props {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
role: 'user' | 'assistant';
|
|
12
|
+
content: string;
|
|
13
|
+
timestamp?: string;
|
|
14
|
+
hasAttachments?: boolean;
|
|
15
|
+
audioData?: string;
|
|
16
|
+
attachments?: StoredAttachment[];
|
|
17
|
+
onImageClick?: (images: string[], index: number) => void;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function formatTime(iso: string): string {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
try {
|
|
22
|
+
const d = new Date(iso);
|
|
23
|
+
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
24
|
+
} catch {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function CopyButton({ text }: { text: string }) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<Check className="h-3.5 w-3.5" />
|
|
48
|
-
) : (
|
|
49
|
-
<Copy className="h-3.5 w-3.5" />
|
|
50
|
-
)}
|
|
51
|
-
</button>
|
|
52
|
-
);
|
|
30
|
+
const [copied, setCopied] = useState(false);
|
|
31
|
+
const handleCopy = async () => {
|
|
32
|
+
try {
|
|
33
|
+
await navigator.clipboard.writeText(text);
|
|
34
|
+
setCopied(true);
|
|
35
|
+
setTimeout(() => setCopied(false), 1500);
|
|
36
|
+
} catch { /* clipboard not available */ }
|
|
37
|
+
};
|
|
38
|
+
return (
|
|
39
|
+
<button
|
|
40
|
+
onClick={handleCopy}
|
|
41
|
+
className="shrink-0 opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded hover:bg-muted-foreground/10 text-muted-foreground/50 hover:text-muted-foreground"
|
|
42
|
+
title="Copy message"
|
|
43
|
+
>
|
|
44
|
+
{copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
|
|
45
|
+
</button>
|
|
46
|
+
);
|
|
53
47
|
}
|
|
54
48
|
|
|
55
|
-
export default function MessageBubble({
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
timestamp,
|
|
59
|
-
hasAttachments,
|
|
60
|
-
audioData,
|
|
61
|
-
attachments,
|
|
62
|
-
onImageClick
|
|
63
|
-
}: Props) {
|
|
64
|
-
const isUser = role === 'user';
|
|
65
|
-
const time = timestamp ? formatTime(timestamp) : '';
|
|
66
|
-
|
|
67
|
-
// Separate image and document attachments
|
|
68
|
-
const imageAtts =
|
|
69
|
-
attachments?.filter(a => a.mediaType?.startsWith('image/')) || [];
|
|
70
|
-
const docAtts =
|
|
71
|
-
attachments?.filter(a => !a.mediaType?.startsWith('image/')) || [];
|
|
49
|
+
export default function MessageBubble({ role, content, timestamp, hasAttachments, audioData, attachments, onImageClick }: Props) {
|
|
50
|
+
const isUser = role === 'user';
|
|
51
|
+
const time = timestamp ? formatTime(timestamp) : '';
|
|
72
52
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
.map(a =>
|
|
77
|
-
a.filePath.startsWith('data:')
|
|
78
|
-
? a.filePath
|
|
79
|
-
: `/api/files/${a.filePath}`
|
|
80
|
-
);
|
|
53
|
+
// Separate image and document attachments
|
|
54
|
+
const imageAtts = attachments?.filter((a) => a.mediaType?.startsWith('image/')) || [];
|
|
55
|
+
const docAtts = attachments?.filter((a) => !a.mediaType?.startsWith('image/')) || [];
|
|
81
56
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<CopyButton text={content} />
|
|
89
|
-
<div className="max-w-[92%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-primary text-primary-foreground">
|
|
90
|
-
<AudioBubble audioData={audioData} />
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
{time && (
|
|
94
|
-
<span className="text-[10px] text-muted-foreground/50 px-1">
|
|
95
|
-
{time}
|
|
96
|
-
</span>
|
|
97
|
-
)}
|
|
98
|
-
</div>
|
|
99
|
-
);
|
|
100
|
-
}
|
|
57
|
+
// Resolve image URLs
|
|
58
|
+
const imageUrls = imageAtts
|
|
59
|
+
.filter((a) => a.filePath)
|
|
60
|
+
.map((a) =>
|
|
61
|
+
a.filePath.startsWith('data:') ? a.filePath : `/api/files/${a.filePath}`
|
|
62
|
+
);
|
|
101
63
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
{imageUrls.length > 0 && (
|
|
112
|
-
<div className="flex gap-1.5 flex-wrap mb-2">
|
|
113
|
-
{imageUrls.map((url, i) => (
|
|
114
|
-
<img
|
|
115
|
-
key={i}
|
|
116
|
-
src={url}
|
|
117
|
-
alt={imageAtts[i]?.name || 'attachment'}
|
|
118
|
-
className="w-28 h-28 rounded-lg object-cover cursor-pointer border border-white/10 hover:opacity-80 transition-opacity"
|
|
119
|
-
role="presentation"
|
|
120
|
-
tabIndex={0}
|
|
121
|
-
onKeyDown={e => {
|
|
122
|
-
if (
|
|
123
|
-
e.key === 'Enter' ||
|
|
124
|
-
e.key === ' '
|
|
125
|
-
) {
|
|
126
|
-
onImageClick?.(imageUrls, i);
|
|
127
|
-
e.preventDefault();
|
|
128
|
-
}
|
|
129
|
-
}}
|
|
130
|
-
onClick={() =>
|
|
131
|
-
onImageClick?.(imageUrls, i)
|
|
132
|
-
}
|
|
133
|
-
/>
|
|
134
|
-
))}
|
|
135
|
-
</div>
|
|
136
|
-
)}
|
|
137
|
-
{/* Document attachments */}
|
|
138
|
-
{docAtts.length > 0 && (
|
|
139
|
-
<span className="inline-flex items-center gap-1 text-primary-foreground/60 mr-1.5">
|
|
140
|
-
<Paperclip className="h-3 w-3" />
|
|
141
|
-
{docAtts.length > 1 && (
|
|
142
|
-
<span className="text-xs">
|
|
143
|
-
{docAtts.length}
|
|
144
|
-
</span>
|
|
145
|
-
)}
|
|
146
|
-
</span>
|
|
147
|
-
)}
|
|
148
|
-
{/* Fallback paperclip for legacy messages with no parsed attachments */}
|
|
149
|
-
{!attachments?.length && hasAttachments && (
|
|
150
|
-
<span className="inline-flex items-center gap-1 text-primary-foreground/60 mr-1.5">
|
|
151
|
-
<Paperclip className="h-3 w-3" />
|
|
152
|
-
</span>
|
|
153
|
-
)}
|
|
154
|
-
{content}
|
|
155
|
-
</div>
|
|
156
|
-
</div>
|
|
157
|
-
{time && (
|
|
158
|
-
<span className="text-[10px] text-muted-foreground/50 px-1">
|
|
159
|
-
{time}
|
|
160
|
-
</span>
|
|
161
|
-
)}
|
|
64
|
+
if (isUser) {
|
|
65
|
+
// Voice message: audio-only, no transcript text
|
|
66
|
+
if (audioData) {
|
|
67
|
+
return (
|
|
68
|
+
<div className="flex flex-col items-end gap-0.5">
|
|
69
|
+
<div className="group flex items-start gap-1.5 justify-end">
|
|
70
|
+
<CopyButton text={content} />
|
|
71
|
+
<div className="max-w-[92%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-primary text-primary-foreground">
|
|
72
|
+
<AudioBubble audioData={audioData} />
|
|
162
73
|
</div>
|
|
163
|
-
|
|
74
|
+
</div>
|
|
75
|
+
{time && <span className="text-[10px] text-muted-foreground/50 px-1">{time}</span>}
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
164
78
|
}
|
|
165
79
|
|
|
166
80
|
return (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (match) {
|
|
192
|
-
return (
|
|
193
|
-
<SyntaxHighlighter
|
|
194
|
-
style={oneDark}
|
|
195
|
-
language={match[1]}
|
|
196
|
-
PreTag="div"
|
|
197
|
-
customStyle={{
|
|
198
|
-
margin: '0.5rem 0',
|
|
199
|
-
borderRadius: '0.5rem',
|
|
200
|
-
fontSize: '0.8rem',
|
|
201
|
-
overflowX: 'auto'
|
|
202
|
-
}}
|
|
203
|
-
>
|
|
204
|
-
{code}
|
|
205
|
-
</SyntaxHighlighter>
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
return (
|
|
209
|
-
<code
|
|
210
|
-
className="bg-white/10 rounded px-1 py-0.5 text-[0.8rem] break-all"
|
|
211
|
-
style={{
|
|
212
|
-
whiteSpace: 'pre-wrap',
|
|
213
|
-
overflowWrap: 'anywhere'
|
|
214
|
-
}}
|
|
215
|
-
{...props}
|
|
216
|
-
>
|
|
217
|
-
{children}
|
|
218
|
-
</code>
|
|
219
|
-
);
|
|
220
|
-
},
|
|
221
|
-
p({ children }) {
|
|
222
|
-
return <p className="my-1.5">{children}</p>;
|
|
223
|
-
},
|
|
224
|
-
ul({ children }) {
|
|
225
|
-
return (
|
|
226
|
-
<ul className="my-1.5 pl-4 list-disc">
|
|
227
|
-
{children}
|
|
228
|
-
</ul>
|
|
229
|
-
);
|
|
230
|
-
},
|
|
231
|
-
ol({ children }) {
|
|
232
|
-
return (
|
|
233
|
-
<ol className="my-1.5 pl-4 list-decimal">
|
|
234
|
-
{children}
|
|
235
|
-
</ol>
|
|
236
|
-
);
|
|
237
|
-
},
|
|
238
|
-
table({ children }) {
|
|
239
|
-
return (
|
|
240
|
-
<div className="overflow-x-auto my-2">
|
|
241
|
-
<table className="border-collapse text-xs">
|
|
242
|
-
{children}
|
|
243
|
-
</table>
|
|
244
|
-
</div>
|
|
245
|
-
);
|
|
246
|
-
},
|
|
247
|
-
th({ children }) {
|
|
248
|
-
return (
|
|
249
|
-
<th className="border border-white/10 px-2 py-1 text-left bg-white/5">
|
|
250
|
-
{children}
|
|
251
|
-
</th>
|
|
252
|
-
);
|
|
253
|
-
},
|
|
254
|
-
td({ children }) {
|
|
255
|
-
return (
|
|
256
|
-
<td className="border border-white/10 px-2 py-1">
|
|
257
|
-
{children}
|
|
258
|
-
</td>
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
}}
|
|
262
|
-
>
|
|
263
|
-
{content}
|
|
264
|
-
</ReactMarkdown>
|
|
265
|
-
</div>
|
|
266
|
-
<CopyButton text={content} />
|
|
267
|
-
</div>
|
|
268
|
-
{time && (
|
|
269
|
-
<span className="text-[10px] text-muted-foreground/50 px-1">
|
|
270
|
-
{time}
|
|
271
|
-
</span>
|
|
81
|
+
<div className="flex flex-col items-end gap-0.5">
|
|
82
|
+
<div className="group flex items-start gap-1.5 justify-end">
|
|
83
|
+
<CopyButton text={content} />
|
|
84
|
+
<div className="max-w-[92%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed whitespace-pre-wrap bg-primary text-primary-foreground break-words overflow-hidden" style={{ overflowWrap: 'anywhere' }}>
|
|
85
|
+
{/* Image thumbnails */}
|
|
86
|
+
{imageUrls.length > 0 && (
|
|
87
|
+
<div className="flex gap-1.5 flex-wrap mb-2">
|
|
88
|
+
{imageUrls.map((url, i) => (
|
|
89
|
+
<img
|
|
90
|
+
key={i}
|
|
91
|
+
src={url}
|
|
92
|
+
alt={imageAtts[i]?.name || 'attachment'}
|
|
93
|
+
className="w-28 h-28 rounded-lg object-cover cursor-pointer border border-white/10 hover:opacity-80 transition-opacity"
|
|
94
|
+
onClick={() => onImageClick?.(imageUrls, i)}
|
|
95
|
+
/>
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
{/* Document attachments */}
|
|
100
|
+
{docAtts.length > 0 && (
|
|
101
|
+
<span className="inline-flex items-center gap-1 text-primary-foreground/60 mr-1.5">
|
|
102
|
+
<Paperclip className="h-3 w-3" />
|
|
103
|
+
{docAtts.length > 1 && <span className="text-xs">{docAtts.length}</span>}
|
|
104
|
+
</span>
|
|
272
105
|
)}
|
|
106
|
+
{/* Fallback paperclip for legacy messages with no parsed attachments */}
|
|
107
|
+
{!attachments?.length && hasAttachments && (
|
|
108
|
+
<span className="inline-flex items-center gap-1 text-primary-foreground/60 mr-1.5">
|
|
109
|
+
<Paperclip className="h-3 w-3" />
|
|
110
|
+
</span>
|
|
111
|
+
)}
|
|
112
|
+
{content}
|
|
113
|
+
</div>
|
|
273
114
|
</div>
|
|
115
|
+
{time && <span className="text-[10px] text-muted-foreground/50 px-1">{time}</span>}
|
|
116
|
+
</div>
|
|
274
117
|
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div className="flex flex-col items-start gap-0.5">
|
|
122
|
+
<div className="group flex items-start gap-1.5">
|
|
123
|
+
<div className="min-w-0 rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-muted text-foreground prose prose-sm prose-invert max-w-none [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 break-words overflow-hidden" style={{ maxWidth: '92%', overflowWrap: 'anywhere' }}>
|
|
124
|
+
<ReactMarkdown
|
|
125
|
+
remarkPlugins={[remarkGfm]}
|
|
126
|
+
components={{
|
|
127
|
+
pre({ children }) {
|
|
128
|
+
return <pre className="overflow-x-auto my-2">{children}</pre>;
|
|
129
|
+
},
|
|
130
|
+
code({ className, children, ...props }) {
|
|
131
|
+
const match = /language-(\w+)/.exec(className || '');
|
|
132
|
+
const code = String(children).replace(/\n$/, '');
|
|
133
|
+
if (match) {
|
|
134
|
+
return (
|
|
135
|
+
<SyntaxHighlighter
|
|
136
|
+
style={oneDark}
|
|
137
|
+
language={match[1]}
|
|
138
|
+
PreTag="div"
|
|
139
|
+
customStyle={{ margin: '0.5rem 0', borderRadius: '0.5rem', fontSize: '0.8rem', overflowX: 'auto' }}
|
|
140
|
+
>
|
|
141
|
+
{code}
|
|
142
|
+
</SyntaxHighlighter>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return (
|
|
146
|
+
<code className="bg-white/10 rounded px-1 py-0.5 text-[0.8rem] break-all" style={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }} {...props}>
|
|
147
|
+
{children}
|
|
148
|
+
</code>
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
p({ children }) {
|
|
152
|
+
return <p className="my-1.5">{children}</p>;
|
|
153
|
+
},
|
|
154
|
+
ul({ children }) {
|
|
155
|
+
return <ul className="my-1.5 pl-4 list-disc">{children}</ul>;
|
|
156
|
+
},
|
|
157
|
+
ol({ children }) {
|
|
158
|
+
return <ol className="my-1.5 pl-4 list-decimal">{children}</ol>;
|
|
159
|
+
},
|
|
160
|
+
table({ children }) {
|
|
161
|
+
return (
|
|
162
|
+
<div className="overflow-x-auto my-2">
|
|
163
|
+
<table className="border-collapse text-xs">{children}</table>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
},
|
|
167
|
+
th({ children }) {
|
|
168
|
+
return <th className="border border-white/10 px-2 py-1 text-left bg-white/5">{children}</th>;
|
|
169
|
+
},
|
|
170
|
+
td({ children }) {
|
|
171
|
+
return <td className="border border-white/10 px-2 py-1">{children}</td>;
|
|
172
|
+
},
|
|
173
|
+
}}
|
|
174
|
+
>
|
|
175
|
+
{content}
|
|
176
|
+
</ReactMarkdown>
|
|
177
|
+
</div>
|
|
178
|
+
<CopyButton text={content} />
|
|
179
|
+
</div>
|
|
180
|
+
{time && <span className="text-[10px] text-muted-foreground/50 px-1">{time}</span>}
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
275
183
|
}
|