weifuwu 0.24.0 → 0.24.2

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 (76) hide show
  1. package/README.md +970 -756
  2. package/cli/template/app.ts +5 -1
  3. package/cli/template/index.ts +4 -1
  4. package/cli/template/locales/en.json +6 -1
  5. package/cli/template/locales/zh-CN.json +6 -1
  6. package/cli/template/locales/zh-TW.json +6 -1
  7. package/cli/template/locales/zh.json +6 -1
  8. package/cli/template/ui/app/globals.css +1 -1
  9. package/cli/template/ui/app/page.tsx +55 -16
  10. package/cli.ts +148 -104
  11. package/dist/agent/rest.d.ts +1 -1
  12. package/dist/agent/run.d.ts +2 -2
  13. package/dist/agent/types.d.ts +2 -1
  14. package/dist/ai/workflow.d.ts +1 -1
  15. package/dist/ai-sdk.d.ts +1 -1
  16. package/dist/analytics.d.ts +2 -2
  17. package/dist/cache.d.ts +4 -4
  18. package/dist/cli.js +135 -97
  19. package/dist/cookie.d.ts +24 -0
  20. package/dist/cors.d.ts +2 -2
  21. package/dist/deploy/types.d.ts +2 -2
  22. package/dist/fts.d.ts +5 -5
  23. package/dist/helmet.d.ts +2 -2
  24. package/dist/hub.d.ts +2 -1
  25. package/dist/iii/index.d.ts +1 -1
  26. package/dist/iii/register-worker.d.ts +1 -1
  27. package/dist/iii/types.d.ts +4 -4
  28. package/dist/index.d.ts +5 -5
  29. package/dist/index.js +905 -442
  30. package/dist/kb/types.d.ts +8 -0
  31. package/dist/live.d.ts +2 -3
  32. package/dist/logdb/rest.d.ts +1 -1
  33. package/dist/logdb/types.d.ts +2 -1
  34. package/dist/mailer.d.ts +3 -2
  35. package/dist/messager/agent.d.ts +2 -2
  36. package/dist/messager/rest.d.ts +3 -3
  37. package/dist/messager/types.d.ts +2 -1
  38. package/dist/messager/ws.d.ts +3 -3
  39. package/dist/opencode/index.d.ts +1 -1
  40. package/dist/opencode/permissions.d.ts +1 -1
  41. package/dist/opencode/run.d.ts +1 -1
  42. package/dist/opencode/session.d.ts +9 -9
  43. package/dist/opencode/tools/web.d.ts +1 -1
  44. package/dist/opencode/types.d.ts +2 -1
  45. package/dist/opencode/ws.d.ts +1 -2
  46. package/dist/permissions.d.ts +4 -4
  47. package/dist/postgres/module.d.ts +5 -4
  48. package/dist/postgres/schema/index.d.ts +1 -1
  49. package/dist/postgres/schema/table.d.ts +22 -20
  50. package/dist/postgres/types.d.ts +6 -6
  51. package/dist/queue/types.d.ts +3 -3
  52. package/dist/rate-limit.d.ts +1 -1
  53. package/dist/react.d.ts +1 -1
  54. package/dist/react.js +141 -96
  55. package/dist/redis/types.d.ts +2 -2
  56. package/dist/router.d.ts +10 -10
  57. package/dist/seo.d.ts +2 -2
  58. package/dist/serve.d.ts +1 -1
  59. package/dist/session.d.ts +8 -5
  60. package/dist/tailwind.d.ts +9 -0
  61. package/dist/tenant/graphql.d.ts +2 -2
  62. package/dist/tenant/index.d.ts +1 -1
  63. package/dist/tenant/rest.d.ts +2 -2
  64. package/dist/tenant/types.d.ts +3 -3
  65. package/dist/test-utils.d.ts +3 -3
  66. package/dist/types.d.ts +8 -0
  67. package/dist/upload.d.ts +4 -2
  68. package/dist/user/index.d.ts +1 -1
  69. package/dist/user/oauth-login.d.ts +2 -2
  70. package/dist/user/types.d.ts +2 -2
  71. package/dist/vendor.d.ts +4 -0
  72. package/opencode/ui/app/globals.css +1 -1
  73. package/opencode/ui/app/layout.tsx +2 -3
  74. package/opencode/ui/app/page.tsx +302 -73
  75. package/package.json +33 -10
  76. package/cli/template/.weifuwu/ssr/2e3a7e60.js +0 -112
@@ -1,9 +1,25 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars */
1
2
  import { useState, useRef, useEffect } from 'react'
2
3
 
3
- interface ToolCallEvent { toolName: string; input: unknown }
4
- interface ToolResultEvent { toolName: string; output: unknown }
5
- interface SessionItem { id: number; title: string; created_at?: string }
6
- interface MessageItem { role: string; content: string; toolCalls?: ToolCallEvent[]; toolResults?: ToolResultEvent[] }
4
+ interface ToolCallEvent {
5
+ toolName: string
6
+ input: unknown
7
+ }
8
+ interface ToolResultEvent {
9
+ toolName: string
10
+ output: unknown
11
+ }
12
+ interface SessionItem {
13
+ id: number
14
+ title: string
15
+ created_at?: string
16
+ }
17
+ interface MessageItem {
18
+ role: string
19
+ content: string
20
+ toolCalls?: ToolCallEvent[]
21
+ toolResults?: ToolResultEvent[]
22
+ }
7
23
 
8
24
  function formatDate(s: string) {
9
25
  const d = new Date(s)
@@ -27,56 +43,93 @@ export default function Page() {
27
43
  const bottomRef = useRef<HTMLDivElement>(null)
28
44
  const inputRef = useRef<HTMLInputElement>(null)
29
45
 
30
- useEffect(() => { fetch('/opencode/sessions').then(r => r.json()).then(setSessions).catch(() => {}) }, [])
31
- useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [messages, streaming])
46
+ useEffect(() => {
47
+ fetch('/opencode/sessions')
48
+ .then((r) => r.json())
49
+ .then(setSessions)
50
+ .catch(() => {})
51
+ }, [])
52
+ useEffect(() => {
53
+ bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
54
+ }, [messages, streaming])
32
55
 
33
56
  async function createSession() {
34
57
  setError('')
35
- const res = await fetch('/opencode/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) })
36
- if (!res.ok) { setError('Failed'); return }
58
+ const res = await fetch('/opencode/sessions', {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json' },
61
+ body: JSON.stringify({}),
62
+ })
63
+ if (!res.ok) {
64
+ setError('Failed')
65
+ return
66
+ }
37
67
  const s = await res.json()
38
- setSessions(p => [s, ...p])
39
- setCurrentId(s.id); setMessages([]); setStreaming(''); textRef.current = ''
68
+ setSessions((p) => [s, ...p])
69
+ setCurrentId(s.id)
70
+ setMessages([])
71
+ setStreaming('')
72
+ textRef.current = ''
40
73
  setTimeout(() => inputRef.current?.focus(), 100)
41
74
  }
42
75
 
43
76
  async function selectSession(id: number) {
44
77
  abortRef.current?.abort()
45
- setCurrentId(id); setStreaming(''); textRef.current = ''
78
+ setCurrentId(id)
79
+ setStreaming('')
80
+ textRef.current = ''
46
81
  const res = await fetch(`/opencode/sessions/${id}`)
47
- if (!res.ok) { setError('Failed to load'); return }
48
- const { messages: msgs } = await res.json() as any
49
- setMessages(msgs.map((m: any) => ({
50
- role: m.role, content: m.content || '',
51
- toolCalls: Array.isArray(m.tool_calls) ? m.tool_calls : undefined,
52
- toolResults: Array.isArray(m.tool_results) ? m.tool_results : undefined,
53
- })))
82
+ if (!res.ok) {
83
+ setError('Failed to load')
84
+ return
85
+ }
86
+ const { messages: msgs } = (await res.json()) as any
87
+ setMessages(
88
+ msgs.map((m: any) => ({
89
+ role: m.role,
90
+ content: m.content || '',
91
+ toolCalls: Array.isArray(m.tool_calls) ? m.tool_calls : undefined,
92
+ toolResults: Array.isArray(m.tool_results) ? m.tool_results : undefined,
93
+ })),
94
+ )
54
95
  setTimeout(() => inputRef.current?.focus(), 100)
55
96
  }
56
97
 
57
98
  function deleteSession(e: React.MouseEvent, id: number) {
58
99
  e.stopPropagation()
59
100
  fetch('/opencode/sessions/' + id, { method: 'DELETE' })
60
- setSessions(p => p.filter(s => s.id !== id))
61
- if (currentId === id) { setCurrentId(null); setMessages([]) }
101
+ setSessions((p) => p.filter((s) => s.id !== id))
102
+ if (currentId === id) {
103
+ setCurrentId(null)
104
+ setMessages([])
105
+ }
62
106
  }
63
107
 
64
108
  async function sendMessage() {
65
109
  const content = input.trim()
66
110
  if (!content || !currentId || loading) return
67
111
  setError('')
68
- setMessages(p => [...p, { role: 'user', content }])
69
- setInput(''); setLoading(true); setStreaming(''); textRef.current = ''
112
+ setMessages((p) => [...p, { role: 'user', content }])
113
+ setInput('')
114
+ setLoading(true)
115
+ setStreaming('')
116
+ textRef.current = ''
70
117
  abortRef.current?.abort()
71
118
  const controller = new AbortController()
72
119
  abortRef.current = controller
73
120
 
74
121
  try {
75
122
  const res = await fetch(`/opencode/sessions/${currentId}/message`, {
76
- method: 'POST', headers: { 'Content-Type': 'application/json' },
77
- body: JSON.stringify({ content }), signal: controller.signal,
123
+ method: 'POST',
124
+ headers: { 'Content-Type': 'application/json' },
125
+ body: JSON.stringify({ content }),
126
+ signal: controller.signal,
78
127
  })
79
- if (!res.ok) { setError('Request failed'); setLoading(false); return }
128
+ if (!res.ok) {
129
+ setError('Request failed')
130
+ setLoading(false)
131
+ return
132
+ }
80
133
 
81
134
  const reader = res.body!.getReader()
82
135
  const decoder = new TextDecoder()
@@ -100,28 +153,51 @@ export default function Page() {
100
153
  setStreaming(textRef.current)
101
154
  break
102
155
  case 'tool-call':
103
- setMessages(p => {
156
+ setMessages((p) => {
104
157
  const last = p[p.length - 1]
105
158
  if (last?.role === 'assistant') {
106
159
  const tcs = last.toolCalls || []
107
- return [...p.slice(0, -1), { ...last, toolCalls: [...tcs, { toolName: d.toolName, input: d.input }] }]
160
+ return [
161
+ ...p.slice(0, -1),
162
+ { ...last, toolCalls: [...tcs, { toolName: d.toolName, input: d.input }] },
163
+ ]
108
164
  }
109
- return [...p, { role: 'assistant', content: '', toolCalls: [{ toolName: d.toolName, input: d.input }] }]
165
+ return [
166
+ ...p,
167
+ {
168
+ role: 'assistant',
169
+ content: '',
170
+ toolCalls: [{ toolName: d.toolName, input: d.input }],
171
+ },
172
+ ]
110
173
  })
111
174
  break
112
175
  case 'tool-result':
113
- setMessages(p => {
176
+ setMessages((p) => {
114
177
  const last = p[p.length - 1]
115
178
  if (last?.role === 'assistant') {
116
179
  const trs = last.toolResults || []
117
- return [...p.slice(0, -1), { ...last, toolResults: [...trs, { toolName: d.toolName, output: d.output }] }]
180
+ return [
181
+ ...p.slice(0, -1),
182
+ {
183
+ ...last,
184
+ toolResults: [...trs, { toolName: d.toolName, output: d.output }],
185
+ },
186
+ ]
118
187
  }
119
- return [...p, { role: 'assistant', content: '', toolResults: [{ toolName: d.toolName, output: d.output }] }]
188
+ return [
189
+ ...p,
190
+ {
191
+ role: 'assistant',
192
+ content: '',
193
+ toolResults: [{ toolName: d.toolName, output: d.output }],
194
+ },
195
+ ]
120
196
  })
121
197
  break
122
- case 'finish':
198
+ case 'finish': {
123
199
  const finalContent = textRef.current
124
- setMessages(p => {
200
+ setMessages((p) => {
125
201
  const last = p[p.length - 1]
126
202
  if (last?.role === 'assistant' && !last.content && finalContent) {
127
203
  return [...p.slice(0, -1), { ...last, content: finalContent }]
@@ -129,9 +205,12 @@ export default function Page() {
129
205
  if (last?.role === 'assistant' && last.content) return p
130
206
  return [...p, { role: 'assistant', content: finalContent }]
131
207
  })
132
- setStreaming(''); textRef.current = ''; setLoading(false)
208
+ setStreaming('')
209
+ textRef.current = ''
210
+ setLoading(false)
133
211
  setTimeout(() => inputRef.current?.focus(), 50)
134
212
  return
213
+ }
135
214
  }
136
215
  } catch {}
137
216
  }
@@ -147,9 +226,18 @@ export default function Page() {
147
226
  {/* Sidebar */}
148
227
  <aside className="w-64 flex flex-col border-r border-zinc-800 bg-zinc-900/50 shrink-0">
149
228
  <div className="p-3 border-b border-zinc-800">
150
- <button onClick={createSession}
151
- className="w-full flex items-center justify-center gap-2 py-2 px-3 bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded-lg text-sm transition-colors cursor-pointer">
152
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4"/></svg>
229
+ <button
230
+ onClick={createSession}
231
+ className="w-full flex items-center justify-center gap-2 py-2 px-3 bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded-lg text-sm transition-colors cursor-pointer"
232
+ >
233
+ <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
234
+ <path
235
+ strokeLinecap="round"
236
+ strokeLinejoin="round"
237
+ strokeWidth={2}
238
+ d="M12 4v16m8-8H4"
239
+ />
240
+ </svg>
153
241
  New Chat
154
242
  </button>
155
243
  </div>
@@ -157,17 +245,42 @@ export default function Page() {
157
245
  {sessions.length === 0 && (
158
246
  <div className="text-xs text-zinc-600 text-center py-8">No sessions yet</div>
159
247
  )}
160
- {sessions.map(s => (
161
- <div key={s.id}
248
+ {sessions.map((s) => (
249
+ <div
250
+ key={s.id}
162
251
  onClick={() => selectSession(s.id)}
163
252
  className={`group flex items-center gap-2 p-2 rounded-lg cursor-pointer text-sm transition-colors ${
164
- currentId === s.id ? 'bg-zinc-700/60 text-zinc-100' : 'text-zinc-400 hover:bg-zinc-800/60 hover:text-zinc-200'
165
- }`}>
166
- <svg className="w-4 h-4 shrink-0 opacity-60" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/></svg>
253
+ currentId === s.id
254
+ ? 'bg-zinc-700/60 text-zinc-100'
255
+ : 'text-zinc-400 hover:bg-zinc-800/60 hover:text-zinc-200'
256
+ }`}
257
+ >
258
+ <svg
259
+ className="w-4 h-4 shrink-0 opacity-60"
260
+ fill="none"
261
+ viewBox="0 0 24 24"
262
+ stroke="currentColor"
263
+ >
264
+ <path
265
+ strokeLinecap="round"
266
+ strokeLinejoin="round"
267
+ strokeWidth={2}
268
+ d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"
269
+ />
270
+ </svg>
167
271
  <span className="truncate flex-1">{s.title || `Session ${s.id}`}</span>
168
- <button onClick={e => deleteSession(e, s.id)}
169
- className="opacity-0 group-hover:opacity-100 p-0.5 rounded hover:bg-zinc-600 transition-all cursor-pointer">
170
- <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
272
+ <button
273
+ onClick={(e) => deleteSession(e, s.id)}
274
+ className="opacity-0 group-hover:opacity-100 p-0.5 rounded hover:bg-zinc-600 transition-all cursor-pointer"
275
+ >
276
+ <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
277
+ <path
278
+ strokeLinecap="round"
279
+ strokeLinejoin="round"
280
+ strokeWidth={2}
281
+ d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
282
+ />
283
+ </svg>
171
284
  </button>
172
285
  </div>
173
286
  ))}
@@ -179,10 +292,14 @@ export default function Page() {
179
292
  {/* Header */}
180
293
  {currentId && (
181
294
  <header className="flex items-center gap-2 px-5 py-2.5 border-b border-zinc-800 bg-zinc-900/30">
182
- <svg className="w-4 h-4 text-emerald-500" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
295
+ <svg className="w-4 h-4 text-emerald-500" viewBox="0 0 24 24" fill="currentColor">
296
+ <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
297
+ </svg>
183
298
  <span className="text-xs text-zinc-500 font-mono">opencode</span>
184
299
  <span className="text-xs text-zinc-600 mx-1">/</span>
185
- <span className="text-sm text-zinc-300 truncate">{sessions.find(s => s.id === currentId)?.title || `Session ${currentId}`}</span>
300
+ <span className="text-sm text-zinc-300 truncate">
301
+ {sessions.find((s) => s.id === currentId)?.title || `Session ${currentId}`}
302
+ </span>
186
303
  </header>
187
304
  )}
188
305
 
@@ -191,38 +308,98 @@ export default function Page() {
191
308
  <div className="max-w-3xl mx-auto px-4 py-6 space-y-4">
192
309
  {!currentId && (
193
310
  <div className="flex flex-col items-center justify-center h-[70vh] text-zinc-600">
194
- <svg className="w-12 h-12 mb-4 opacity-40" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/></svg>
311
+ <svg
312
+ className="w-12 h-12 mb-4 opacity-40"
313
+ fill="none"
314
+ viewBox="0 0 24 24"
315
+ stroke="currentColor"
316
+ >
317
+ <path
318
+ strokeLinecap="round"
319
+ strokeLinejoin="round"
320
+ strokeWidth={1.5}
321
+ d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"
322
+ />
323
+ </svg>
195
324
  <p className="text-sm mb-2">Select a session or create a new one</p>
196
- <button onClick={createSession} className="mt-2 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded-lg text-sm transition-colors cursor-pointer">
325
+ <button
326
+ onClick={createSession}
327
+ className="mt-2 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded-lg text-sm transition-colors cursor-pointer"
328
+ >
197
329
  + New Chat
198
330
  </button>
199
331
  </div>
200
332
  )}
201
333
 
202
334
  {messages.map((m, i) => (
203
- <div key={i} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
204
- <div className={`max-w-[75%] rounded-2xl px-4 py-2.5 leading-relaxed whitespace-pre-wrap text-sm ${
205
- m.role === 'user'
206
- ? 'bg-indigo-600 text-white rounded-br-md'
207
- : 'bg-zinc-800/80 text-zinc-200 rounded-bl-md border border-zinc-700/50'
208
- }`}>
335
+ <div
336
+ key={i}
337
+ className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}
338
+ >
339
+ <div
340
+ className={`max-w-[75%] rounded-2xl px-4 py-2.5 leading-relaxed whitespace-pre-wrap text-sm ${
341
+ m.role === 'user'
342
+ ? 'bg-indigo-600 text-white rounded-br-md'
343
+ : 'bg-zinc-800/80 text-zinc-200 rounded-bl-md border border-zinc-700/50'
344
+ }`}
345
+ >
209
346
  {m.content || <span className="text-zinc-500 italic">No response</span>}
210
347
  {m.toolCalls?.map((tc, j) => (
211
- <details key={j} className="mt-2 rounded-lg overflow-hidden bg-black/20 border border-zinc-700/50">
348
+ <details
349
+ key={j}
350
+ className="mt-2 rounded-lg overflow-hidden bg-black/20 border border-zinc-700/50"
351
+ >
212
352
  <summary className="px-3 py-1.5 text-xs text-zinc-400 cursor-pointer hover:text-zinc-200 select-none flex items-center gap-1.5">
213
- <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
353
+ <svg
354
+ className="w-3.5 h-3.5"
355
+ fill="none"
356
+ viewBox="0 0 24 24"
357
+ stroke="currentColor"
358
+ >
359
+ <path
360
+ strokeLinecap="round"
361
+ strokeLinejoin="round"
362
+ strokeWidth={2}
363
+ d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
364
+ />
365
+ <path
366
+ strokeLinecap="round"
367
+ strokeLinejoin="round"
368
+ strokeWidth={2}
369
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
370
+ />
371
+ </svg>
214
372
  {tc.toolName}
215
373
  </summary>
216
- <pre className="px-3 py-2 text-xs text-zinc-400 overflow-x-auto">{JSON.stringify(tc.input, null, 2)}</pre>
374
+ <pre className="px-3 py-2 text-xs text-zinc-400 overflow-x-auto">
375
+ {JSON.stringify(tc.input, null, 2)}
376
+ </pre>
217
377
  </details>
218
378
  ))}
219
379
  {m.toolResults?.map((tr, j) => (
220
- <details key={j} className="mt-1.5 rounded-lg overflow-hidden bg-black/20 border border-zinc-700/50">
380
+ <details
381
+ key={j}
382
+ className="mt-1.5 rounded-lg overflow-hidden bg-black/20 border border-zinc-700/50"
383
+ >
221
384
  <summary className="px-3 py-1.5 text-xs text-zinc-400 cursor-pointer hover:text-zinc-200 select-none flex items-center gap-1.5">
222
- <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
385
+ <svg
386
+ className="w-3.5 h-3.5"
387
+ fill="none"
388
+ viewBox="0 0 24 24"
389
+ stroke="currentColor"
390
+ >
391
+ <path
392
+ strokeLinecap="round"
393
+ strokeLinejoin="round"
394
+ strokeWidth={2}
395
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
396
+ />
397
+ </svg>
223
398
  {tr.toolName} result
224
399
  </summary>
225
- <pre className="px-3 py-2 text-xs text-zinc-400 overflow-x-auto max-h-48">{JSON.stringify(tr.output, null, 2)}</pre>
400
+ <pre className="px-3 py-2 text-xs text-zinc-400 overflow-x-auto max-h-48">
401
+ {JSON.stringify(tr.output, null, 2)}
402
+ </pre>
226
403
  </details>
227
404
  ))}
228
405
  </div>
@@ -232,7 +409,8 @@ export default function Page() {
232
409
  {streaming && (
233
410
  <div className="flex justify-start">
234
411
  <div className="max-w-[75%] rounded-2xl px-4 py-2.5 bg-zinc-800/80 text-zinc-200 rounded-bl-md border border-zinc-700/50 leading-relaxed whitespace-pre-wrap text-sm">
235
- {streaming}<span className="inline-block w-1.5 h-4 bg-indigo-400 ml-0.5 animate-pulse rounded-sm" />
412
+ {streaming}
413
+ <span className="inline-block w-1.5 h-4 bg-indigo-400 ml-0.5 animate-pulse rounded-sm" />
236
414
  </div>
237
415
  </div>
238
416
  )}
@@ -241,9 +419,18 @@ export default function Page() {
241
419
  <div className="flex justify-start">
242
420
  <div className="flex items-center gap-2 px-4 py-3 bg-zinc-800/60 rounded-2xl rounded-bl-md border border-zinc-700/40">
243
421
  <div className="flex gap-1">
244
- <span className="w-2 h-2 bg-zinc-500 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
245
- <span className="w-2 h-2 bg-zinc-500 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
246
- <span className="w-2 h-2 bg-zinc-500 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
422
+ <span
423
+ className="w-2 h-2 bg-zinc-500 rounded-full animate-bounce"
424
+ style={{ animationDelay: '0ms' }}
425
+ />
426
+ <span
427
+ className="w-2 h-2 bg-zinc-500 rounded-full animate-bounce"
428
+ style={{ animationDelay: '150ms' }}
429
+ />
430
+ <span
431
+ className="w-2 h-2 bg-zinc-500 rounded-full animate-bounce"
432
+ style={{ animationDelay: '300ms' }}
433
+ />
247
434
  </div>
248
435
  </div>
249
436
  </div>
@@ -252,7 +439,19 @@ export default function Page() {
252
439
  {error && (
253
440
  <div className="flex justify-center">
254
441
  <div className="flex items-center gap-2 px-4 py-2 bg-red-900/30 text-red-400 rounded-lg text-xs border border-red-800/40">
255
- <svg className="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
442
+ <svg
443
+ className="w-4 h-4 shrink-0"
444
+ fill="none"
445
+ viewBox="0 0 24 24"
446
+ stroke="currentColor"
447
+ >
448
+ <path
449
+ strokeLinecap="round"
450
+ strokeLinejoin="round"
451
+ strokeWidth={2}
452
+ d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
453
+ />
454
+ </svg>
256
455
  {error}
257
456
  </div>
258
457
  </div>
@@ -266,20 +465,50 @@ export default function Page() {
266
465
  <div className="border-t border-zinc-800 bg-zinc-900/50">
267
466
  <div className="max-w-3xl mx-auto px-4 py-3">
268
467
  <div className="flex items-end gap-2 bg-zinc-800/80 rounded-xl border border-zinc-700/50 px-3 py-2 focus-within:border-zinc-500 transition-colors">
269
- <input ref={inputRef}
468
+ <input
469
+ ref={inputRef}
270
470
  value={input}
271
- onChange={e => setInput(e.target.value)}
272
- onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage() } }}
471
+ onChange={(e) => setInput(e.target.value)}
472
+ onKeyDown={(e) => {
473
+ if (e.key === 'Enter' && !e.shiftKey) {
474
+ e.preventDefault()
475
+ sendMessage()
476
+ }
477
+ }}
273
478
  placeholder={currentId ? 'Type a message...' : 'Create a session first'}
274
479
  disabled={!currentId || loading}
275
480
  className="flex-1 bg-transparent text-sm text-zinc-100 placeholder-zinc-500 outline-none resize-none disabled:opacity-40"
276
481
  />
277
- <button onClick={sendMessage} disabled={!currentId || loading || !input.trim()}
278
- className="flex items-center justify-center p-1.5 rounded-lg bg-indigo-600 hover:bg-indigo-500 disabled:bg-zinc-700 disabled:text-zinc-500 text-white transition-colors cursor-pointer disabled:cursor-default shrink-0">
482
+ <button
483
+ onClick={sendMessage}
484
+ disabled={!currentId || loading || !input.trim()}
485
+ className="flex items-center justify-center p-1.5 rounded-lg bg-indigo-600 hover:bg-indigo-500 disabled:bg-zinc-700 disabled:text-zinc-500 text-white transition-colors cursor-pointer disabled:cursor-default shrink-0"
486
+ >
279
487
  {loading ? (
280
- <svg className="w-4 h-4 animate-spin" viewBox="0 0 24 24" fill="none"><circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"/><path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"/></svg>
488
+ <svg className="w-4 h-4 animate-spin" viewBox="0 0 24 24" fill="none">
489
+ <circle
490
+ className="opacity-25"
491
+ cx="12"
492
+ cy="12"
493
+ r="10"
494
+ stroke="currentColor"
495
+ strokeWidth="4"
496
+ />
497
+ <path
498
+ className="opacity-75"
499
+ fill="currentColor"
500
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
501
+ />
502
+ </svg>
281
503
  ) : (
282
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19V5m0 0l-7 7m7-7l7 7"/></svg>
504
+ <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
505
+ <path
506
+ strokeLinecap="round"
507
+ strokeLinejoin="round"
508
+ strokeWidth={2}
509
+ d="M12 19V5m0 0l-7 7m7-7l7 7"
510
+ />
511
+ </svg>
283
512
  )}
284
513
  </button>
285
514
  </div>
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "weifuwu",
3
- "version": "0.24.0",
3
+ "type": "module",
4
+ "version": "0.24.2",
4
5
  "description": "Web-standard HTTP framework for Node.js — (req, ctx) => Response",
5
6
  "main": "./dist/index.js",
6
7
  "types": "./dist/index.d.ts",
@@ -24,9 +25,15 @@
24
25
  "start": "cd cli/template && node index.ts",
25
26
  "build": "esbuild index.ts --bundle --format=esm --platform=node --outfile=dist/index.js --packages=external --define:__WFW_BUNDLED__=true && esbuild cli.ts --bundle --format=esm --platform=node --outfile=dist/cli.js --packages=external && esbuild react.ts --bundle --format=esm --outfile=dist/react.js --external:react --external:react-dom",
26
27
  "prepublishOnly": "npm run build && tsc --emitDeclarationOnly --outdir dist",
28
+ "typecheck": "tsc --noEmit",
29
+ "format": "prettier --write .",
30
+ "format:check": "prettier --check .",
31
+ "lint": "eslint --ext .ts,.tsx .",
27
32
  "test": "node --test 'test/**/*.test.ts'",
33
+ "test:coverage": "node --experimental-test-coverage --test 'test/**/*.test.ts'",
28
34
  "test:unit": "bash scripts/test-unit.sh",
29
- "test:ci": "node --test 'test/**/*.test.ts'"
35
+ "test:ci": "node --test 'test/**/*.test.ts'",
36
+ "prepare": "husky"
30
37
  },
31
38
  "dependencies": {
32
39
  "@ai-sdk/openai": "^3.0.66",
@@ -48,15 +55,31 @@
48
55
  "yaml": "^2.9.0",
49
56
  "zod": "^4.4.3"
50
57
  },
51
- "type": "module",
52
- "license": "MIT",
58
+ "lint-staged": {
59
+ "*.{ts,tsx}": [
60
+ "prettier --write",
61
+ "eslint --fix"
62
+ ],
63
+ "*.{json,css,md}": [
64
+ "prettier --write"
65
+ ]
66
+ },
53
67
  "devDependencies": {
54
- "@types/jsonwebtoken": "^9.0.10",
55
- "@types/node": "^24.0.0",
56
- "@types/nodemailer": "^8.0.0",
57
- "@types/react": "^19",
58
- "@types/react-dom": "^19",
68
+ "@eslint/js": "^10.0.1",
69
+ "@types/jsonwebtoken": "^9.0.9",
70
+ "@types/node": "^25.9.3",
71
+ "@types/nodemailer": "^6.4.17",
72
+ "@types/react": "^19.2.17",
73
+ "@types/react-dom": "^19.2.3",
59
74
  "@types/ws": "^8.18.1",
60
- "typescript": "^6.0.3"
75
+ "eslint": "^10.5.0",
76
+ "globals": "^17.6.0",
77
+ "happy-dom": "^20.10.3",
78
+ "husky": "^9.1.7",
79
+ "lint-staged": "^17.0.7",
80
+ "postcss": "^8.5.3",
81
+ "prettier": "^3.8.4",
82
+ "tailwindcss": "^4.0.0",
83
+ "typescript-eslint": "^8.61.0"
61
84
  }
62
85
  }