更新回饋系統,新增多語言支持的狀態指示器、圖片上傳組件及設定卡片,重構相關樣式以提升使用體驗。移除不必要的超時設置,簡化頁面結構。

This commit is contained in:
Minidoracat 2025-06-06 17:56:31 +08:00
parent d5494943dd
commit 4381da3faf
11 changed files with 912 additions and 1024 deletions

View File

@ -122,7 +122,19 @@
"connecting": "Connecting...",
"disconnected": "Disconnected",
"reconnecting": "Reconnecting...",
"error": "Connection Error"
"error": "Connection Error",
"waiting": {
"title": "Waiting for Feedback",
"message": "Please provide your feedback"
},
"processing": {
"title": "Processing",
"message": "Submitting your feedback..."
},
"submitted": {
"title": "Feedback Submitted",
"message": "Waiting for next MCP call"
}
},
"notifications": {
"feedback_sent": "Feedback sent",
@ -131,6 +143,11 @@
"connection_lost": "Connection lost",
"connection_restored": "Connection restored"
},
"connection": {
"waiting": "Connected - Waiting for feedback",
"submitted": "Connected - Feedback submitted",
"processing": "Connected - Processing"
},
"errors": {
"connection_failed": "Connection failed",
"upload_failed": "Upload failed",
@ -142,6 +159,8 @@
"ok": "OK",
"cancel": "❌ Cancel",
"submit": "✅ Submit Feedback",
"processing": "Processing...",
"submitted": "Submitted",
"retry": "Retry",
"close": "Close",
"upload": "Upload",
@ -153,22 +172,7 @@
"timeoutDescription": "Due to prolonged inactivity, the session has timed out. The interface will automatically close in 3 seconds.",
"closing": "Closing..."
},
"timeout": {
"enable": "Auto Close",
"enableTooltip": "When enabled, the interface will automatically close after the specified time",
"duration": {
"label": "Timeout Duration",
"description": "Set the auto-close time (30 seconds - 2 hours)"
},
"seconds": "seconds",
"remaining": "Time Remaining",
"expired": "⏰ Time expired, interface will close automatically",
"autoCloseMessage": "Interface will automatically close in {seconds} seconds",
"settings": {
"title": "Timeout Settings",
"description": "When enabled, the interface will automatically close after the specified time. The countdown timer will be displayed in the header area."
}
},
"dynamic": {
"aiSummary": "Test Web UI Functionality\n\n🎯 **Test Items:**\n- Web UI server startup and operation\n- WebSocket real-time communication\n- Feedback submission functionality\n- Image upload and preview\n- Command execution functionality\n- Smart Ctrl+V image pasting\n- Multi-language interface functionality\n\n📋 **Test Steps:**\n1. Test image upload (drag-drop, file selection, clipboard)\n2. Press Ctrl+V in text box to test smart pasting\n3. Try switching languages (Traditional Chinese/Simplified Chinese/English)\n4. Test command execution functionality\n5. Submit feedback and images\n\nPlease test these features and provide feedback!",
"terminalWelcome": "Welcome to Interactive Feedback Terminal\n========================================\nProject Directory: {sessionId}\nEnter commands and press Enter or click Execute button\nSupported commands: ls, dir, pwd, cat, type, etc.\n\n$ "

View File

@ -122,7 +122,19 @@
"connecting": "连接中...",
"disconnected": "已断开连接",
"reconnecting": "重新连接中...",
"error": "连接错误"
"error": "连接错误",
"waiting": {
"title": "等待反馈",
"message": "请提供您的反馈意见"
},
"processing": {
"title": "处理中",
"message": "正在提交您的反馈..."
},
"submitted": {
"title": "反馈已提交",
"message": "等待下次 MCP 调用"
}
},
"notifications": {
"feedback_sent": "反馈已发送",
@ -131,6 +143,11 @@
"connection_lost": "连接中断",
"connection_restored": "连接已恢复"
},
"connection": {
"waiting": "已连接 - 等待反馈",
"submitted": "已连接 - 反馈已提交",
"processing": "已连接 - 处理中"
},
"errors": {
"connection_failed": "连接失败",
"upload_failed": "上传失败",
@ -142,6 +159,8 @@
"ok": "确定",
"cancel": "❌ 取消",
"submit": "✅ 提交反馈",
"processing": "处理中...",
"submitted": "已提交",
"retry": "重试",
"close": "关闭",
"upload": "上传",
@ -153,22 +172,7 @@
"timeoutDescription": "由于长时间无响应,会话已超时。界面将在 3 秒后自动关闭。",
"closing": "正在关闭..."
},
"timeout": {
"enable": "自动关闭",
"enableTooltip": "启用后将在指定时间后自动关闭界面",
"duration": {
"label": "超时时间",
"description": "设置自动关闭的时间30秒 - 2小时"
},
"seconds": "秒",
"remaining": "剩余时间",
"expired": "⏰ 时间已到,界面将自动关闭",
"autoCloseMessage": "界面将在 {seconds} 秒后自动关闭",
"settings": {
"title": "超时设置",
"description": "启用后,界面将在指定时间后自动关闭。倒数计时器会显示在顶部区域。"
}
},
"dynamic": {
"aiSummary": "测试 Web UI 功能\n\n🎯 **功能测试项目:**\n- Web UI 服务器启动和运行\n- WebSocket 实时通讯\n- 反馈提交功能\n- 图片上传和预览\n- 命令执行功能\n- 智能 Ctrl+V 图片粘贴\n- 多语言界面功能\n\n📋 **测试步骤:**\n1. 测试图片上传(拖拽、选择文件、剪贴板)\n2. 在文本框内按 Ctrl+V 测试智能粘贴\n3. 尝试切换语言(繁中/简中/英文)\n4. 测试命令执行功能\n5. 提交反馈和图片\n\n请测试这些功能并提供反馈",
"terminalWelcome": "欢迎使用交互反馈终端\n========================================\n项目目录: {sessionId}\n输入命令后按 Enter 或点击执行按钮\n支持的命令: ls, dir, pwd, cat, type 等\n\n$ "

View File

@ -122,7 +122,19 @@
"connecting": "連線中...",
"disconnected": "已中斷連線",
"reconnecting": "重新連線中...",
"error": "連線錯誤"
"error": "連線錯誤",
"waiting": {
"title": "等待回饋",
"message": "請提供您的回饋意見"
},
"processing": {
"title": "處理中",
"message": "正在提交您的回饋..."
},
"submitted": {
"title": "回饋已提交",
"message": "等待下次 MCP 調用"
}
},
"notifications": {
"feedback_sent": "回饋已發送",
@ -131,6 +143,11 @@
"connection_lost": "連線中斷",
"connection_restored": "連線已恢復"
},
"connection": {
"waiting": "已連線 - 等待回饋",
"submitted": "已連線 - 反饋已提交",
"processing": "已連線 - 處理中"
},
"errors": {
"connection_failed": "連線失敗",
"upload_failed": "上傳失敗",
@ -142,6 +159,8 @@
"ok": "確定",
"cancel": "❌ 取消",
"submit": "✅ 提交回饋",
"processing": "處理中...",
"submitted": "已提交",
"retry": "重試",
"close": "關閉",
"upload": "上傳",
@ -153,22 +172,7 @@
"timeoutDescription": "由於長時間無回應,會話已超時。介面將在 3 秒後自動關閉。",
"closing": "正在關閉..."
},
"timeout": {
"enable": "自動關閉",
"enableTooltip": "啟用後將在指定時間後自動關閉介面",
"duration": {
"label": "超時時間",
"description": "設置自動關閉的時間30秒 - 2小時"
},
"seconds": "秒",
"remaining": "剩餘時間",
"expired": "⏰ 時間已到,介面將自動關閉",
"autoCloseMessage": "介面將在 {seconds} 秒後自動關閉",
"settings": {
"title": "超時設置",
"description": "啟用後,介面將在指定時間後自動關閉。倒數計時器會顯示在頂部區域。"
}
},
"dynamic": {
"aiSummary": "測試 Web UI 功能\n\n🎯 **功能測試項目:**\n- Web UI 服務器啟動和運行\n- WebSocket 即時通訊\n- 回饋提交功能\n- 圖片上傳和預覽\n- 命令執行功能\n- 智能 Ctrl+V 圖片貼上\n- 多語言介面功能\n\n📋 **測試步驟:**\n1. 測試圖片上傳(拖拽、選擇檔案、剪貼簿)\n2. 在文字框內按 Ctrl+V 測試智能貼上\n3. 嘗試切換語言(繁中/簡中/英文)\n4. 測試命令執行功能\n5. 提交回饋和圖片\n\n請測試這些功能並提供回饋",
"terminalWelcome": "歡迎使用互動回饋終端\n========================================\n專案目錄: {sessionId}\n輸入命令後按 Enter 或點擊執行按鈕\n支援的命令: ls, dir, pwd, cat, type 等\n\n$ "

View File

@ -327,4 +327,611 @@ pre code {
color: var(--text-secondary);
font-size: 0.9em;
margin-top: 4px;
}
}
/* ===== Feedback.html 專用樣式 ===== */
/* CSS 變數定義 */
:root {
/* 深色主題顏色變數 */
--bg-primary: #1e1e1e;
--bg-secondary: #2d2d30;
--bg-tertiary: #252526;
--surface-color: #333333;
--text-primary: #cccccc;
--text-secondary: #9e9e9e;
--accent-color: #007acc;
--accent-hover: #005a9e;
--border-color: #464647;
--success-color: #4caf50;
--warning-color: #ff9800;
--error-color: #f44336;
--info-color: #2196f3;
}
/* 基礎重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 主體樣式 */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 容器樣式 */
.container {
max-width: 1200px;
width: 100%;
margin: 0 auto;
padding: 20px;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 頭部樣式 */
.header {
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
padding: 15px 0;
margin-bottom: 20px;
border-radius: 8px 8px 0 0;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
}
.header-left {
display: flex;
align-items: center;
gap: 16px;
}
.title {
font-size: 24px;
font-weight: bold;
color: var(--accent-color);
margin: 0;
}
.project-info {
color: var(--text-secondary);
font-size: 14px;
}
/* 倒數計時器樣式 */
.countdown-display {
display: flex;
align-items: center;
gap: 6px;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 4px 8px;
}
.countdown-label {
color: var(--text-secondary);
font-size: 11px;
}
.countdown-timer {
color: var(--warning-color);
font-size: 13px;
font-weight: bold;
font-family: 'Consolas', 'Monaco', monospace;
min-width: 45px;
text-align: center;
}
.countdown-timer.warning {
color: var(--warning-color);
}
.countdown-timer.danger {
color: var(--error-color);
}
/* 語言選擇器 */
.language-selector {
display: flex;
align-items: center;
gap: 10px;
}
.language-selector select {
background: var(--bg-tertiary);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 5px 10px;
font-size: 14px;
}
/* 設定項目樣式 */
.setting-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid var(--border-color);
}
.setting-item:last-child {
border-bottom: none;
}
.setting-info {
flex: 1;
}
.setting-label {
font-weight: 500;
color: var(--text-primary);
font-size: 16px;
margin-bottom: 4px;
}
.setting-description {
color: var(--text-secondary);
font-size: 14px;
line-height: 1.4;
}
/* 設定卡片樣式 */
.settings-card {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 12px;
margin-bottom: 20px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.3s ease;
}
.settings-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.settings-card-header {
background: var(--bg-secondary);
padding: 16px 20px;
border-bottom: 1px solid var(--border-color);
}
.settings-card-title {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
margin: 0;
display: flex;
align-items: center;
gap: 8px;
}
.settings-card-body {
padding: 20px;
}
.settings-card .setting-item {
padding: 16px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.settings-card .setting-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
.settings-card .setting-label {
font-size: 16px;
font-weight: 500;
margin-bottom: 4px;
}
.settings-card .setting-description {
font-size: 14px;
color: var(--text-secondary);
line-height: 1.4;
}
/* 佈局模式選擇器 */
.layout-mode-selector {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 8px;
}
.layout-option {
display: flex;
align-items: flex-start;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: var(--bg-tertiary);
transition: all 0.3s ease;
cursor: pointer;
}
.layout-option:hover {
border-color: var(--accent-color);
background: rgba(0, 122, 204, 0.1);
}
.layout-option input[type="radio"] {
margin: 0;
margin-right: 12px;
margin-top: 2px;
accent-color: var(--accent-color);
transform: scale(1.2);
}
.layout-option label {
flex: 1;
cursor: pointer;
}
.layout-option-title {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
}
.layout-option-desc {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.4;
}
.layout-option input[type="radio"]:checked + label {
color: var(--accent-color);
}
.layout-option input[type="radio"]:checked + label .layout-option-title {
color: var(--accent-color);
}
.layout-option:has(input[type="radio"]:checked) {
border-color: var(--accent-color);
background: rgba(0, 122, 204, 0.15);
}
/* 語言選擇器現代化樣式 */
.language-selector-modern {
display: flex;
align-items: center;
gap: 10px;
}
.language-options {
display: flex;
gap: 12px;
}
.language-option {
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 16px;
background: var(--bg-primary);
border: 2px solid var(--border-color);
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
min-width: 80px;
}
.language-option:hover {
border-color: var(--accent-color);
background: rgba(0, 122, 204, 0.1);
}
.language-option.active {
border-color: var(--accent-color);
background: var(--accent-color);
color: white;
}
.language-flag {
font-size: 24px;
margin-bottom: 4px;
}
.language-name {
font-size: 12px;
font-weight: 500;
text-align: center;
}
.language-option.active .language-name {
color: white;
}
/* 表單元素樣式 */
.input-group {
margin-bottom: 20px;
width: 100%;
}
.input-label {
display: block;
font-weight: 500;
margin-bottom: 8px;
color: var(--text-primary);
}
.text-input,
.command-input {
width: 100%;
max-width: 100%;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 12px;
color: var(--text-primary);
font-size: 14px;
line-height: 1.5;
resize: vertical;
min-height: 220px;
font-family: inherit;
transition: border-color 0.3s ease;
box-sizing: border-box;
}
.text-input:focus,
.command-input:focus {
outline: none;
border-color: var(--accent-color);
}
.command-input {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
min-height: 80px;
}
/* 新增:單行命令輸入框樣式 */
.command-input-line {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 14px;
border: 1px solid var(--border-color);
transition: border-color 0.3s ease;
}
.command-input-line:focus {
outline: none;
border-color: var(--accent-color);
}
/* 命令輸出區域 */
.command-output {
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 12px;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 14px;
line-height: 1.4;
color: var(--text-primary);
white-space: pre-wrap;
overflow-y: auto;
height: 320px;
width: 100%;
box-sizing: border-box;
/* 添加 terminal 風格 */
background: #0f0f0f;
border: 2px solid var(--border-color);
color: #00ff00;
text-shadow: 0 0 5px #00ff00;
/* 確保尺寸固定 */
flex-shrink: 0;
resize: none;
}
/* Terminal 提示符樣式 */
.terminal-prompt {
color: var(--accent-color);
font-weight: bold;
}
/* 按鈕樣式 */
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-primary {
background: var(--accent-color);
color: white;
}
.btn-primary:hover {
background: var(--accent-hover);
}
.btn-secondary {
background: var(--bg-tertiary);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background: var(--surface-color);
}
.btn-success {
background: var(--success-color);
color: white;
}
.btn-success:hover {
background: #45a049;
}
/* 底部操作按鈕 */
.footer-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
padding: 20px;
border-top: 1px solid var(--border-color);
background: var(--bg-secondary);
border-radius: 0 0 8px 8px;
margin-top: auto;
}
/* 主內容區域 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
max-width: 100%;
overflow: hidden;
}
/* 分頁樣式 */
.tabs {
border-bottom: 2px solid var(--border-color);
margin-bottom: 20px;
}
.tab-buttons {
display: flex;
gap: 2px;
}
.tab-button {
background: transparent;
border: none;
color: var(--text-secondary);
padding: 12px 20px;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.3s ease;
font-size: 14px;
font-weight: 500;
border-radius: 4px 4px 0 0;
}
.tab-button.active {
color: var(--accent-color);
border-bottom-color: var(--accent-color);
background: var(--bg-tertiary);
}
.tab-button:hover:not(.active) {
color: var(--text-primary);
background: rgba(255, 255, 255, 0.05);
}
.tab-button.hidden {
display: none;
}
/* 分頁內容 */
.tab-content {
display: none;
flex: 1;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 20px;
overflow-y: auto;
width: 100%;
max-width: 100%;
box-sizing: border-box;
min-height: 600px;
}
.tab-content.active {
display: flex;
flex-direction: column;
}
/* 分割器樣式(用於合併模式) */
.splitter-container {
display: flex;
flex-direction: column;
flex: 1;
gap: 8px;
}
.splitter-section {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 16px;
transition: all 0.3s ease;
}
.splitter-handle {
height: 8px;
background: var(--border-color);
border-radius: 4px;
cursor: row-resize;
transition: background 0.3s ease;
margin: 4px 0;
}
.splitter-handle:hover {
background: var(--accent-color);
}
/* 響應式設計 */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.header-content {
flex-direction: column;
gap: 10px;
}
.tab-buttons {
flex-wrap: wrap;
}
/* 小屏幕下調整命令輸出區域高度 */
.command-output {
height: 250px;
}
}
/* 更小屏幕的調整 */
@media (max-width: 480px) {
.command-output {
height: 200px;
font-size: 12px;
}
}

View File

@ -186,9 +186,6 @@ class FeedbackApp {
// 設定
this.autoClose = false;
this.layoutMode = 'separate';
this.timeoutEnabled = false;
this.timeoutDuration = 600;
this.timeoutTimer = null;
// 語言設定
this.currentLanguage = 'zh-TW';
@ -516,15 +513,15 @@ class FeedbackApp {
switch (this.feedbackState) {
case 'waiting_for_feedback':
this.submitBtn.textContent = '提交回饋';
this.submitBtn.textContent = window.i18nManager ? window.i18nManager.t('buttons.submit') : '提交回饋';
this.submitBtn.className = 'btn btn-primary';
break;
case 'processing':
this.submitBtn.textContent = '處理中...';
this.submitBtn.textContent = window.i18nManager ? window.i18nManager.t('buttons.processing') : '處理中...';
this.submitBtn.className = 'btn btn-secondary';
break;
case 'feedback_submitted':
this.submitBtn.textContent = '已提交';
this.submitBtn.textContent = window.i18nManager ? window.i18nManager.t('buttons.submitted') : '已提交';
this.submitBtn.className = 'btn btn-success';
break;
}
@ -586,22 +583,26 @@ class FeedbackApp {
switch (this.feedbackState) {
case 'waiting_for_feedback':
const waitingTitle = window.i18nManager ? window.i18nManager.t('status.waiting.title') : '等待回饋';
const waitingMessage = window.i18nManager ? window.i18nManager.t('status.waiting.message') : '請提供您的回饋意見';
statusHTML = `
<div class="status-icon"></div>
<div class="status-text">
<strong>等待回饋</strong>
<span>請提供您的回饋意見</span>
<strong>${waitingTitle}</strong>
<span>${waitingMessage}</span>
</div>
`;
statusClass = 'status-waiting';
break;
case 'processing':
const processingTitle = window.i18nManager ? window.i18nManager.t('status.processing.title') : '處理中';
const processingMessage = window.i18nManager ? window.i18nManager.t('status.processing.message') : '正在提交您的回饋...';
statusHTML = `
<div class="status-icon"></div>
<div class="status-text">
<strong>處理中</strong>
<span>正在提交您的回饋...</span>
<strong>${processingTitle}</strong>
<span>${processingMessage}</span>
</div>
`;
statusClass = 'status-processing';
@ -610,11 +611,13 @@ class FeedbackApp {
case 'feedback_submitted':
const timeStr = this.lastSubmissionTime ?
new Date(this.lastSubmissionTime).toLocaleTimeString() : '';
const submittedTitle = window.i18nManager ? window.i18nManager.t('status.submitted.title') : '回饋已提交';
const submittedMessage = window.i18nManager ? window.i18nManager.t('status.submitted.message') : '等待下次 MCP 調用';
statusHTML = `
<div class="status-icon"></div>
<div class="status-text">
<strong>回饋已提交</strong>
<span>等待下次 MCP 調用 ${timeStr ? `(${timeStr})` : ''}</span>
<strong>${submittedTitle}</strong>
<span>${submittedMessage} ${timeStr ? `(${timeStr})` : ''}</span>
</div>
`;
statusClass = 'status-submitted';
@ -851,7 +854,8 @@ class FeedbackApp {
case 'feedback_submitted':
this.setFeedbackState('feedback_submitted', sessionId);
this.updateSummaryStatus('已送出反饋,等待下次 MCP 調用...');
this.updateConnectionStatus('connected', '已連接 - 反饋已提交');
const submittedConnectionText = window.i18nManager ? window.i18nManager.t('connection.submitted') : '已連接 - 反饋已提交';
this.updateConnectionStatus('connected', submittedConnectionText);
break;
case 'active':
@ -868,7 +872,8 @@ class FeedbackApp {
if (statusInfo.status === 'waiting') {
this.updateSummaryStatus('等待用戶回饋...');
}
this.updateConnectionStatus('connected', '已連接 - 等待回饋');
const waitingConnectionText = window.i18nManager ? window.i18nManager.t('connection.waiting') : '已連接 - 等待回饋';
this.updateConnectionStatus('connected', waitingConnectionText);
break;
default:
@ -880,7 +885,7 @@ class FeedbackApp {
const submitBtn = document.getElementById('submitBtn');
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.textContent = '✅ 已提交';
submitBtn.textContent = window.i18nManager ? window.i18nManager.t('buttons.submitted') : '✅ 已提交';
submitBtn.style.background = 'var(--success-color)';
}
}
@ -889,7 +894,7 @@ class FeedbackApp {
const submitBtn = document.getElementById('submitBtn');
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = '📤 提交回饋';
submitBtn.textContent = window.i18nManager ? window.i18nManager.t('buttons.submit') : '📤 提交回饋';
submitBtn.style.background = 'var(--accent-color)';
}
}
@ -1212,7 +1217,7 @@ class FeedbackApp {
// 重新啟用提交按鈕
if (this.submitBtn) {
this.submitBtn.disabled = false;
this.submitBtn.textContent = '提交回饋';
this.submitBtn.textContent = window.i18nManager ? window.i18nManager.t('buttons.submit') : '提交回饋';
}
}

View File

@ -169,6 +169,12 @@ class I18nManager {
updateDynamicContent() {
// 只更新終端歡迎信息,不要覆蓋 AI 摘要
this.updateTerminalWelcome();
// 更新應用程式中的動態狀態文字
if (window.feedbackApp) {
window.feedbackApp.updateUIState();
window.feedbackApp.updateStatusIndicator();
}
}
updateTerminalWelcome() {

View File

@ -0,0 +1,70 @@
{#
圖片上傳組件
============
參數:
- id_prefix: ID 前綴,用於區分不同實例 (例如: "feedback", "combined")
- label_text: 標籤文字 (預設: "圖片附件(可選)")
- upload_text: 上傳提示文字
- min_height: 最小高度 (預設: "120px")
使用方式:
{% include 'components/image-upload.html' with context %}
{% include 'components/image-upload.html' with id_prefix="feedback" %}
#}
{% set id_prefix = id_prefix or "default" %}
{% set label_text = label_text or "圖片附件(可選)" %}
{% set min_height = min_height or "120px" %}
{% set upload_text = upload_text or "📎 點擊選擇圖片或拖放圖片到此處<br><small>支援 PNG、JPG、JPEG、GIF、BMP、WebP 等格式</small>" %}
<!-- 圖片設定區域 -->
<div class="input-group" style="margin-bottom: 12px;">
<details class="image-settings-details">
<summary class="image-settings-summary" data-i18n="images.settings.title">⚙️ 圖片設定</summary>
<div class="image-settings-content">
<div class="image-setting-row">
<label class="image-setting-label" data-i18n="images.settings.sizeLimit">圖片大小限制:</label>
<select id="{{ id_prefix }}ImageSizeLimit" class="image-setting-select">
<option value="0" data-i18n="images.settings.sizeLimitOptions.unlimited">無限制</option>
<option value="1048576" data-i18n="images.settings.sizeLimitOptions.1mb">1MB</option>
<option value="3145728" data-i18n="images.settings.sizeLimitOptions.3mb">3MB</option>
<option value="5242880" data-i18n="images.settings.sizeLimitOptions.5mb">5MB</option>
</select>
</div>
<div class="image-setting-row">
<label class="image-setting-checkbox-container">
<input type="checkbox" id="{{ id_prefix }}EnableBase64Detail" class="image-setting-checkbox">
<span class="image-setting-checkmark"></span>
<span class="image-setting-label" data-i18n="images.settings.base64Detail">Base64 相容模式</span>
</label>
<small class="image-setting-help" data-i18n="images.settings.base64Warning">⚠️ 會增加傳輸量</small>
</div>
<div class="image-setting-help-text" data-i18n="images.settings.base64DetailHelp">
啟用後會在文字中包含完整的 Base64 圖片資料,提升部分 AI 模型的相容性
</div>
</div>
</details>
</div>
<div class="input-group">
<label class="input-label" data-i18n="feedback.imageLabel">{{ label_text }}</label>
<!-- 相容性提示區域 -->
<div id="{{ id_prefix }}CompatibilityHint" class="compatibility-hint" style="display: none;">
<span data-i18n="images.settings.compatibilityHint">💡 圖片無法正確識別?</span>
<button type="button" id="{{ id_prefix }}EnableBase64Hint" class="compatibility-hint-btn" data-i18n="images.settings.enableBase64Hint">
嘗試啟用 Base64 相容模式
</button>
</div>
<!-- 圖片上傳區域 -->
<div id="{{ id_prefix }}ImageUploadArea" class="image-upload-area" style="min-height: {{ min_height }};">
<div id="{{ id_prefix }}ImageUploadText" data-i18n="feedback.imageUploadText">
{{ upload_text|safe }}
</div>
<div id="{{ id_prefix }}ImagePreviewContainer" class="image-preview-container"></div>
<input type="file" id="{{ id_prefix }}ImageInput" multiple accept="image/*" style="display: none;">
</div>
</div>

View File

@ -0,0 +1,58 @@
{#
設定卡片組件
============
參數:
- title: 卡片標題
- icon: 標題圖標 (可選)
- card_id: 卡片 ID (可選)
- content: 卡片內容 (使用 caller() 傳入)
使用方式:
{% call settings_card(title="介面設定", icon="🎨") %}
<!-- 卡片內容 -->
{% endcall %}
#}
{% macro settings_card(title, icon="", card_id="") %}
<div class="settings-card"{% if card_id %} id="{{ card_id }}"{% endif %}>
<div class="settings-card-header">
<h3 class="settings-card-title">
{% if icon %}{{ icon }} {% endif %}{{ title }}
</h3>
</div>
<div class="settings-card-body">
{{ caller() }}
</div>
</div>
{% endmacro %}
{#
設定項目組件
============
參數:
- label: 設定項目標籤
- description: 設定項目描述
- is_last: 是否為最後一個項目 (影響邊框顯示)
- control: 控制元件內容 (使用 caller() 傳入)
使用方式:
{% call setting_item(label="自動關閉頁面", description="提交回饋後自動關閉頁面") %}
<div id="autoCloseToggle" class="toggle-switch active">
<div class="toggle-knob"></div>
</div>
{% endcall %}
#}
{% macro setting_item(label, description="", is_last=false) %}
<div class="setting-item"{% if is_last %} style="border-bottom: none;"{% endif %}>
<div class="setting-info">
<div class="setting-label">{{ label }}</div>
{% if description %}
<div class="setting-description">{{ description }}</div>
{% endif %}
</div>
{{ caller() }}
</div>
{% endmacro %}

View File

@ -0,0 +1,32 @@
{#
狀態指示器組件
==============
參數:
- id: 指示器 ID
- status: 狀態類型 ("waiting", "processing", "submitted")
- icon: 狀態圖標
- title: 狀態標題
- message: 狀態訊息
- visible: 是否顯示 (預設: false)
使用方式:
{% include 'components/status-indicator.html' with
id="feedbackStatusIndicator",
status="waiting",
icon="⏳",
title="等待您的回饋",
message="請提供您對 AI 工作成果的意見和建議" %}
#}
{% set visible = visible or false %}
{% set status = status or "waiting" %}
{% set icon = icon or "⏳" %}
<div id="{{ id }}" class="feedback-status-indicator status-{{ status }}"{% if not visible %} style="display: none;"{% endif %}>
<div class="status-icon">{{ icon }}</div>
<div class="status-text">
<strong data-i18n="feedback.status.{{ status }}.title">{{ title }}</strong>
<span data-i18n="feedback.status.{{ status }}.message">{{ message }}</span>
</div>
</div>

View File

@ -0,0 +1,19 @@
{#
切換開關組件
============
參數:
- id: 開關 ID
- active: 是否預設啟用 (預設: false)
- class: 額外的 CSS 類別 (可選)
使用方式:
{% include 'components/toggle-switch.html' with id="autoCloseToggle", active=true %}
#}
{% set active = active or false %}
{% set class = class or "" %}
<div id="{{ id }}" class="toggle-switch{% if active %} active{% endif %}{% if class %} {{ class }}{% endif %}">
<div class="toggle-knob"></div>
</div>

File diff suppressed because it is too large Load Diff