@hirohsu/user-web-feedback 2.8.2 → 2.8.9

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 +35128 -22228
  3. package/dist/index.cjs +39895 -26993
  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 -417
  30. package/dist/static/settings.js +767 -705
  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 -93
@@ -1,246 +1,246 @@
1
- /**
2
- * ui-helpers.js
3
- * UI 輔助函數模組
4
- * 包含 Toast、Modal、Loading、HTML 轉義等工具函數
5
- */
6
-
7
- /**
8
- * HTML 轉義
9
- * @param {string} text - 要轉義的文字
10
- * @returns {string} - 轉義後的文字
11
- */
12
- export function escapeHtml(text) {
13
- if (!text) return "";
14
- const div = document.createElement("div");
15
- div.textContent = text;
16
- return div.innerHTML;
17
- }
18
-
19
- /**
20
- * 正則表達式轉義
21
- * @param {string} str - 要轉義的字串
22
- * @returns {string} - 轉義後的字串
23
- */
24
- export function escapeRegex(str) {
25
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26
- }
27
-
28
- /**
29
- * 顯示 Toast 通知
30
- * @param {string} type - 類型: success, error, info
31
- * @param {string} title - 標題
32
- * @param {string} message - 訊息
33
- */
34
- export function showToast(type, title, message) {
35
- const container = document.getElementById("toastContainer");
36
- if (!container) return;
37
-
38
- const toast = document.createElement("div");
39
- toast.className = `toast ${type}`;
40
- toast.innerHTML = `
41
- <div class="toast-icon">${getToastIcon(type)}</div>
42
- <div class="toast-content">
43
- <div class="toast-title">${escapeHtml(title)}</div>
44
- <div class="toast-message">${escapeHtml(message)}</div>
45
- </div>
46
- `;
47
-
48
- container.appendChild(toast);
49
-
50
- setTimeout(() => {
51
- toast.style.opacity = "0";
52
- setTimeout(() => toast.remove(), 300);
53
- }, 3000);
54
- }
55
-
56
- /**
57
- * 取得 Toast 圖標
58
- * @param {string} type - 類型
59
- * @returns {string} - 圖標
60
- */
61
- function getToastIcon(type) {
62
- const icons = {
63
- success: "✅",
64
- error: "❌",
65
- info: "ℹ️",
66
- };
67
- return icons[type] || "📢";
68
- }
69
-
70
- /**
71
- * 格式化 API 錯誤為字串
72
- * @param {Object|string} data - 錯誤資料
73
- * @returns {string} - 格式化後的錯誤訊息
74
- */
75
- export function formatApiError(data) {
76
- if (!data) return "未知錯誤";
77
- if (typeof data === "string") return data;
78
-
79
- try {
80
- const parts = [];
81
- if (data.error) parts.push(data.error);
82
- if (data.details) {
83
- parts.push(
84
- typeof data.details === "string"
85
- ? data.details
86
- : JSON.stringify(data.details)
87
- );
88
- }
89
- if (data.stack) parts.push(data.stack);
90
- return parts.join("\n") || JSON.stringify(data);
91
- } catch (e) {
92
- return String(data);
93
- }
94
- }
95
-
96
- /**
97
- * 顯示提醒彈窗
98
- * @param {string} title - 標題
99
- * @param {string} message - 訊息
100
- * @param {Function} onConfirm - 確認回調
101
- * @param {Function} onCancel - 取消回調
102
- */
103
- export function showAlertModal(
104
- title,
105
- message,
106
- onConfirm = null,
107
- onCancel = null
108
- ) {
109
- const modal = document.getElementById("alertModal");
110
- if (!modal) return;
111
-
112
- const titleEl = document.getElementById("alertModalTitle");
113
- const bodyEl = document.getElementById("alertModalBody");
114
- const confirmBtn = document.getElementById("alertModalConfirm");
115
- const cancelBtn = document.getElementById("alertModalCancel");
116
-
117
- if (titleEl) titleEl.textContent = title;
118
- if (bodyEl) bodyEl.textContent = message;
119
-
120
- // 設置確認按鈕
121
- if (confirmBtn) {
122
- confirmBtn.onclick = () => {
123
- hideAlertModal();
124
- if (onConfirm) onConfirm();
125
- };
126
- }
127
-
128
- // 設置取消按鈕
129
- if (cancelBtn) {
130
- if (onCancel) {
131
- cancelBtn.style.display = "block";
132
- cancelBtn.onclick = () => {
133
- hideAlertModal();
134
- onCancel();
135
- };
136
- } else {
137
- cancelBtn.style.display = "none";
138
- }
139
- }
140
-
141
- modal.classList.add("show");
142
- }
143
-
144
- /**
145
- * 隱藏提醒彈窗
146
- */
147
- export function hideAlertModal() {
148
- const modal = document.getElementById("alertModal");
149
- if (modal) {
150
- modal.classList.remove("show");
151
- }
152
- }
153
-
154
- /**
155
- * 顯示載入遮罩
156
- * @param {string} text - 載入文字
157
- */
158
- export function showLoadingOverlay(text = "處理中...") {
159
- const overlay = document.getElementById("loadingOverlay");
160
- const loadingText = document.getElementById("loadingText");
161
-
162
- if (loadingText) loadingText.textContent = text;
163
- if (overlay) overlay.style.display = "flex";
164
- }
165
-
166
- /**
167
- * 隱藏載入遮罩
168
- */
169
- export function hideLoadingOverlay() {
170
- const overlay = document.getElementById("loadingOverlay");
171
- if (overlay) overlay.style.display = "none";
172
- }
173
-
174
- /**
175
- * 顯示專案資訊
176
- * @param {string} projectName - 專案名稱
177
- * @param {string} projectPath - 專案路徑
178
- */
179
- export function displayProjectInfo(projectName, projectPath) {
180
- const projectInfoEl = document.getElementById("projectInfo");
181
- if (!projectInfoEl) return;
182
-
183
- if (projectName || projectPath) {
184
- const name = projectName || "未命名專案";
185
- const path = projectPath ? ` (${projectPath})` : "";
186
- projectInfoEl.innerHTML = `<span class="icon">📁</span> ${name}${path}`;
187
- projectInfoEl.title = projectPath || projectName || "";
188
- } else {
189
- projectInfoEl.innerHTML = "";
190
- }
191
- }
192
-
193
- /**
194
- * 顯示 AI 訊息
195
- * @param {string} message - 訊息內容 (Markdown)
196
- */
197
- export function displayAIMessage(message) {
198
- const displayEl = document.getElementById("aiMessageDisplay");
199
- if (!displayEl) return;
200
-
201
- const htmlContent = marked.parse(message);
202
- displayEl.innerHTML = `<div class="ai-message-content">${htmlContent}</div>`;
203
- }
204
-
205
- /**
206
- * 更新字元計數
207
- */
208
- export function updateCharCount() {
209
- const textEl = document.getElementById("feedbackText");
210
- const countEl = document.getElementById("charCount");
211
-
212
- if (textEl && countEl) {
213
- countEl.textContent = `${textEl.value.length} 字元`;
214
- }
215
- }
216
-
217
- /**
218
- * 截斷過長的文字
219
- * @param {string|Object} text - 要截斷的文字
220
- * @param {number} maxLength - 最大長度
221
- * @returns {string} - 截斷後的文字
222
- */
223
- export function truncateResult(text, maxLength = 500) {
224
- if (typeof text !== "string") {
225
- text = JSON.stringify(text, null, 2);
226
- }
227
- if (text.length > maxLength) {
228
- return text.substring(0, maxLength) + "\n... (已截斷)";
229
- }
230
- return text;
231
- }
232
-
233
- export default {
234
- escapeHtml,
235
- escapeRegex,
236
- showToast,
237
- formatApiError,
238
- showAlertModal,
239
- hideAlertModal,
240
- showLoadingOverlay,
241
- hideLoadingOverlay,
242
- displayProjectInfo,
243
- displayAIMessage,
244
- updateCharCount,
245
- truncateResult,
246
- };
1
+ /**
2
+ * ui-helpers.js
3
+ * UI 輔助函數模組
4
+ * 包含 Toast、Modal、Loading、HTML 轉義等工具函數
5
+ */
6
+
7
+ /**
8
+ * HTML 轉義
9
+ * @param {string} text - 要轉義的文字
10
+ * @returns {string} - 轉義後的文字
11
+ */
12
+ export function escapeHtml(text) {
13
+ if (!text) return "";
14
+ const div = document.createElement("div");
15
+ div.textContent = text;
16
+ return div.innerHTML;
17
+ }
18
+
19
+ /**
20
+ * 正則表達式轉義
21
+ * @param {string} str - 要轉義的字串
22
+ * @returns {string} - 轉義後的字串
23
+ */
24
+ export function escapeRegex(str) {
25
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26
+ }
27
+
28
+ /**
29
+ * 顯示 Toast 通知
30
+ * @param {string} type - 類型: success, error, info
31
+ * @param {string} title - 標題
32
+ * @param {string} message - 訊息
33
+ */
34
+ export function showToast(type, title, message) {
35
+ const container = document.getElementById("toastContainer");
36
+ if (!container) return;
37
+
38
+ const toast = document.createElement("div");
39
+ toast.className = `toast ${type}`;
40
+ toast.innerHTML = `
41
+ <div class="toast-icon">${getToastIcon(type)}</div>
42
+ <div class="toast-content">
43
+ <div class="toast-title">${escapeHtml(title)}</div>
44
+ <div class="toast-message">${escapeHtml(message)}</div>
45
+ </div>
46
+ `;
47
+
48
+ container.appendChild(toast);
49
+
50
+ setTimeout(() => {
51
+ toast.style.opacity = "0";
52
+ setTimeout(() => toast.remove(), 300);
53
+ }, 3000);
54
+ }
55
+
56
+ /**
57
+ * 取得 Toast 圖標
58
+ * @param {string} type - 類型
59
+ * @returns {string} - 圖標
60
+ */
61
+ function getToastIcon(type) {
62
+ const icons = {
63
+ success: "✅",
64
+ error: "❌",
65
+ info: "ℹ️",
66
+ };
67
+ return icons[type] || "📢";
68
+ }
69
+
70
+ /**
71
+ * 格式化 API 錯誤為字串
72
+ * @param {Object|string} data - 錯誤資料
73
+ * @returns {string} - 格式化後的錯誤訊息
74
+ */
75
+ export function formatApiError(data) {
76
+ if (!data) return "未知錯誤";
77
+ if (typeof data === "string") return data;
78
+
79
+ try {
80
+ const parts = [];
81
+ if (data.error) parts.push(data.error);
82
+ if (data.details) {
83
+ parts.push(
84
+ typeof data.details === "string"
85
+ ? data.details
86
+ : JSON.stringify(data.details)
87
+ );
88
+ }
89
+ if (data.stack) parts.push(data.stack);
90
+ return parts.join("\n") || JSON.stringify(data);
91
+ } catch (e) {
92
+ return String(data);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * 顯示提醒彈窗
98
+ * @param {string} title - 標題
99
+ * @param {string} message - 訊息
100
+ * @param {Function} onConfirm - 確認回調
101
+ * @param {Function} onCancel - 取消回調
102
+ */
103
+ export function showAlertModal(
104
+ title,
105
+ message,
106
+ onConfirm = null,
107
+ onCancel = null
108
+ ) {
109
+ const modal = document.getElementById("alertModal");
110
+ if (!modal) return;
111
+
112
+ const titleEl = document.getElementById("alertModalTitle");
113
+ const bodyEl = document.getElementById("alertModalBody");
114
+ const confirmBtn = document.getElementById("alertModalConfirm");
115
+ const cancelBtn = document.getElementById("alertModalCancel");
116
+
117
+ if (titleEl) titleEl.textContent = title;
118
+ if (bodyEl) bodyEl.textContent = message;
119
+
120
+ // 設置確認按鈕
121
+ if (confirmBtn) {
122
+ confirmBtn.onclick = () => {
123
+ hideAlertModal();
124
+ if (onConfirm) onConfirm();
125
+ };
126
+ }
127
+
128
+ // 設置取消按鈕
129
+ if (cancelBtn) {
130
+ if (onCancel) {
131
+ cancelBtn.style.display = "block";
132
+ cancelBtn.onclick = () => {
133
+ hideAlertModal();
134
+ onCancel();
135
+ };
136
+ } else {
137
+ cancelBtn.style.display = "none";
138
+ }
139
+ }
140
+
141
+ modal.classList.add("show");
142
+ }
143
+
144
+ /**
145
+ * 隱藏提醒彈窗
146
+ */
147
+ export function hideAlertModal() {
148
+ const modal = document.getElementById("alertModal");
149
+ if (modal) {
150
+ modal.classList.remove("show");
151
+ }
152
+ }
153
+
154
+ /**
155
+ * 顯示載入遮罩
156
+ * @param {string} text - 載入文字
157
+ */
158
+ export function showLoadingOverlay(text = "處理中...") {
159
+ const overlay = document.getElementById("loadingOverlay");
160
+ const loadingText = document.getElementById("loadingText");
161
+
162
+ if (loadingText) loadingText.textContent = text;
163
+ if (overlay) overlay.style.display = "flex";
164
+ }
165
+
166
+ /**
167
+ * 隱藏載入遮罩
168
+ */
169
+ export function hideLoadingOverlay() {
170
+ const overlay = document.getElementById("loadingOverlay");
171
+ if (overlay) overlay.style.display = "none";
172
+ }
173
+
174
+ /**
175
+ * 顯示專案資訊
176
+ * @param {string} projectName - 專案名稱
177
+ * @param {string} projectPath - 專案路徑
178
+ */
179
+ export function displayProjectInfo(projectName, projectPath) {
180
+ const projectInfoEl = document.getElementById("projectInfo");
181
+ if (!projectInfoEl) return;
182
+
183
+ if (projectName || projectPath) {
184
+ const name = projectName || "未命名專案";
185
+ const path = projectPath ? ` (${projectPath})` : "";
186
+ projectInfoEl.innerHTML = `<span class="icon">📁</span> ${name}${path}`;
187
+ projectInfoEl.title = projectPath || projectName || "";
188
+ } else {
189
+ projectInfoEl.innerHTML = "";
190
+ }
191
+ }
192
+
193
+ /**
194
+ * 顯示 AI 訊息
195
+ * @param {string} message - 訊息內容 (Markdown)
196
+ */
197
+ export function displayAIMessage(message) {
198
+ const displayEl = document.getElementById("aiMessageDisplay");
199
+ if (!displayEl) return;
200
+
201
+ const htmlContent = marked.parse(message);
202
+ displayEl.innerHTML = `<div class="ai-message-content">${htmlContent}</div>`;
203
+ }
204
+
205
+ /**
206
+ * 更新字元計數
207
+ */
208
+ export function updateCharCount() {
209
+ const textEl = document.getElementById("feedbackText");
210
+ const countEl = document.getElementById("charCount");
211
+
212
+ if (textEl && countEl) {
213
+ countEl.textContent = `${textEl.value.length} 字元`;
214
+ }
215
+ }
216
+
217
+ /**
218
+ * 截斷過長的文字
219
+ * @param {string|Object} text - 要截斷的文字
220
+ * @param {number} maxLength - 最大長度
221
+ * @returns {string} - 截斷後的文字
222
+ */
223
+ export function truncateResult(text, maxLength = 500) {
224
+ if (typeof text !== "string") {
225
+ text = JSON.stringify(text, null, 2);
226
+ }
227
+ if (text.length > maxLength) {
228
+ return text.substring(0, maxLength) + "\n... (已截斷)";
229
+ }
230
+ return text;
231
+ }
232
+
233
+ export default {
234
+ escapeHtml,
235
+ escapeRegex,
236
+ showToast,
237
+ formatApiError,
238
+ showAlertModal,
239
+ hideAlertModal,
240
+ showLoadingOverlay,
241
+ hideLoadingOverlay,
242
+ displayProjectInfo,
243
+ displayAIMessage,
244
+ updateCharCount,
245
+ truncateResult,
246
+ };