@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,170 @@
1
+ /**
2
+ * socket-manager.js
3
+ * Socket.IO 連線和事件管理模組
4
+ */
5
+
6
+ import {
7
+ setSocket,
8
+ setSessionId,
9
+ setWorkSummary,
10
+ setCurrentProjectName,
11
+ setCurrentProjectPath,
12
+ getSocket,
13
+ getSessionId,
14
+ } from "./state-manager.js";
15
+
16
+ import {
17
+ showToast,
18
+ displayProjectInfo,
19
+ displayAIMessage,
20
+ } from "./ui-helpers.js";
21
+
22
+ import { startCloseCountdown } from "./timer-controller.js";
23
+
24
+ // 動態導入以避免循環依賴
25
+ async function loadClearSubmissionInputs() {
26
+ const module = await import("./feedback-handler.js");
27
+ return module.clearSubmissionInputs;
28
+ }
29
+
30
+ /**
31
+ * 初始化 Socket.IO 連線
32
+ */
33
+ export function initSocketIO() {
34
+ const socket = io();
35
+ setSocket(socket);
36
+
37
+ socket.on("connect", () => {
38
+ console.log("已連線到伺服器");
39
+ updateConnectionStatus(true);
40
+ // 請求會話
41
+ socket.emit("request_session");
42
+ });
43
+
44
+ socket.on("disconnect", () => {
45
+ console.log("與伺服器斷線");
46
+ updateConnectionStatus(false);
47
+ });
48
+
49
+ socket.on("session_assigned", (data) => {
50
+ console.log("收到會話分配:", data);
51
+ // 注意:伺服器使用蛇形命名 (session_id, work_summary 等)
52
+ setSessionId(data.session_id);
53
+ setWorkSummary(data.work_summary);
54
+ setCurrentProjectName(data.project_name || null);
55
+ setCurrentProjectPath(data.project_path || null);
56
+
57
+ displayProjectInfo(data.project_name, data.project_path);
58
+ displayAIMessage(data.work_summary);
59
+
60
+ // 通知伺服器 session 已就緒
61
+ socket.emit("session_ready", {
62
+ sessionId: data.session_id,
63
+ workSummary: data.work_summary,
64
+ });
65
+ });
66
+
67
+ socket.on("feedback_submitted", async (data) => {
68
+ console.log("反饋已提交:", data);
69
+
70
+ if (data.success) {
71
+ showToast("success", "提交成功", "您的回應已送出,頁面將在 3 秒後關閉");
72
+ const clearFn = await loadClearSubmissionInputs();
73
+ if (clearFn) clearFn();
74
+
75
+ // 啟動 3 秒關閉倒數
76
+ startCloseCountdown();
77
+ } else {
78
+ showToast("error", "提交失敗", data.error || "請稍後再試");
79
+ }
80
+ });
81
+
82
+ socket.on("auto_reply", (data) => {
83
+ console.log("收到自動回覆觸發:", data);
84
+ if (data.autoSubmit) {
85
+ console.log("準備自動提交...");
86
+ }
87
+ });
88
+
89
+ socket.on("error", (error) => {
90
+ console.error("Socket 錯誤:", error);
91
+ showToast("error", "連線錯誤", error.message || "請檢查網路連線");
92
+ });
93
+
94
+ socket.on("close_session", () => {
95
+ console.log("收到關閉會話訊號");
96
+ window.close();
97
+ });
98
+
99
+ return socket;
100
+ }
101
+
102
+ /**
103
+ * 更新連線狀態顯示
104
+ * @param {boolean} connected - 是否已連線
105
+ */
106
+ export function updateConnectionStatus(connected) {
107
+ const containerEl = document.getElementById("connectionStatus");
108
+ if (!containerEl) return;
109
+
110
+ const dotEl = containerEl.querySelector(".status-dot");
111
+ const textEl = containerEl.querySelector(".status-text");
112
+
113
+ if (connected) {
114
+ containerEl.classList.remove("disconnected");
115
+ containerEl.classList.add("connected");
116
+ if (dotEl) dotEl.classList.add("online");
117
+ if (textEl) textEl.textContent = "已連線";
118
+ } else {
119
+ containerEl.classList.remove("connected");
120
+ containerEl.classList.add("disconnected");
121
+ if (dotEl) dotEl.classList.remove("online");
122
+ if (textEl) textEl.textContent = "已斷線";
123
+ }
124
+ }
125
+
126
+ /**
127
+ * 通知伺服器使用者活動
128
+ */
129
+ export function emitUserActivity() {
130
+ const socket = getSocket();
131
+ const sessionId = getSessionId();
132
+
133
+ if (socket && sessionId) {
134
+ socket.emit("user_activity", {
135
+ sessionId: sessionId,
136
+ timestamp: Date.now(),
137
+ });
138
+ }
139
+ }
140
+
141
+ /**
142
+ * 發送反饋到伺服器
143
+ * @param {Object} feedbackData - 反饋資料
144
+ */
145
+ export function emitSubmitFeedback(feedbackData) {
146
+ const socket = getSocket();
147
+ if (socket) {
148
+ socket.emit("submit_feedback", feedbackData);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * 取消自動回覆
154
+ */
155
+ export function emitCancelAutoReply() {
156
+ const socket = getSocket();
157
+ const sessionId = getSessionId();
158
+
159
+ if (socket && sessionId) {
160
+ socket.emit("cancel_auto_reply", { sessionId });
161
+ }
162
+ }
163
+
164
+ export default {
165
+ initSocketIO,
166
+ updateConnectionStatus,
167
+ emitUserActivity,
168
+ emitSubmitFeedback,
169
+ emitCancelAutoReply,
170
+ };
@@ -0,0 +1,352 @@
1
+ /**
2
+ * state-manager.js
3
+ * 全域狀態管理模組
4
+ * 集中管理 app.js 中的所有全域狀態變數
5
+ */
6
+
7
+ const AppState = {
8
+ // Socket.IO 連線
9
+ socket: null,
10
+ sessionId: null,
11
+
12
+ // 專案資訊
13
+ currentProjectName: null,
14
+ currentProjectPath: null,
15
+
16
+ // AI 相關
17
+ workSummary: null,
18
+ aiSettings: null,
19
+ maxToolRounds: 5,
20
+ debugMode: false,
21
+
22
+ // 圖片
23
+ currentImages: [],
24
+
25
+ // 提示詞
26
+ prompts: [],
27
+ isEditingPrompt: false,
28
+ editingPromptId: null,
29
+
30
+ // 使用者偏好
31
+ preferences: {},
32
+
33
+ // 計時器狀態
34
+ DIALOG_TIMEOUT_SECONDS: 60,
35
+ AUTO_REPLY_TIMER_SECONDS: 300,
36
+ autoReplyTimerRemaining: 0,
37
+ autoReplyTimerPaused: false,
38
+ autoReplyPausedByFocus: false,
39
+ autoReplyData: null,
40
+
41
+ // 計時器 ID
42
+ dialogTimeoutInterval: null,
43
+ closeCountdownInterval: null,
44
+ autoReplyTimerInterval: null,
45
+ autoReplyCountdownInterval: null,
46
+ autoReplyWarningTimeout: null,
47
+ autoReplyConfirmationTimeout: null,
48
+
49
+ // Streaming
50
+ streamingAbortController: null,
51
+
52
+ // MCP Servers
53
+ mcpServers: [],
54
+ editingMcpServerId: null,
55
+
56
+ // 日誌
57
+ currentLogPage: 1,
58
+ totalLogPages: 1,
59
+ logSources: [],
60
+ };
61
+
62
+ // Getter 函數
63
+ export function getSocket() {
64
+ return AppState.socket;
65
+ }
66
+
67
+ export function getSessionId() {
68
+ return AppState.sessionId;
69
+ }
70
+
71
+ export function getWorkSummary() {
72
+ return AppState.workSummary;
73
+ }
74
+
75
+ export function getCurrentImages() {
76
+ return AppState.currentImages;
77
+ }
78
+
79
+ export function getPrompts() {
80
+ return AppState.prompts;
81
+ }
82
+
83
+ export function getAISettings() {
84
+ return AppState.aiSettings;
85
+ }
86
+
87
+ export function getPreferences() {
88
+ return AppState.preferences;
89
+ }
90
+
91
+ export function getMaxToolRounds() {
92
+ return AppState.maxToolRounds;
93
+ }
94
+
95
+ export function getDebugMode() {
96
+ return AppState.debugMode;
97
+ }
98
+
99
+ export function getDialogTimeoutSeconds() {
100
+ return AppState.DIALOG_TIMEOUT_SECONDS;
101
+ }
102
+
103
+ export function getAutoReplyTimerSeconds() {
104
+ return AppState.AUTO_REPLY_TIMER_SECONDS;
105
+ }
106
+
107
+ export function getCurrentProjectName() {
108
+ return AppState.currentProjectName;
109
+ }
110
+
111
+ export function getCurrentProjectPath() {
112
+ return AppState.currentProjectPath;
113
+ }
114
+
115
+ export function getStreamingAbortController() {
116
+ return AppState.streamingAbortController;
117
+ }
118
+
119
+ export function getMcpServers() {
120
+ return AppState.mcpServers;
121
+ }
122
+
123
+ export function getEditingMcpServerId() {
124
+ return AppState.editingMcpServerId;
125
+ }
126
+
127
+ export function isEditingPrompt() {
128
+ return AppState.isEditingPrompt;
129
+ }
130
+
131
+ export function getEditingPromptId() {
132
+ return AppState.editingPromptId;
133
+ }
134
+
135
+ export function getAutoReplyTimerRemaining() {
136
+ return AppState.autoReplyTimerRemaining;
137
+ }
138
+
139
+ export function isAutoReplyTimerPaused() {
140
+ return AppState.autoReplyTimerPaused;
141
+ }
142
+
143
+ export function isAutoReplyPausedByFocus() {
144
+ return AppState.autoReplyPausedByFocus;
145
+ }
146
+
147
+ export function getAutoReplyData() {
148
+ return AppState.autoReplyData;
149
+ }
150
+
151
+ // Timer ID getters
152
+ export function getDialogTimeoutInterval() {
153
+ return AppState.dialogTimeoutInterval;
154
+ }
155
+
156
+ export function getCloseCountdownInterval() {
157
+ return AppState.closeCountdownInterval;
158
+ }
159
+
160
+ export function getAutoReplyTimerInterval() {
161
+ return AppState.autoReplyTimerInterval;
162
+ }
163
+
164
+ export function getAutoReplyCountdownInterval() {
165
+ return AppState.autoReplyCountdownInterval;
166
+ }
167
+
168
+ export function getAutoReplyWarningTimeout() {
169
+ return AppState.autoReplyWarningTimeout;
170
+ }
171
+
172
+ export function getAutoReplyConfirmationTimeout() {
173
+ return AppState.autoReplyConfirmationTimeout;
174
+ }
175
+
176
+ // Log getters
177
+ export function getCurrentLogPage() {
178
+ return AppState.currentLogPage;
179
+ }
180
+
181
+ export function getTotalLogPages() {
182
+ return AppState.totalLogPages;
183
+ }
184
+
185
+ export function getLogSources() {
186
+ return AppState.logSources;
187
+ }
188
+
189
+ // Setter 函數
190
+ export function setSocket(socket) {
191
+ AppState.socket = socket;
192
+ }
193
+
194
+ export function setSessionId(id) {
195
+ AppState.sessionId = id;
196
+ }
197
+
198
+ export function setWorkSummary(summary) {
199
+ AppState.workSummary = summary;
200
+ }
201
+
202
+ export function setCurrentImages(images) {
203
+ AppState.currentImages = images;
204
+ }
205
+
206
+ export function addImage(image) {
207
+ AppState.currentImages.push(image);
208
+ }
209
+
210
+ export function removeImageAt(index) {
211
+ AppState.currentImages.splice(index, 1);
212
+ }
213
+
214
+ export function clearImages() {
215
+ AppState.currentImages = [];
216
+ }
217
+
218
+ export function setPrompts(prompts) {
219
+ AppState.prompts = prompts;
220
+ }
221
+
222
+ export function setAISettings(settings) {
223
+ AppState.aiSettings = settings;
224
+ }
225
+
226
+ export function setPreferences(prefs) {
227
+ AppState.preferences = prefs;
228
+ }
229
+
230
+ export function setMaxToolRounds(rounds) {
231
+ AppState.maxToolRounds = rounds;
232
+ }
233
+
234
+ export function setDebugMode(mode) {
235
+ AppState.debugMode = mode;
236
+ }
237
+
238
+ export function setDialogTimeoutSeconds(seconds) {
239
+ AppState.DIALOG_TIMEOUT_SECONDS = seconds;
240
+ }
241
+
242
+ export function setAutoReplyTimerSeconds(seconds) {
243
+ AppState.AUTO_REPLY_TIMER_SECONDS = seconds;
244
+ }
245
+
246
+ export function setCurrentProjectName(name) {
247
+ AppState.currentProjectName = name;
248
+ }
249
+
250
+ export function setCurrentProjectPath(path) {
251
+ AppState.currentProjectPath = path;
252
+ }
253
+
254
+ export function setStreamingAbortController(controller) {
255
+ AppState.streamingAbortController = controller;
256
+ }
257
+
258
+ export function setMcpServers(servers) {
259
+ AppState.mcpServers = servers;
260
+ }
261
+
262
+ export function setEditingMcpServerId(id) {
263
+ AppState.editingMcpServerId = id;
264
+ }
265
+
266
+ export function setIsEditingPrompt(editing) {
267
+ AppState.isEditingPrompt = editing;
268
+ }
269
+
270
+ export function setEditingPromptId(id) {
271
+ AppState.editingPromptId = id;
272
+ }
273
+
274
+ export function setAutoReplyTimerRemaining(remaining) {
275
+ AppState.autoReplyTimerRemaining = remaining;
276
+ }
277
+
278
+ export function setAutoReplyTimerPaused(paused) {
279
+ AppState.autoReplyTimerPaused = paused;
280
+ }
281
+
282
+ export function setAutoReplyPausedByFocus(paused) {
283
+ AppState.autoReplyPausedByFocus = paused;
284
+ }
285
+
286
+ export function setAutoReplyData(data) {
287
+ AppState.autoReplyData = data;
288
+ }
289
+
290
+ // Timer ID setters
291
+ export function setDialogTimeoutInterval(id) {
292
+ AppState.dialogTimeoutInterval = id;
293
+ }
294
+
295
+ export function setCloseCountdownInterval(id) {
296
+ AppState.closeCountdownInterval = id;
297
+ }
298
+
299
+ export function setAutoReplyTimerInterval(id) {
300
+ AppState.autoReplyTimerInterval = id;
301
+ }
302
+
303
+ export function setAutoReplyCountdownInterval(id) {
304
+ AppState.autoReplyCountdownInterval = id;
305
+ }
306
+
307
+ export function setAutoReplyWarningTimeout(id) {
308
+ AppState.autoReplyWarningTimeout = id;
309
+ }
310
+
311
+ export function setAutoReplyConfirmationTimeout(id) {
312
+ AppState.autoReplyConfirmationTimeout = id;
313
+ }
314
+
315
+ // Log setters
316
+ export function setCurrentLogPage(page) {
317
+ AppState.currentLogPage = page;
318
+ }
319
+
320
+ export function setTotalLogPages(pages) {
321
+ AppState.totalLogPages = pages;
322
+ }
323
+
324
+ export function setLogSources(sources) {
325
+ AppState.logSources = sources;
326
+ }
327
+
328
+ // 工具函數
329
+ export function findPromptById(id) {
330
+ return AppState.prompts.find((p) => p.id === id);
331
+ }
332
+
333
+ export function findMcpServerById(id) {
334
+ return AppState.mcpServers.find((s) => s.id === id);
335
+ }
336
+
337
+ // 清除所有計時器 ID
338
+ export function clearAllTimerIds() {
339
+ AppState.dialogTimeoutInterval = null;
340
+ AppState.closeCountdownInterval = null;
341
+ AppState.autoReplyTimerInterval = null;
342
+ AppState.autoReplyCountdownInterval = null;
343
+ AppState.autoReplyWarningTimeout = null;
344
+ AppState.autoReplyConfirmationTimeout = null;
345
+ }
346
+
347
+ // 匯出整個狀態(僅供調試)
348
+ export function getFullState() {
349
+ return { ...AppState };
350
+ }
351
+
352
+ export default AppState;