@hirohsu/user-web-feedback 2.6.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.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +953 -0
  3. package/dist/cli.cjs +95778 -0
  4. package/dist/index.cjs +92818 -0
  5. package/dist/static/app.js +385 -0
  6. package/dist/static/components/navbar.css +406 -0
  7. package/dist/static/components/navbar.html +49 -0
  8. package/dist/static/components/navbar.js +211 -0
  9. package/dist/static/dashboard.css +495 -0
  10. package/dist/static/dashboard.html +95 -0
  11. package/dist/static/dashboard.js +540 -0
  12. package/dist/static/favicon.svg +27 -0
  13. package/dist/static/index.html +541 -0
  14. package/dist/static/logs.html +376 -0
  15. package/dist/static/logs.js +442 -0
  16. package/dist/static/mcp-settings.html +797 -0
  17. package/dist/static/mcp-settings.js +884 -0
  18. package/dist/static/modules/app-core.js +124 -0
  19. package/dist/static/modules/conversation-panel.js +247 -0
  20. package/dist/static/modules/feedback-handler.js +1420 -0
  21. package/dist/static/modules/image-handler.js +155 -0
  22. package/dist/static/modules/log-viewer.js +296 -0
  23. package/dist/static/modules/mcp-manager.js +474 -0
  24. package/dist/static/modules/prompt-manager.js +364 -0
  25. package/dist/static/modules/settings-manager.js +299 -0
  26. package/dist/static/modules/socket-manager.js +170 -0
  27. package/dist/static/modules/state-manager.js +352 -0
  28. package/dist/static/modules/timer-controller.js +243 -0
  29. package/dist/static/modules/ui-helpers.js +246 -0
  30. package/dist/static/settings.html +355 -0
  31. package/dist/static/settings.js +425 -0
  32. package/dist/static/socket.io.min.js +7 -0
  33. package/dist/static/style.css +2157 -0
  34. package/dist/static/terminals.html +357 -0
  35. package/dist/static/terminals.js +321 -0
  36. package/package.json +91 -0
@@ -0,0 +1,474 @@
1
+ /**
2
+ * mcp-manager.js
3
+ * MCP Servers 管理模組
4
+ */
5
+
6
+ import {
7
+ getMcpServers,
8
+ setMcpServers,
9
+ getEditingMcpServerId,
10
+ setEditingMcpServerId,
11
+ findMcpServerById,
12
+ } from "./state-manager.js";
13
+
14
+ import {
15
+ showToast,
16
+ showLoadingOverlay,
17
+ hideLoadingOverlay,
18
+ escapeHtml,
19
+ } from "./ui-helpers.js";
20
+
21
+ /**
22
+ * 載入 MCP Servers
23
+ */
24
+ export async function loadMCPServers() {
25
+ try {
26
+ const response = await fetch("/api/mcp-servers");
27
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
28
+ const data = await response.json();
29
+ if (data.success) {
30
+ setMcpServers(data.servers || []);
31
+ renderMCPServerList();
32
+ }
33
+ } catch (error) {
34
+ console.error("載入 MCP Servers 失敗:", error);
35
+ showToast("error", "錯誤", "載入 MCP Servers 失敗");
36
+ }
37
+ }
38
+
39
+ /**
40
+ * 渲染 MCP Server 列表
41
+ */
42
+ export function renderMCPServerList() {
43
+ const container = document.getElementById("mcpServerList");
44
+ const mcpServers = getMcpServers();
45
+
46
+ if (!mcpServers || mcpServers.length === 0) {
47
+ container.innerHTML = `
48
+ <div class="placeholder">
49
+ <span class="icon">🔌</span>
50
+ <p>尚無 MCP Server</p>
51
+ </div>
52
+ `;
53
+ return;
54
+ }
55
+
56
+ container.innerHTML = mcpServers
57
+ .map((server) => {
58
+ const state = server.state || { status: "disconnected", tools: [] };
59
+ const toolsCount = state.tools?.length || 0;
60
+ const statusText = getStatusText(state.status);
61
+
62
+ return `
63
+ <div class="mcp-server-item" data-id="${server.id}">
64
+ <div class="mcp-server-status ${
65
+ state.status
66
+ }" title="${statusText}"></div>
67
+ <div class="mcp-server-info">
68
+ <div class="mcp-server-name">${escapeHtml(server.name)}</div>
69
+ <div class="mcp-server-details">
70
+ <span class="mcp-server-transport">${server.transport}</span>
71
+ ${
72
+ state.status === "connected"
73
+ ? `<span class="mcp-server-tools-count">${toolsCount} 工具</span>`
74
+ : ""
75
+ }
76
+ ${
77
+ !server.enabled
78
+ ? '<span style="color: var(--text-muted)">已停用</span>'
79
+ : ""
80
+ }
81
+ </div>
82
+ ${
83
+ state.error
84
+ ? `<div class="mcp-server-error">錯誤: ${escapeHtml(
85
+ state.error
86
+ )}</div>`
87
+ : ""
88
+ }
89
+ ${
90
+ state.status === "connected" && toolsCount > 0
91
+ ? renderToolsList(state.tools)
92
+ : ""
93
+ }
94
+ </div>
95
+ <div class="mcp-server-actions">
96
+ ${
97
+ state.status === "connected"
98
+ ? `<button class="btn btn-ghost btn-disconnect" onclick="disconnectMCPServer(${server.id})" title="斷開">🔌</button>`
99
+ : `<button class="btn btn-ghost btn-connect" onclick="connectMCPServer(${
100
+ server.id
101
+ })" title="連接" ${
102
+ !server.enabled ? "disabled" : ""
103
+ }>🔗</button>`
104
+ }
105
+ <button class="btn btn-ghost btn-edit" onclick="editMCPServer(${
106
+ server.id
107
+ })" title="編輯">✏️</button>
108
+ <button class="btn btn-ghost btn-delete" onclick="deleteMCPServerConfirm(${
109
+ server.id
110
+ })" title="刪除">🗑️</button>
111
+ </div>
112
+ </div>
113
+ `;
114
+ })
115
+ .join("");
116
+ }
117
+
118
+ /**
119
+ * 渲染工具列表
120
+ */
121
+ function renderToolsList(tools) {
122
+ if (!tools || tools.length === 0) return "";
123
+
124
+ const displayTools = tools.slice(0, 5);
125
+ const remaining = tools.length - 5;
126
+
127
+ return `
128
+ <div class="mcp-tools-list">
129
+ ${displayTools
130
+ .map(
131
+ (tool) => `
132
+ <div class="mcp-tool-item">
133
+ <span class="mcp-tool-name">${escapeHtml(tool.name)}</span>
134
+ <span class="mcp-tool-desc">${escapeHtml(
135
+ tool.description || ""
136
+ )}</span>
137
+ </div>
138
+ `
139
+ )
140
+ .join("")}
141
+ ${
142
+ remaining > 0
143
+ ? `<div class="mcp-tool-item" style="color: var(--text-muted)">...還有 ${remaining} 個工具</div>`
144
+ : ""
145
+ }
146
+ </div>
147
+ `;
148
+ }
149
+
150
+ /**
151
+ * 取得狀態文字
152
+ */
153
+ function getStatusText(status) {
154
+ const texts = {
155
+ disconnected: "未連接",
156
+ connecting: "連接中...",
157
+ connected: "已連接",
158
+ error: "連接錯誤",
159
+ };
160
+ return texts[status] || status;
161
+ }
162
+
163
+ /**
164
+ * 開啟 MCP Servers 彈窗
165
+ */
166
+ export function openMCPServersModal() {
167
+ document.getElementById("mcpServersModal").classList.add("show");
168
+ loadMCPServers();
169
+ }
170
+
171
+ /**
172
+ * 關閉 MCP Servers 彈窗
173
+ */
174
+ export function closeMCPServersModal() {
175
+ document.getElementById("mcpServersModal").classList.remove("show");
176
+ }
177
+
178
+ /**
179
+ * 開啟 MCP Server 編輯彈窗
180
+ */
181
+ export function openMCPServerEditModal(server = null) {
182
+ setEditingMcpServerId(server?.id || null);
183
+
184
+ document.getElementById("mcpServerEditTitle").textContent = server
185
+ ? "編輯 MCP Server"
186
+ : "新增 MCP Server";
187
+ document.getElementById("mcpServerId").value = server?.id || "";
188
+ document.getElementById("mcpServerName").value = server?.name || "";
189
+ document.getElementById("mcpServerTransport").value =
190
+ server?.transport || "stdio";
191
+ document.getElementById("mcpServerCommand").value = server?.command || "";
192
+ document.getElementById("mcpServerArgs").value = (server?.args || []).join(
193
+ "\n"
194
+ );
195
+ document.getElementById("mcpServerEnv").value = server?.env
196
+ ? Object.entries(server.env)
197
+ .map(([k, v]) => `${k}=${v}`)
198
+ .join("\n")
199
+ : "";
200
+ document.getElementById("mcpServerUrl").value = server?.url || "";
201
+ document.getElementById("mcpServerEnabled").checked =
202
+ server?.enabled !== false;
203
+
204
+ onTransportChange();
205
+ document.getElementById("mcpServerEditModal").classList.add("show");
206
+ }
207
+
208
+ /**
209
+ * 關閉 MCP Server 編輯彈窗
210
+ */
211
+ export function closeMCPServerEditModal() {
212
+ document.getElementById("mcpServerEditModal").classList.remove("show");
213
+ setEditingMcpServerId(null);
214
+ }
215
+
216
+ /**
217
+ * 傳輸方式變更處理
218
+ */
219
+ export function onTransportChange() {
220
+ const transport = document.getElementById("mcpServerTransport").value;
221
+ const stdioSettings = document.getElementById("stdioSettings");
222
+ const httpSettings = document.getElementById("httpSettings");
223
+
224
+ if (transport === "stdio") {
225
+ stdioSettings.style.display = "block";
226
+ httpSettings.style.display = "none";
227
+ } else {
228
+ stdioSettings.style.display = "none";
229
+ httpSettings.style.display = "block";
230
+ }
231
+ }
232
+
233
+ /**
234
+ * 儲存 MCP Server
235
+ */
236
+ export async function saveMCPServer() {
237
+ const id = document.getElementById("mcpServerId").value;
238
+ const name = document.getElementById("mcpServerName").value.trim();
239
+ const transport = document.getElementById("mcpServerTransport").value;
240
+ const command = document.getElementById("mcpServerCommand").value.trim();
241
+ const argsText = document.getElementById("mcpServerArgs").value.trim();
242
+ const envText = document.getElementById("mcpServerEnv").value.trim();
243
+ const url = document.getElementById("mcpServerUrl").value.trim();
244
+ const enabled = document.getElementById("mcpServerEnabled").checked;
245
+
246
+ if (!name) {
247
+ showToast("error", "錯誤", "請輸入名稱");
248
+ return;
249
+ }
250
+
251
+ if (transport === "stdio" && !command) {
252
+ showToast("error", "錯誤", "stdio 傳輸方式需要指定命令");
253
+ return;
254
+ }
255
+
256
+ if ((transport === "sse" || transport === "streamable-http") && !url) {
257
+ showToast("error", "錯誤", `${transport} 傳輸方式需要指定 URL`);
258
+ return;
259
+ }
260
+
261
+ const args = argsText
262
+ ? argsText.split("\n").filter((a) => a.trim())
263
+ : undefined;
264
+ const env = envText
265
+ ? Object.fromEntries(
266
+ envText
267
+ .split("\n")
268
+ .filter((line) => line.includes("="))
269
+ .map((line) => {
270
+ const idx = line.indexOf("=");
271
+ return [
272
+ line.substring(0, idx).trim(),
273
+ line.substring(idx + 1).trim(),
274
+ ];
275
+ })
276
+ )
277
+ : undefined;
278
+
279
+ const data = {
280
+ name,
281
+ transport,
282
+ command: transport === "stdio" ? command : undefined,
283
+ args: transport === "stdio" ? args : undefined,
284
+ env: transport === "stdio" ? env : undefined,
285
+ url: transport !== "stdio" ? url : undefined,
286
+ enabled,
287
+ };
288
+
289
+ try {
290
+ showLoadingOverlay("儲存中...");
291
+
292
+ const response = await fetch(
293
+ id ? `/api/mcp-servers/${id}` : "/api/mcp-servers",
294
+ {
295
+ method: id ? "PUT" : "POST",
296
+ headers: { "Content-Type": "application/json" },
297
+ body: JSON.stringify(data),
298
+ }
299
+ );
300
+
301
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
302
+
303
+ const result = await response.json();
304
+ if (result.success) {
305
+ showToast(
306
+ "success",
307
+ "成功",
308
+ id ? "MCP Server 已更新" : "MCP Server 已建立"
309
+ );
310
+ closeMCPServerEditModal();
311
+ await loadMCPServers();
312
+ } else {
313
+ throw new Error(result.error || "儲存失敗");
314
+ }
315
+ } catch (error) {
316
+ console.error("儲存 MCP Server 失敗:", error);
317
+ showToast("error", "錯誤", error.message);
318
+ } finally {
319
+ hideLoadingOverlay();
320
+ }
321
+ }
322
+
323
+ /**
324
+ * 連接 MCP Server
325
+ */
326
+ export async function connectMCPServer(id) {
327
+ try {
328
+ showLoadingOverlay("連接中...");
329
+ const response = await fetch(`/api/mcp-servers/${id}/connect`, {
330
+ method: "POST",
331
+ });
332
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
333
+
334
+ const result = await response.json();
335
+ if (result.success) {
336
+ showToast("success", "成功", "MCP Server 已連接");
337
+ } else {
338
+ showToast("warning", "連接失敗", result.state?.error || "未知錯誤");
339
+ }
340
+ await loadMCPServers();
341
+ } catch (error) {
342
+ console.error("連接 MCP Server 失敗:", error);
343
+ showToast("error", "錯誤", error.message);
344
+ } finally {
345
+ hideLoadingOverlay();
346
+ }
347
+ }
348
+
349
+ /**
350
+ * 斷開 MCP Server
351
+ */
352
+ export async function disconnectMCPServer(id) {
353
+ try {
354
+ showLoadingOverlay("斷開中...");
355
+ const response = await fetch(`/api/mcp-servers/${id}/disconnect`, {
356
+ method: "POST",
357
+ });
358
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
359
+
360
+ showToast("success", "成功", "MCP Server 已斷開");
361
+ await loadMCPServers();
362
+ } catch (error) {
363
+ console.error("斷開 MCP Server 失敗:", error);
364
+ showToast("error", "錯誤", error.message);
365
+ } finally {
366
+ hideLoadingOverlay();
367
+ }
368
+ }
369
+
370
+ /**
371
+ * 編輯 MCP Server
372
+ */
373
+ export function editMCPServer(id) {
374
+ const server = findMcpServerById(id);
375
+ if (server) {
376
+ openMCPServerEditModal(server);
377
+ }
378
+ }
379
+
380
+ /**
381
+ * 刪除 MCP Server 確認
382
+ */
383
+ export async function deleteMCPServerConfirm(id) {
384
+ const server = findMcpServerById(id);
385
+ if (!server) return;
386
+
387
+ if (!confirm(`確定要刪除 MCP Server "${server.name}" 嗎?此操作無法復原。`)) {
388
+ return;
389
+ }
390
+
391
+ try {
392
+ showLoadingOverlay("刪除中...");
393
+ const response = await fetch(`/api/mcp-servers/${id}`, {
394
+ method: "DELETE",
395
+ });
396
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
397
+
398
+ showToast("success", "成功", "MCP Server 已刪除");
399
+ await loadMCPServers();
400
+ } catch (error) {
401
+ console.error("刪除 MCP Server 失敗:", error);
402
+ showToast("error", "錯誤", error.message);
403
+ } finally {
404
+ hideLoadingOverlay();
405
+ }
406
+ }
407
+
408
+ /**
409
+ * 連接所有 MCP Servers
410
+ */
411
+ export async function connectAllMCPServers() {
412
+ try {
413
+ showLoadingOverlay("連接所有 MCP Servers...");
414
+ const response = await fetch("/api/mcp-servers/connect-all", {
415
+ method: "POST",
416
+ });
417
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
418
+
419
+ const result = await response.json();
420
+ if (result.success) {
421
+ const succeeded = result.results.filter((r) => r.success).length;
422
+ const total = result.results.length;
423
+ showToast(
424
+ "success",
425
+ "完成",
426
+ `已連接 ${succeeded}/${total} 個 MCP Servers`
427
+ );
428
+ }
429
+ await loadMCPServers();
430
+ } catch (error) {
431
+ console.error("連接所有 MCP Servers 失敗:", error);
432
+ showToast("error", "錯誤", error.message);
433
+ } finally {
434
+ hideLoadingOverlay();
435
+ }
436
+ }
437
+
438
+ /**
439
+ * 斷開所有 MCP Servers
440
+ */
441
+ export async function disconnectAllMCPServers() {
442
+ try {
443
+ showLoadingOverlay("斷開所有 MCP Servers...");
444
+ const response = await fetch("/api/mcp-servers/disconnect-all", {
445
+ method: "POST",
446
+ });
447
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
448
+
449
+ showToast("success", "成功", "已斷開所有 MCP Servers");
450
+ await loadMCPServers();
451
+ } catch (error) {
452
+ console.error("斷開所有 MCP Servers 失敗:", error);
453
+ showToast("error", "錯誤", error.message);
454
+ } finally {
455
+ hideLoadingOverlay();
456
+ }
457
+ }
458
+
459
+ export default {
460
+ loadMCPServers,
461
+ renderMCPServerList,
462
+ openMCPServersModal,
463
+ closeMCPServersModal,
464
+ openMCPServerEditModal,
465
+ closeMCPServerEditModal,
466
+ onTransportChange,
467
+ saveMCPServer,
468
+ connectMCPServer,
469
+ disconnectMCPServer,
470
+ editMCPServer,
471
+ deleteMCPServerConfirm,
472
+ connectAllMCPServers,
473
+ disconnectAllMCPServers,
474
+ };