@wener/mcps 1.0.1

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.
Files changed (141) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.mjs +15 -0
  3. package/dist/mcps-cli.mjs +174727 -0
  4. package/lib/chat/agent.js +187 -0
  5. package/lib/chat/agent.js.map +1 -0
  6. package/lib/chat/audit.js +238 -0
  7. package/lib/chat/audit.js.map +1 -0
  8. package/lib/chat/converters.js +467 -0
  9. package/lib/chat/converters.js.map +1 -0
  10. package/lib/chat/handler.js +1068 -0
  11. package/lib/chat/handler.js.map +1 -0
  12. package/lib/chat/index.js +12 -0
  13. package/lib/chat/index.js.map +1 -0
  14. package/lib/chat/types.js +35 -0
  15. package/lib/chat/types.js.map +1 -0
  16. package/lib/contracts/AuditContract.js +85 -0
  17. package/lib/contracts/AuditContract.js.map +1 -0
  18. package/lib/contracts/McpsContract.js +113 -0
  19. package/lib/contracts/McpsContract.js.map +1 -0
  20. package/lib/contracts/index.js +3 -0
  21. package/lib/contracts/index.js.map +1 -0
  22. package/lib/dev.server.js +7 -0
  23. package/lib/dev.server.js.map +1 -0
  24. package/lib/entities/ChatRequestEntity.js +318 -0
  25. package/lib/entities/ChatRequestEntity.js.map +1 -0
  26. package/lib/entities/McpRequestEntity.js +271 -0
  27. package/lib/entities/McpRequestEntity.js.map +1 -0
  28. package/lib/entities/RequestLogEntity.js +177 -0
  29. package/lib/entities/RequestLogEntity.js.map +1 -0
  30. package/lib/entities/ResponseEntity.js +150 -0
  31. package/lib/entities/ResponseEntity.js.map +1 -0
  32. package/lib/entities/index.js +11 -0
  33. package/lib/entities/index.js.map +1 -0
  34. package/lib/entities/types.js +11 -0
  35. package/lib/entities/types.js.map +1 -0
  36. package/lib/index.js +3 -0
  37. package/lib/index.js.map +1 -0
  38. package/lib/mcps-cli.js +44 -0
  39. package/lib/mcps-cli.js.map +1 -0
  40. package/lib/providers/McpServerHandlerDef.js +40 -0
  41. package/lib/providers/McpServerHandlerDef.js.map +1 -0
  42. package/lib/providers/findMcpServerDef.js +26 -0
  43. package/lib/providers/findMcpServerDef.js.map +1 -0
  44. package/lib/providers/prometheus/def.js +24 -0
  45. package/lib/providers/prometheus/def.js.map +1 -0
  46. package/lib/providers/prometheus/index.js +2 -0
  47. package/lib/providers/prometheus/index.js.map +1 -0
  48. package/lib/providers/relay/def.js +32 -0
  49. package/lib/providers/relay/def.js.map +1 -0
  50. package/lib/providers/relay/index.js +2 -0
  51. package/lib/providers/relay/index.js.map +1 -0
  52. package/lib/providers/sql/def.js +31 -0
  53. package/lib/providers/sql/def.js.map +1 -0
  54. package/lib/providers/sql/index.js +2 -0
  55. package/lib/providers/sql/index.js.map +1 -0
  56. package/lib/providers/tencent-cls/def.js +44 -0
  57. package/lib/providers/tencent-cls/def.js.map +1 -0
  58. package/lib/providers/tencent-cls/index.js +2 -0
  59. package/lib/providers/tencent-cls/index.js.map +1 -0
  60. package/lib/scripts/bundle.js +90 -0
  61. package/lib/scripts/bundle.js.map +1 -0
  62. package/lib/server/api-routes.js +96 -0
  63. package/lib/server/api-routes.js.map +1 -0
  64. package/lib/server/audit.js +274 -0
  65. package/lib/server/audit.js.map +1 -0
  66. package/lib/server/chat-routes.js +82 -0
  67. package/lib/server/chat-routes.js.map +1 -0
  68. package/lib/server/config.js +223 -0
  69. package/lib/server/config.js.map +1 -0
  70. package/lib/server/db.js +97 -0
  71. package/lib/server/db.js.map +1 -0
  72. package/lib/server/index.js +2 -0
  73. package/lib/server/index.js.map +1 -0
  74. package/lib/server/mcp-handler.js +167 -0
  75. package/lib/server/mcp-handler.js.map +1 -0
  76. package/lib/server/mcp-routes.js +112 -0
  77. package/lib/server/mcp-routes.js.map +1 -0
  78. package/lib/server/mcps-router.js +119 -0
  79. package/lib/server/mcps-router.js.map +1 -0
  80. package/lib/server/schema.js +129 -0
  81. package/lib/server/schema.js.map +1 -0
  82. package/lib/server/server.js +166 -0
  83. package/lib/server/server.js.map +1 -0
  84. package/lib/web/ChatPage.js +827 -0
  85. package/lib/web/ChatPage.js.map +1 -0
  86. package/lib/web/McpInspectorPage.js +214 -0
  87. package/lib/web/McpInspectorPage.js.map +1 -0
  88. package/lib/web/ServersPage.js +93 -0
  89. package/lib/web/ServersPage.js.map +1 -0
  90. package/lib/web/main.js +541 -0
  91. package/lib/web/main.js.map +1 -0
  92. package/package.json +83 -0
  93. package/src/chat/agent.ts +240 -0
  94. package/src/chat/audit.ts +377 -0
  95. package/src/chat/converters.test.ts +325 -0
  96. package/src/chat/converters.ts +459 -0
  97. package/src/chat/handler.test.ts +137 -0
  98. package/src/chat/handler.ts +1233 -0
  99. package/src/chat/index.ts +16 -0
  100. package/src/chat/types.ts +72 -0
  101. package/src/contracts/AuditContract.ts +93 -0
  102. package/src/contracts/McpsContract.ts +141 -0
  103. package/src/contracts/index.ts +18 -0
  104. package/src/dev.server.ts +7 -0
  105. package/src/entities/ChatRequestEntity.ts +157 -0
  106. package/src/entities/McpRequestEntity.ts +149 -0
  107. package/src/entities/RequestLogEntity.ts +78 -0
  108. package/src/entities/ResponseEntity.ts +75 -0
  109. package/src/entities/index.ts +12 -0
  110. package/src/entities/types.ts +188 -0
  111. package/src/index.ts +1 -0
  112. package/src/mcps-cli.ts +59 -0
  113. package/src/providers/McpServerHandlerDef.ts +105 -0
  114. package/src/providers/findMcpServerDef.ts +31 -0
  115. package/src/providers/prometheus/def.ts +21 -0
  116. package/src/providers/prometheus/index.ts +1 -0
  117. package/src/providers/relay/def.ts +31 -0
  118. package/src/providers/relay/index.ts +1 -0
  119. package/src/providers/relay/relay.test.ts +47 -0
  120. package/src/providers/sql/def.ts +33 -0
  121. package/src/providers/sql/index.ts +1 -0
  122. package/src/providers/tencent-cls/def.ts +38 -0
  123. package/src/providers/tencent-cls/index.ts +1 -0
  124. package/src/scripts/bundle.ts +82 -0
  125. package/src/server/api-routes.ts +98 -0
  126. package/src/server/audit.ts +310 -0
  127. package/src/server/chat-routes.ts +95 -0
  128. package/src/server/config.test.ts +162 -0
  129. package/src/server/config.ts +198 -0
  130. package/src/server/db.ts +115 -0
  131. package/src/server/index.ts +1 -0
  132. package/src/server/mcp-handler.ts +209 -0
  133. package/src/server/mcp-routes.ts +133 -0
  134. package/src/server/mcps-router.ts +133 -0
  135. package/src/server/schema.ts +175 -0
  136. package/src/server/server.ts +163 -0
  137. package/src/web/ChatPage.tsx +1005 -0
  138. package/src/web/McpInspectorPage.tsx +254 -0
  139. package/src/web/ServersPage.tsx +139 -0
  140. package/src/web/main.tsx +600 -0
  141. package/src/web/styles.css +15 -0
@@ -0,0 +1,827 @@
1
+ 'use client';
2
+ import { Combobox } from '@base-ui/react/combobox';
3
+ import { cjk } from '@streamdown/cjk';
4
+ import { code } from '@streamdown/code';
5
+ import { math } from '@streamdown/math';
6
+ import { Brain, Check, ChevronDown, ChevronUp, Clock, Copy, Edit2, ImagePlus, Menu, MessageSquarePlus, RefreshCw, Send, Settings, Square, Trash2, Wrench, X, XCircle, Zap } from 'lucide-react';
7
+ import { useCallback, useEffect, useRef, useState, memo } from 'react';
8
+ import { Streamdown } from 'streamdown';
9
+ // Streamdown Markdown component
10
+ const MarkdownContent = /*#__PURE__*/ memo(({ children, className })=>/*#__PURE__*/ React.createElement(Streamdown, {
11
+ className: className,
12
+ plugins: {
13
+ code,
14
+ math,
15
+ cjk
16
+ }
17
+ }, children), (prev, next)=>prev.children === next.children);
18
+ MarkdownContent.displayName = 'MarkdownContent';
19
+ // Tool Call Display
20
+ function ToolCallDisplay({ toolCall }) {
21
+ const [isOpen, setIsOpen] = useState(false);
22
+ return /*#__PURE__*/ React.createElement("div", {
23
+ className: "border border-base-300 rounded-lg my-2 overflow-hidden bg-base-100"
24
+ }, /*#__PURE__*/ React.createElement("button", {
25
+ type: "button",
26
+ className: "w-full flex items-center justify-between p-2 hover:bg-base-200",
27
+ onClick: ()=>setIsOpen(!isOpen)
28
+ }, /*#__PURE__*/ React.createElement("div", {
29
+ className: "flex items-center gap-2"
30
+ }, toolCall.status === 'completed' ? /*#__PURE__*/ React.createElement(Check, {
31
+ className: "w-3.5 h-3.5 text-success"
32
+ }) : toolCall.status === 'error' ? /*#__PURE__*/ React.createElement(XCircle, {
33
+ className: "w-3.5 h-3.5 text-error"
34
+ }) : /*#__PURE__*/ React.createElement(Clock, {
35
+ className: "w-3.5 h-3.5 text-warning animate-pulse"
36
+ }), /*#__PURE__*/ React.createElement(Wrench, {
37
+ className: "w-3.5 h-3.5 text-base-content/60"
38
+ }), /*#__PURE__*/ React.createElement("span", {
39
+ className: "font-mono text-sm"
40
+ }, toolCall.name)), isOpen ? /*#__PURE__*/ React.createElement(ChevronUp, {
41
+ className: "w-4 h-4"
42
+ }) : /*#__PURE__*/ React.createElement(ChevronDown, {
43
+ className: "w-4 h-4"
44
+ })), isOpen && /*#__PURE__*/ React.createElement("div", {
45
+ className: "p-2 space-y-2 text-xs border-t border-base-300"
46
+ }, /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("div", {
47
+ className: "font-semibold text-base-content/70 mb-1"
48
+ }, "Arguments:"), /*#__PURE__*/ React.createElement("pre", {
49
+ className: "bg-base-200 p-2 rounded overflow-auto max-h-32"
50
+ }, JSON.stringify(toolCall.arguments, null, 2))), toolCall.result !== undefined && /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("div", {
51
+ className: "font-semibold text-base-content/70 mb-1"
52
+ }, "Result:"), /*#__PURE__*/ React.createElement("pre", {
53
+ className: "bg-base-200 p-2 rounded overflow-auto max-h-32"
54
+ }, typeof toolCall.result === 'string' ? toolCall.result : JSON.stringify(toolCall.result, null, 2))), toolCall.error && /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("div", {
55
+ className: "font-semibold text-error mb-1"
56
+ }, "Error:"), /*#__PURE__*/ React.createElement("pre", {
57
+ className: "bg-error/10 text-error p-2 rounded"
58
+ }, toolCall.error))));
59
+ }
60
+ // Reasoning/Thinking Display
61
+ function ReasoningDisplay({ content, isStreaming }) {
62
+ const [isOpen, setIsOpen] = useState(true);
63
+ return /*#__PURE__*/ React.createElement("div", {
64
+ className: "border border-base-300 rounded-lg my-2 overflow-hidden bg-base-100"
65
+ }, /*#__PURE__*/ React.createElement("button", {
66
+ type: "button",
67
+ className: "w-full flex items-center justify-between p-2 hover:bg-base-200",
68
+ onClick: ()=>setIsOpen(!isOpen)
69
+ }, /*#__PURE__*/ React.createElement("div", {
70
+ className: "flex items-center gap-2 text-base-content/70"
71
+ }, /*#__PURE__*/ React.createElement(Brain, {
72
+ className: `w-4 h-4 ${isStreaming ? 'animate-pulse' : ''}`
73
+ }), /*#__PURE__*/ React.createElement("span", {
74
+ className: "text-sm"
75
+ }, isStreaming ? 'Thinking...' : 'Reasoning')), isOpen ? /*#__PURE__*/ React.createElement(ChevronUp, {
76
+ className: "w-4 h-4"
77
+ }) : /*#__PURE__*/ React.createElement(ChevronDown, {
78
+ className: "w-4 h-4"
79
+ })), isOpen && /*#__PURE__*/ React.createElement("div", {
80
+ className: "p-3 text-sm text-base-content/80 prose prose-sm max-w-none border-t border-base-300"
81
+ }, /*#__PURE__*/ React.createElement(MarkdownContent, null, content || '...')));
82
+ }
83
+ // Helper functions
84
+ function generateSessionId() {
85
+ return `chat-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
86
+ }
87
+ function generateTitle(messages) {
88
+ const firstUserMessage = messages.find((m)=>m.role === 'user');
89
+ if (firstUserMessage) {
90
+ const content = firstUserMessage.content.slice(0, 50);
91
+ return content.length < firstUserMessage.content.length ? `${content}...` : content;
92
+ }
93
+ return 'New Chat';
94
+ }
95
+ function loadSessions() {
96
+ try {
97
+ const data = localStorage.getItem('mcps-chat-sessions');
98
+ if (data) return JSON.parse(data);
99
+ } catch (e) {
100
+ console.error('Failed to load sessions:', e);
101
+ }
102
+ return [];
103
+ }
104
+ function saveSessions(sessions) {
105
+ try {
106
+ localStorage.setItem('mcps-chat-sessions', JSON.stringify(sessions));
107
+ } catch (e) {
108
+ console.error('Failed to save sessions:', e);
109
+ }
110
+ }
111
+ function loadSettings() {
112
+ try {
113
+ const data = localStorage.getItem('mcps-chat-settings');
114
+ if (data) return {
115
+ ...defaultSettings,
116
+ ...JSON.parse(data)
117
+ };
118
+ } catch (e) {
119
+ console.error('Failed to load settings:', e);
120
+ }
121
+ return defaultSettings;
122
+ }
123
+ function saveSettings(settings) {
124
+ try {
125
+ localStorage.setItem('mcps-chat-settings', JSON.stringify(settings));
126
+ } catch (e) {
127
+ console.error('Failed to save settings:', e);
128
+ }
129
+ }
130
+ const defaultSettings = {
131
+ temperature: 0.7,
132
+ topP: 1.0,
133
+ topK: 40,
134
+ maxTokens: 4096,
135
+ mcpServers: []
136
+ };
137
+ export function ChatPage() {
138
+ const [models, setModels] = useState([]);
139
+ const [selectedModel, setSelectedModel] = useState('');
140
+ const [input, setInput] = useState('');
141
+ const [images, setImages] = useState([]);
142
+ const [isLoading, setIsLoading] = useState(false);
143
+ const [sessions, setSessions] = useState(()=>loadSessions());
144
+ const [currentSessionId, setCurrentSessionId] = useState(null);
145
+ const [showSidebar, setShowSidebar] = useState(true);
146
+ const [showSettings, setShowSettings] = useState(false);
147
+ const [editingMessageId, setEditingMessageId] = useState(null);
148
+ const [editContent, setEditContent] = useState('');
149
+ const [mcpServers, setMcpServers] = useState([]);
150
+ const [settings, setSettings] = useState(()=>loadSettings());
151
+ const [inputHistory, setInputHistory] = useState([]);
152
+ const [historyIndex, setHistoryIndex] = useState(-1);
153
+ const messagesEndRef = useRef(null);
154
+ const abortControllerRef = useRef(null);
155
+ const fileInputRef = useRef(null);
156
+ const inputRef = useRef(null);
157
+ const currentSession = sessions.find((s)=>s.id === currentSessionId);
158
+ const messages = currentSession?.messages || [];
159
+ useEffect(()=>{
160
+ saveSessions(sessions);
161
+ }, [
162
+ sessions
163
+ ]);
164
+ useEffect(()=>{
165
+ saveSettings(settings);
166
+ }, [
167
+ settings
168
+ ]);
169
+ useEffect(()=>{
170
+ messagesEndRef.current?.scrollIntoView({
171
+ behavior: 'smooth'
172
+ });
173
+ }, [
174
+ messages
175
+ ]);
176
+ useEffect(()=>{
177
+ fetch('/api/mcps/models').then((res)=>res.json()).then((data)=>{
178
+ const modelList = data.models || [];
179
+ const validModels = modelList.filter((m)=>!m.name.includes('*')).map((m)=>({
180
+ id: m.name,
181
+ value: m.name,
182
+ adapter: m.adapter || undefined,
183
+ baseUrl: m.baseUrl || undefined
184
+ }));
185
+ setModels(validModels);
186
+ if (validModels.length > 0 && !selectedModel) {
187
+ setSelectedModel(validModels[0].value);
188
+ }
189
+ }).catch(console.error);
190
+ fetch('/api/mcps/servers').then((res)=>res.json()).then((data)=>{
191
+ setMcpServers(data.servers || []);
192
+ }).catch(console.error);
193
+ }, []);
194
+ const updateSession = useCallback((sessionId, updater)=>{
195
+ setSessions((prev)=>prev.map((s)=>s.id === sessionId ? updater(s) : s));
196
+ }, []);
197
+ const sendMessage = async (content, sessionId, existingMessages, msgImages)=>{
198
+ const startTime = Date.now();
199
+ const userMessage = {
200
+ id: `user-${Date.now()}`,
201
+ role: 'user',
202
+ content,
203
+ images: msgImages,
204
+ createdAt: new Date()
205
+ };
206
+ const assistantMessage = {
207
+ id: `assistant-${Date.now()}`,
208
+ role: 'assistant',
209
+ content: '',
210
+ createdAt: new Date()
211
+ };
212
+ updateSession(sessionId, (s)=>({
213
+ ...s,
214
+ messages: [
215
+ ...existingMessages,
216
+ userMessage,
217
+ assistantMessage
218
+ ],
219
+ title: generateTitle([
220
+ ...existingMessages,
221
+ userMessage
222
+ ]),
223
+ updatedAt: new Date()
224
+ }));
225
+ setIsLoading(true);
226
+ abortControllerRef.current = new AbortController();
227
+ try {
228
+ // Build messages with image support
229
+ const apiMessages = [
230
+ ...existingMessages,
231
+ userMessage
232
+ ].map((m)=>{
233
+ if (m.images && m.images.length > 0) {
234
+ // Multi-modal message
235
+ return {
236
+ role: m.role,
237
+ content: [
238
+ {
239
+ type: 'text',
240
+ text: m.content
241
+ },
242
+ ...m.images.map((img)=>({
243
+ type: 'image_url',
244
+ image_url: {
245
+ url: img.base64 || img.url
246
+ }
247
+ }))
248
+ ]
249
+ };
250
+ }
251
+ return {
252
+ role: m.role,
253
+ content: m.content
254
+ };
255
+ });
256
+ // Always use agent endpoint for tool support
257
+ const requestBody = {
258
+ model: selectedModel,
259
+ messages: apiMessages,
260
+ stream: true,
261
+ temperature: settings.temperature,
262
+ top_p: settings.topP,
263
+ max_tokens: settings.maxTokens
264
+ };
265
+ if (settings.mcpServers.length > 0) {
266
+ requestBody.mcpServers = settings.mcpServers;
267
+ }
268
+ const response = await fetch('/v1/agent/chat', {
269
+ method: 'POST',
270
+ headers: {
271
+ 'Content-Type': 'application/json'
272
+ },
273
+ body: JSON.stringify(requestBody),
274
+ signal: abortControllerRef.current.signal
275
+ });
276
+ if (!response.ok) {
277
+ const errorData = await response.json().catch(()=>({}));
278
+ throw new Error(errorData.error?.message || `HTTP ${response.status}`);
279
+ }
280
+ const reader = response.body?.getReader();
281
+ if (!reader) throw new Error('No response body');
282
+ const decoder = new TextDecoder();
283
+ let buffer = '';
284
+ let fullContent = '';
285
+ let reasoning = '';
286
+ let usage;
287
+ while(true){
288
+ const { done, value } = await reader.read();
289
+ if (done) break;
290
+ buffer += decoder.decode(value, {
291
+ stream: true
292
+ });
293
+ const lines = buffer.split('\n');
294
+ buffer = lines.pop() || '';
295
+ for (const line of lines){
296
+ if (!line.trim() || !line.startsWith('data: ')) continue;
297
+ const data = line.slice(6);
298
+ if (data === '[DONE]') continue;
299
+ try {
300
+ const parsed = JSON.parse(data);
301
+ // Handle agent streaming format
302
+ if (parsed.type === 'text') {
303
+ fullContent += parsed.content || '';
304
+ } else if (parsed.type === 'usage') {
305
+ usage = {
306
+ promptTokens: parsed.usage?.promptTokens,
307
+ completionTokens: parsed.usage?.completionTokens,
308
+ totalTokens: parsed.usage?.totalTokens
309
+ };
310
+ } else if (parsed.type === 'step') {
311
+ // Handle step with reasoning
312
+ if (parsed.text) fullContent = parsed.text;
313
+ }
314
+ // Handle OpenAI format
315
+ const delta = parsed.choices?.[0]?.delta;
316
+ if (delta?.content) {
317
+ fullContent += delta.content;
318
+ }
319
+ if (delta?.reasoning_content) {
320
+ reasoning += delta.reasoning_content;
321
+ }
322
+ if (parsed.usage) {
323
+ usage = {
324
+ promptTokens: parsed.usage.prompt_tokens,
325
+ completionTokens: parsed.usage.completion_tokens,
326
+ totalTokens: parsed.usage.total_tokens
327
+ };
328
+ }
329
+ updateSession(sessionId, (s)=>({
330
+ ...s,
331
+ messages: s.messages.map((m)=>m.id === assistantMessage.id ? {
332
+ ...m,
333
+ content: fullContent,
334
+ reasoning: reasoning || undefined,
335
+ usage,
336
+ durationMs: Date.now() - startTime
337
+ } : m)
338
+ }));
339
+ } catch {
340
+ // Skip invalid JSON
341
+ }
342
+ }
343
+ }
344
+ updateSession(sessionId, (s)=>({
345
+ ...s,
346
+ messages: s.messages.map((m)=>m.id === assistantMessage.id ? {
347
+ ...m,
348
+ durationMs: Date.now() - startTime
349
+ } : m)
350
+ }));
351
+ } catch (err) {
352
+ if (err.name === 'AbortError') return;
353
+ const errorMsg = err instanceof Error ? err.message : 'Failed to send message';
354
+ updateSession(sessionId, (s)=>({
355
+ ...s,
356
+ messages: s.messages.map((m)=>m.id === assistantMessage.id ? {
357
+ ...m,
358
+ content: '',
359
+ error: errorMsg
360
+ } : m)
361
+ }));
362
+ } finally{
363
+ setIsLoading(false);
364
+ abortControllerRef.current = null;
365
+ }
366
+ };
367
+ const handleSubmit = async (e)=>{
368
+ e.preventDefault();
369
+ if (!input.trim() || !selectedModel || isLoading) return;
370
+ // Add to history
371
+ setInputHistory((prev)=>[
372
+ input.trim(),
373
+ ...prev.slice(0, 49)
374
+ ]);
375
+ setHistoryIndex(-1);
376
+ let sessionId = currentSessionId;
377
+ let existingMessages = messages;
378
+ if (!sessionId) {
379
+ const newSession = {
380
+ id: generateSessionId(),
381
+ title: 'New Chat',
382
+ model: selectedModel,
383
+ messages: [],
384
+ createdAt: new Date(),
385
+ updatedAt: new Date()
386
+ };
387
+ setSessions((prev)=>[
388
+ newSession,
389
+ ...prev
390
+ ]);
391
+ setCurrentSessionId(newSession.id);
392
+ sessionId = newSession.id;
393
+ existingMessages = [];
394
+ }
395
+ const content = input.trim();
396
+ const msgImages = images.length > 0 ? [
397
+ ...images
398
+ ] : undefined;
399
+ setInput('');
400
+ setImages([]);
401
+ await sendMessage(content, sessionId, existingMessages, msgImages);
402
+ };
403
+ const handleKeyDown = (e)=>{
404
+ if (e.key === 'ArrowUp' && !input && inputHistory.length > 0) {
405
+ e.preventDefault();
406
+ const newIndex = Math.min(historyIndex + 1, inputHistory.length - 1);
407
+ setHistoryIndex(newIndex);
408
+ setInput(inputHistory[newIndex]);
409
+ } else if (e.key === 'ArrowDown' && historyIndex >= 0) {
410
+ e.preventDefault();
411
+ const newIndex = historyIndex - 1;
412
+ setHistoryIndex(newIndex);
413
+ setInput(newIndex >= 0 ? inputHistory[newIndex] : '');
414
+ }
415
+ };
416
+ const handleRetry = async (messageId)=>{
417
+ if (!currentSessionId || isLoading) return;
418
+ const msgIndex = messages.findIndex((m)=>m.id === messageId);
419
+ if (msgIndex === -1) return;
420
+ const message = messages[msgIndex];
421
+ if (message.role !== 'assistant') return;
422
+ const userMsgIndex = msgIndex - 1;
423
+ if (userMsgIndex < 0) return;
424
+ const userMessage = messages[userMsgIndex];
425
+ if (userMessage.role !== 'user') return;
426
+ const existingMessages = messages.slice(0, userMsgIndex);
427
+ updateSession(currentSessionId, (s)=>({
428
+ ...s,
429
+ messages: existingMessages
430
+ }));
431
+ await sendMessage(userMessage.content, currentSessionId, existingMessages, userMessage.images);
432
+ };
433
+ const handleEditMessage = (messageId)=>{
434
+ const message = messages.find((m)=>m.id === messageId);
435
+ if (!message || message.role !== 'user') return;
436
+ setEditingMessageId(messageId);
437
+ setEditContent(message.content);
438
+ };
439
+ const handleSaveEdit = async ()=>{
440
+ if (!editingMessageId || !currentSessionId || !editContent.trim() || isLoading) return;
441
+ const msgIndex = messages.findIndex((m)=>m.id === editingMessageId);
442
+ if (msgIndex === -1) return;
443
+ const existingMessages = messages.slice(0, msgIndex);
444
+ setEditingMessageId(null);
445
+ setEditContent('');
446
+ updateSession(currentSessionId, (s)=>({
447
+ ...s,
448
+ messages: existingMessages
449
+ }));
450
+ await sendMessage(editContent.trim(), currentSessionId, existingMessages);
451
+ };
452
+ const handleCancelEdit = ()=>{
453
+ setEditingMessageId(null);
454
+ setEditContent('');
455
+ };
456
+ const handleStop = ()=>{
457
+ abortControllerRef.current?.abort();
458
+ };
459
+ const handleImageUpload = (e)=>{
460
+ const files = e.target.files;
461
+ if (!files) return;
462
+ for (const file of files){
463
+ const reader = new FileReader();
464
+ reader.onload = (ev)=>{
465
+ const base64 = ev.target?.result;
466
+ setImages((prev)=>[
467
+ ...prev,
468
+ {
469
+ type: 'image',
470
+ url: file.name,
471
+ base64
472
+ }
473
+ ]);
474
+ };
475
+ reader.readAsDataURL(file);
476
+ }
477
+ if (fileInputRef.current) fileInputRef.current.value = '';
478
+ };
479
+ const removeImage = (index)=>{
480
+ setImages((prev)=>prev.filter((_, i)=>i !== index));
481
+ };
482
+ const copyToClipboard = (text)=>{
483
+ navigator.clipboard.writeText(text);
484
+ };
485
+ const createSession = useCallback(()=>{
486
+ const newSession = {
487
+ id: generateSessionId(),
488
+ title: 'New Chat',
489
+ model: selectedModel,
490
+ messages: [],
491
+ createdAt: new Date(),
492
+ updatedAt: new Date()
493
+ };
494
+ setSessions((prev)=>[
495
+ newSession,
496
+ ...prev
497
+ ]);
498
+ setCurrentSessionId(newSession.id);
499
+ }, [
500
+ selectedModel
501
+ ]);
502
+ const deleteSession = useCallback((sessionId)=>{
503
+ setSessions((prev)=>prev.filter((s)=>s.id !== sessionId));
504
+ if (currentSessionId === sessionId) setCurrentSessionId(null);
505
+ }, [
506
+ currentSessionId
507
+ ]);
508
+ return /*#__PURE__*/ React.createElement("div", {
509
+ className: "flex h-full min-h-[600px]"
510
+ }, showSidebar && /*#__PURE__*/ React.createElement("div", {
511
+ className: "w-56 border-r border-base-300 bg-base-100 flex flex-col flex-shrink-0"
512
+ }, /*#__PURE__*/ React.createElement("div", {
513
+ className: "p-2 border-b border-base-300"
514
+ }, /*#__PURE__*/ React.createElement("button", {
515
+ type: "button",
516
+ className: "btn btn-primary btn-sm w-full gap-1",
517
+ onClick: createSession
518
+ }, /*#__PURE__*/ React.createElement(MessageSquarePlus, {
519
+ className: "w-4 h-4"
520
+ }), " New Chat")), /*#__PURE__*/ React.createElement("div", {
521
+ className: "flex-1 overflow-y-auto"
522
+ }, sessions.length === 0 ? /*#__PURE__*/ React.createElement("div", {
523
+ className: "p-4 text-center text-base-content/50 text-sm"
524
+ }, "No chat history") : /*#__PURE__*/ React.createElement("ul", {
525
+ className: "menu p-1 gap-0.5"
526
+ }, sessions.map((session)=>/*#__PURE__*/ React.createElement("li", {
527
+ key: session.id
528
+ }, /*#__PURE__*/ React.createElement("button", {
529
+ type: "button",
530
+ className: `flex justify-between items-center w-full text-left py-2 px-2 ${currentSessionId === session.id ? 'active' : ''}`,
531
+ onClick: ()=>setCurrentSessionId(session.id)
532
+ }, /*#__PURE__*/ React.createElement("span", {
533
+ className: "flex-1 truncate text-xs"
534
+ }, session.title), /*#__PURE__*/ React.createElement("button", {
535
+ type: "button",
536
+ className: "btn btn-ghost btn-xs opacity-50 hover:opacity-100",
537
+ onClick: (e)=>{
538
+ e.stopPropagation();
539
+ deleteSession(session.id);
540
+ }
541
+ }, /*#__PURE__*/ React.createElement(Trash2, {
542
+ className: "w-3 h-3"
543
+ })))))))), /*#__PURE__*/ React.createElement("div", {
544
+ className: "flex-1 flex flex-col min-w-0"
545
+ }, /*#__PURE__*/ React.createElement("div", {
546
+ className: "h-12 px-3 border-b border-base-300 bg-base-100 flex items-center gap-2 flex-shrink-0"
547
+ }, /*#__PURE__*/ React.createElement("button", {
548
+ type: "button",
549
+ className: "btn btn-ghost btn-sm btn-square",
550
+ onClick: ()=>setShowSidebar(!showSidebar)
551
+ }, /*#__PURE__*/ React.createElement(Menu, {
552
+ className: "w-4 h-4"
553
+ })), /*#__PURE__*/ React.createElement("div", {
554
+ className: "flex-1 max-w-xs"
555
+ }, /*#__PURE__*/ React.createElement(Combobox.Root, {
556
+ items: models,
557
+ itemToStringValue: (item)=>item.value,
558
+ value: models.find((m)=>m.value === selectedModel) || null,
559
+ onValueChange: (item)=>{
560
+ if (item) setSelectedModel(item.value);
561
+ },
562
+ onInputValueChange: (value)=>{
563
+ if (value) setSelectedModel(value);
564
+ }
565
+ }, /*#__PURE__*/ React.createElement(Combobox.Input, {
566
+ placeholder: "Select model...",
567
+ className: "input input-bordered input-sm w-full"
568
+ }), /*#__PURE__*/ React.createElement(Combobox.Portal, null, /*#__PURE__*/ React.createElement(Combobox.Positioner, {
569
+ sideOffset: 4
570
+ }, /*#__PURE__*/ React.createElement(Combobox.Popup, {
571
+ className: "bg-base-100 rounded-box shadow-lg border border-base-300 max-h-60 overflow-auto z-50"
572
+ }, /*#__PURE__*/ React.createElement(Combobox.Empty, {
573
+ className: "p-2 text-sm text-base-content/50"
574
+ }, "No models"), /*#__PURE__*/ React.createElement(Combobox.List, {
575
+ className: "p-1"
576
+ }, (item)=>/*#__PURE__*/ React.createElement(Combobox.Item, {
577
+ key: item.id,
578
+ value: item,
579
+ className: "p-2 rounded cursor-pointer hover:bg-base-200 data-[highlighted]:bg-base-200 text-sm"
580
+ }, item.value))))))), /*#__PURE__*/ React.createElement("div", {
581
+ className: "flex-1"
582
+ }), /*#__PURE__*/ React.createElement("button", {
583
+ type: "button",
584
+ className: `btn btn-ghost btn-sm btn-square ${showSettings ? 'btn-active' : ''}`,
585
+ onClick: ()=>setShowSettings(!showSettings)
586
+ }, /*#__PURE__*/ React.createElement(Settings, {
587
+ className: "w-4 h-4"
588
+ }))), /*#__PURE__*/ React.createElement("div", {
589
+ className: "flex-1 flex overflow-hidden"
590
+ }, /*#__PURE__*/ React.createElement("div", {
591
+ className: "flex-1 overflow-y-auto p-4"
592
+ }, messages.length === 0 && /*#__PURE__*/ React.createElement("div", {
593
+ className: "text-center text-base-content/50 py-8"
594
+ }, /*#__PURE__*/ React.createElement("p", null, "Start a conversation by sending a message."), selectedModel && /*#__PURE__*/ React.createElement("p", {
595
+ className: "text-sm mt-2 opacity-70"
596
+ }, "Model: ", selectedModel)), /*#__PURE__*/ React.createElement("div", {
597
+ className: "space-y-6 max-w-3xl mx-auto"
598
+ }, messages.map((message)=>/*#__PURE__*/ React.createElement("div", {
599
+ key: message.id
600
+ }, message.role === 'user' ? // User message
601
+ /*#__PURE__*/ React.createElement("div", {
602
+ className: "flex justify-end"
603
+ }, /*#__PURE__*/ React.createElement("div", {
604
+ className: "max-w-[80%]"
605
+ }, editingMessageId === message.id ? /*#__PURE__*/ React.createElement("div", {
606
+ className: "bg-base-200 rounded-lg p-3"
607
+ }, /*#__PURE__*/ React.createElement("textarea", {
608
+ className: "textarea textarea-bordered w-full min-w-64",
609
+ value: editContent,
610
+ onChange: (e)=>setEditContent(e.target.value),
611
+ rows: 3
612
+ }), /*#__PURE__*/ React.createElement("div", {
613
+ className: "flex gap-2 mt-2"
614
+ }, /*#__PURE__*/ React.createElement("button", {
615
+ type: "button",
616
+ className: "btn btn-primary btn-xs",
617
+ onClick: handleSaveEdit
618
+ }, "Save & Send"), /*#__PURE__*/ React.createElement("button", {
619
+ type: "button",
620
+ className: "btn btn-ghost btn-xs",
621
+ onClick: handleCancelEdit
622
+ }, "Cancel"))) : /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement("div", {
623
+ className: "bg-primary text-primary-content rounded-2xl rounded-br-md px-4 py-2"
624
+ }, message.images && message.images.length > 0 && /*#__PURE__*/ React.createElement("div", {
625
+ className: "flex flex-wrap gap-2 mb-2"
626
+ }, message.images.map((img, i)=>/*#__PURE__*/ React.createElement("img", {
627
+ key: i,
628
+ src: img.base64 || img.url,
629
+ alt: "uploaded",
630
+ className: "max-h-32 rounded"
631
+ }))), /*#__PURE__*/ React.createElement("p", {
632
+ className: "whitespace-pre-wrap"
633
+ }, message.content)), /*#__PURE__*/ React.createElement("div", {
634
+ className: "flex justify-end gap-1 mt-1"
635
+ }, /*#__PURE__*/ React.createElement("button", {
636
+ type: "button",
637
+ className: "btn btn-ghost btn-xs opacity-50 hover:opacity-100",
638
+ onClick: ()=>handleEditMessage(message.id)
639
+ }, /*#__PURE__*/ React.createElement(Edit2, {
640
+ className: "w-3 h-3"
641
+ })))))) : // Assistant message - flat display
642
+ /*#__PURE__*/ React.createElement("div", null, message.error ? /*#__PURE__*/ React.createElement("div", {
643
+ className: "text-error flex items-center gap-2"
644
+ }, /*#__PURE__*/ React.createElement(XCircle, {
645
+ className: "w-4 h-4"
646
+ }), /*#__PURE__*/ React.createElement("span", null, "Error: ", message.error), /*#__PURE__*/ React.createElement("button", {
647
+ type: "button",
648
+ className: "btn btn-ghost btn-xs",
649
+ onClick: ()=>handleRetry(message.id)
650
+ }, /*#__PURE__*/ React.createElement(RefreshCw, {
651
+ className: "w-3 h-3"
652
+ }))) : /*#__PURE__*/ React.createElement(React.Fragment, null, message.reasoning && /*#__PURE__*/ React.createElement(ReasoningDisplay, {
653
+ content: message.reasoning,
654
+ isStreaming: isLoading
655
+ }), message.toolCalls?.map((tc)=>/*#__PURE__*/ React.createElement(ToolCallDisplay, {
656
+ key: tc.id,
657
+ toolCall: tc
658
+ })), /*#__PURE__*/ React.createElement("div", {
659
+ className: "prose prose-sm max-w-none [&>*:first-child]:mt-0 [&>*:last-child]:mb-0"
660
+ }, /*#__PURE__*/ React.createElement(MarkdownContent, null, message.content || (isLoading ? '...' : ''))), /*#__PURE__*/ React.createElement("div", {
661
+ className: "flex items-center gap-3 mt-2 text-xs text-base-content/50"
662
+ }, message.usage && /*#__PURE__*/ React.createElement("span", {
663
+ className: "flex items-center gap-1"
664
+ }, /*#__PURE__*/ React.createElement(Zap, {
665
+ className: "w-3 h-3"
666
+ }), message.usage.totalTokens, " tokens", message.usage.promptTokens != null && /*#__PURE__*/ React.createElement("span", {
667
+ className: "opacity-70"
668
+ }, "(", message.usage.promptTokens, "/", message.usage.completionTokens, ")")), message.durationMs && /*#__PURE__*/ React.createElement("span", {
669
+ className: "flex items-center gap-1"
670
+ }, /*#__PURE__*/ React.createElement(Clock, {
671
+ className: "w-3 h-3"
672
+ }), (message.durationMs / 1000).toFixed(1), "s"), message.usage?.completionTokens && message.durationMs && /*#__PURE__*/ React.createElement("span", null, Math.round(message.usage.completionTokens / message.durationMs * 1000), " tok/s"), /*#__PURE__*/ React.createElement("button", {
673
+ type: "button",
674
+ className: "btn btn-ghost btn-xs opacity-50 hover:opacity-100",
675
+ onClick: ()=>copyToClipboard(message.content)
676
+ }, /*#__PURE__*/ React.createElement(Copy, {
677
+ className: "w-3 h-3"
678
+ })), /*#__PURE__*/ React.createElement("button", {
679
+ type: "button",
680
+ className: "btn btn-ghost btn-xs opacity-50 hover:opacity-100",
681
+ onClick: ()=>handleRetry(message.id)
682
+ }, /*#__PURE__*/ React.createElement(RefreshCw, {
683
+ className: "w-3 h-3"
684
+ })))))))), /*#__PURE__*/ React.createElement("div", {
685
+ ref: messagesEndRef
686
+ })), showSettings && /*#__PURE__*/ React.createElement("div", {
687
+ className: "w-64 border-l border-base-300 bg-base-100 p-4 overflow-y-auto flex-shrink-0"
688
+ }, /*#__PURE__*/ React.createElement("h3", {
689
+ className: "font-semibold mb-4"
690
+ }, "Settings"), /*#__PURE__*/ React.createElement("div", {
691
+ className: "space-y-4"
692
+ }, /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("label", {
693
+ className: "text-xs font-medium"
694
+ }, "Temperature: ", settings.temperature), /*#__PURE__*/ React.createElement("input", {
695
+ type: "range",
696
+ min: "0",
697
+ max: "2",
698
+ step: "0.1",
699
+ value: settings.temperature,
700
+ onChange: (e)=>setSettings((s)=>({
701
+ ...s,
702
+ temperature: parseFloat(e.target.value)
703
+ })),
704
+ className: "range range-xs range-primary w-full"
705
+ })), /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("label", {
706
+ className: "text-xs font-medium"
707
+ }, "Top P: ", settings.topP), /*#__PURE__*/ React.createElement("input", {
708
+ type: "range",
709
+ min: "0",
710
+ max: "1",
711
+ step: "0.05",
712
+ value: settings.topP,
713
+ onChange: (e)=>setSettings((s)=>({
714
+ ...s,
715
+ topP: parseFloat(e.target.value)
716
+ })),
717
+ className: "range range-xs range-primary w-full"
718
+ })), /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("label", {
719
+ className: "text-xs font-medium"
720
+ }, "Top K: ", settings.topK), /*#__PURE__*/ React.createElement("input", {
721
+ type: "range",
722
+ min: "1",
723
+ max: "100",
724
+ step: "1",
725
+ value: settings.topK,
726
+ onChange: (e)=>setSettings((s)=>({
727
+ ...s,
728
+ topK: parseInt(e.target.value, 10)
729
+ })),
730
+ className: "range range-xs range-primary w-full"
731
+ })), /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("label", {
732
+ className: "text-xs font-medium"
733
+ }, "Max Tokens: ", settings.maxTokens), /*#__PURE__*/ React.createElement("input", {
734
+ type: "range",
735
+ min: "256",
736
+ max: "16384",
737
+ step: "256",
738
+ value: settings.maxTokens,
739
+ onChange: (e)=>setSettings((s)=>({
740
+ ...s,
741
+ maxTokens: parseInt(e.target.value, 10)
742
+ })),
743
+ className: "range range-xs range-primary w-full"
744
+ })), /*#__PURE__*/ React.createElement("div", {
745
+ className: "divider text-xs"
746
+ }, "MCP Servers"), mcpServers.length === 0 ? /*#__PURE__*/ React.createElement("p", {
747
+ className: "text-xs text-base-content/50"
748
+ }, "No servers configured") : /*#__PURE__*/ React.createElement("div", {
749
+ className: "space-y-2"
750
+ }, mcpServers.map((server)=>/*#__PURE__*/ React.createElement("label", {
751
+ key: server.name,
752
+ className: "flex items-center gap-2 cursor-pointer"
753
+ }, /*#__PURE__*/ React.createElement("input", {
754
+ type: "checkbox",
755
+ className: "checkbox checkbox-xs checkbox-primary",
756
+ checked: settings.mcpServers.includes(server.name),
757
+ onChange: (e)=>{
758
+ setSettings((s)=>({
759
+ ...s,
760
+ mcpServers: e.target.checked ? [
761
+ ...s.mcpServers,
762
+ server.name
763
+ ] : s.mcpServers.filter((n)=>n !== server.name)
764
+ }));
765
+ }
766
+ }), /*#__PURE__*/ React.createElement("span", {
767
+ className: "text-xs"
768
+ }, server.name), /*#__PURE__*/ React.createElement("span", {
769
+ className: "text-xs text-base-content/50"
770
+ }, "(", server.type, ")"))))))), /*#__PURE__*/ React.createElement("div", {
771
+ className: "p-3 border-t border-base-300 bg-base-100 flex-shrink-0"
772
+ }, images.length > 0 && /*#__PURE__*/ React.createElement("div", {
773
+ className: "flex flex-wrap gap-2 mb-2"
774
+ }, images.map((img, i)=>/*#__PURE__*/ React.createElement("div", {
775
+ key: i,
776
+ className: "relative"
777
+ }, /*#__PURE__*/ React.createElement("img", {
778
+ src: img.base64 || img.url,
779
+ alt: "preview",
780
+ className: "h-16 rounded"
781
+ }), /*#__PURE__*/ React.createElement("button", {
782
+ type: "button",
783
+ className: "btn btn-circle btn-xs absolute -top-1 -right-1 btn-error",
784
+ onClick: ()=>removeImage(i)
785
+ }, /*#__PURE__*/ React.createElement(X, {
786
+ className: "w-3 h-3"
787
+ }))))), /*#__PURE__*/ React.createElement("form", {
788
+ onSubmit: handleSubmit,
789
+ className: "flex gap-2"
790
+ }, /*#__PURE__*/ React.createElement("input", {
791
+ type: "file",
792
+ ref: fileInputRef,
793
+ accept: "image/*",
794
+ multiple: true,
795
+ className: "hidden",
796
+ onChange: handleImageUpload
797
+ }), /*#__PURE__*/ React.createElement("button", {
798
+ type: "button",
799
+ className: "btn btn-ghost btn-sm btn-square",
800
+ onClick: ()=>fileInputRef.current?.click()
801
+ }, /*#__PURE__*/ React.createElement(ImagePlus, {
802
+ className: "w-4 h-4"
803
+ })), /*#__PURE__*/ React.createElement("input", {
804
+ ref: inputRef,
805
+ type: "text",
806
+ className: "input input-bordered flex-1 input-sm",
807
+ value: input,
808
+ onChange: (e)=>setInput(e.target.value),
809
+ onKeyDown: handleKeyDown,
810
+ placeholder: selectedModel ? 'Type a message... (Up arrow for history)' : 'Select a model first',
811
+ disabled: !selectedModel || isLoading
812
+ }), isLoading ? /*#__PURE__*/ React.createElement("button", {
813
+ type: "button",
814
+ className: "btn btn-error btn-sm",
815
+ onClick: handleStop
816
+ }, /*#__PURE__*/ React.createElement(Square, {
817
+ className: "w-4 h-4"
818
+ })) : /*#__PURE__*/ React.createElement("button", {
819
+ type: "submit",
820
+ className: "btn btn-primary btn-sm",
821
+ disabled: !input.trim() || !selectedModel
822
+ }, /*#__PURE__*/ React.createElement(Send, {
823
+ className: "w-4 h-4"
824
+ }))))));
825
+ }
826
+
827
+ //# sourceMappingURL=ChatPage.js.map