From 7f9e2e267d5dbb8061ebaf28ab136d57bce2584e Mon Sep 17 00:00:00 2001 From: SWangHash <88996709+SWangHash@users.noreply.github.com> Date: Sat, 23 May 2026 16:14:33 +0800 Subject: [PATCH 1/3] Remove Computer use setting from permissions page --- .../settings/settingsTabSearchContent.ts | 4 - .../config/components/SessionConfig.tsx | 196 +----------------- .../en-US/settings/session-config.json | 18 +- .../zh-CN/settings/session-config.json | 18 +- .../zh-TW/settings/session-config.json | 18 +- 5 files changed, 7 insertions(+), 247 deletions(-) diff --git a/src/web-ui/src/app/scenes/settings/settingsTabSearchContent.ts b/src/web-ui/src/app/scenes/settings/settingsTabSearchContent.ts index c1e124788b..4ce08909fe 100644 --- a/src/web-ui/src/app/scenes/settings/settingsTabSearchContent.ts +++ b/src/web-ui/src/app/scenes/settings/settingsTabSearchContent.ts @@ -63,10 +63,6 @@ export const SETTINGS_TAB_SEARCH_CONTENT: Record = ({ variant } const [confirmationTimeout, setConfirmationTimeout] = useState(''); const [toolExecConfigLoading, setToolExecConfigLoading] = useState(false); - const [computerUseEnabled, setComputerUseEnabled] = useState(false); - const [computerUseAccess, setComputerUseAccess] = useState(false); - const [computerUseScreen, setComputerUseScreen] = useState(false); - const [computerUseBusy, setComputerUseBusy] = useState(false); - const [computerUseStatusLoading, setComputerUseStatusLoading] = useState(false); - // ── Browser control state ─────────────────────────────────────────────── const [browserCdpAvailable, setBrowserCdpAvailable] = useState(false); const [browserKind, setBrowserKind] = useState(''); @@ -123,24 +110,6 @@ const SessionSettingsPanels: React.FC = ({ variant } const [expandedTemplates, setExpandedTemplates] = useState>(new Set()); const [isTemplatesModalOpen, setIsTemplatesModalOpen] = useState(false); - const refreshComputerUseStatus = useCallback(async (): Promise => { - if (!IS_TAURI_DESKTOP) return false; - setComputerUseStatusLoading(true); - try { - const { invoke } = await import('@tauri-apps/api/core'); - const s = await invoke('computer_use_get_status'); - setComputerUseEnabled(s.computerUseEnabled); - setComputerUseAccess(s.accessibilityGranted); - setComputerUseScreen(s.screenCaptureGranted); - return true; - } catch (error) { - log.error('computer_use_get_status failed', error); - return false; - } finally { - setComputerUseStatusLoading(false); - } - }, []); - const refreshBrowserControlStatus = useCallback(async () => { if (!IS_TAURI_DESKTOP) return; setBrowserStatusLoading(true); @@ -168,22 +137,15 @@ const SessionSettingsPanels: React.FC = ({ variant } } }, []); - const refreshDesktopStatus = useCallback((computerUseCfg: boolean | null | undefined) => { - if (!IS_TAURI_DESKTOP) { - setComputerUseEnabled(computerUseCfg ?? false); - return; - } - - void refreshComputerUseStatus().then((ok) => { - if (!ok) setComputerUseEnabled(computerUseCfg ?? false); - }); + const refreshDesktopStatus = useCallback(() => { + if (!IS_TAURI_DESKTOP) return; void refreshBrowserControlStatus(); void systemAPI.getSystemInfo() .then((info) => setPlatform(info.platform || '')) .catch((error) => log.warn('getSystemInfo failed', error)); - }, [refreshComputerUseStatus, refreshBrowserControlStatus]); + }, [refreshBrowserControlStatus]); const loadAllData = useCallback(async () => { setIsLoading(true); @@ -196,7 +158,6 @@ const SessionSettingsPanels: React.FC = ({ variant } execTimeout, confirmTimeout, debugConfigData, - computerUseCfg, browserControlPreferredBrowser, loadedCompanionPets, ] = await Promise.all([ @@ -207,7 +168,6 @@ const SessionSettingsPanels: React.FC = ({ variant } configManager.getConfig('ai.tool_execution_timeout_secs'), configManager.getConfig('ai.tool_confirmation_timeout_secs'), configManager.getConfig('ai.debug_mode_config'), - configManager.getConfig('ai.computer_use_enabled'), configManager.getConfig('ai.browser_control_preferred_browser'), listAgentCompanionPets(), ]); @@ -222,7 +182,7 @@ const SessionSettingsPanels: React.FC = ({ variant } if (debugConfigData) setDebugConfig(debugConfigData); setPreferredBrowser(browserControlPreferredBrowser || DEFAULT_BROWSER_CONTROL_BROWSER); - refreshDesktopStatus(computerUseCfg); + refreshDesktopStatus(); } catch (error) { log.error('Failed to load session config data', error); setSettings(await aiExperienceConfigService.getSettingsAsync()); @@ -414,37 +374,6 @@ const SessionSettingsPanels: React.FC = ({ variant } } }; - const handleComputerUseEnabledChange = async (checked: boolean) => { - setComputerUseBusy(true); - setComputerUseEnabled(checked); - try { - await configManager.setConfig('ai.computer_use_enabled', checked); - const { globalEventBus } = await import('@/infrastructure/event-bus'); - globalEventBus.emit('mode:config:updated'); - notificationService.success( - checked ? t('messages.saveSuccess') : t('messages.saveSuccess'), - { duration: 2000 } - ); - await refreshComputerUseStatus(); - } catch (error) { - log.error('Failed to save computer_use_enabled', error); - notificationService.error(t('messages.saveFailed')); - setComputerUseEnabled(!checked); - } finally { - setComputerUseBusy(false); - } - }; - - const handleComputerUseOpenSettings = async (pane: 'accessibility' | 'screen_capture') => { - try { - const { invoke } = await import('@tauri-apps/api/core'); - await invoke('computer_use_open_system_settings', { request: { pane } }); - } catch (error) { - log.error('computer_use_open_system_settings failed', error); - notificationService.error(t('messages.saveFailed')); - } - }; - const handleBrowserControlBrowserChange = async (value: string | number) => { const nextValue = String(value || DEFAULT_BROWSER_CONTROL_BROWSER); const previousValue = preferredBrowser; @@ -680,12 +609,6 @@ const SessionSettingsPanels: React.FC = ({ variant } const enabledModels = models.filter((m: AIModelConfig) => m.enabled); const sessionTitleModelId = funcAgentModels[AGENT_SESSION_TITLE] || 'fast'; const templateEntries = getTemplateEntries(); - const computerUseAccessLabel = computerUseStatusLoading - ? t('loading.text') - : computerUseAccess ? t('computerUse.granted') : t('computerUse.notGranted'); - const computerUseScreenLabel = computerUseStatusLoading - ? t('loading.text') - : computerUseScreen ? t('computerUse.granted') : t('computerUse.notGranted'); const browserStatusLabel = browserCdpAvailable ? `${browserKind} · ${browserPageCount} ${t('browserControl.tabs')}` : browserStatusLoading ? t('loading.text') : t('browserControl.notConnected'); @@ -1008,117 +931,6 @@ const SessionSettingsPanels: React.FC = ({ variant } - {/* ── Computer use (desktop) ─────────────────────────────── */} - - {IS_TAURI_DESKTOP ? ( - <> - -
- handleComputerUseEnabledChange(e.target.checked)} - disabled={computerUseBusy || computerUseStatusLoading} - size="small" - /> -
-
- -
- - - {computerUseAccessLabel} - - void refreshComputerUseStatus()} - > - - - - -
-
- -
- - - {computerUseScreenLabel} - - void refreshComputerUseStatus()} - > - - - - -
-
- - ) : null} -
- {/* ── Browser control (CDP) ──────────────────────────────── */} Date: Sat, 23 May 2026 16:17:58 +0800 Subject: [PATCH 2/3] Remove 4 cowork examples: reply_email, make_spreadsheet, desktop_cleanup, make_docx --- .../components/CoworkExampleCards.tsx | 12 ----------- src/web-ui/src/locales/en-US/flow-chat.json | 20 ------------------- src/web-ui/src/locales/zh-CN/flow-chat.json | 20 ------------------- src/web-ui/src/locales/zh-TW/flow-chat.json | 20 ------------------- 4 files changed, 72 deletions(-) diff --git a/src/web-ui/src/flow_chat/components/CoworkExampleCards.tsx b/src/web-ui/src/flow_chat/components/CoworkExampleCards.tsx index 82d0836963..626ee6504d 100644 --- a/src/web-ui/src/flow_chat/components/CoworkExampleCards.tsx +++ b/src/web-ui/src/flow_chat/components/CoworkExampleCards.tsx @@ -6,17 +6,13 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { type LucideIcon, - Image, Plane, Presentation, ListTodo, CalendarDays, ClipboardList, - Mail, - FileSpreadsheet, HandCoins, TrendingUp, - FileText, X, RotateCcw, Plus, @@ -25,16 +21,12 @@ import { Card, IconButton, Tooltip } from '@/component-library'; import './CoworkExampleCards.scss'; type ExampleId = - | 'desktop_cleanup' | 'vacation_plan' | 'make_ppt' | 'todo_breakdown' | 'optimize_week' | 'weekly_plan' | 'meeting_minutes' - | 'reply_email' - | 'make_docx' - | 'make_spreadsheet' | 'budget_plan'; interface ExampleItem { @@ -43,16 +35,12 @@ interface ExampleItem { } const EXAMPLES: ExampleItem[] = [ - { id: 'desktop_cleanup', icon: Image }, { id: 'vacation_plan', icon: Plane }, { id: 'make_ppt', icon: Presentation }, { id: 'todo_breakdown', icon: ListTodo }, { id: 'optimize_week', icon: TrendingUp }, { id: 'weekly_plan', icon: CalendarDays }, { id: 'meeting_minutes', icon: ClipboardList }, - { id: 'reply_email', icon: Mail }, - { id: 'make_docx', icon: FileText }, - { id: 'make_spreadsheet', icon: FileSpreadsheet }, { id: 'budget_plan', icon: HandCoins }, ]; diff --git a/src/web-ui/src/locales/en-US/flow-chat.json b/src/web-ui/src/locales/en-US/flow-chat.json index f2c423e6ca..f9708f4bab 100644 --- a/src/web-ui/src/locales/en-US/flow-chat.json +++ b/src/web-ui/src/locales/en-US/flow-chat.json @@ -1896,11 +1896,6 @@ "refresh": "Shuffle", "close": "Close", "items": { - "desktop_cleanup": { - "title": "Organize desktop screenshots", - "description": "Rename, group, and file recent screenshots.", - "prompt": "Help me organize recent screenshots on my Desktop.\n\nFirst, scan my Desktop and count how many screenshots/images are there. Show me:\n\n- Total count\n\n- Date range (oldest to newest)\n\nThen, focus only on screenshots from the last 14 days. For each one:\n\n- Identify what it shows\n\n- Suggest a descriptive filename\n\n- Propose which folder it belongs in (or if it can be deleted)\n\nGroup similar screenshots together. Show me the plan before making any changes.\n\nAfter I approve, start by organizing just 10 files as a preview. If there are more than 10 files, check in with me before continuing with the rest." - }, "vacation_plan": { "title": "Plan my vacation", "description": "Build a day-by-day itinerary with budget + backups.", @@ -1931,21 +1926,6 @@ "description": "Summarize decisions, action items, and open questions.", "prompt": "Turn my meeting notes into clear meeting minutes.\n\nTopic:\nAttendees:\nTime:\nRaw notes (paste here):\n\nOutput:\n1) Key outcomes (3–6)\n2) Decisions\n3) Action items (owner / due date / acceptance criteria)\n4) Risks + open questions\n5) Suggested agenda for the next meeting" }, - "reply_email": { - "title": "Reply to an email", - "description": "Generate a polite, clear email template (two tones).", - "prompt": "Help me write an email reply.\n\nOriginal email (paste here):\nMy goal (confirm / decline / push forward / clarify):\nTone (formal / friendly / firm but polite):\nKey points to include:\n\nOutput:\n1) Subject line suggestions\n2) Body (2 versions: more formal / more concise)\n3) Questions for the recipient to confirm (if any)" - }, - "make_docx": { - "title": "Draft a DOCX", - "description": "Write a structured document and export as DOCX.", - "prompt": "Help me write a document and export it as a .docx file.\n\nDocument type (PRD / proposal / meeting summary / report / SOP):\nAudience:\nTone (formal / friendly / concise):\nMust-include points:\nLength target:\n\nDeliverable:\n1) Suggested outline\n2) Full content\n3) Export as a .docx file (save it under artifacts/)" - }, - "make_spreadsheet": { - "title": "Design a spreadsheet", - "description": "Define fields and formulas for reusable tracking.", - "prompt": "Design a spreadsheet schema (columns + example rows) for the following purpose.\n\nPurpose:\nData sources:\nMetrics / aggregation needs:\nPreferred output (weekly report / dashboard / checklist):\n\nOutput:\n1) Recommended columns (type + notes)\n2) Example data (3–5 rows)\n3) Useful formulas / pivot suggestions\n4) Data validation + filling guidelines" - }, "budget_plan": { "title": "Create a budget plan", "description": "Allocate by category + timeline with review rules.", diff --git a/src/web-ui/src/locales/zh-CN/flow-chat.json b/src/web-ui/src/locales/zh-CN/flow-chat.json index 741935ac99..8e8837e5a4 100644 --- a/src/web-ui/src/locales/zh-CN/flow-chat.json +++ b/src/web-ui/src/locales/zh-CN/flow-chat.json @@ -1896,11 +1896,6 @@ "refresh": "换一换", "close": "关闭", "items": { - "desktop_cleanup": { - "title": "整理桌面截图", - "description": "对最近的截图分组、重命名并归档。", - "prompt": "请帮我整理桌面上最近的截图。\n\n第一步:先扫描我的桌面,统计有多少张截图/图片,并告诉我:\n\n- 总数量\n\n- 时间范围(最早到最新)\n\n第二步:只关注最近 14 天的截图。对每一张:\n\n- 识别它展示了什么\n\n- 建议一个更描述性的文件名\n\n- 建议它应该放到哪个文件夹(或是否可以删除)\n\n把相似的截图分组。开始任何改动之前,先把你的整理方案给我确认。\n\n我确认后:先只整理 10 个文件作为预览。如果总数超过 10 个,继续之前先跟我确认是否要处理剩下的。" - }, "vacation_plan": { "title": "计划一下我的假期", "description": "把行程拆到每天,并考虑预算、交通与备选方案。", @@ -1931,21 +1926,6 @@ "description": "把讨论内容结构化成结论、行动项与风险。", "prompt": "请帮我把会议内容整理成一份会议纪要。\n\n会议主题:\n参会人:\n时间:\n原始记录(可粘贴):\n\n请输出:\n1) 会议结论(3-6 条)\n2) 决策项(Decision)\n3) 行动项(Action Items:负责人/截止时间/验收标准)\n4) 风险与待确认问题(Open Questions)\n5) 下次会议建议议程" }, - "reply_email": { - "title": "写一封邮件回复", - "description": "给出礼貌、清晰、可复制的邮件模板(含不同语气版本)。", - "prompt": "请帮我写一封邮件回复。\n\n对方邮件内容(可粘贴):\n我的目标(确认/拒绝/推进/澄清):\n语气(正式/友好/强硬但礼貌):\n需要包含的信息点:\n\n请输出:\n1) 主题(Subject)建议\n2) 邮件正文(2 个版本:更正式/更简洁)\n3) 需要对方确认的问题列表(如有)" - }, - "make_docx": { - "title": "写一份 Word 文档", - "description": "把内容结构化成文档,并导出为 DOCX。", - "prompt": "请帮我写一份文档,并导出为 .docx 文件。\n\n文档类型(PRD/方案/复盘/报告/SOP):\n受众:\n语气(正式/友好/简洁):\n必须包含的信息点:\n期望长度:\n\n最终交付:\n1) 建议目录结构\n2) 完整正文内容\n3) 导出为 DOCX 文件(保存到 artifacts/)" - }, - "make_spreadsheet": { - "title": "做一张表格", - "description": "设计字段与公式,让表格能自动汇总与复用。", - "prompt": "请帮我设计一张表格结构(字段 + 示例行),用于下面的用途:\n\n用途:\n数据来源:\n需要的统计口径/汇总方式:\n输出格式偏好(周报/仪表盘/清单):\n\n请输出:\n1) 推荐字段列表(含字段类型与说明)\n2) 示例数据(3-5 行)\n3) 常用公式/透视表建议\n4) 数据校验与填表规范" - }, "budget_plan": { "title": "做个预算规划", "description": "把预算拆到类别与时间,并给出控制与复盘方法。", diff --git a/src/web-ui/src/locales/zh-TW/flow-chat.json b/src/web-ui/src/locales/zh-TW/flow-chat.json index 9cac7a796f..feb55ad969 100644 --- a/src/web-ui/src/locales/zh-TW/flow-chat.json +++ b/src/web-ui/src/locales/zh-TW/flow-chat.json @@ -1896,11 +1896,6 @@ "refresh": "換一換", "close": "關閉", "items": { - "desktop_cleanup": { - "title": "整理桌面截圖", - "description": "對最近的截圖分組、重命名並歸檔。", - "prompt": "請幫我整理桌面上最近的截圖。\n\n第一步:先掃描我的桌面,統計有多少張截圖/圖片,並告訴我:\n\n- 總數量\n\n- 時間範圍(最早到最新)\n\n第二步:只關注最近 14 天的截圖。對每一張:\n\n- 識別它展示了什麼\n\n- 建議一個更描述性的文件名\n\n- 建議它應該放到哪個文件夾(或是否可以刪除)\n\n把相似的截圖分組。開始任何改動之前,先把你的整理方案給我確認。\n\n我確認後:先只整理 10 個文件作為預覽。如果總數超過 10 個,繼續之前先跟我確認是否要處理剩下的。" - }, "vacation_plan": { "title": "計劃一下我的假期", "description": "把行程拆到每天,並考慮預算、交通與備選方案。", @@ -1931,21 +1926,6 @@ "description": "把討論內容結構化成結論、行動項與風險。", "prompt": "請幫我把會議內容整理成一份會議紀要。\n\n會議主題:\n參會人:\n時間:\n原始記錄(可粘貼):\n\n請輸出:\n1) 會議結論(3-6 條)\n2) 決策項(Decision)\n3) 行動項(Action Items:負責人/截止時間/驗收標準)\n4) 風險與待確認問題(Open Questions)\n5) 下次會議建議議程" }, - "reply_email": { - "title": "寫一封郵件回覆", - "description": "給出禮貌、清晰、可複製的郵件模板(含不同語氣版本)。", - "prompt": "請幫我寫一封郵件回覆。\n\n對方郵件內容(可粘貼):\n我的目標(確認/拒絕/推進/澄清):\n語氣(正式/友好/強硬但禮貌):\n需要包含的信息點:\n\n請輸出:\n1) 主題(Subject)建議\n2) 郵件正文(2 個版本:更正式/更簡潔)\n3) 需要對方確認的問題列表(如有)" - }, - "make_docx": { - "title": "寫一份 Word 文檔", - "description": "把內容結構化成文檔,並導出為 DOCX。", - "prompt": "請幫我寫一份文檔,並導出為 .docx 文件。\n\n文檔類型(PRD/方案/覆盤/報告/SOP):\n受眾:\n語氣(正式/友好/簡潔):\n必須包含的信息點:\n期望長度:\n\n最終交付:\n1) 建議目錄結構\n2) 完整正文內容\n3) 導出為 DOCX 文件(保存到 artifacts/)" - }, - "make_spreadsheet": { - "title": "做一張表格", - "description": "設計字段與公式,讓表格能自動彙總與複用。", - "prompt": "請幫我設計一張表格結構(字段 + 示例行),用於下面的用途:\n\n用途:\n數據來源:\n需要的統計口徑/彙總方式:\n輸出格式偏好(週報/儀表盤/清單):\n\n請輸出:\n1) 推薦字段列表(含字段類型與說明)\n2) 示例數據(3-5 行)\n3) 常用公式/透視表建議\n4) 數據校驗與填表規範" - }, "budget_plan": { "title": "做個預算規劃", "description": "把預算拆到類別與時間,並給出控制與覆盤方法。", From 1adf590207d744f5a2071de4ab1b9650283b4ac9 Mon Sep 17 00:00:00 2001 From: SWangHash <88996709+SWangHash@users.noreply.github.com> Date: Sat, 23 May 2026 16:20:42 +0800 Subject: [PATCH 3/3] [bugfix] add git availability check for coding-selfie miniapp --- .../builtin/assets/coding-selfie/style.css | 33 ++++++++ .../builtin/assets/coding-selfie/ui.js | 77 +++++++++++++++---- 2 files changed, 97 insertions(+), 13 deletions(-) diff --git a/src/crates/core/src/miniapp/builtin/assets/coding-selfie/style.css b/src/crates/core/src/miniapp/builtin/assets/coding-selfie/style.css index 086ac38b2a..c8434a533e 100644 --- a/src/crates/core/src/miniapp/builtin/assets/coding-selfie/style.css +++ b/src/crates/core/src/miniapp/builtin/assets/coding-selfie/style.css @@ -757,6 +757,39 @@ html, body { } .cs-empty.is-error .cs-empty-art { color: var(--cs-del); } +.cs-empty-action { + margin-top: 12px; + padding: 8px 16px; + border-radius: var(--cs-radius-sm); + background: var(--cs-accent); + color: var(--cs-bg); + font-family: var(--cs-sans); + font-size: 13px; + font-weight: 500; + border: none; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 6px; + transition: background 140ms ease, transform 100ms ease; + flex-shrink: 0; +} +.cs-empty-action:hover { + background: var(--cs-accent-2); +} +.cs-empty-action:active { + transform: scale(0.97); +} +.cs-empty-action svg { + width: 14px; + height: 14px; +} + +.cs-empty.has-action { + flex-direction: column; + align-items: flex-start; +} + /* ─── Adaptive: gracefully shed less-essential tiles ── The dashboard prefers to fit on a single screen on ANY aspect ratio. When space gets tight we hide optional blocks instead of overflowing. */ diff --git a/src/crates/core/src/miniapp/builtin/assets/coding-selfie/ui.js b/src/crates/core/src/miniapp/builtin/assets/coding-selfie/ui.js index 6acc5b60cb..172125fde0 100644 --- a/src/crates/core/src/miniapp/builtin/assets/coding-selfie/ui.js +++ b/src/crates/core/src/miniapp/builtin/assets/coding-selfie/ui.js @@ -128,6 +128,8 @@ const I18N = { errScan: '扫描失败', errScanReason: (r) => `原因:${r}`, errScanRuntime: '扫描出错', + errGitNotFound: '未检测到 Git', + errGitNotFoundDesc: '请先安装 Git 并确保已添加到系统环境变量中。', styleNight: '凌晨刺客', styleMorning: '早起鸟', styleAm: '上午型选手', @@ -222,6 +224,8 @@ const I18N = { errScan: '掃描失敗', errScanReason: (r) => `原因:${r}`, errScanRuntime: '掃描出錯', + errGitNotFound: '未檢測到 Git', + errGitNotFoundDesc: '請先安裝 Git 並確保已添加到系統環境變量中。', styleNight: '凌晨刺客', styleMorning: '早起鳥', styleAm: '上午型選手', @@ -317,6 +321,8 @@ const I18N = { errScan: 'Scan failed', errScanReason: (r) => `reason: ${r}`, errScanRuntime: 'Scan error', + errGitNotFound: 'Git not found', + errGitNotFoundDesc: 'Please install Git and ensure it is added to your system PATH.', styleNight: 'Midnight Hacker', styleMorning: 'Early Bird', styleAm: 'Morning Brew', @@ -389,23 +395,51 @@ function greetingFor(d, name) { return t('greetWith')(part, name); } -function setEmpty(state, title, desc, icon) { +function setEmpty(state, title, desc, icon, actionLabel, actionUrl) { const el = $('empty'); - el.classList.remove('is-loading', 'is-error'); - if (state === 'loading') el.classList.add('is-loading'); - if (state === 'error') el.classList.add('is-error'); - $('empty-icon').innerHTML = icon || SVG.loader; - $('empty-title').textContent = title; - $('empty-desc').textContent = desc || ''; + el.classList.remove('is-loading', 'is-error', 'has-action'); + if (state === 'loading') el.classList.add('is-loading'); + if (state === 'error') { + el.classList.add('is-error'); + if (actionLabel && actionUrl) { + el.classList.add('has-action'); + } + } + $('empty-icon').innerHTML = icon || SVG.loader; + $('empty-title').textContent = title; + $('empty-desc').textContent = desc || ''; + + const actionEl = $('empty-action'); + if (actionLabel && actionUrl) { + if (!actionEl) { + actionEl = document.createElement('button'); + actionEl.className = 'cs-empty-action'; + actionEl.id = 'empty-action'; + const container = $('empty'); + container.appendChild(actionEl); + } + actionEl.textContent = actionLabel; + actionEl.onclick = () => { + if (window.app && window.app.system && window.app.system.openExternal) { + window.app.system.openExternal(actionUrl); + } else { + window.open(actionUrl, '_blank'); + } + }; + } else if (actionEl) { + actionEl.remove(); + } } -function showEmpty(state, title, desc, icon) { - setEmpty(state, title, desc, icon); - $('empty').removeAttribute('hidden'); - $('content').setAttribute('hidden', ''); + +function showEmpty(state, title, desc, icon, actionLabel, actionUrl) { + setEmpty(state, title, desc, icon, actionLabel, actionUrl); + $('empty').removeAttribute('hidden'); + $('content').setAttribute('hidden', ''); } + function hideEmpty() { - $('empty').setAttribute('hidden', ''); - $('content').removeAttribute('hidden'); + $('empty').setAttribute('hidden', ''); + $('content').removeAttribute('hidden'); } function renderDonut(langs) { @@ -1280,6 +1314,15 @@ async function scanGitWorkspace(cwd) { let lastData = null; let scanning = false; +async function checkGitAvailable() { + try { + await window.app.shell.exec(['git', '--version'], { timeout: 5000 }); + return true; + } catch (_e) { + return false; + } +} + async function scan() { if (scanning) return; scanning = true; @@ -1293,6 +1336,14 @@ async function scan() { return; } + const gitAvailable = await checkGitAvailable(); + if (!gitAvailable) { + showEmpty('error', t('errGitNotFound'), t('errGitNotFoundDesc'), SVG.gitBranch, t('errGitNotFoundAction'), t('gitDownloadUrl')); + lastData = null; + scanning = false; + return; + } + if (!lastData) { showEmpty('loading', t('loadingTitle'), shortPath(ws), SVG.loader); }