@hirohsu/user-web-feedback 2.8.20 → 2.8.22

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
  }
@@ -681,6 +681,7 @@
681
681
  showToast("提示詞配置已儲存", "success");
682
682
  if (data.prompts) {
683
683
  promptConfigs = data.prompts;
684
+ renderPromptConfigs();
684
685
  }
685
686
  } else {
686
687
  showToast(`儲存失敗: ${data.error || "未知錯誤"}`, "error");
@@ -1961,303 +1961,494 @@ textarea.form-control {
1961
1961
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
1962
1962
  }
1963
1963
 
1964
- /* ============ Conversation Panel Styles ============ */
1964
+ /* ============ AI Console ============ */
1965
1965
 
1966
- .ai-conversation-overlay {
1966
+ :root {
1967
+ --console-blue: #3b82f6;
1968
+ --console-green: #22c55e;
1969
+ --console-yellow: #eab308;
1970
+ --console-red: #ef4444;
1971
+ --console-purple: #a855f7;
1972
+ --console-cyan: #06b6d4;
1973
+ --console-orange: #f97316;
1974
+ }
1975
+
1976
+ .ai-console-overlay {
1967
1977
  position: fixed;
1968
- top: 0;
1969
- left: 0;
1970
- right: 0;
1971
- bottom: 0;
1972
- background: rgba(0, 0, 0, 0.85);
1978
+ inset: 0;
1979
+ background: rgba(0, 0, 0, 0.7);
1980
+ backdrop-filter: blur(4px);
1973
1981
  z-index: 2000;
1974
1982
  display: flex;
1975
1983
  align-items: center;
1976
1984
  justify-content: center;
1985
+ animation: fadeIn 0.15s ease;
1977
1986
  }
1978
1987
 
1979
- .conversation-panel {
1988
+ @keyframes fadeIn { from { opacity: 0 } to { opacity: 1 } }
1989
+
1990
+ .ai-console {
1980
1991
  background: var(--bg-secondary);
1981
1992
  border: 1px solid var(--border-color);
1982
- border-radius: var(--radius-lg);
1983
- width: 90%;
1984
- max-width: 900px;
1985
- height: 85vh;
1986
- max-height: 85vh;
1993
+ border-radius: 16px;
1994
+ width: 92%;
1995
+ max-width: 960px;
1996
+ height: 88vh;
1997
+ max-height: 88vh;
1987
1998
  display: flex;
1988
1999
  flex-direction: column;
1989
- box-shadow: var(--shadow-lg);
2000
+ box-shadow: 0 24px 64px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.05);
1990
2001
  overflow: hidden;
2002
+ animation: slideUp 0.2s ease;
1991
2003
  }
1992
2004
 
1993
- .conversation-header {
2005
+ @keyframes slideUp { from { transform: translateY(16px); opacity: 0 } to { transform: translateY(0); opacity: 1 } }
2006
+
2007
+ /* ── Header ── */
2008
+ .console-header {
1994
2009
  display: flex;
1995
2010
  justify-content: space-between;
1996
2011
  align-items: center;
1997
- padding: var(--spacing-md) var(--spacing-lg);
2012
+ padding: 12px 20px;
1998
2013
  border-bottom: 1px solid var(--border-color);
1999
2014
  background: var(--bg-tertiary);
2000
- border-radius: var(--radius-lg) var(--radius-lg) 0 0;
2015
+ border-radius: 16px 16px 0 0;
2016
+ gap: 12px;
2017
+ flex-wrap: wrap;
2001
2018
  }
2002
2019
 
2003
- .conversation-title {
2020
+ .console-title-group {
2004
2021
  display: flex;
2005
2022
  align-items: center;
2006
- gap: var(--spacing-sm);
2023
+ gap: 10px;
2024
+ min-width: 0;
2025
+ }
2026
+
2027
+ .console-logo {
2028
+ font-size: 20px;
2029
+ filter: drop-shadow(0 0 6px rgba(59,130,246,0.5));
2030
+ }
2031
+
2032
+ .console-title {
2033
+ margin: 0;
2007
2034
  font-size: 16px;
2008
- font-weight: 600;
2035
+ font-weight: 700;
2009
2036
  color: var(--text-primary);
2037
+ white-space: nowrap;
2038
+ overflow: hidden;
2039
+ text-overflow: ellipsis;
2010
2040
  }
2011
2041
 
2012
- .conversation-mode {
2042
+ .console-mode-badge {
2043
+ font-size: 11px;
2044
+ font-weight: 600;
2045
+ padding: 3px 10px;
2046
+ border-radius: 20px;
2047
+ background: var(--bg-hover);
2048
+ color: var(--text-secondary);
2049
+ white-space: nowrap;
2050
+ transition: background 0.2s, color 0.2s;
2051
+ }
2052
+
2053
+ .console-mode-badge.mode-api {
2054
+ background: rgba(59,130,246,0.15);
2055
+ color: var(--console-blue);
2056
+ }
2057
+
2058
+ .console-mode-badge.mode-cli {
2059
+ background: rgba(168,85,247,0.15);
2060
+ color: var(--console-purple);
2061
+ }
2062
+
2063
+ .console-actions {
2013
2064
  display: flex;
2014
2065
  align-items: center;
2015
- gap: var(--spacing-sm);
2066
+ gap: 12px;
2067
+ }
2068
+
2069
+ /* ── Tabs ── */
2070
+ .console-tabs {
2071
+ display: flex;
2072
+ background: var(--bg-primary);
2073
+ border-radius: 8px;
2074
+ padding: 2px;
2075
+ gap: 2px;
2076
+ }
2077
+
2078
+ .tab-btn {
2079
+ padding: 5px 14px;
2016
2080
  font-size: 12px;
2081
+ font-weight: 600;
2082
+ border: none;
2083
+ border-radius: 6px;
2084
+ background: transparent;
2017
2085
  color: var(--text-secondary);
2018
- padding: 4px 12px;
2086
+ cursor: pointer;
2087
+ transition: all 0.15s;
2088
+ display: flex;
2089
+ align-items: center;
2090
+ gap: 6px;
2091
+ }
2092
+
2093
+ .tab-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
2094
+ .tab-btn.active { background: var(--bg-secondary); color: var(--text-primary); box-shadow: 0 1px 3px rgba(0,0,0,0.15); }
2095
+
2096
+ .history-count {
2097
+ font-size: 10px;
2098
+ min-width: 18px;
2099
+ height: 18px;
2100
+ display: inline-flex;
2101
+ align-items: center;
2102
+ justify-content: center;
2019
2103
  background: var(--bg-hover);
2020
- border-radius: 12px;
2104
+ border-radius: 9px;
2105
+ color: var(--text-secondary);
2021
2106
  }
2022
2107
 
2023
- .mode-indicator {
2024
- width: 8px;
2025
- height: 8px;
2026
- border-radius: 50%;
2027
- background: var(--text-muted);
2108
+ /* ── Debug Switch ── */
2109
+ .debug-switch {
2110
+ display: flex;
2111
+ align-items: center;
2112
+ gap: 6px;
2113
+ cursor: pointer;
2114
+ user-select: none;
2028
2115
  }
2029
2116
 
2030
- .mode-indicator.mode-api {
2031
- background: var(--accent-blue);
2117
+ .debug-switch input { display: none; }
2118
+
2119
+ .debug-switch-track {
2120
+ width: 32px;
2121
+ height: 18px;
2122
+ border-radius: 9px;
2123
+ background: var(--bg-hover);
2124
+ position: relative;
2125
+ transition: background 0.2s;
2032
2126
  }
2033
2127
 
2034
- .mode-indicator.mode-cli {
2035
- background: var(--accent-purple, #a855f7);
2128
+ .debug-switch input:checked + .debug-switch-track {
2129
+ background: var(--console-orange);
2036
2130
  }
2037
2131
 
2038
- .conversation-body {
2039
- flex: 1 1 0;
2040
- min-height: 0;
2041
- overflow-y: auto;
2042
- overflow-x: hidden;
2043
- padding: var(--spacing-md) var(--spacing-lg);
2132
+ .debug-switch-thumb {
2133
+ position: absolute;
2134
+ top: 2px;
2135
+ left: 2px;
2136
+ width: 14px;
2137
+ height: 14px;
2138
+ border-radius: 50%;
2139
+ background: white;
2140
+ transition: transform 0.2s;
2141
+ box-shadow: 0 1px 2px rgba(0,0,0,0.2);
2044
2142
  }
2045
2143
 
2046
- .conversation-entry {
2047
- border-left: 3px solid var(--border-color);
2048
- border-radius: var(--radius-sm);
2049
- background: var(--bg-tertiary);
2050
- overflow: hidden;
2051
- margin-bottom: var(--spacing-md);
2144
+ .debug-switch input:checked + .debug-switch-track .debug-switch-thumb {
2145
+ transform: translateX(14px);
2052
2146
  }
2053
2147
 
2054
- .conversation-entry.entry-prompt {
2055
- border-left-color: var(--accent-blue);
2148
+ .debug-switch-label {
2149
+ font-size: 12px;
2150
+ font-weight: 500;
2151
+ color: var(--text-secondary);
2056
2152
  }
2057
2153
 
2058
- .conversation-entry.entry-thinking {
2059
- border-left-color: var(--accent-yellow);
2154
+ /* ── Close Button ── */
2155
+ .console-close-btn {
2156
+ width: 28px;
2157
+ height: 28px;
2158
+ border: none;
2159
+ border-radius: 8px;
2160
+ background: transparent;
2161
+ color: var(--text-secondary);
2162
+ font-size: 18px;
2163
+ cursor: pointer;
2164
+ display: flex;
2165
+ align-items: center;
2166
+ justify-content: center;
2167
+ transition: all 0.15s;
2168
+ line-height: 1;
2060
2169
  }
2061
2170
 
2062
- .conversation-entry.entry-tool {
2063
- border-left-color: var(--accent-purple, #a855f7);
2171
+ .console-close-btn:hover {
2172
+ background: rgba(239,68,68,0.15);
2173
+ color: var(--console-red);
2064
2174
  }
2065
2175
 
2066
- .conversation-entry.entry-result {
2067
- border-left-color: var(--accent-cyan, #06b6d4);
2176
+ /* ── Body ── */
2177
+ .console-body {
2178
+ flex: 1 1 0;
2179
+ min-height: 0;
2180
+ overflow: hidden;
2181
+ position: relative;
2068
2182
  }
2069
2183
 
2070
- .conversation-entry.entry-ai {
2071
- border-left-color: var(--accent-green);
2184
+ .console-tab-content {
2185
+ display: none;
2186
+ height: 100%;
2187
+ overflow-y: auto;
2188
+ overflow-x: hidden;
2189
+ padding: 16px 20px;
2072
2190
  }
2073
2191
 
2074
- .conversation-entry.entry-error {
2075
- border-left-color: var(--accent-red);
2192
+ .console-tab-content.active { display: block; }
2193
+
2194
+ /* ── Entry ── */
2195
+ .console-entry {
2196
+ border-radius: 10px;
2197
+ background: var(--bg-tertiary);
2198
+ margin-bottom: 10px;
2199
+ border: 1px solid var(--border-color);
2200
+ overflow: hidden;
2201
+ position: relative;
2202
+ transition: border-color 0.2s;
2076
2203
  }
2077
2204
 
2078
- .conversation-entry.entry-debug {
2079
- border-left-color: var(--accent-orange, #f97316);
2205
+ .console-entry::before {
2206
+ content: '';
2207
+ position: absolute;
2208
+ left: 0;
2209
+ top: 0;
2210
+ bottom: 0;
2211
+ width: 3px;
2212
+ background: var(--entry-accent, var(--border-color));
2213
+ border-radius: 3px 0 0 3px;
2080
2214
  }
2081
2215
 
2082
- .conversation-header-right {
2216
+ .console-entry:hover { border-color: color-mix(in srgb, var(--entry-accent) 30%, var(--border-color)); }
2217
+
2218
+ .entry-row {
2219
+ padding: 10px 14px;
2220
+ cursor: pointer;
2083
2221
  display: flex;
2084
2222
  align-items: center;
2085
- gap: var(--spacing-sm);
2223
+ gap: 8px;
2224
+ user-select: none;
2225
+ list-style: none;
2226
+ background: transparent;
2227
+ transition: background 0.1s;
2086
2228
  }
2087
2229
 
2088
- .debug-toggle {
2230
+ .entry-row:hover { background: var(--bg-hover); }
2231
+ .entry-row::-webkit-details-marker { display: none; }
2232
+
2233
+ .entry-chevron {
2234
+ width: 16px;
2235
+ height: 16px;
2089
2236
  display: flex;
2090
2237
  align-items: center;
2091
- gap: 4px;
2092
- cursor: pointer;
2093
- font-size: 14px;
2094
- user-select: none;
2095
- opacity: 0.6;
2096
- transition: opacity 0.2s;
2238
+ justify-content: center;
2239
+ font-size: 10px;
2240
+ color: var(--text-muted);
2241
+ transition: transform 0.2s;
2097
2242
  }
2098
2243
 
2099
- .debug-toggle:hover {
2100
- opacity: 1;
2101
- }
2244
+ .entry-chevron::after { content: '▶'; }
2245
+ details[open] > .entry-row .entry-chevron { transform: rotate(90deg); }
2102
2246
 
2103
- .debug-toggle input[type="checkbox"] {
2104
- width: 14px;
2105
- height: 14px;
2106
- cursor: pointer;
2247
+ .entry-dot {
2248
+ width: 8px;
2249
+ height: 8px;
2250
+ border-radius: 50%;
2251
+ background: var(--entry-accent, var(--text-muted));
2252
+ flex-shrink: 0;
2107
2253
  }
2108
2254
 
2109
- .debug-info-grid {
2110
- display: grid;
2111
- grid-template-columns: 1fr 1fr;
2112
- gap: 4px 16px;
2113
- margin-bottom: 8px;
2114
- }
2255
+ .entry-icon { font-size: 14px; flex-shrink: 0; }
2115
2256
 
2116
- .debug-row {
2117
- display: flex;
2118
- justify-content: space-between;
2119
- padding: 3px 8px;
2120
- border-radius: 4px;
2121
- background: var(--bg-primary);
2122
- font-size: 12px;
2257
+ .entry-title {
2258
+ font-weight: 600;
2259
+ font-size: 13px;
2260
+ color: var(--text-primary);
2261
+ white-space: nowrap;
2262
+ overflow: hidden;
2263
+ text-overflow: ellipsis;
2123
2264
  }
2124
2265
 
2125
- .debug-label {
2266
+ .entry-spacer { flex: 1; }
2267
+
2268
+ .entry-badge {
2269
+ font-size: 11px;
2270
+ padding: 2px 8px;
2271
+ background: var(--bg-primary);
2272
+ border-radius: 10px;
2126
2273
  color: var(--text-secondary);
2127
2274
  font-weight: 500;
2275
+ white-space: nowrap;
2128
2276
  }
2129
2277
 
2130
- .debug-value {
2131
- color: var(--text-primary);
2278
+ .entry-time {
2279
+ font-size: 11px;
2280
+ color: var(--text-muted);
2132
2281
  font-family: "Consolas", "Monaco", monospace;
2282
+ white-space: nowrap;
2133
2283
  }
2134
2284
 
2135
- .debug-sections {
2136
- margin-top: 8px;
2285
+ .entry-body {
2286
+ padding: 12px 14px;
2287
+ border-top: 1px solid var(--border-color);
2137
2288
  }
2138
2289
 
2139
- .debug-section-title {
2290
+ .entry-code {
2291
+ margin: 0;
2292
+ padding: 10px 12px;
2293
+ background: var(--bg-primary);
2294
+ border: 1px solid var(--border-color);
2295
+ border-radius: 8px;
2296
+ font-family: "Consolas", "Monaco", "Courier New", monospace;
2297
+ font-size: 12px;
2298
+ line-height: 1.6;
2299
+ white-space: pre-wrap;
2300
+ word-break: break-word;
2301
+ max-height: 340px;
2302
+ overflow-y: auto;
2303
+ color: var(--text-primary);
2304
+ }
2305
+
2306
+ /* ── Debug Body ── */
2307
+ .dbg-body { font-size: 12px; }
2308
+
2309
+ .dbg-grid {
2310
+ display: grid;
2311
+ grid-template-columns: 1fr 1fr;
2312
+ gap: 4px;
2313
+ margin-bottom: 10px;
2314
+ }
2315
+
2316
+ .dbg-cell {
2317
+ display: flex;
2318
+ justify-content: space-between;
2319
+ padding: 4px 10px;
2320
+ border-radius: 6px;
2321
+ background: var(--bg-primary);
2322
+ }
2323
+
2324
+ .dbg-key { color: var(--text-secondary); font-weight: 500; }
2325
+ .dbg-val { color: var(--text-primary); font-family: "Consolas","Monaco",monospace; }
2326
+
2327
+ .dbg-section-title {
2140
2328
  font-size: 12px;
2141
2329
  font-weight: 600;
2142
2330
  color: var(--text-secondary);
2143
- margin-bottom: 4px;
2331
+ margin: 10px 0 4px;
2144
2332
  }
2145
2333
 
2146
- .debug-table {
2334
+ .dbg-table {
2147
2335
  width: 100%;
2148
2336
  border-collapse: collapse;
2149
2337
  font-size: 12px;
2150
- font-family: "Consolas", "Monaco", monospace;
2338
+ font-family: "Consolas","Monaco",monospace;
2339
+ margin-bottom: 8px;
2151
2340
  }
2152
2341
 
2153
- .debug-table th,
2154
- .debug-table td {
2342
+ .dbg-table th, .dbg-table td {
2155
2343
  padding: 4px 8px;
2156
2344
  border: 1px solid var(--border-color);
2157
2345
  text-align: left;
2158
2346
  }
2159
2347
 
2160
- .debug-table th {
2161
- background: var(--bg-hover);
2162
- color: var(--text-secondary);
2163
- font-weight: 600;
2164
- }
2165
-
2166
- .debug-table td {
2167
- color: var(--text-primary);
2168
- }
2348
+ .dbg-table th { background: var(--bg-hover); color: var(--text-secondary); font-weight: 600; }
2349
+ .dbg-table td { color: var(--text-primary); }
2169
2350
 
2170
- .debug-error {
2351
+ .dbg-error {
2171
2352
  margin-top: 8px;
2172
- padding: 6px 10px;
2173
- background: rgba(239, 68, 68, 0.1);
2174
- border: 1px solid rgba(239, 68, 68, 0.3);
2175
- border-radius: 4px;
2353
+ padding: 8px 12px;
2354
+ background: rgba(239,68,68,0.08);
2355
+ border: 1px solid rgba(239,68,68,0.25);
2356
+ border-radius: 8px;
2176
2357
  font-size: 12px;
2177
- color: var(--accent-red);
2358
+ color: var(--console-red);
2178
2359
  }
2179
2360
 
2180
- .debug-body {
2181
- font-size: 12px;
2361
+ /* ── History ── */
2362
+ .history-empty {
2363
+ text-align: center;
2364
+ padding: 48px 20px;
2365
+ color: var(--text-muted);
2366
+ font-size: 14px;
2182
2367
  }
2183
2368
 
2184
- .entry-summary {
2185
- padding: var(--spacing-sm) var(--spacing-md);
2186
- cursor: pointer;
2187
- display: flex;
2188
- align-items: center;
2189
- gap: var(--spacing-sm);
2190
- user-select: none;
2191
- list-style: none;
2192
- background: var(--bg-hover);
2369
+ .history-card {
2370
+ border: 1px solid var(--border-color);
2371
+ border-radius: 10px;
2372
+ background: var(--bg-tertiary);
2373
+ margin-bottom: 8px;
2374
+ overflow: hidden;
2375
+ transition: border-color 0.2s;
2193
2376
  }
2194
2377
 
2195
- .entry-summary::-webkit-details-marker {
2196
- display: none;
2197
- }
2378
+ .history-card:hover { border-color: var(--console-blue); }
2198
2379
 
2199
- .entry-summary::before {
2200
- content: "▶";
2201
- font-size: 10px;
2202
- transition: transform 0.2s ease;
2203
- color: var(--text-secondary);
2380
+ .history-card-header {
2381
+ display: flex;
2382
+ align-items: center;
2383
+ gap: 8px;
2384
+ padding: 10px 14px;
2385
+ cursor: pointer;
2386
+ transition: background 0.15s;
2204
2387
  }
2205
2388
 
2206
- details[open] > .entry-summary::before {
2207
- transform: rotate(90deg);
2208
- }
2389
+ .history-card-header:hover { background: var(--bg-hover); }
2209
2390
 
2210
- .entry-icon {
2211
- font-size: 14px;
2212
- }
2391
+ .history-status { font-size: 14px; }
2213
2392
 
2214
- .entry-title {
2393
+ .history-session-title {
2215
2394
  flex: 1;
2216
2395
  font-weight: 600;
2217
2396
  font-size: 13px;
2218
2397
  color: var(--text-primary);
2398
+ overflow: hidden;
2399
+ text-overflow: ellipsis;
2400
+ white-space: nowrap;
2219
2401
  }
2220
2402
 
2221
- .entry-timestamp {
2403
+ .history-time {
2222
2404
  font-size: 11px;
2223
2405
  color: var(--text-muted);
2224
- font-family: "Consolas", "Monaco", monospace;
2406
+ font-family: "Consolas","Monaco",monospace;
2225
2407
  }
2226
2408
 
2227
- .entry-badge {
2228
- font-size: 12px;
2229
- padding: 2px 8px;
2230
- background: var(--bg-secondary);
2231
- border-radius: 10px;
2409
+ .history-card-meta {
2410
+ display: flex;
2411
+ gap: 12px;
2412
+ padding: 0 14px 10px;
2413
+ }
2414
+
2415
+ .history-meta-item {
2416
+ font-size: 11px;
2232
2417
  color: var(--text-secondary);
2233
2418
  }
2234
2419
 
2235
- .entry-body {
2236
- padding: var(--spacing-md);
2420
+ .history-card-entries {
2421
+ padding: 8px 12px 12px;
2237
2422
  border-top: 1px solid var(--border-color);
2238
2423
  }
2239
2424
 
2240
- .entry-content {
2241
- margin: 0;
2242
- padding: var(--spacing-sm);
2243
- background: var(--bg-primary);
2244
- border: 1px solid var(--border-color);
2245
- border-radius: var(--radius-sm);
2246
- font-family: "Consolas", "Monaco", monospace;
2425
+ /* ── Footer ── */
2426
+ .console-footer {
2427
+ display: flex;
2428
+ justify-content: space-between;
2429
+ align-items: center;
2430
+ padding: 10px 20px;
2431
+ border-top: 1px solid var(--border-color);
2432
+ background: var(--bg-tertiary);
2433
+ border-radius: 0 0 16px 16px;
2434
+ }
2435
+
2436
+ .console-stats {
2247
2437
  font-size: 12px;
2248
- line-height: 1.5;
2249
- white-space: pre-wrap;
2250
- word-break: break-word;
2251
- max-height: 300px;
2252
- overflow-y: auto;
2438
+ color: var(--text-muted);
2439
+ font-family: "Consolas","Monaco",monospace;
2440
+ }
2441
+
2442
+ .console-btn-close {
2443
+ padding: 6px 20px;
2444
+ font-size: 13px;
2445
+ font-weight: 600;
2446
+ border: 1px solid var(--border-color);
2447
+ border-radius: 8px;
2448
+ background: var(--bg-primary);
2253
2449
  color: var(--text-primary);
2450
+ cursor: pointer;
2451
+ transition: all 0.15s;
2254
2452
  }
2255
2453
 
2256
- .conversation-footer {
2257
- display: flex;
2258
- justify-content: flex-end;
2259
- padding: var(--spacing-md) var(--spacing-lg);
2260
- border-top: 1px solid var(--border-color);
2261
- background: var(--bg-tertiary);
2262
- border-radius: 0 0 var(--radius-lg) var(--radius-lg);
2263
- }
2454
+ .console-btn-close:hover { background: var(--bg-hover); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hirohsu/user-web-feedback",
3
- "version": "2.8.20",
3
+ "version": "2.8.22",
4
4
  "description": "基於Node.js的MCP回饋收集器 - 支持AI工作彙報和用戶回饋收集",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {