@hirohsu/user-web-feedback 2.8.8 → 2.8.10

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 (34) hide show
  1. package/README.md +953 -953
  2. package/dist/cli.cjs +35102 -22236
  3. package/dist/index.cjs +39858 -26990
  4. package/dist/static/app.js +385 -385
  5. package/dist/static/components/navbar.css +406 -406
  6. package/dist/static/components/navbar.html +49 -49
  7. package/dist/static/components/navbar.js +211 -211
  8. package/dist/static/dashboard.css +495 -495
  9. package/dist/static/dashboard.html +95 -95
  10. package/dist/static/dashboard.js +540 -540
  11. package/dist/static/favicon.svg +27 -27
  12. package/dist/static/index.html +541 -541
  13. package/dist/static/logs.html +376 -376
  14. package/dist/static/logs.js +442 -442
  15. package/dist/static/mcp-settings.html +797 -797
  16. package/dist/static/mcp-settings.js +884 -884
  17. package/dist/static/modules/app-core.js +124 -124
  18. package/dist/static/modules/conversation-panel.js +247 -247
  19. package/dist/static/modules/feedback-handler.js +1420 -1420
  20. package/dist/static/modules/image-handler.js +155 -155
  21. package/dist/static/modules/log-viewer.js +296 -296
  22. package/dist/static/modules/mcp-manager.js +474 -474
  23. package/dist/static/modules/prompt-manager.js +364 -364
  24. package/dist/static/modules/settings-manager.js +299 -299
  25. package/dist/static/modules/socket-manager.js +170 -170
  26. package/dist/static/modules/state-manager.js +352 -352
  27. package/dist/static/modules/timer-controller.js +243 -243
  28. package/dist/static/modules/ui-helpers.js +246 -246
  29. package/dist/static/settings.html +426 -426
  30. package/dist/static/settings.js +767 -767
  31. package/dist/static/style.css +2156 -2156
  32. package/dist/static/terminals.html +357 -357
  33. package/dist/static/terminals.js +321 -321
  34. package/package.json +95 -95
@@ -1,474 +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
- };
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
+ };