@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.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * conversation-panel.js
3
- * 對話面板元件 - 顯示 AI 對話流程
4
- * 支援 7 種對話條目類型: prompt, thinking, tool, result, ai, error, debug
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 entryConfig = {
20
- prompt: {
21
- icon: '📤',
22
- title: '提示詞',
23
- className: 'entry-prompt',
24
- borderColor: 'var(--accent-blue)'
25
- },
26
- thinking: {
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
- export function createConversationPanel() {
65
- const panel = document.createElement('div');
66
- panel.id = 'conversationPanel';
67
- panel.className = 'conversation-panel';
68
- panel.innerHTML = `
69
- <div class="conversation-header">
70
- <div class="conversation-title">
71
- <span class="icon">💬</span>
72
- <span id="conversationTitle">AI 對話</span>
73
- </div>
74
- <div class="conversation-header-right">
75
- <div class="conversation-mode">
76
- <span class="mode-indicator" id="conversationModeIndicator"></span>
77
- <span id="conversationMode">準備中</span>
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="關閉">&times;</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
- </div>
85
- <div class="conversation-body" id="conversationBody"></div>
86
- <div class="conversation-footer">
87
- <button type="button" id="closeConversation" class="btn btn-secondary">關閉</button>
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
- export function createConversationEntry(type, content, options = {}) {
94
- const config = entryConfig[type] || entryConfig.ai;
95
- const entry = document.createElement('div');
96
- entry.className = `conversation-entry ${config.className}`;
97
- entry.style.borderLeftColor = config.borderColor;
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
- const titleText = options.title || config.title;
100
- const collapsed = options.collapsed ?? (type === 'prompt' || type === 'tool' || type === 'debug');
101
- const timestamp = options.timestamp ? formatTimestamp(options.timestamp) : '';
94
+ function bindEvents(overlay) {
95
+ overlay.querySelector('#consoleCloseBtn').onclick = hideConversationPanel;
96
+ overlay.querySelector('#consoleFooterClose').onclick = hideConversationPanel;
102
97
 
103
- let contentHtml = '';
104
- if (typeof content === 'string') {
105
- contentHtml = `<pre class="entry-content">${escapeHtml(content)}</pre>`;
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
- entry.innerHTML = `
111
- <details ${collapsed ? '' : 'open'}>
112
- <summary class="entry-summary">
113
- <span class="entry-icon">${config.icon}</span>
114
- <span class="entry-title">${titleText}</span>
115
- ${timestamp ? `<span class="entry-timestamp">${timestamp}</span>` : ''}
116
- ${options.badge ? `<span class="entry-badge">${options.badge}</span>` : ''}
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
- return entry;
110
+ overlay.addEventListener('click', (e) => {
111
+ if (e.target === overlay) hideConversationPanel();
112
+ });
125
113
  }
126
114
 
127
- /**
128
- * 建立 Debug 資訊條目(結構化表格)
129
- */
130
- export function createDebugEntry(debugInfo, options = {}) {
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
- const titleText = options.title || config.title;
137
- const collapsed = options.collapsed ?? true;
138
- const timestamp = options.timestamp ? formatTimestamp(options.timestamp) : '';
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
- let bodyHtml = '<div class="debug-info-grid">';
124
+ if (tab === 'history') renderHistory();
125
+ }
141
126
 
142
- if (debugInfo.elapsedMs !== undefined) {
143
- bodyHtml += debugRow('⏱️ 耗時', `${debugInfo.elapsedMs} ms`);
144
- }
145
- if (debugInfo.model) {
146
- bodyHtml += debugRow('🧠 模型', debugInfo.model);
147
- }
148
- if (debugInfo.temperature !== undefined) {
149
- bodyHtml += debugRow('🌡️ Temperature', debugInfo.temperature);
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
- if (debugInfo.componentCount !== undefined) {
161
- bodyHtml += debugRow('🧩 組件數量', debugInfo.componentCount);
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
- if (debugInfo.mcpToolsCount !== undefined) {
164
- bodyHtml += debugRow('🔧 MCP 工具數', debugInfo.mcpToolsCount);
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
- bodyHtml += '</div>';
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
- if (debugInfo.sections && debugInfo.sections.length > 0) {
170
- bodyHtml += '<div class="debug-sections">';
171
- bodyHtml += '<div class="debug-section-title">📋 提示詞區段順序(實際送出)</div>';
172
- bodyHtml += '<table class="debug-table"><thead><tr><th>#</th><th>區段名稱</th><th>順序</th><th>長度</th></tr></thead><tbody>';
173
- debugInfo.sections.forEach((s, i) => {
174
- bodyHtml += `<tr><td>${i + 1}</td><td>${escapeHtml(s.name)}</td><td>${s.order}</td><td>${s.length} chars</td></tr>`;
175
- });
176
- bodyHtml += '</tbody></table></div>';
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 && debugInfo.promptConfigs.length > 0) {
180
- bodyHtml += '<div class="debug-sections">';
181
- bodyHtml += '<div class="debug-section-title">⚙️ 提示詞配置(設定值)</div>';
182
- bodyHtml += '<table class="debug-table"><thead><tr><th>ID</th><th>啟用</th><th>第一次</th><th>第二次</th></tr></thead><tbody>';
183
- debugInfo.promptConfigs.forEach(c => {
184
- const enabledIcon = c.enabled ? '' : '❌';
185
- bodyHtml += `<tr><td>${escapeHtml(c.id)}</td><td>${enabledIcon}</td><td>${c.firstOrder}</td><td>${c.secondOrder}</td></tr>`;
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
- bodyHtml += `<div class="debug-error">❌ 錯誤: ${escapeHtml(debugInfo.error)}</div>`;
251
+ grid += `<div class="dbg-error">❌ ${escapeHtml(debugInfo.error)}</div>`;
192
252
  }
193
253
 
194
- entry.innerHTML = `
254
+ return `
195
255
  <details ${collapsed ? '' : 'open'}>
196
- <summary class="entry-summary">
197
- <span class="entry-icon">${config.icon}</span>
198
- <span class="entry-title">${titleText}</span>
199
- ${timestamp ? `<span class="entry-timestamp">${timestamp}</span>` : ''}
200
- ${options.badge ? `<span class="entry-badge">${options.badge}</span>` : ''}
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 debug-body">
203
- ${bodyHtml}
204
- </div>
205
- </details>
206
- `;
265
+ <div class="entry-body dbg-body">${grid}</div>
266
+ </details>`;
267
+ }
207
268
 
208
- return entry;
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 debugRow(label, value) {
212
- return `<div class="debug-row"><span class="debug-label">${label}</span><span class="debug-value">${escapeHtml(String(value))}</span></div>`;
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 body = document.getElementById('conversationBody');
217
- if (!body) return null;
308
+ const container = document.getElementById('liveEntries');
309
+ if (!container) return null;
218
310
 
219
- const entry = createConversationEntry(type, content, options);
220
- body.appendChild(entry);
221
- body.scrollTop = body.scrollHeight;
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 body = document.getElementById('conversationBody');
227
- if (!body) return null;
323
+ const container = document.getElementById('liveEntries');
324
+ if (!container) return null;
228
325
 
229
- const entry = createDebugEntry(debugInfo, options);
230
- body.appendChild(entry);
231
- body.scrollTop = body.scrollHeight;
326
+ const entry = renderEntry('debug', null, options, debugInfo);
327
+ container.appendChild(entry);
328
+ container.scrollTop = container.scrollHeight;
232
329
 
233
- const debugToggle = document.getElementById('debugToggle');
234
- if (debugToggle && !debugToggle.checked) {
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 body = document.getElementById('conversationBody');
243
- if (body) {
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 modeElement = document.getElementById('conversationMode');
250
- const indicator = document.getElementById('conversationModeIndicator');
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
- if (indicator) {
263
- indicator.className = 'mode-indicator';
264
- if (mode === 'cli') {
265
- indicator.classList.add('mode-cli');
266
- } else if (mode === 'api') {
267
- indicator.classList.add('mode-api');
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 titleElement = document.getElementById('conversationTitle');
274
- if (titleElement) {
275
- titleElement.textContent = title;
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
- let panel = document.getElementById('aiConversationPanel');
281
- if (!panel) {
282
- panel = document.createElement('div');
283
- panel.id = 'aiConversationPanel';
284
- panel.className = 'ai-conversation-overlay';
285
- panel.appendChild(createConversationPanel());
286
- document.body.appendChild(panel);
287
-
288
- const closeBtn = panel.querySelector('#closeConversation');
289
- if (closeBtn) {
290
- closeBtn.onclick = hideConversationPanel;
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 panel = document.getElementById('aiConversationPanel');
309
- if (panel) {
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 body = document.getElementById('conversationBody');
328
- if (!body) return;
329
-
330
- const thinkingEntries = body.querySelectorAll('.entry-thinking');
331
- thinkingEntries.forEach(entry => entry.remove());
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
  }