@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.
- package/LICENSE +21 -0
- package/README.md +953 -0
- package/dist/cli.cjs +95778 -0
- package/dist/index.cjs +92818 -0
- package/dist/static/app.js +385 -0
- package/dist/static/components/navbar.css +406 -0
- package/dist/static/components/navbar.html +49 -0
- package/dist/static/components/navbar.js +211 -0
- package/dist/static/dashboard.css +495 -0
- package/dist/static/dashboard.html +95 -0
- package/dist/static/dashboard.js +540 -0
- package/dist/static/favicon.svg +27 -0
- package/dist/static/index.html +541 -0
- package/dist/static/logs.html +376 -0
- package/dist/static/logs.js +442 -0
- package/dist/static/mcp-settings.html +797 -0
- package/dist/static/mcp-settings.js +884 -0
- package/dist/static/modules/app-core.js +124 -0
- package/dist/static/modules/conversation-panel.js +247 -0
- package/dist/static/modules/feedback-handler.js +1420 -0
- package/dist/static/modules/image-handler.js +155 -0
- package/dist/static/modules/log-viewer.js +296 -0
- package/dist/static/modules/mcp-manager.js +474 -0
- package/dist/static/modules/prompt-manager.js +364 -0
- package/dist/static/modules/settings-manager.js +299 -0
- package/dist/static/modules/socket-manager.js +170 -0
- package/dist/static/modules/state-manager.js +352 -0
- package/dist/static/modules/timer-controller.js +243 -0
- package/dist/static/modules/ui-helpers.js +246 -0
- package/dist/static/settings.html +355 -0
- package/dist/static/settings.js +425 -0
- package/dist/static/socket.io.min.js +7 -0
- package/dist/static/style.css +2157 -0
- package/dist/static/terminals.html +357 -0
- package/dist/static/terminals.js +321 -0
- package/package.json +91 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* app-core.js
|
|
3
|
+
* 應用程式核心邏輯模組
|
|
4
|
+
* 包含初始化、資料載入和核心功能聚合
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
setDialogTimeoutSeconds,
|
|
9
|
+
setAutoReplyTimerSeconds,
|
|
10
|
+
setMaxToolRounds,
|
|
11
|
+
setDebugMode,
|
|
12
|
+
} from "./state-manager.js";
|
|
13
|
+
|
|
14
|
+
import { loadPrompts, autoLoadPinnedPrompts } from "./prompt-manager.js";
|
|
15
|
+
|
|
16
|
+
import { loadAISettings, loadPreferences } from "./settings-manager.js";
|
|
17
|
+
|
|
18
|
+
import { startAutoReplyTimer } from "./timer-controller.js";
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
handleUserActivity as handleUserActivityImpl,
|
|
22
|
+
submitFeedback as submitFeedbackImpl,
|
|
23
|
+
} from "./feedback-handler.js";
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
handleFileSelect as handleFileSelectImpl,
|
|
27
|
+
handleFileDrop as handleFileDropImpl,
|
|
28
|
+
handlePaste as handlePasteImpl,
|
|
29
|
+
removeImage as removeImageImpl,
|
|
30
|
+
clearImages as clearImagesImpl,
|
|
31
|
+
} from "./image-handler.js";
|
|
32
|
+
|
|
33
|
+
import { updateCharCount as updateCharCountImpl } from "./ui-helpers.js";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 載入初始資料
|
|
37
|
+
*/
|
|
38
|
+
export async function loadInitialData() {
|
|
39
|
+
try {
|
|
40
|
+
// 首先從伺服器讀取配置,包括 MCP_DIALOG_TIMEOUT
|
|
41
|
+
await loadServerConfig();
|
|
42
|
+
|
|
43
|
+
// 載入並顯示版本號
|
|
44
|
+
await loadVersion();
|
|
45
|
+
|
|
46
|
+
// 載入提示詞
|
|
47
|
+
await loadPrompts();
|
|
48
|
+
|
|
49
|
+
// 載入 AI 設定
|
|
50
|
+
await loadAISettings();
|
|
51
|
+
|
|
52
|
+
// 載入使用者偏好
|
|
53
|
+
await loadPreferences();
|
|
54
|
+
|
|
55
|
+
// 自動載入釘選提示詞
|
|
56
|
+
await autoLoadPinnedPrompts();
|
|
57
|
+
|
|
58
|
+
// 頁面載入完成後,啟動 300 秒計時器
|
|
59
|
+
// 當倒數到 0 時自動啟動 AI 回應
|
|
60
|
+
startAutoReplyTimer();
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error("載入初始資料失敗:", error);
|
|
63
|
+
// showToast("error", "載入失敗", "無法載入初始資料"); // showToast might not be available here if not imported
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 載入伺服器配置
|
|
69
|
+
*/
|
|
70
|
+
async function loadServerConfig() {
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetch("/api/config");
|
|
73
|
+
const data = await response.json();
|
|
74
|
+
|
|
75
|
+
if (data.dialog_timeout) {
|
|
76
|
+
// 伺服器返回的是秒,直接使用
|
|
77
|
+
setDialogTimeoutSeconds(data.dialog_timeout);
|
|
78
|
+
console.log(`從伺服器讀取 MCP_DIALOG_TIMEOUT: ${data.dialog_timeout}s`);
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error("載入伺服器配置失敗,使用預設值:", error);
|
|
82
|
+
// 使用預設值 60 秒 (state-manager 中已預設)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 載入版本資訊
|
|
88
|
+
*/
|
|
89
|
+
async function loadVersion() {
|
|
90
|
+
try {
|
|
91
|
+
const response = await fetch("/api/version");
|
|
92
|
+
if (response.ok) {
|
|
93
|
+
const data = await response.json();
|
|
94
|
+
const versionDisplay = document.getElementById("version-display");
|
|
95
|
+
if (versionDisplay && data.version) {
|
|
96
|
+
versionDisplay.textContent = `v${data.version}`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error("載入版本資訊失敗:", error);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 重新導出核心功能
|
|
105
|
+
export const handleUserActivity = handleUserActivityImpl;
|
|
106
|
+
export const submitFeedback = submitFeedbackImpl;
|
|
107
|
+
export const handleFileSelect = handleFileSelectImpl;
|
|
108
|
+
export const handleFileDrop = handleFileDropImpl;
|
|
109
|
+
export const handlePaste = handlePasteImpl;
|
|
110
|
+
export const removeImage = removeImageImpl;
|
|
111
|
+
export const clearImages = clearImagesImpl;
|
|
112
|
+
export const updateCharCount = updateCharCountImpl;
|
|
113
|
+
|
|
114
|
+
export default {
|
|
115
|
+
loadInitialData,
|
|
116
|
+
handleUserActivity,
|
|
117
|
+
submitFeedback,
|
|
118
|
+
handleFileSelect,
|
|
119
|
+
handleFileDrop,
|
|
120
|
+
handlePaste,
|
|
121
|
+
removeImage,
|
|
122
|
+
clearImages,
|
|
123
|
+
updateCharCount,
|
|
124
|
+
};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* conversation-panel.js
|
|
3
|
+
* 對話面板元件 - 顯示 AI 對話流程
|
|
4
|
+
* 支援 6 種對話條目類型: prompt, thinking, tool, result, ai, error
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { escapeHtml } from './ui-helpers.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 對話條目類型
|
|
11
|
+
*/
|
|
12
|
+
export const ConversationEntryType = {
|
|
13
|
+
PROMPT: 'prompt',
|
|
14
|
+
THINKING: 'thinking',
|
|
15
|
+
TOOL: 'tool',
|
|
16
|
+
RESULT: 'result',
|
|
17
|
+
AI: 'ai',
|
|
18
|
+
ERROR: 'error'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 對話條目視覺配置
|
|
23
|
+
*/
|
|
24
|
+
const entryConfig = {
|
|
25
|
+
prompt: {
|
|
26
|
+
icon: '📤',
|
|
27
|
+
title: '提示詞',
|
|
28
|
+
className: 'entry-prompt',
|
|
29
|
+
borderColor: 'var(--accent-blue)'
|
|
30
|
+
},
|
|
31
|
+
thinking: {
|
|
32
|
+
icon: '🤔',
|
|
33
|
+
title: 'AI 思考中',
|
|
34
|
+
className: 'entry-thinking',
|
|
35
|
+
borderColor: 'var(--accent-yellow)'
|
|
36
|
+
},
|
|
37
|
+
tool: {
|
|
38
|
+
icon: '🔧',
|
|
39
|
+
title: '工具呼叫',
|
|
40
|
+
className: 'entry-tool',
|
|
41
|
+
borderColor: 'var(--accent-purple, #a855f7)'
|
|
42
|
+
},
|
|
43
|
+
result: {
|
|
44
|
+
icon: '📥',
|
|
45
|
+
title: '工具結果',
|
|
46
|
+
className: 'entry-result',
|
|
47
|
+
borderColor: 'var(--accent-cyan, #06b6d4)'
|
|
48
|
+
},
|
|
49
|
+
ai: {
|
|
50
|
+
icon: '🤖',
|
|
51
|
+
title: 'AI 回覆',
|
|
52
|
+
className: 'entry-ai',
|
|
53
|
+
borderColor: 'var(--accent-green)'
|
|
54
|
+
},
|
|
55
|
+
error: {
|
|
56
|
+
icon: '❌',
|
|
57
|
+
title: '錯誤',
|
|
58
|
+
className: 'entry-error',
|
|
59
|
+
borderColor: 'var(--accent-red)'
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 建立對話面板容器
|
|
65
|
+
*/
|
|
66
|
+
export function createConversationPanel() {
|
|
67
|
+
const panel = document.createElement('div');
|
|
68
|
+
panel.id = 'conversationPanel';
|
|
69
|
+
panel.className = 'conversation-panel';
|
|
70
|
+
panel.innerHTML = `
|
|
71
|
+
<div class="conversation-header">
|
|
72
|
+
<div class="conversation-title">
|
|
73
|
+
<span class="icon">💬</span>
|
|
74
|
+
<span id="conversationTitle">AI 對話</span>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="conversation-mode">
|
|
77
|
+
<span class="mode-indicator" id="conversationModeIndicator"></span>
|
|
78
|
+
<span id="conversationMode">準備中</span>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="conversation-body" id="conversationBody">
|
|
82
|
+
<!-- 對話條目會動態添加 -->
|
|
83
|
+
</div>
|
|
84
|
+
<div class="conversation-footer">
|
|
85
|
+
<button type="button" id="closeConversation" class="btn btn-secondary">關閉</button>
|
|
86
|
+
</div>
|
|
87
|
+
`;
|
|
88
|
+
return panel;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 建立對話條目元素
|
|
93
|
+
*/
|
|
94
|
+
export function createConversationEntry(type, content, options = {}) {
|
|
95
|
+
const config = entryConfig[type] || entryConfig.ai;
|
|
96
|
+
const entry = document.createElement('div');
|
|
97
|
+
entry.className = `conversation-entry ${config.className}`;
|
|
98
|
+
entry.style.borderLeftColor = config.borderColor;
|
|
99
|
+
|
|
100
|
+
const titleText = options.title || config.title;
|
|
101
|
+
const collapsed = options.collapsed ?? (type === 'prompt' || type === 'tool');
|
|
102
|
+
const timestamp = options.timestamp ? formatTimestamp(options.timestamp) : '';
|
|
103
|
+
|
|
104
|
+
let contentHtml = '';
|
|
105
|
+
if (typeof content === 'string') {
|
|
106
|
+
contentHtml = `<pre class="entry-content">${escapeHtml(content)}</pre>`;
|
|
107
|
+
} else if (content && typeof content === 'object') {
|
|
108
|
+
contentHtml = `<pre class="entry-content">${escapeHtml(JSON.stringify(content, null, 2))}</pre>`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
entry.innerHTML = `
|
|
112
|
+
<details ${collapsed ? '' : 'open'}>
|
|
113
|
+
<summary class="entry-summary">
|
|
114
|
+
<span class="entry-icon">${config.icon}</span>
|
|
115
|
+
<span class="entry-title">${titleText}</span>
|
|
116
|
+
${timestamp ? `<span class="entry-timestamp">${timestamp}</span>` : ''}
|
|
117
|
+
${options.badge ? `<span class="entry-badge">${options.badge}</span>` : ''}
|
|
118
|
+
</summary>
|
|
119
|
+
<div class="entry-body">
|
|
120
|
+
${contentHtml}
|
|
121
|
+
</div>
|
|
122
|
+
</details>
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
return entry;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 新增對話條目到面板
|
|
130
|
+
*/
|
|
131
|
+
export function addConversationEntry(type, content, options = {}) {
|
|
132
|
+
const body = document.getElementById('conversationBody');
|
|
133
|
+
if (!body) return null;
|
|
134
|
+
|
|
135
|
+
const entry = createConversationEntry(type, content, options);
|
|
136
|
+
body.appendChild(entry);
|
|
137
|
+
body.scrollTop = body.scrollHeight;
|
|
138
|
+
|
|
139
|
+
return entry;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 清空對話面板
|
|
144
|
+
*/
|
|
145
|
+
export function clearConversationPanel() {
|
|
146
|
+
const body = document.getElementById('conversationBody');
|
|
147
|
+
if (body) {
|
|
148
|
+
body.innerHTML = '';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 更新對話面板模式顯示
|
|
154
|
+
*/
|
|
155
|
+
export function updateConversationMode(mode, cliTool = null) {
|
|
156
|
+
const modeElement = document.getElementById('conversationMode');
|
|
157
|
+
const indicator = document.getElementById('conversationModeIndicator');
|
|
158
|
+
|
|
159
|
+
if (modeElement) {
|
|
160
|
+
if (mode === 'cli' && cliTool) {
|
|
161
|
+
modeElement.textContent = `CLI (${cliTool})`;
|
|
162
|
+
} else if (mode === 'api') {
|
|
163
|
+
modeElement.textContent = 'API';
|
|
164
|
+
} else {
|
|
165
|
+
modeElement.textContent = mode;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (indicator) {
|
|
170
|
+
indicator.className = 'mode-indicator';
|
|
171
|
+
if (mode === 'cli') {
|
|
172
|
+
indicator.classList.add('mode-cli');
|
|
173
|
+
} else if (mode === 'api') {
|
|
174
|
+
indicator.classList.add('mode-api');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 更新對話面板標題
|
|
181
|
+
*/
|
|
182
|
+
export function updateConversationTitle(title) {
|
|
183
|
+
const titleElement = document.getElementById('conversationTitle');
|
|
184
|
+
if (titleElement) {
|
|
185
|
+
titleElement.textContent = title;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 顯示對話面板
|
|
191
|
+
*/
|
|
192
|
+
export function showConversationPanel() {
|
|
193
|
+
let panel = document.getElementById('aiConversationPanel');
|
|
194
|
+
if (!panel) {
|
|
195
|
+
panel = document.createElement('div');
|
|
196
|
+
panel.id = 'aiConversationPanel';
|
|
197
|
+
panel.className = 'ai-conversation-overlay';
|
|
198
|
+
panel.appendChild(createConversationPanel());
|
|
199
|
+
document.body.appendChild(panel);
|
|
200
|
+
|
|
201
|
+
const closeBtn = panel.querySelector('#closeConversation');
|
|
202
|
+
if (closeBtn) {
|
|
203
|
+
closeBtn.onclick = hideConversationPanel;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
panel.style.display = 'flex';
|
|
207
|
+
clearConversationPanel();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 隱藏對話面板
|
|
212
|
+
*/
|
|
213
|
+
export function hideConversationPanel() {
|
|
214
|
+
const panel = document.getElementById('aiConversationPanel');
|
|
215
|
+
if (panel) {
|
|
216
|
+
panel.style.display = 'none';
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 格式化時間戳記
|
|
222
|
+
*/
|
|
223
|
+
function formatTimestamp(timestamp) {
|
|
224
|
+
const date = new Date(timestamp);
|
|
225
|
+
return date.toLocaleTimeString('zh-TW', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 新增思考中動畫條目
|
|
230
|
+
*/
|
|
231
|
+
export function addThinkingEntry(message = 'AI 思考中...') {
|
|
232
|
+
return addConversationEntry(ConversationEntryType.THINKING, message, {
|
|
233
|
+
collapsed: false,
|
|
234
|
+
badge: '⏳'
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* 移除思考中條目
|
|
240
|
+
*/
|
|
241
|
+
export function removeThinkingEntry() {
|
|
242
|
+
const body = document.getElementById('conversationBody');
|
|
243
|
+
if (!body) return;
|
|
244
|
+
|
|
245
|
+
const thinkingEntries = body.querySelectorAll('.entry-thinking');
|
|
246
|
+
thinkingEntries.forEach(entry => entry.remove());
|
|
247
|
+
}
|