@hirohsu/user-web-feedback 2.8.21 → 2.8.23
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/dist/cli.cjs +1 -1
- package/dist/index.cjs +1 -1
- package/dist/static/modules/conversation-panel.js +312 -243
- package/dist/static/modules/feedback-handler.js +14 -0
- package/dist/static/style.css +360 -169
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* conversation-panel.js
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* AI Console - 現代化對話面板
|
|
4
|
+
* 支援即時對話流、歷史記錄瀏覽、完整 Debug 資訊
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { escapeHtml } from './ui-helpers.js';
|
|
@@ -16,304 +16,372 @@ export const ConversationEntryType = {
|
|
|
16
16
|
DEBUG: 'debug'
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
const
|
|
20
|
-
prompt:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
icon: '🤔',
|
|
28
|
-
title: 'AI 思考中',
|
|
29
|
-
className: 'entry-thinking',
|
|
30
|
-
borderColor: 'var(--accent-yellow)'
|
|
31
|
-
},
|
|
32
|
-
tool: {
|
|
33
|
-
icon: '🔧',
|
|
34
|
-
title: '工具呼叫',
|
|
35
|
-
className: 'entry-tool',
|
|
36
|
-
borderColor: 'var(--accent-purple, #a855f7)'
|
|
37
|
-
},
|
|
38
|
-
result: {
|
|
39
|
-
icon: '📥',
|
|
40
|
-
title: '工具結果',
|
|
41
|
-
className: 'entry-result',
|
|
42
|
-
borderColor: 'var(--accent-cyan, #06b6d4)'
|
|
43
|
-
},
|
|
44
|
-
ai: {
|
|
45
|
-
icon: '🤖',
|
|
46
|
-
title: 'AI 回覆',
|
|
47
|
-
className: 'entry-ai',
|
|
48
|
-
borderColor: 'var(--accent-green)'
|
|
49
|
-
},
|
|
50
|
-
error: {
|
|
51
|
-
icon: '❌',
|
|
52
|
-
title: '錯誤',
|
|
53
|
-
className: 'entry-error',
|
|
54
|
-
borderColor: 'var(--accent-red)'
|
|
55
|
-
},
|
|
56
|
-
debug: {
|
|
57
|
-
icon: '🐛',
|
|
58
|
-
title: 'Debug',
|
|
59
|
-
className: 'entry-debug',
|
|
60
|
-
borderColor: 'var(--accent-orange, #f97316)'
|
|
61
|
-
}
|
|
19
|
+
const ENTRY_CONFIG = {
|
|
20
|
+
prompt: { icon: '📤', label: '提示詞', cls: 'entry-prompt', color: 'var(--console-blue, #3b82f6)' },
|
|
21
|
+
thinking: { icon: '🤔', label: 'AI 思考中', cls: 'entry-thinking', color: 'var(--console-yellow, #eab308)' },
|
|
22
|
+
tool: { icon: '🔧', label: '工具呼叫', cls: 'entry-tool', color: 'var(--console-purple, #a855f7)' },
|
|
23
|
+
result: { icon: '📥', label: '工具結果', cls: 'entry-result', color: 'var(--console-cyan, #06b6d4)' },
|
|
24
|
+
ai: { icon: '🤖', label: 'AI 回覆', cls: 'entry-ai', color: 'var(--console-green, #22c55e)' },
|
|
25
|
+
error: { icon: '❌', label: '錯誤', cls: 'entry-error', color: 'var(--console-red, #ef4444)' },
|
|
26
|
+
debug: { icon: '🐛', label: 'Debug', cls: 'entry-debug', color: 'var(--console-orange, #f97316)' }
|
|
62
27
|
};
|
|
63
28
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
29
|
+
const MAX_HISTORY = 50;
|
|
30
|
+
let conversationHistory = [];
|
|
31
|
+
let currentSession = null;
|
|
32
|
+
let activeTab = 'live';
|
|
33
|
+
let debugVisible = false;
|
|
34
|
+
|
|
35
|
+
function newSessionId() {
|
|
36
|
+
return Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function ts() {
|
|
40
|
+
return new Date().toLocaleTimeString('zh-TW', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function buildPanel() {
|
|
44
|
+
const el = document.createElement('div');
|
|
45
|
+
el.id = 'aiConsoleOverlay';
|
|
46
|
+
el.className = 'ai-console-overlay';
|
|
47
|
+
el.innerHTML = `
|
|
48
|
+
<div class="ai-console">
|
|
49
|
+
<header class="console-header">
|
|
50
|
+
<div class="console-title-group">
|
|
51
|
+
<span class="console-logo">⚡</span>
|
|
52
|
+
<h2 class="console-title" id="consoleTitle">AI Console</h2>
|
|
53
|
+
<span class="console-mode-badge" id="consoleBadge">準備中</span>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="console-actions">
|
|
56
|
+
<div class="console-tabs">
|
|
57
|
+
<button class="tab-btn active" data-tab="live">即時</button>
|
|
58
|
+
<button class="tab-btn" data-tab="history">歷史 <span class="history-count" id="historyCount">0</span></button>
|
|
59
|
+
</div>
|
|
60
|
+
<label class="debug-switch" title="Debug 模式">
|
|
61
|
+
<input type="checkbox" id="consoleDebugToggle">
|
|
62
|
+
<span class="debug-switch-track"><span class="debug-switch-thumb"></span></span>
|
|
63
|
+
<span class="debug-switch-label">Debug</span>
|
|
64
|
+
</label>
|
|
65
|
+
<button class="console-close-btn" id="consoleCloseBtn" title="關閉">×</button>
|
|
66
|
+
</div>
|
|
67
|
+
</header>
|
|
68
|
+
<div class="console-body">
|
|
69
|
+
<div class="console-tab-content active" id="tabLive">
|
|
70
|
+
<div class="live-entries" id="liveEntries"></div>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="console-tab-content" id="tabHistory">
|
|
73
|
+
<div class="history-list" id="historyList"></div>
|
|
78
74
|
</div>
|
|
79
|
-
<label class="debug-toggle" title="顯示 Debug 資訊">
|
|
80
|
-
<input type="checkbox" id="debugToggle">
|
|
81
|
-
<span>🐛</span>
|
|
82
|
-
</label>
|
|
83
75
|
</div>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
</div
|
|
89
|
-
|
|
90
|
-
return panel;
|
|
76
|
+
<footer class="console-footer">
|
|
77
|
+
<div class="console-stats" id="consoleStats"></div>
|
|
78
|
+
<button class="console-btn-close" id="consoleFooterClose">關閉</button>
|
|
79
|
+
</footer>
|
|
80
|
+
</div>`;
|
|
81
|
+
return el;
|
|
91
82
|
}
|
|
92
83
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
84
|
+
function getOrCreatePanel() {
|
|
85
|
+
let overlay = document.getElementById('aiConsoleOverlay');
|
|
86
|
+
if (!overlay) {
|
|
87
|
+
overlay = buildPanel();
|
|
88
|
+
document.body.appendChild(overlay);
|
|
89
|
+
bindEvents(overlay);
|
|
90
|
+
}
|
|
91
|
+
return overlay;
|
|
92
|
+
}
|
|
98
93
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
function bindEvents(overlay) {
|
|
95
|
+
overlay.querySelector('#consoleCloseBtn').onclick = hideConversationPanel;
|
|
96
|
+
overlay.querySelector('#consoleFooterClose').onclick = hideConversationPanel;
|
|
102
97
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
} else if (content && typeof content === 'object') {
|
|
107
|
-
contentHtml = `<pre class="entry-content">${escapeHtml(JSON.stringify(content, null, 2))}</pre>`;
|
|
108
|
-
}
|
|
98
|
+
overlay.querySelectorAll('.tab-btn').forEach(btn => {
|
|
99
|
+
btn.onclick = () => switchTab(btn.dataset.tab);
|
|
100
|
+
});
|
|
109
101
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
</summary>
|
|
118
|
-
<div class="entry-body">
|
|
119
|
-
${contentHtml}
|
|
120
|
-
</div>
|
|
121
|
-
</details>
|
|
122
|
-
`;
|
|
102
|
+
const toggle = overlay.querySelector('#consoleDebugToggle');
|
|
103
|
+
toggle.addEventListener('change', (e) => {
|
|
104
|
+
debugVisible = e.target.checked;
|
|
105
|
+
overlay.querySelectorAll('.entry-debug').forEach(el => {
|
|
106
|
+
el.style.display = debugVisible ? '' : 'none';
|
|
107
|
+
});
|
|
108
|
+
});
|
|
123
109
|
|
|
124
|
-
|
|
110
|
+
overlay.addEventListener('click', (e) => {
|
|
111
|
+
if (e.target === overlay) hideConversationPanel();
|
|
112
|
+
});
|
|
125
113
|
}
|
|
126
114
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const config = entryConfig.debug;
|
|
132
|
-
const entry = document.createElement('div');
|
|
133
|
-
entry.className = `conversation-entry ${config.className}`;
|
|
134
|
-
entry.style.borderLeftColor = config.borderColor;
|
|
115
|
+
function switchTab(tab) {
|
|
116
|
+
activeTab = tab;
|
|
117
|
+
const overlay = document.getElementById('aiConsoleOverlay');
|
|
118
|
+
if (!overlay) return;
|
|
135
119
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
120
|
+
overlay.querySelectorAll('.tab-btn').forEach(b => b.classList.toggle('active', b.dataset.tab === tab));
|
|
121
|
+
overlay.querySelector('#tabLive').classList.toggle('active', tab === 'live');
|
|
122
|
+
overlay.querySelector('#tabHistory').classList.toggle('active', tab === 'history');
|
|
139
123
|
|
|
140
|
-
|
|
124
|
+
if (tab === 'history') renderHistory();
|
|
125
|
+
}
|
|
141
126
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
if (debugInfo.maxTokens !== undefined) {
|
|
152
|
-
bodyHtml += debugRow('📏 Max Tokens', debugInfo.maxTokens);
|
|
153
|
-
}
|
|
154
|
-
if (debugInfo.tokenEstimate !== undefined) {
|
|
155
|
-
bodyHtml += debugRow('🔢 Token 預估', `~${debugInfo.tokenEstimate}`);
|
|
156
|
-
}
|
|
157
|
-
if (debugInfo.totalPromptLength !== undefined) {
|
|
158
|
-
bodyHtml += debugRow('📐 Prompt 長度', `${debugInfo.totalPromptLength} chars`);
|
|
127
|
+
function renderHistory() {
|
|
128
|
+
const list = document.getElementById('historyList');
|
|
129
|
+
if (!list) return;
|
|
130
|
+
list.innerHTML = '';
|
|
131
|
+
|
|
132
|
+
if (conversationHistory.length === 0) {
|
|
133
|
+
list.innerHTML = '<div class="history-empty">尚無歷史記錄</div>';
|
|
134
|
+
return;
|
|
159
135
|
}
|
|
160
|
-
|
|
161
|
-
|
|
136
|
+
|
|
137
|
+
conversationHistory.slice().reverse().forEach(session => {
|
|
138
|
+
const card = document.createElement('div');
|
|
139
|
+
card.className = 'history-card';
|
|
140
|
+
|
|
141
|
+
const hasError = session.entries.some(e => e.type === 'error');
|
|
142
|
+
const statusIcon = hasError ? '❌' : '✅';
|
|
143
|
+
const entryCount = session.entries.length;
|
|
144
|
+
const debugEntry = session.entries.find(e => e.type === 'debug');
|
|
145
|
+
const elapsed = debugEntry?.data?.elapsedMs;
|
|
146
|
+
|
|
147
|
+
card.innerHTML = `
|
|
148
|
+
<div class="history-card-header">
|
|
149
|
+
<span class="history-status">${statusIcon}</span>
|
|
150
|
+
<span class="history-session-title">${escapeHtml(session.title)}</span>
|
|
151
|
+
<span class="history-time">${session.startTime}</span>
|
|
152
|
+
</div>
|
|
153
|
+
<div class="history-card-meta">
|
|
154
|
+
<span class="history-meta-item">📊 ${entryCount} 筆</span>
|
|
155
|
+
<span class="history-meta-item">${session.mode || '—'}</span>
|
|
156
|
+
${elapsed ? `<span class="history-meta-item">⏱ ${elapsed}ms</span>` : ''}
|
|
157
|
+
</div>
|
|
158
|
+
<div class="history-card-entries" style="display:none;"></div>
|
|
159
|
+
`;
|
|
160
|
+
|
|
161
|
+
card.querySelector('.history-card-header').onclick = () => {
|
|
162
|
+
const body = card.querySelector('.history-card-entries');
|
|
163
|
+
const open = body.style.display !== 'none';
|
|
164
|
+
body.style.display = open ? 'none' : '';
|
|
165
|
+
if (!open && body.childElementCount === 0) {
|
|
166
|
+
session.entries.forEach(e => {
|
|
167
|
+
body.appendChild(renderEntry(e.type, e.content, e.options, e.data));
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
list.appendChild(card);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function renderEntry(type, content, options = {}, debugData = null) {
|
|
177
|
+
const cfg = ENTRY_CONFIG[type] || ENTRY_CONFIG.ai;
|
|
178
|
+
const el = document.createElement('div');
|
|
179
|
+
el.className = `console-entry ${cfg.cls}`;
|
|
180
|
+
el.style.setProperty('--entry-accent', cfg.color);
|
|
181
|
+
|
|
182
|
+
if (type === 'debug' && debugData) {
|
|
183
|
+
el.innerHTML = buildDebugHtml(debugData, options);
|
|
184
|
+
if (!debugVisible) el.style.display = 'none';
|
|
185
|
+
return el;
|
|
162
186
|
}
|
|
163
|
-
|
|
164
|
-
|
|
187
|
+
|
|
188
|
+
const titleText = options.title || cfg.label;
|
|
189
|
+
const collapsed = options.collapsed ?? (type === 'prompt' || type === 'tool');
|
|
190
|
+
const time = options.timestamp ? ts() : '';
|
|
191
|
+
|
|
192
|
+
let bodyHtml = '';
|
|
193
|
+
if (typeof content === 'string') {
|
|
194
|
+
bodyHtml = `<pre class="entry-code">${escapeHtml(content)}</pre>`;
|
|
195
|
+
} else if (content && typeof content === 'object') {
|
|
196
|
+
bodyHtml = `<pre class="entry-code">${escapeHtml(JSON.stringify(content, null, 2))}</pre>`;
|
|
165
197
|
}
|
|
166
198
|
|
|
167
|
-
|
|
199
|
+
el.innerHTML = `
|
|
200
|
+
<details ${collapsed ? '' : 'open'}>
|
|
201
|
+
<summary class="entry-row">
|
|
202
|
+
<span class="entry-chevron"></span>
|
|
203
|
+
<span class="entry-dot"></span>
|
|
204
|
+
<span class="entry-icon">${cfg.icon}</span>
|
|
205
|
+
<span class="entry-title">${escapeHtml(titleText)}</span>
|
|
206
|
+
<span class="entry-spacer"></span>
|
|
207
|
+
${options.badge ? `<span class="entry-badge">${escapeHtml(String(options.badge))}</span>` : ''}
|
|
208
|
+
${time ? `<span class="entry-time">${time}</span>` : ''}
|
|
209
|
+
</summary>
|
|
210
|
+
<div class="entry-body">${bodyHtml}</div>
|
|
211
|
+
</details>`;
|
|
212
|
+
return el;
|
|
213
|
+
}
|
|
168
214
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
215
|
+
function buildDebugHtml(debugInfo, options = {}) {
|
|
216
|
+
const time = options.timestamp ? ts() : '';
|
|
217
|
+
const titleText = options.title || 'Debug';
|
|
218
|
+
const collapsed = options.collapsed ?? true;
|
|
219
|
+
|
|
220
|
+
const rows = [];
|
|
221
|
+
if (debugInfo.elapsedMs !== undefined) rows.push(['⏱️ 耗時', `${debugInfo.elapsedMs} ms`]);
|
|
222
|
+
if (debugInfo.model) rows.push(['🧠 模型', debugInfo.model]);
|
|
223
|
+
if (debugInfo.temperature !== undefined) rows.push(['🌡️ Temperature', debugInfo.temperature]);
|
|
224
|
+
if (debugInfo.maxTokens !== undefined) rows.push(['📏 Max Tokens', debugInfo.maxTokens]);
|
|
225
|
+
if (debugInfo.tokenEstimate !== undefined) rows.push(['🔢 Token 預估', `~${debugInfo.tokenEstimate}`]);
|
|
226
|
+
if (debugInfo.totalPromptLength !== undefined) rows.push(['📐 Prompt 長度', `${debugInfo.totalPromptLength} chars`]);
|
|
227
|
+
if (debugInfo.componentCount !== undefined) rows.push(['🧩 組件數量', debugInfo.componentCount]);
|
|
228
|
+
if (debugInfo.mcpToolsCount !== undefined) rows.push(['🔧 MCP 工具數', debugInfo.mcpToolsCount]);
|
|
229
|
+
|
|
230
|
+
let grid = '<div class="dbg-grid">' + rows.map(([k, v]) =>
|
|
231
|
+
`<div class="dbg-cell"><span class="dbg-key">${k}</span><span class="dbg-val">${escapeHtml(String(v))}</span></div>`
|
|
232
|
+
).join('') + '</div>';
|
|
233
|
+
|
|
234
|
+
if (debugInfo.sections?.length) {
|
|
235
|
+
grid += `<div class="dbg-section-title">📋 提示詞區段順序(實際送出)</div>
|
|
236
|
+
<table class="dbg-table"><thead><tr><th>#</th><th>區段名稱</th><th>順序</th><th>長度</th></tr></thead><tbody>` +
|
|
237
|
+
debugInfo.sections.map((s, i) => `<tr><td>${i + 1}</td><td>${escapeHtml(s.name)}</td><td>${s.order}</td><td>${s.length} chars</td></tr>`).join('') +
|
|
238
|
+
'</tbody></table>';
|
|
177
239
|
}
|
|
178
240
|
|
|
179
|
-
if (debugInfo.promptConfigs
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
});
|
|
187
|
-
bodyHtml += '</tbody></table></div>';
|
|
241
|
+
if (debugInfo.promptConfigs?.length) {
|
|
242
|
+
grid += `<div class="dbg-section-title">⚙️ 提示詞配置(設定值)</div>
|
|
243
|
+
<table class="dbg-table"><thead><tr><th>ID</th><th>啟用</th><th>第一次</th><th>第二次</th></tr></thead><tbody>` +
|
|
244
|
+
debugInfo.promptConfigs.map(c =>
|
|
245
|
+
`<tr><td>${escapeHtml(c.id)}</td><td>${c.enabled ? '✅' : '❌'}</td><td>${c.firstOrder}</td><td>${c.secondOrder}</td></tr>`
|
|
246
|
+
).join('') +
|
|
247
|
+
'</tbody></table>';
|
|
188
248
|
}
|
|
189
249
|
|
|
190
250
|
if (debugInfo.error) {
|
|
191
|
-
|
|
251
|
+
grid += `<div class="dbg-error">❌ ${escapeHtml(debugInfo.error)}</div>`;
|
|
192
252
|
}
|
|
193
253
|
|
|
194
|
-
|
|
254
|
+
return `
|
|
195
255
|
<details ${collapsed ? '' : 'open'}>
|
|
196
|
-
<summary class="entry-
|
|
197
|
-
<span class="entry-
|
|
198
|
-
<span class="entry-
|
|
199
|
-
|
|
200
|
-
|
|
256
|
+
<summary class="entry-row">
|
|
257
|
+
<span class="entry-chevron"></span>
|
|
258
|
+
<span class="entry-dot"></span>
|
|
259
|
+
<span class="entry-icon">🐛</span>
|
|
260
|
+
<span class="entry-title">${escapeHtml(titleText)}</span>
|
|
261
|
+
<span class="entry-spacer"></span>
|
|
262
|
+
${options.badge ? `<span class="entry-badge">${escapeHtml(String(options.badge))}</span>` : ''}
|
|
263
|
+
${time ? `<span class="entry-time">${time}</span>` : ''}
|
|
201
264
|
</summary>
|
|
202
|
-
<div class="entry-body
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
</details>
|
|
206
|
-
`;
|
|
265
|
+
<div class="entry-body dbg-body">${grid}</div>
|
|
266
|
+
</details>`;
|
|
267
|
+
}
|
|
207
268
|
|
|
208
|
-
|
|
269
|
+
function saveCurrentSession() {
|
|
270
|
+
if (!currentSession || currentSession.entries.length === 0) return;
|
|
271
|
+
conversationHistory.push({ ...currentSession });
|
|
272
|
+
if (conversationHistory.length > MAX_HISTORY) conversationHistory.shift();
|
|
273
|
+
updateHistoryCount();
|
|
209
274
|
}
|
|
210
275
|
|
|
211
|
-
function
|
|
212
|
-
|
|
276
|
+
function updateHistoryCount() {
|
|
277
|
+
const el = document.getElementById('historyCount');
|
|
278
|
+
if (el) el.textContent = conversationHistory.length;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function updateStats() {
|
|
282
|
+
const el = document.getElementById('consoleStats');
|
|
283
|
+
if (!el || !currentSession) return;
|
|
284
|
+
const count = currentSession.entries.length;
|
|
285
|
+
const debugEntry = currentSession.entries.find(e => e.type === 'debug');
|
|
286
|
+
const elapsed = debugEntry?.data?.elapsedMs;
|
|
287
|
+
let text = `${count} 筆記錄`;
|
|
288
|
+
if (elapsed) text += ` · ${elapsed}ms`;
|
|
289
|
+
if (currentSession.mode) text += ` · ${currentSession.mode}`;
|
|
290
|
+
el.textContent = text;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ─── Public API (backward-compatible) ───
|
|
294
|
+
|
|
295
|
+
export function createConversationPanel() {
|
|
296
|
+
return buildPanel().querySelector('.ai-console');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function createConversationEntry(type, content, options = {}) {
|
|
300
|
+
return renderEntry(type, content, options);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function createDebugEntry(debugInfo, options = {}) {
|
|
304
|
+
return renderEntry('debug', null, options, debugInfo);
|
|
213
305
|
}
|
|
214
306
|
|
|
215
307
|
export function addConversationEntry(type, content, options = {}) {
|
|
216
|
-
const
|
|
217
|
-
if (!
|
|
308
|
+
const container = document.getElementById('liveEntries');
|
|
309
|
+
if (!container) return null;
|
|
218
310
|
|
|
219
|
-
const entry =
|
|
220
|
-
|
|
221
|
-
|
|
311
|
+
const entry = renderEntry(type, content, options);
|
|
312
|
+
container.appendChild(entry);
|
|
313
|
+
container.scrollTop = container.scrollHeight;
|
|
314
|
+
|
|
315
|
+
if (currentSession) {
|
|
316
|
+
currentSession.entries.push({ type, content, options, data: null });
|
|
317
|
+
}
|
|
318
|
+
updateStats();
|
|
222
319
|
return entry;
|
|
223
320
|
}
|
|
224
321
|
|
|
225
322
|
export function addDebugEntry(debugInfo, options = {}) {
|
|
226
|
-
const
|
|
227
|
-
if (!
|
|
323
|
+
const container = document.getElementById('liveEntries');
|
|
324
|
+
if (!container) return null;
|
|
228
325
|
|
|
229
|
-
const entry =
|
|
230
|
-
|
|
231
|
-
|
|
326
|
+
const entry = renderEntry('debug', null, options, debugInfo);
|
|
327
|
+
container.appendChild(entry);
|
|
328
|
+
container.scrollTop = container.scrollHeight;
|
|
232
329
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
entry.style.display = 'none';
|
|
330
|
+
if (currentSession) {
|
|
331
|
+
currentSession.entries.push({ type: 'debug', content: null, options, data: debugInfo });
|
|
236
332
|
}
|
|
237
|
-
|
|
333
|
+
updateStats();
|
|
238
334
|
return entry;
|
|
239
335
|
}
|
|
240
336
|
|
|
241
337
|
export function clearConversationPanel() {
|
|
242
|
-
const
|
|
243
|
-
if (
|
|
244
|
-
body.innerHTML = '';
|
|
245
|
-
}
|
|
338
|
+
const container = document.getElementById('liveEntries');
|
|
339
|
+
if (container) container.innerHTML = '';
|
|
246
340
|
}
|
|
247
341
|
|
|
248
342
|
export function updateConversationMode(mode, cliTool = null) {
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (modeElement) {
|
|
253
|
-
if (mode === 'cli' && cliTool) {
|
|
254
|
-
modeElement.textContent = `CLI (${cliTool})`;
|
|
255
|
-
} else if (mode === 'api') {
|
|
256
|
-
modeElement.textContent = 'API';
|
|
257
|
-
} else {
|
|
258
|
-
modeElement.textContent = mode;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
343
|
+
const badge = document.getElementById('consoleBadge');
|
|
344
|
+
if (!badge) return;
|
|
261
345
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
346
|
+
let text = mode;
|
|
347
|
+
if (mode === 'cli' && cliTool) text = `CLI (${cliTool})`;
|
|
348
|
+
else if (mode === 'api') text = 'API';
|
|
349
|
+
badge.textContent = text;
|
|
350
|
+
badge.className = 'console-mode-badge mode-' + (mode === 'cli' ? 'cli' : mode === 'api' ? 'api' : 'pending');
|
|
351
|
+
|
|
352
|
+
if (currentSession) {
|
|
353
|
+
currentSession.mode = text;
|
|
269
354
|
}
|
|
270
355
|
}
|
|
271
356
|
|
|
272
357
|
export function updateConversationTitle(title) {
|
|
273
|
-
const
|
|
274
|
-
if (
|
|
275
|
-
|
|
276
|
-
}
|
|
358
|
+
const el = document.getElementById('consoleTitle');
|
|
359
|
+
if (el) el.textContent = title;
|
|
360
|
+
if (currentSession) currentSession.title = title;
|
|
277
361
|
}
|
|
278
362
|
|
|
279
363
|
export function showConversationPanel() {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const debugToggle = panel.querySelector('#debugToggle');
|
|
294
|
-
if (debugToggle) {
|
|
295
|
-
debugToggle.addEventListener('change', (e) => {
|
|
296
|
-
const show = e.target.checked;
|
|
297
|
-
panel.querySelectorAll('.entry-debug').forEach(el => {
|
|
298
|
-
el.style.display = show ? '' : 'none';
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
panel.style.display = 'flex';
|
|
364
|
+
const overlay = getOrCreatePanel();
|
|
365
|
+
overlay.style.display = 'flex';
|
|
366
|
+
|
|
367
|
+
saveCurrentSession();
|
|
368
|
+
currentSession = {
|
|
369
|
+
id: newSessionId(),
|
|
370
|
+
title: 'AI Console',
|
|
371
|
+
mode: '',
|
|
372
|
+
startTime: ts(),
|
|
373
|
+
entries: []
|
|
374
|
+
};
|
|
375
|
+
|
|
304
376
|
clearConversationPanel();
|
|
377
|
+
switchTab('live');
|
|
378
|
+
updateHistoryCount();
|
|
379
|
+
updateStats();
|
|
305
380
|
}
|
|
306
381
|
|
|
307
382
|
export function hideConversationPanel() {
|
|
308
|
-
const
|
|
309
|
-
if (
|
|
310
|
-
panel.style.display = 'none';
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function formatTimestamp(timestamp) {
|
|
315
|
-
const date = new Date(timestamp);
|
|
316
|
-
return date.toLocaleTimeString('zh-TW', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
383
|
+
const overlay = document.getElementById('aiConsoleOverlay');
|
|
384
|
+
if (overlay) overlay.style.display = 'none';
|
|
317
385
|
}
|
|
318
386
|
|
|
319
387
|
export function addThinkingEntry(message = 'AI 思考中...') {
|
|
@@ -324,9 +392,10 @@ export function addThinkingEntry(message = 'AI 思考中...') {
|
|
|
324
392
|
}
|
|
325
393
|
|
|
326
394
|
export function removeThinkingEntry() {
|
|
327
|
-
const
|
|
328
|
-
if (!
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
395
|
+
const container = document.getElementById('liveEntries');
|
|
396
|
+
if (!container) return;
|
|
397
|
+
container.querySelectorAll('.entry-thinking').forEach(el => el.remove());
|
|
398
|
+
if (currentSession) {
|
|
399
|
+
currentSession.entries = currentSession.entries.filter(e => e.type !== 'thinking');
|
|
400
|
+
}
|
|
332
401
|
}
|