@hirohsu/user-web-feedback 2.8.12 → 2.8.13

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 +33267 -46031
  3. package/dist/index.cjs +35731 -48497
  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,296 +1,296 @@
1
- /**
2
- * log-viewer.js
3
- * 日誌檢視器模組
4
- */
5
-
6
- import {
7
- showToast,
8
- showLoadingOverlay,
9
- hideLoadingOverlay,
10
- escapeHtml,
11
- } from "./ui-helpers.js";
12
-
13
- // 模組內部狀態
14
- let currentLogPage = 1;
15
- let totalLogPages = 1;
16
- let logSources = [];
17
-
18
- /**
19
- * 開啟日誌檢視器彈窗
20
- */
21
- export async function openLogViewerModal() {
22
- const modal = document.getElementById("logViewerModal");
23
- if (modal) {
24
- modal.classList.add("show");
25
-
26
- // 載入日誌來源列表
27
- await loadLogSources();
28
-
29
- // 載入第一頁日誌
30
- await loadLogs(1);
31
- }
32
- }
33
-
34
- /**
35
- * 關閉日誌檢視器彈窗
36
- */
37
- export function closeLogViewerModal() {
38
- const modal = document.getElementById("logViewerModal");
39
- if (modal) {
40
- modal.classList.remove("show");
41
- }
42
- }
43
-
44
- /**
45
- * 載入日誌來源
46
- */
47
- async function loadLogSources() {
48
- try {
49
- const response = await fetch("/api/logs/sources");
50
- if (response.ok) {
51
- const data = await response.json();
52
- logSources = data.sources || [];
53
-
54
- // 更新來源下拉選單
55
- const sourceFilter = document.getElementById("logSourceFilter");
56
- if (sourceFilter) {
57
- // 保留第一個選項
58
- sourceFilter.innerHTML = '<option value="">全部來源</option>';
59
- logSources.forEach((source) => {
60
- const option = document.createElement("option");
61
- option.value = source;
62
- option.textContent = source;
63
- sourceFilter.appendChild(option);
64
- });
65
- }
66
- }
67
- } catch (error) {
68
- console.error("載入日誌來源失敗:", error);
69
- }
70
- }
71
-
72
- /**
73
- * 載入日誌
74
- * @param {number} page 頁碼
75
- */
76
- export async function loadLogs(page = 1) {
77
- const container = document.getElementById("logEntriesContainer");
78
- if (!container) return;
79
-
80
- // 顯示載入中
81
- container.innerHTML =
82
- '<div class="log-loading"><div class="spinner"></div>載入中...</div>';
83
-
84
- try {
85
- // 收集篩選參數
86
- const params = new URLSearchParams();
87
- params.set("page", page.toString());
88
- params.set("limit", "50");
89
-
90
- const level = document.getElementById("logLevelFilter").value;
91
- if (level) params.set("level", level);
92
-
93
- const source = document.getElementById("logSourceFilter").value;
94
- if (source) params.set("source", source);
95
-
96
- const search = document.getElementById("logSearch").value.trim();
97
- if (search) params.set("search", search);
98
-
99
- const startDate = document.getElementById("logStartDate").value;
100
- if (startDate) params.set("startDate", startDate);
101
-
102
- const endDate = document.getElementById("logEndDate").value;
103
- if (endDate) params.set("endDate", endDate);
104
-
105
- const response = await fetch(`/api/logs?${params.toString()}`);
106
- if (!response.ok) {
107
- throw new Error(`HTTP ${response.status}`);
108
- }
109
-
110
- const data = await response.json();
111
- const logs = data.logs || [];
112
- currentLogPage = data.pagination?.page || 1;
113
- totalLogPages = data.pagination?.totalPages || 1;
114
-
115
- // 渲染日誌條目
116
- if (logs.length === 0) {
117
- container.innerHTML = `
118
- <div class="placeholder">
119
- <span class="icon">📭</span>
120
- <p>沒有符合條件的日誌記錄</p>
121
- </div>
122
- `;
123
- } else {
124
- container.innerHTML = logs.map((log) => renderLogEntry(log)).join("");
125
- }
126
-
127
- // 更新分頁控制
128
- updateLogPagination();
129
- } catch (error) {
130
- console.error("載入日誌失敗:", error);
131
- container.innerHTML = `
132
- <div class="placeholder">
133
- <span class="icon">❌</span>
134
- <p>載入日誌失敗: ${error.message}</p>
135
- </div>
136
- `;
137
- }
138
- }
139
-
140
- /**
141
- * 渲染單條日誌
142
- */
143
- function renderLogEntry(log) {
144
- const timestamp = new Date(log.timestamp).toLocaleString("zh-TW", {
145
- year: "numeric",
146
- month: "2-digit",
147
- day: "2-digit",
148
- hour: "2-digit",
149
- minute: "2-digit",
150
- second: "2-digit",
151
- hour12: false,
152
- });
153
-
154
- const levelClass = `log-level-${log.level}`;
155
- const searchTerm = document.getElementById("logSearch").value.trim();
156
-
157
- // 高亮搜尋詞
158
- let message = escapeHtml(log.message);
159
- if (searchTerm) {
160
- const regex = new RegExp(`(${escapeRegex(searchTerm)})`, "gi");
161
- message = message.replace(regex, "<mark>$1</mark>");
162
- }
163
-
164
- // 格式化 meta 資訊
165
- let metaHtml = "";
166
- if (log.meta) {
167
- try {
168
- const metaObj =
169
- typeof log.meta === "string" ? JSON.parse(log.meta) : log.meta;
170
- if (Object.keys(metaObj).length > 0) {
171
- metaHtml = `<div class="log-meta"><pre>${escapeHtml(
172
- JSON.stringify(metaObj, null, 2)
173
- )}</pre></div>`;
174
- }
175
- } catch (e) {
176
- // 如果無法解析,顯示原始字串
177
- if (log.meta) {
178
- metaHtml = `<div class="log-meta">${escapeHtml(
179
- String(log.meta)
180
- )}</div>`;
181
- }
182
- }
183
- }
184
-
185
- return `
186
- <div class="log-entry">
187
- <div class="log-entry-header">
188
- <span class="log-timestamp">${timestamp}</span>
189
- <span class="log-level ${levelClass}">${log.level}</span>
190
- <span class="log-source">[${escapeHtml(log.source)}]</span>
191
- </div>
192
- <div class="log-message">${message}</div>
193
- ${metaHtml}
194
- </div>
195
- `;
196
- }
197
-
198
- /**
199
- * 更新分頁控制
200
- */
201
- function updateLogPagination() {
202
- const pageInfo = document.getElementById("logPageInfo");
203
- const prevBtn = document.getElementById("logPrevPage");
204
- const nextBtn = document.getElementById("logNextPage");
205
-
206
- if (pageInfo) {
207
- pageInfo.textContent = `${currentLogPage} / ${totalLogPages}`;
208
- }
209
-
210
- if (prevBtn) {
211
- prevBtn.disabled = currentLogPage <= 1;
212
- }
213
-
214
- if (nextBtn) {
215
- nextBtn.disabled = currentLogPage >= totalLogPages;
216
- }
217
- }
218
-
219
- /**
220
- * 搜尋日誌
221
- */
222
- export function searchLogs() {
223
- loadLogs(1);
224
- }
225
-
226
- /**
227
- * 清除舊日誌
228
- */
229
- export async function clearOldLogs() {
230
- // 預設清除 7 天前的日誌
231
- const daysToKeep = 7;
232
- const cutoffDate = new Date();
233
- cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
234
-
235
- if (!confirm(`確定要清除 ${daysToKeep} 天前的所有日誌嗎?此操作無法復原。`)) {
236
- return;
237
- }
238
-
239
- try {
240
- showLoadingOverlay("清除舊日誌中...");
241
-
242
- const response = await fetch(
243
- `/api/logs?endDate=${cutoffDate.toISOString().split("T")[0]}`,
244
- {
245
- method: "DELETE",
246
- }
247
- );
248
-
249
- if (!response.ok) {
250
- throw new Error(`HTTP ${response.status}`);
251
- }
252
-
253
- const data = await response.json();
254
- showToast(
255
- "success",
256
- "清除成功",
257
- `已刪除 ${data.deletedCount || 0} 條舊日誌`
258
- );
259
-
260
- // 重新載入日誌
261
- await loadLogs(1);
262
- } catch (error) {
263
- console.error("清除舊日誌失敗:", error);
264
- showToast("error", "清除失敗", error.message);
265
- } finally {
266
- hideLoadingOverlay();
267
- }
268
- }
269
-
270
- /**
271
- * 正則表達式轉義
272
- */
273
- function escapeRegex(str) {
274
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
275
- }
276
-
277
- /**
278
- * 處理分頁點擊
279
- * @param {string} direction 'prev' or 'next'
280
- */
281
- export function handlePagination(direction) {
282
- if (direction === "prev" && currentLogPage > 1) {
283
- loadLogs(currentLogPage - 1);
284
- } else if (direction === "next" && currentLogPage < totalLogPages) {
285
- loadLogs(currentLogPage + 1);
286
- }
287
- }
288
-
289
- export default {
290
- openLogViewerModal,
291
- closeLogViewerModal,
292
- loadLogs,
293
- searchLogs,
294
- clearOldLogs,
295
- handlePagination,
296
- };
1
+ /**
2
+ * log-viewer.js
3
+ * 日誌檢視器模組
4
+ */
5
+
6
+ import {
7
+ showToast,
8
+ showLoadingOverlay,
9
+ hideLoadingOverlay,
10
+ escapeHtml,
11
+ } from "./ui-helpers.js";
12
+
13
+ // 模組內部狀態
14
+ let currentLogPage = 1;
15
+ let totalLogPages = 1;
16
+ let logSources = [];
17
+
18
+ /**
19
+ * 開啟日誌檢視器彈窗
20
+ */
21
+ export async function openLogViewerModal() {
22
+ const modal = document.getElementById("logViewerModal");
23
+ if (modal) {
24
+ modal.classList.add("show");
25
+
26
+ // 載入日誌來源列表
27
+ await loadLogSources();
28
+
29
+ // 載入第一頁日誌
30
+ await loadLogs(1);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * 關閉日誌檢視器彈窗
36
+ */
37
+ export function closeLogViewerModal() {
38
+ const modal = document.getElementById("logViewerModal");
39
+ if (modal) {
40
+ modal.classList.remove("show");
41
+ }
42
+ }
43
+
44
+ /**
45
+ * 載入日誌來源
46
+ */
47
+ async function loadLogSources() {
48
+ try {
49
+ const response = await fetch("/api/logs/sources");
50
+ if (response.ok) {
51
+ const data = await response.json();
52
+ logSources = data.sources || [];
53
+
54
+ // 更新來源下拉選單
55
+ const sourceFilter = document.getElementById("logSourceFilter");
56
+ if (sourceFilter) {
57
+ // 保留第一個選項
58
+ sourceFilter.innerHTML = '<option value="">全部來源</option>';
59
+ logSources.forEach((source) => {
60
+ const option = document.createElement("option");
61
+ option.value = source;
62
+ option.textContent = source;
63
+ sourceFilter.appendChild(option);
64
+ });
65
+ }
66
+ }
67
+ } catch (error) {
68
+ console.error("載入日誌來源失敗:", error);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * 載入日誌
74
+ * @param {number} page 頁碼
75
+ */
76
+ export async function loadLogs(page = 1) {
77
+ const container = document.getElementById("logEntriesContainer");
78
+ if (!container) return;
79
+
80
+ // 顯示載入中
81
+ container.innerHTML =
82
+ '<div class="log-loading"><div class="spinner"></div>載入中...</div>';
83
+
84
+ try {
85
+ // 收集篩選參數
86
+ const params = new URLSearchParams();
87
+ params.set("page", page.toString());
88
+ params.set("limit", "50");
89
+
90
+ const level = document.getElementById("logLevelFilter").value;
91
+ if (level) params.set("level", level);
92
+
93
+ const source = document.getElementById("logSourceFilter").value;
94
+ if (source) params.set("source", source);
95
+
96
+ const search = document.getElementById("logSearch").value.trim();
97
+ if (search) params.set("search", search);
98
+
99
+ const startDate = document.getElementById("logStartDate").value;
100
+ if (startDate) params.set("startDate", startDate);
101
+
102
+ const endDate = document.getElementById("logEndDate").value;
103
+ if (endDate) params.set("endDate", endDate);
104
+
105
+ const response = await fetch(`/api/logs?${params.toString()}`);
106
+ if (!response.ok) {
107
+ throw new Error(`HTTP ${response.status}`);
108
+ }
109
+
110
+ const data = await response.json();
111
+ const logs = data.logs || [];
112
+ currentLogPage = data.pagination?.page || 1;
113
+ totalLogPages = data.pagination?.totalPages || 1;
114
+
115
+ // 渲染日誌條目
116
+ if (logs.length === 0) {
117
+ container.innerHTML = `
118
+ <div class="placeholder">
119
+ <span class="icon">📭</span>
120
+ <p>沒有符合條件的日誌記錄</p>
121
+ </div>
122
+ `;
123
+ } else {
124
+ container.innerHTML = logs.map((log) => renderLogEntry(log)).join("");
125
+ }
126
+
127
+ // 更新分頁控制
128
+ updateLogPagination();
129
+ } catch (error) {
130
+ console.error("載入日誌失敗:", error);
131
+ container.innerHTML = `
132
+ <div class="placeholder">
133
+ <span class="icon">❌</span>
134
+ <p>載入日誌失敗: ${error.message}</p>
135
+ </div>
136
+ `;
137
+ }
138
+ }
139
+
140
+ /**
141
+ * 渲染單條日誌
142
+ */
143
+ function renderLogEntry(log) {
144
+ const timestamp = new Date(log.timestamp).toLocaleString("zh-TW", {
145
+ year: "numeric",
146
+ month: "2-digit",
147
+ day: "2-digit",
148
+ hour: "2-digit",
149
+ minute: "2-digit",
150
+ second: "2-digit",
151
+ hour12: false,
152
+ });
153
+
154
+ const levelClass = `log-level-${log.level}`;
155
+ const searchTerm = document.getElementById("logSearch").value.trim();
156
+
157
+ // 高亮搜尋詞
158
+ let message = escapeHtml(log.message);
159
+ if (searchTerm) {
160
+ const regex = new RegExp(`(${escapeRegex(searchTerm)})`, "gi");
161
+ message = message.replace(regex, "<mark>$1</mark>");
162
+ }
163
+
164
+ // 格式化 meta 資訊
165
+ let metaHtml = "";
166
+ if (log.meta) {
167
+ try {
168
+ const metaObj =
169
+ typeof log.meta === "string" ? JSON.parse(log.meta) : log.meta;
170
+ if (Object.keys(metaObj).length > 0) {
171
+ metaHtml = `<div class="log-meta"><pre>${escapeHtml(
172
+ JSON.stringify(metaObj, null, 2)
173
+ )}</pre></div>`;
174
+ }
175
+ } catch (e) {
176
+ // 如果無法解析,顯示原始字串
177
+ if (log.meta) {
178
+ metaHtml = `<div class="log-meta">${escapeHtml(
179
+ String(log.meta)
180
+ )}</div>`;
181
+ }
182
+ }
183
+ }
184
+
185
+ return `
186
+ <div class="log-entry">
187
+ <div class="log-entry-header">
188
+ <span class="log-timestamp">${timestamp}</span>
189
+ <span class="log-level ${levelClass}">${log.level}</span>
190
+ <span class="log-source">[${escapeHtml(log.source)}]</span>
191
+ </div>
192
+ <div class="log-message">${message}</div>
193
+ ${metaHtml}
194
+ </div>
195
+ `;
196
+ }
197
+
198
+ /**
199
+ * 更新分頁控制
200
+ */
201
+ function updateLogPagination() {
202
+ const pageInfo = document.getElementById("logPageInfo");
203
+ const prevBtn = document.getElementById("logPrevPage");
204
+ const nextBtn = document.getElementById("logNextPage");
205
+
206
+ if (pageInfo) {
207
+ pageInfo.textContent = `${currentLogPage} / ${totalLogPages}`;
208
+ }
209
+
210
+ if (prevBtn) {
211
+ prevBtn.disabled = currentLogPage <= 1;
212
+ }
213
+
214
+ if (nextBtn) {
215
+ nextBtn.disabled = currentLogPage >= totalLogPages;
216
+ }
217
+ }
218
+
219
+ /**
220
+ * 搜尋日誌
221
+ */
222
+ export function searchLogs() {
223
+ loadLogs(1);
224
+ }
225
+
226
+ /**
227
+ * 清除舊日誌
228
+ */
229
+ export async function clearOldLogs() {
230
+ // 預設清除 7 天前的日誌
231
+ const daysToKeep = 7;
232
+ const cutoffDate = new Date();
233
+ cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
234
+
235
+ if (!confirm(`確定要清除 ${daysToKeep} 天前的所有日誌嗎?此操作無法復原。`)) {
236
+ return;
237
+ }
238
+
239
+ try {
240
+ showLoadingOverlay("清除舊日誌中...");
241
+
242
+ const response = await fetch(
243
+ `/api/logs?endDate=${cutoffDate.toISOString().split("T")[0]}`,
244
+ {
245
+ method: "DELETE",
246
+ }
247
+ );
248
+
249
+ if (!response.ok) {
250
+ throw new Error(`HTTP ${response.status}`);
251
+ }
252
+
253
+ const data = await response.json();
254
+ showToast(
255
+ "success",
256
+ "清除成功",
257
+ `已刪除 ${data.deletedCount || 0} 條舊日誌`
258
+ );
259
+
260
+ // 重新載入日誌
261
+ await loadLogs(1);
262
+ } catch (error) {
263
+ console.error("清除舊日誌失敗:", error);
264
+ showToast("error", "清除失敗", error.message);
265
+ } finally {
266
+ hideLoadingOverlay();
267
+ }
268
+ }
269
+
270
+ /**
271
+ * 正則表達式轉義
272
+ */
273
+ function escapeRegex(str) {
274
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
275
+ }
276
+
277
+ /**
278
+ * 處理分頁點擊
279
+ * @param {string} direction 'prev' or 'next'
280
+ */
281
+ export function handlePagination(direction) {
282
+ if (direction === "prev" && currentLogPage > 1) {
283
+ loadLogs(currentLogPage - 1);
284
+ } else if (direction === "next" && currentLogPage < totalLogPages) {
285
+ loadLogs(currentLogPage + 1);
286
+ }
287
+ }
288
+
289
+ export default {
290
+ openLogViewerModal,
291
+ closeLogViewerModal,
292
+ loadLogs,
293
+ searchLogs,
294
+ clearOldLogs,
295
+ handlePagination,
296
+ };