mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-18 14:32:06 +08:00
Add desktop app packaging and settings updates
This commit is contained in:
61
.github/workflows/desktop-windows-branch-build.yml
vendored
Normal file
61
.github/workflows/desktop-windows-branch-build.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Desktop Windows Branch Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
name: Build unsigned Windows desktop artifact
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Typecheck
|
||||
run: npm run typecheck
|
||||
|
||||
- name: Resolve artifact metadata
|
||||
id: artifact
|
||||
shell: bash
|
||||
run: |
|
||||
SAFE_REF="$(printf '%s' "${GITHUB_REF_NAME}" | tr -c 'A-Za-z0-9._-' '-')"
|
||||
echo "name=CloudCLI-windows-${SAFE_REF}-${GITHUB_RUN_NUMBER}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build unsigned Windows artifacts
|
||||
run: npm run desktop:dist:win -- --publish never
|
||||
env:
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
||||
|
||||
- name: Verify Windows artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
test -n "$(find release -maxdepth 1 -name '*.exe' -print -quit)"
|
||||
test -n "$(find release -maxdepth 1 -name '*.zip' -print -quit)"
|
||||
sha256sum release/*.{exe,zip} > release/SHASUMS256.txt
|
||||
cat release/SHASUMS256.txt
|
||||
|
||||
- name: Upload branch build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.artifact.outputs.name }}
|
||||
path: |
|
||||
release/*.exe
|
||||
release/*.zip
|
||||
release/*.yml
|
||||
release/*.blockmap
|
||||
release/SHASUMS256.txt
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
@@ -209,6 +209,11 @@ export class DesktopWindowManager {
|
||||
this.mainWindow.webContents.send('cloudcli-desktop:state-updated', this.getDesktopState());
|
||||
}
|
||||
|
||||
emitLauncherCommand(command) {
|
||||
if (!this.mainWindow || this.mainWindow.webContents.isDestroyed()) return;
|
||||
this.mainWindow.webContents.send('cloudcli-desktop:launcher-command', command);
|
||||
}
|
||||
|
||||
async showTarget(target, { trackTab = true } = {}) {
|
||||
if (!this.mainWindow) return;
|
||||
if (trackTab) {
|
||||
@@ -367,8 +372,8 @@ export class DesktopWindowManager {
|
||||
label: 'Services',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Computer Use Preview',
|
||||
click: () => void this.actions.showComputerUsePreview(),
|
||||
label: 'Computer Access',
|
||||
click: () => void this.actions.showComputerAccess(),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -565,19 +570,10 @@ export class DesktopWindowManager {
|
||||
this.tray.setContextMenu(Menu.buildFromTemplate(template));
|
||||
}
|
||||
|
||||
async showDesktopAppMenu() {
|
||||
async showDesktopSettings() {
|
||||
if (!this.mainWindow) return this.getDesktopState();
|
||||
const menu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Copy Diagnostics',
|
||||
click: () => void this.actions.copyDiagnostics(),
|
||||
},
|
||||
{
|
||||
label: 'Computer Use Preview',
|
||||
click: () => void this.actions.showComputerUsePreview(),
|
||||
},
|
||||
]);
|
||||
menu.popup({ window: this.mainWindow });
|
||||
await this.showLauncher();
|
||||
this.emitLauncherCommand({ type: 'open-sheet', sheet: 'app-settings' });
|
||||
return this.getDesktopState();
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -3,11 +3,12 @@ window.__MOCK_STATE__ = {
|
||||
account: { connected: true, email: 'you@cloudcli.ai' },
|
||||
activeTarget: { kind: 'launcher', name: 'Launcher', url: null },
|
||||
cloudLoading: false,
|
||||
desktopSettings: { keepLocalServerRunning: false, exposeLocalServerOnNetwork: false },
|
||||
desktopSettings: { keepLocalServerRunning: false, exposeLocalServerOnNetwork: false, themeMode: 'system' },
|
||||
localWebUrl: 'http://localhost:3001',
|
||||
shareableWebUrl: 'http://localhost:3001',
|
||||
localServerRunning: false,
|
||||
localStartupLogs: [],
|
||||
computerUse: { enabled: false, consentMode: 'ask', running: false, connectedCount: 0, targetCount: 0 },
|
||||
environments: [
|
||||
{ id: 'env-api', name: 'api-gateway', subdomain: 'api-gateway', access_url: 'https://api-gateway.cloudcli.ai', status: 'running', region: 'fra1', agent: 'Claude Code' },
|
||||
{ id: 'env-web', name: 'web-frontend', subdomain: 'web-frontend', access_url: 'https://web-frontend.cloudcli.ai', status: 'stopped', region: 'sfo1', agent: 'Codex' },
|
||||
@@ -44,10 +45,10 @@ window.__MOCK_STATE__ = {
|
||||
},
|
||||
refreshEnvironments: function () { return Promise.resolve(clone(mockState)); },
|
||||
copyDiagnostics: function () { return Promise.resolve(clone(mockState)); },
|
||||
showComputerUsePreview: function () { return Promise.resolve(clone(mockState)); },
|
||||
showComputerAccess: function () { return Promise.resolve(clone(mockState)); },
|
||||
showEnvironmentPicker: function () { return Promise.resolve(clone(mockState)); },
|
||||
showLauncher: function () { return Promise.resolve(clone(mockState)); },
|
||||
showDesktopAppMenu: function () { return Promise.resolve(clone(mockState)); },
|
||||
showDesktopSettings: function () { return Promise.resolve(clone(mockState)); },
|
||||
showActiveEnvironmentActionsMenu: function () { return Promise.resolve(clone(mockState)); },
|
||||
openCloudDashboard: function () { return Promise.resolve(clone(mockState)); },
|
||||
runActiveEnvironmentAction: function () { return Promise.resolve(clone(mockState)); },
|
||||
@@ -59,7 +60,17 @@ window.__MOCK_STATE__ = {
|
||||
},
|
||||
updateSetting: function (key, value) {
|
||||
mockState.desktopSettings = mockState.desktopSettings || {};
|
||||
mockState.desktopSettings[key] = !!value;
|
||||
mockState.desktopSettings[key] = key === 'themeMode' ? value : !!value;
|
||||
return Promise.resolve(clone(mockState));
|
||||
},
|
||||
updateComputerUse: function (settings) {
|
||||
mockState.computerUse = mockState.computerUse || { enabled: false, consentMode: 'ask', running: false, connectedCount: 0, targetCount: 0 };
|
||||
if (typeof settings.enabled === 'boolean') mockState.computerUse.enabled = settings.enabled;
|
||||
if (settings.consentMode === 'auto' || settings.consentMode === 'ask') mockState.computerUse.consentMode = settings.consentMode;
|
||||
mockState.computerUse.running = mockState.computerUse.enabled;
|
||||
return Promise.resolve(clone(mockState));
|
||||
},
|
||||
showComputerAccessPermissions: function () {
|
||||
return Promise.resolve(clone(mockState));
|
||||
},
|
||||
openEnvironment: function (id) {
|
||||
@@ -144,6 +155,30 @@ window.__MOCK_STATE__ = {
|
||||
return error && error.message ? error.message : String(error);
|
||||
}
|
||||
|
||||
function themeLabel(mode) {
|
||||
if (mode === 'light') return 'Light';
|
||||
if (mode === 'dark') return 'Dark';
|
||||
return 'System';
|
||||
}
|
||||
|
||||
function resolveTheme(state) {
|
||||
var settings = state && state.desktopSettings ? state.desktopSettings : {};
|
||||
var mode = settings.themeMode || 'system';
|
||||
if (mode === 'light' || mode === 'dark') return mode;
|
||||
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
function computerUseStatus(state) {
|
||||
var computerUse = state && state.computerUse ? state.computerUse : {};
|
||||
if (!computerUse.enabled) {
|
||||
return { label: 'Off', tone: 'idle', detail: 'CloudCLI cannot use this computer.' };
|
||||
}
|
||||
if (computerUse.consentMode === 'auto') {
|
||||
return { label: 'Ready · Unattended', tone: 'warn', detail: 'Trusted agents can use this computer without a local approval prompt.' };
|
||||
}
|
||||
return { label: 'Ready · Ask first', tone: 'ok', detail: 'CloudCLI can use this computer. Agents need approval before control starts.' };
|
||||
}
|
||||
|
||||
var CC = {
|
||||
icon: icon,
|
||||
esc: esc,
|
||||
@@ -172,7 +207,19 @@ window.__MOCK_STATE__ = {
|
||||
|
||||
CC.setState = function (state) {
|
||||
if (state && typeof state === 'object') CC.state = state;
|
||||
CC.applyTheme(CC.state);
|
||||
CC.render(CC.state);
|
||||
if (CC.ui.openSheet) {
|
||||
CC.openSheet(CC.ui.openSheet);
|
||||
}
|
||||
};
|
||||
|
||||
CC.applyTheme = function (state) {
|
||||
var settings = state && state.desktopSettings ? state.desktopSettings : {};
|
||||
var themeMode = settings.themeMode || 'system';
|
||||
var resolvedTheme = resolveTheme(state);
|
||||
document.documentElement.setAttribute('data-theme', resolvedTheme);
|
||||
document.documentElement.setAttribute('data-theme-mode', themeMode);
|
||||
};
|
||||
|
||||
CC.refresh = function () {
|
||||
@@ -268,12 +315,30 @@ window.__MOCK_STATE__ = {
|
||||
return CC.run('Copied local URL to clipboard', function () { return bridge.copyLocalWebUrl(); });
|
||||
case 'diagnostics':
|
||||
return CC.run('Copied diagnostics to clipboard', function () { return bridge.copyDiagnostics(); });
|
||||
case 'computer-use':
|
||||
return CC.run('Opening Computer Use preview...', function () { return bridge.showComputerUsePreview(); });
|
||||
case 'set-setting':
|
||||
return CC.run('Saved', function () { return bridge.updateSetting(node.key, node.value); });
|
||||
case 'set-theme-mode':
|
||||
return CC.run('Saved', function () { return bridge.updateSetting('themeMode', node.value); });
|
||||
case 'set-computer-mode':
|
||||
return CC.run('Saved', function () {
|
||||
return bridge.updateComputerUse({
|
||||
enabled: true,
|
||||
consentMode: node.value,
|
||||
});
|
||||
});
|
||||
case 'set-computer-enabled':
|
||||
return CC.run('Saved', function () {
|
||||
var current = (CC.state && CC.state.computerUse) || { consentMode: 'ask' };
|
||||
return bridge.updateComputerUse({
|
||||
enabled: !!node.value,
|
||||
consentMode: current.consentMode === 'auto' ? 'auto' : 'ask',
|
||||
});
|
||||
});
|
||||
case 'computer-permissions':
|
||||
return CC.run('Opening system permissions...', function () { return bridge.showComputerAccessPermissions(); });
|
||||
case 'settings-toggle':
|
||||
return CC.run('Opening settings...', function () { return bridge.showDesktopAppMenu(); });
|
||||
CC.openSheet('app-settings');
|
||||
return;
|
||||
case 'dashboard':
|
||||
return CC.run('Opening CloudCLI dashboard...', function () { return bridge.openCloudDashboard(); });
|
||||
case 'env-action':
|
||||
@@ -283,11 +348,13 @@ window.__MOCK_STATE__ = {
|
||||
case 'env-row-menu':
|
||||
return CC.run('Opening environment actions...', function () { return bridge.showEnvironmentActionsMenu(node.getAttribute('data-cc-environment-id')); });
|
||||
case 'local-settings-toggle':
|
||||
CC.renderLocalSettings();
|
||||
overlay.classList.toggle('open');
|
||||
CC.openSheet('local-settings');
|
||||
return;
|
||||
case 'computer-settings-toggle':
|
||||
CC.openSheet('computer-access');
|
||||
return;
|
||||
case 'settings-close':
|
||||
overlay.classList.remove('open');
|
||||
CC.closeSheet();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
@@ -334,22 +401,164 @@ window.__MOCK_STATE__ = {
|
||||
'</div>';
|
||||
};
|
||||
|
||||
CC.renderLocalSettings = function () {
|
||||
var state = CC.state || {};
|
||||
var settings = state.desktopSettings || {};
|
||||
var url = localUrl(state) || 'starts on demand';
|
||||
CC.renderSheet = function (title, subtitle, sections, footer) {
|
||||
overlay.innerHTML =
|
||||
'<div class="cc-sheet cc-modal">' +
|
||||
'<div class="cc-sheet-h"><span class="lbl">Local Settings</span><button class="icon-btn" data-cc-action="settings-close">' + icon('x', 16) + '</button></div>' +
|
||||
'<div class="cc-grp"><div class="lbl">Local server</div>' +
|
||||
'<div class="cc-meta mono">' + esc(url) + '</div>' +
|
||||
'<div class="cc-row2"><button class="btn sm" data-cc-action="open-web">' + icon('arrow', 14) + 'Open in browser</button><button class="btn sm" data-cc-action="copy-web">' + icon('copy', 14) + 'Copy URL</button></div>' +
|
||||
'<label class="cc-toggle"><input type="checkbox" data-cc-setting="keepLocalServerRunning"' + (settings.keepLocalServerRunning ? ' checked' : '') + '><span><b>Keep server running</b><br>Leave Local CloudCLI available after you quit the app.</span></label>' +
|
||||
'<label class="cc-toggle"><input type="checkbox" data-cc-setting="exposeLocalServerOnNetwork"' + (settings.exposeLocalServerOnNetwork ? ' checked' : '') + '><span><b>Allow LAN access</b><br>Use the copied URL from another device on this network.</span></label>' +
|
||||
'<div class="cc-sheet-header">' +
|
||||
'<div class="cc-sheet-copy"><div class="cc-sheet-title">' + esc(title) + '</div><div class="cc-sheet-subtitle">' + esc(subtitle || '') + '</div></div>' +
|
||||
'<button class="icon-btn cc-sheet-close" data-cc-action="settings-close" title="Close">' + icon('x', 16) + '</button>' +
|
||||
'</div>' +
|
||||
'<div class="cc-sheet-body">' + sections.join('') + '</div>' +
|
||||
(footer ? '<div class="cc-sheet-footer">' + footer + '</div>' : '') +
|
||||
'</div>';
|
||||
};
|
||||
|
||||
CC.renderSection = function (eyebrow, title, body) {
|
||||
return '<section class="cc-section">' +
|
||||
'<div class="cc-section-head">' +
|
||||
'<div class="lbl">' + esc(eyebrow) + '</div>' +
|
||||
'<div class="cc-section-title">' + esc(title) + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="cc-section-body">' + body + '</div>' +
|
||||
'</section>';
|
||||
};
|
||||
|
||||
CC.renderRadioOption = function (name, value, checked, title, description) {
|
||||
return '<label class="cc-choice">' +
|
||||
'<input type="radio" name="' + esc(name) + '" value="' + esc(value) + '"' + (checked ? ' checked' : '') + '>' +
|
||||
'<span><b>' + esc(title) + '</b><br>' + esc(description) + '</span>' +
|
||||
'</label>';
|
||||
};
|
||||
|
||||
CC.openSheet = function (sheet) {
|
||||
if (sheet === 'app-settings') {
|
||||
CC.renderAppSettings();
|
||||
} else if (sheet === 'computer-access') {
|
||||
CC.renderComputerAccess();
|
||||
} else {
|
||||
CC.renderLocalSettings();
|
||||
}
|
||||
CC.ui.openSheet = sheet;
|
||||
overlay.classList.add('open');
|
||||
};
|
||||
|
||||
CC.closeSheet = function () {
|
||||
CC.ui.openSheet = null;
|
||||
overlay.classList.remove('open');
|
||||
};
|
||||
|
||||
CC.buildLocalServerSection = function (state, options) {
|
||||
options = options || {};
|
||||
var settings = state.desktopSettings || {};
|
||||
var url = localUrl(state) || 'starts on demand';
|
||||
var body = '<div class="cc-surface">' +
|
||||
'<div class="cc-meta mono">' + esc(url) + '</div>' +
|
||||
'<div class="cc-row2"><button class="btn sm" data-cc-action="open-web">' + icon('arrow', 14) + 'Open in browser</button><button class="btn sm" data-cc-action="copy-web">' + icon('copy', 14) + 'Copy URL</button></div>';
|
||||
if (options.includePreferences) {
|
||||
body +=
|
||||
'<label class="cc-toggle"><input type="checkbox" data-cc-setting="keepLocalServerRunning"' + (settings.keepLocalServerRunning ? ' checked' : '') + '><span><b>Keep server running</b><br>Leave Local CloudCLI available after you quit the app.</span></label>' +
|
||||
'<label class="cc-toggle"><input type="checkbox" data-cc-setting="exposeLocalServerOnNetwork"' + (settings.exposeLocalServerOnNetwork ? ' checked' : '') + '><span><b>Allow LAN access</b><br>Use the copied URL from another device on this network.</span></label>';
|
||||
}
|
||||
body += '</div>';
|
||||
return CC.renderSection(
|
||||
options.eyebrow || 'LOCAL SERVER',
|
||||
options.title || 'Run Local CloudCLI on this machine',
|
||||
body
|
||||
);
|
||||
};
|
||||
|
||||
CC.buildThemeSection = function (state) {
|
||||
var settings = state.desktopSettings || {};
|
||||
return CC.renderSection('APPEARANCE', 'Desktop theme', '' +
|
||||
'<div class="cc-surface cc-choice-group">' +
|
||||
CC.renderRadioOption('desktop-theme', 'system', settings.themeMode === 'system', 'System', 'Follow the operating system appearance.') +
|
||||
CC.renderRadioOption('desktop-theme', 'light', settings.themeMode === 'light', 'Light', 'Use the light interface appearance.') +
|
||||
CC.renderRadioOption('desktop-theme', 'dark', settings.themeMode === 'dark', 'Dark', 'Use the dark interface appearance.') +
|
||||
'</div>'
|
||||
);
|
||||
};
|
||||
|
||||
CC.buildComputerAccessSections = function (state, options) {
|
||||
options = options || {};
|
||||
var computerUse = state.computerUse || {};
|
||||
var status = computerUseStatus(state);
|
||||
var sections = [];
|
||||
|
||||
if (options.includeStatus !== false) {
|
||||
sections.push(CC.renderSection(options.statusEyebrow || 'STATUS', status.label, '' +
|
||||
'<div class="cc-surface">' +
|
||||
'<div class="cc-status-badge ' + esc(status.tone) + '">' + esc(status.label) + '</div>' +
|
||||
'<div class="cc-meta">' + esc(status.detail) + '</div>' +
|
||||
'</div>'
|
||||
));
|
||||
}
|
||||
|
||||
sections.push(CC.renderSection(options.accessEyebrow || 'ACCESS', 'Allow desktop access', '' +
|
||||
'<div class="cc-surface">' +
|
||||
'<label class="cc-toggle"><input type="checkbox" data-cc-computer-enabled="true"' + (computerUse.enabled ? ' checked' : '') + '><span><b>Allow desktop access</b><br>Let CloudCLI use the computer. Agents cannot act until you approve a session.</span></label>' +
|
||||
'</div>'
|
||||
));
|
||||
|
||||
sections.push(CC.renderSection(options.modeEyebrow || 'ACCESS MODE', 'Choose how agent approval works', '' +
|
||||
'<div class="cc-surface cc-choice-group">' +
|
||||
CC.renderRadioOption('computer-access-mode', 'ask', computerUse.consentMode !== 'auto', 'Ask before each session', 'Agents can request control, but you approve every session.') +
|
||||
CC.renderRadioOption('computer-access-mode', 'auto', computerUse.consentMode === 'auto', 'Unattended access', 'Trusted agents can use this computer without a local approval prompt.') +
|
||||
'</div>'
|
||||
));
|
||||
|
||||
var setupBody = '<div class="cc-surface">' +
|
||||
'<div class="cc-kv"><span>Linked environments</span><span>' + esc(String(computerUse.connectedCount || 0)) + '</span></div>' +
|
||||
'<div class="cc-kv"><span>Target environments</span><span>' + esc(String(computerUse.targetCount || 0)) + '</span></div>';
|
||||
if (options.includeTheme) {
|
||||
setupBody += '<div class="cc-kv"><span>Theme</span><span>' + esc(themeLabel((state.desktopSettings && state.desktopSettings.themeMode) || 'system')) + '</span></div>';
|
||||
}
|
||||
if (CC.platform === 'mac') {
|
||||
setupBody += '<div class="cc-actions-inline"><button class="btn sm" data-cc-action="computer-permissions">Open macOS permissions</button></div>';
|
||||
}
|
||||
setupBody += '</div>';
|
||||
sections.push(CC.renderSection(options.setupEyebrow || 'SETUP', 'System permissions and environment links', setupBody));
|
||||
return sections;
|
||||
};
|
||||
|
||||
CC.renderLocalSettings = function () {
|
||||
var state = CC.state || {};
|
||||
var sections = [
|
||||
CC.buildLocalServerSection(state, { includePreferences: false }),
|
||||
CC.renderSection('PREFERENCES', 'How the local service behaves', '' +
|
||||
'<div class="cc-surface">' +
|
||||
'<label class="cc-toggle"><input type="checkbox" data-cc-setting="keepLocalServerRunning"' + ((state.desktopSettings || {}).keepLocalServerRunning ? ' checked' : '') + '><span><b>Keep server running</b><br>Leave Local CloudCLI available after you quit the app.</span></label>' +
|
||||
'<label class="cc-toggle"><input type="checkbox" data-cc-setting="exposeLocalServerOnNetwork"' + ((state.desktopSettings || {}).exposeLocalServerOnNetwork ? ' checked' : '') + '><span><b>Allow LAN access</b><br>Use the copied URL from another device on this network.</span></label>' +
|
||||
'</div>'
|
||||
),
|
||||
CC.buildThemeSection(state),
|
||||
];
|
||||
CC.renderSheet('Local Settings', 'Manage how Local CloudCLI runs and appears on this computer.', sections);
|
||||
};
|
||||
|
||||
CC.renderAppSettings = function () {
|
||||
var state = CC.state || {};
|
||||
var sections = [
|
||||
CC.buildLocalServerSection(state, {
|
||||
eyebrow: 'GENERAL',
|
||||
title: 'Local CloudCLI',
|
||||
includePreferences: true,
|
||||
}),
|
||||
CC.buildThemeSection(state),
|
||||
];
|
||||
sections.push.apply(sections, CC.buildComputerAccessSections(state, {
|
||||
statusEyebrow: 'COMPUTER ACCESS',
|
||||
modeEyebrow: 'APPROVAL MODE',
|
||||
includeTheme: false,
|
||||
}));
|
||||
CC.renderSheet('Settings', 'Manage local behavior, appearance, and desktop access for this computer.', sections);
|
||||
};
|
||||
|
||||
CC.renderComputerAccess = function () {
|
||||
var state = CC.state || {};
|
||||
var sections = CC.buildComputerAccessSections(state, { includeTheme: true });
|
||||
CC.renderSheet('Computer Access', 'Let cloud agents use this computer with explicit approval or unattended access.', sections);
|
||||
};
|
||||
|
||||
CC.render = function (state) {
|
||||
state = state || CC.state;
|
||||
var titlebar = (CC._reg.titlebar || CC.titlebar)(state);
|
||||
@@ -387,7 +596,7 @@ window.__MOCK_STATE__ = {
|
||||
return;
|
||||
}
|
||||
if (overlay.classList.contains('open') && !event.target.closest('.cc-sheet')) {
|
||||
overlay.classList.remove('open');
|
||||
CC.closeSheet();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -398,12 +607,27 @@ window.__MOCK_STATE__ = {
|
||||
key: setting.getAttribute('data-cc-setting'),
|
||||
value: setting.checked,
|
||||
});
|
||||
return;
|
||||
}
|
||||
var theme = event.target.closest('[name="desktop-theme"]');
|
||||
if (theme) {
|
||||
CC.act('set-theme-mode', { value: theme.value });
|
||||
return;
|
||||
}
|
||||
var computerMode = event.target.closest('[name="computer-access-mode"]');
|
||||
if (computerMode) {
|
||||
CC.act('set-computer-mode', { value: computerMode.value });
|
||||
return;
|
||||
}
|
||||
var computerEnabled = event.target.closest('[data-cc-computer-enabled]');
|
||||
if (computerEnabled) {
|
||||
CC.act('set-computer-enabled', { value: computerEnabled.checked });
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Escape' && overlay.classList.contains('open')) {
|
||||
overlay.classList.remove('open');
|
||||
CC.closeSheet();
|
||||
return;
|
||||
}
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === ',') {
|
||||
@@ -429,9 +653,21 @@ window.__MOCK_STATE__ = {
|
||||
document.body.classList.add(CC.platform);
|
||||
|
||||
wireEvents();
|
||||
if (window.matchMedia) {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () {
|
||||
CC.applyTheme(CC.state);
|
||||
});
|
||||
}
|
||||
if (bridge.onStateUpdated) {
|
||||
bridge.onStateUpdated(function (state) { CC.setState(state); });
|
||||
}
|
||||
if (bridge.onLauncherCommand) {
|
||||
bridge.onLauncherCommand(function (command) {
|
||||
if (command && command.type === 'open-sheet') {
|
||||
CC.openSheet(command.sheet);
|
||||
}
|
||||
});
|
||||
}
|
||||
CC.refresh().catch(function (error) {
|
||||
CC._status = { msg: errMsg(error), tone: 'error' };
|
||||
CC.render(CC.state);
|
||||
@@ -462,7 +698,9 @@ window.__MOCK_STATE__ = {
|
||||
function localPane(state) {
|
||||
return '<div class="pane-h"><div><h2 class="pane-title">Local CloudCLI</h2><p class="pane-sub">Run the open-source app on this machine. No account required.</p></div></div>' +
|
||||
'<div class="card"><div class="card-head"><div><div class="card-t">Local server</div><div class="card-sub mono">' + CC.esc(CC.localUrl(state) || 'Starts on demand') + '</div></div><div class="card-tools"><span class="dot" style="background:' + (state.localServerRunning ? 'var(--ok)' : 'var(--tx3)') + '"></span><button class="icon-btn" data-cc-action="local-settings-toggle" title="Local settings">' + CC.icon('gear', 16) + '</button></div></div>' +
|
||||
'<div class="card-actions"><button class="btn pri" data-cc-action="local">' + CC.icon('play', 15) + 'Open Local CloudCLI</button><button class="btn" data-cc-action="open-web">' + CC.icon('arrow', 14) + 'Open in browser</button><button class="btn" data-cc-action="copy-web">' + CC.icon('copy', 14) + 'Copy URL</button></div></div>';
|
||||
'<div class="card-actions"><button class="btn pri" data-cc-action="local">' + CC.icon('play', 15) + 'Open Local CloudCLI</button><button class="btn" data-cc-action="open-web">' + CC.icon('arrow', 14) + 'Open in browser</button><button class="btn" data-cc-action="copy-web">' + CC.icon('copy', 14) + 'Copy URL</button></div></div>' +
|
||||
'<div class="card"><div class="card-head"><div><div class="card-t">Computer Access</div><div class="card-sub">' + CC.esc(computerUseStatus(state).detail) + '</div></div><div class="card-tools"><span class="badge ' + CC.esc(computerUseStatus(state).tone) + '">' + CC.esc(computerUseStatus(state).label) + '</span><button class="icon-btn" data-cc-action="computer-settings-toggle" title="Computer access">' + CC.icon('monitor', 16) + '</button></div></div>' +
|
||||
'<div class="card-actions"><button class="btn" data-cc-action="computer-settings-toggle">' + CC.icon('settings', 14) + 'Manage access</button></div></div>';
|
||||
}
|
||||
|
||||
function envRow(environment) {
|
||||
|
||||
@@ -222,6 +222,7 @@ export class LocalServerController {
|
||||
this.desktopSettings = {
|
||||
keepLocalServerRunning: false,
|
||||
exposeLocalServerOnNetwork: false,
|
||||
themeMode: 'system',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -295,11 +296,13 @@ export class LocalServerController {
|
||||
this.desktopSettings = {
|
||||
keepLocalServerRunning: Boolean(stored.keepLocalServerRunning),
|
||||
exposeLocalServerOnNetwork: Boolean(stored.exposeLocalServerOnNetwork),
|
||||
themeMode: stored.themeMode === 'light' || stored.themeMode === 'dark' ? stored.themeMode : 'system',
|
||||
};
|
||||
} catch {
|
||||
this.desktopSettings = {
|
||||
keepLocalServerRunning: false,
|
||||
exposeLocalServerOnNetwork: false,
|
||||
themeMode: 'system',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -308,6 +311,7 @@ export class LocalServerController {
|
||||
this.desktopSettings = {
|
||||
keepLocalServerRunning: Boolean(nextSettings.keepLocalServerRunning),
|
||||
exposeLocalServerOnNetwork: Boolean(nextSettings.exposeLocalServerOnNetwork),
|
||||
themeMode: nextSettings.themeMode === 'light' || nextSettings.themeMode === 'dark' ? nextSettings.themeMode : 'system',
|
||||
};
|
||||
await fs.mkdir(path.dirname(this.settingsPath), { recursive: true });
|
||||
await fs.writeFile(this.settingsPath, JSON.stringify(this.desktopSettings, null, 2), 'utf8');
|
||||
@@ -321,7 +325,8 @@ export class LocalServerController {
|
||||
|
||||
const wasExposeSetting = key === 'exposeLocalServerOnNetwork';
|
||||
const wasLocalRunning = Boolean(this.localServerUrl);
|
||||
await this.saveDesktopSettings({ ...this.desktopSettings, [key]: Boolean(value) });
|
||||
const nextValue = key === 'themeMode' ? value : Boolean(value);
|
||||
await this.saveDesktopSettings({ ...this.desktopSettings, [key]: nextValue });
|
||||
|
||||
return {
|
||||
desktopSettings: this.desktopSettings,
|
||||
|
||||
@@ -71,7 +71,7 @@ async function promptComputerUseConsent(sessionId) {
|
||||
buttons: ['Allow this session', 'Deny'],
|
||||
defaultId: 0,
|
||||
cancelId: 1,
|
||||
title: 'Computer Use request',
|
||||
title: 'Computer Access request',
|
||||
message: 'An agent wants to control this computer',
|
||||
detail: [
|
||||
'A cloud agent is requesting control of your mouse, keyboard, and screen for this session.',
|
||||
@@ -248,7 +248,7 @@ async function copyDiagnostics() {
|
||||
});
|
||||
}
|
||||
|
||||
async function showMacComputerUsePermissions() {
|
||||
async function showMacComputerAccessPermissions() {
|
||||
if (process.platform !== 'darwin') return;
|
||||
const screenStatus = systemPreferences.getMediaAccessStatus('screen');
|
||||
const accessibilityTrusted = systemPreferences.isTrustedAccessibilityClient(false);
|
||||
@@ -256,7 +256,7 @@ async function showMacComputerUsePermissions() {
|
||||
`Screen Recording: ${screenStatus === 'granted' ? 'granted' : 'not granted'}`,
|
||||
`Accessibility: ${accessibilityTrusted ? 'granted' : 'not granted'}`,
|
||||
'',
|
||||
'Computer Use needs both permissions to capture the screen and control the mouse and keyboard.',
|
||||
'Computer Access needs both permissions to capture the screen and control the mouse and keyboard.',
|
||||
'After granting a permission, fully quit and reopen CloudCLI so the change takes effect.',
|
||||
].join('\n');
|
||||
|
||||
@@ -265,8 +265,8 @@ async function showMacComputerUsePermissions() {
|
||||
buttons: ['Open Screen Recording', 'Open Accessibility', 'Close'],
|
||||
defaultId: 0,
|
||||
cancelId: 2,
|
||||
title: 'Computer Use Permissions',
|
||||
message: 'Grant macOS permissions for Computer Use',
|
||||
title: 'Computer Access Permissions',
|
||||
message: 'Grant macOS permissions for Computer Access',
|
||||
detail,
|
||||
});
|
||||
|
||||
@@ -277,58 +277,21 @@ async function showMacComputerUsePermissions() {
|
||||
}
|
||||
}
|
||||
|
||||
// Desktop control for cloud Computer Use: the desktop acts as a TeamViewer-style
|
||||
// agent for hosted environments. Enabling here lets cloud agents drive THIS
|
||||
// machine; the user picks whether to auto-connect or be asked per session.
|
||||
async function showComputerUsePreview() {
|
||||
const state = computerAgent?.getState() || { enabled: false, consentMode: 'ask' };
|
||||
const buttons = [];
|
||||
const actions = [];
|
||||
async function showComputerAccess() {
|
||||
await desktopWindow?.showLauncher();
|
||||
desktopWindow?.emitLauncherCommand({ type: 'open-sheet', sheet: 'computer-access' });
|
||||
return getDesktopState();
|
||||
}
|
||||
|
||||
if (!state.enabled) {
|
||||
buttons.push('Enable — ask each session'); actions.push({ kind: 'enable', consentMode: 'ask' });
|
||||
buttons.push('Enable — auto-connect'); actions.push({ kind: 'enable', consentMode: 'auto' });
|
||||
} else {
|
||||
buttons.push('Disable Computer Use'); actions.push({ kind: 'disable' });
|
||||
const otherMode = state.consentMode === 'auto' ? 'ask' : 'auto';
|
||||
buttons.push(`Switch to ${otherMode === 'auto' ? 'auto-connect' : 'ask each session'}`);
|
||||
actions.push({ kind: 'enable', consentMode: otherMode });
|
||||
}
|
||||
if (process.platform === 'darwin') {
|
||||
buttons.push('macOS Permissions…'); actions.push({ kind: 'permissions' });
|
||||
}
|
||||
buttons.push('Close'); actions.push({ kind: 'close' });
|
||||
|
||||
const statusLine = state.enabled
|
||||
? `Enabled — ${state.consentMode === 'auto' ? 'auto-connect' : 'ask each session'} · ${state.connectedCount || 0} environment(s) linked`
|
||||
: 'Disabled';
|
||||
|
||||
const { response } = await dialog.showMessageBox(desktopWindow?.getMainWindow() || undefined, {
|
||||
type: 'question',
|
||||
buttons,
|
||||
defaultId: 0,
|
||||
cancelId: buttons.length - 1,
|
||||
title: 'Computer Use (Desktop Agent)',
|
||||
message: 'Let cloud agents control this computer',
|
||||
detail: [
|
||||
`Status: ${statusLine}`,
|
||||
'',
|
||||
'When enabled, agents running in your CloudCLI cloud environments can see this screen and drive its mouse and keyboard.',
|
||||
'• Ask each session: you approve a prompt the first time each session wants control.',
|
||||
'• Auto-connect: sessions can act without a prompt.',
|
||||
process.platform === 'linux' ? '\nLinux needs X utilities (libxtst, imagemagick) installed to capture the screen and drive input.' : '',
|
||||
].join('\n'),
|
||||
});
|
||||
|
||||
const action = actions[response];
|
||||
if (!action) return;
|
||||
if (action.kind === 'enable') {
|
||||
await computerAgent?.saveSettings({ enabled: true, consentMode: action.consentMode });
|
||||
} else if (action.kind === 'disable') {
|
||||
await computerAgent?.saveSettings({ enabled: false, consentMode: state.consentMode });
|
||||
} else if (action.kind === 'permissions') {
|
||||
await showMacComputerUsePermissions();
|
||||
}
|
||||
async function updateComputerUse(settings) {
|
||||
const current = computerAgent?.getSettings() || { enabled: false, consentMode: 'ask' };
|
||||
const next = {
|
||||
enabled: typeof settings?.enabled === 'boolean' ? settings.enabled : current.enabled,
|
||||
consentMode: settings?.consentMode === 'auto' ? 'auto' : 'ask',
|
||||
};
|
||||
await computerAgent?.saveSettings(next);
|
||||
syncDesktopState();
|
||||
return getDesktopState();
|
||||
}
|
||||
|
||||
async function refreshCloudEnvironments({ showErrors = false } = {}) {
|
||||
@@ -725,11 +688,16 @@ function registerIpcHandlers() {
|
||||
await desktopWindow.showLauncher();
|
||||
return getDesktopState();
|
||||
});
|
||||
ipcMain.handle('cloudcli-desktop:show-computer-use-preview', async () => {
|
||||
await showComputerUsePreview();
|
||||
ipcMain.handle('cloudcli-desktop:show-computer-access', async () => {
|
||||
await showComputerAccess();
|
||||
return getDesktopState();
|
||||
});
|
||||
ipcMain.handle('cloudcli-desktop:show-desktop-app-menu', async () => desktopWindow.showDesktopAppMenu());
|
||||
ipcMain.handle('cloudcli-desktop:update-computer-use', async (_event, settings) => updateComputerUse(settings));
|
||||
ipcMain.handle('cloudcli-desktop:show-computer-use-permissions', async () => {
|
||||
await showMacComputerAccessPermissions();
|
||||
return getDesktopState();
|
||||
});
|
||||
ipcMain.handle('cloudcli-desktop:show-desktop-settings', async () => desktopWindow.showDesktopSettings());
|
||||
ipcMain.handle('cloudcli-desktop:show-active-environment-actions-menu', async () => desktopWindow.showActiveEnvironmentActionsMenu());
|
||||
ipcMain.handle('cloudcli-desktop:show-environment-actions-menu', async (_event, environmentId) => desktopWindow.showEnvironmentActionsMenu(environmentId));
|
||||
ipcMain.handle('cloudcli-desktop:switch-tab', async (_event, tabId) => desktopWindow.switchDesktopTab(tabId));
|
||||
@@ -812,7 +780,7 @@ async function createDesktopWindow() {
|
||||
openCloudDashboard,
|
||||
refreshCloudEnvironments: () => refreshCloudEnvironments({ showErrors: true }),
|
||||
setActiveTarget,
|
||||
showComputerUsePreview,
|
||||
showComputerAccess,
|
||||
showEnvironmentPicker,
|
||||
showError,
|
||||
startEnvironment,
|
||||
|
||||
@@ -14,8 +14,10 @@ if (window.location.protocol === 'file:') {
|
||||
refreshEnvironments: () => ipcRenderer.invoke('cloudcli-desktop:refresh-environments'),
|
||||
showEnvironmentPicker: () => ipcRenderer.invoke('cloudcli-desktop:show-environment-picker'),
|
||||
showLauncher: () => ipcRenderer.invoke('cloudcli-desktop:show-launcher'),
|
||||
showComputerUsePreview: () => ipcRenderer.invoke('cloudcli-desktop:show-computer-use-preview'),
|
||||
showDesktopAppMenu: () => ipcRenderer.invoke('cloudcli-desktop:show-desktop-app-menu'),
|
||||
showComputerAccess: () => ipcRenderer.invoke('cloudcli-desktop:show-computer-access'),
|
||||
updateComputerUse: (settings) => ipcRenderer.invoke('cloudcli-desktop:update-computer-use', settings),
|
||||
showComputerAccessPermissions: () => ipcRenderer.invoke('cloudcli-desktop:show-computer-access-permissions'),
|
||||
showDesktopSettings: () => ipcRenderer.invoke('cloudcli-desktop:show-desktop-settings'),
|
||||
showActiveEnvironmentActionsMenu: () => ipcRenderer.invoke('cloudcli-desktop:show-active-environment-actions-menu'),
|
||||
showEnvironmentActionsMenu: (environmentId) => ipcRenderer.invoke('cloudcli-desktop:show-environment-actions-menu', environmentId),
|
||||
switchTab: (tabId) => ipcRenderer.invoke('cloudcli-desktop:switch-tab', tabId),
|
||||
@@ -24,5 +26,8 @@ if (window.location.protocol === 'file:') {
|
||||
onStateUpdated: (callback) => {
|
||||
ipcRenderer.on('cloudcli-desktop:state-updated', (_event, state) => callback(state));
|
||||
},
|
||||
onLauncherCommand: (callback) => {
|
||||
ipcRenderer.on('cloudcli-desktop:launcher-command', (_event, command) => callback(command));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"desktop:dev": "cross-env ELECTRON_DEV_URL=http://127.0.0.1:5173 electron electron/main.js",
|
||||
"desktop:pack": "npm run build && electron-builder --dir",
|
||||
"desktop:dist:mac": "npm run build && electron-builder --mac dmg zip",
|
||||
"desktop:dist:win": "npm run build && electron-builder --win nsis zip",
|
||||
"desktop:icon:mac": "node electron/scripts/generate-macos-icon.js",
|
||||
"build": "npm run build:client && npm run build:server",
|
||||
"build:client": "vite build",
|
||||
@@ -100,6 +101,12 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis",
|
||||
"zip"
|
||||
]
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
|
||||
@@ -264,7 +264,7 @@ export default function SidebarContent({
|
||||
<div key={projectResult.projectName} className="space-y-1">
|
||||
<div className="flex items-center gap-1.5 px-1 py-1">
|
||||
<Folder className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
||||
<span className="truncate text-xs font-medium text-foreground">
|
||||
<span className="truncate text-xs font-normal text-foreground">
|
||||
{projectResult.projectDisplayName}
|
||||
</span>
|
||||
</div>
|
||||
@@ -284,7 +284,7 @@ export default function SidebarContent({
|
||||
>
|
||||
<div className="mb-1 flex items-center gap-1.5">
|
||||
<MessageSquare className="h-3 w-3 flex-shrink-0 text-primary" />
|
||||
<span className="truncate text-xs font-medium text-foreground">
|
||||
<span className="truncate text-xs font-normal text-foreground">
|
||||
{session.sessionSummary}
|
||||
</span>
|
||||
{session.provider && session.provider !== 'claude' && (
|
||||
@@ -296,7 +296,7 @@ export default function SidebarContent({
|
||||
<div className="space-y-1 pl-4">
|
||||
{session.matches.map((match, idx) => (
|
||||
<div key={idx} className="flex items-start gap-1">
|
||||
<span className="mt-0.5 flex-shrink-0 text-[10px] font-medium uppercase text-muted-foreground/60">
|
||||
<span className="mt-0.5 flex-shrink-0 text-[10px] font-normal uppercase text-muted-foreground/60">
|
||||
{match.role === 'user' ? 'U' : 'A'}
|
||||
</span>
|
||||
<HighlightedSnippet
|
||||
@@ -334,11 +334,11 @@ export default function SidebarContent({
|
||||
<span className="flex h-6 w-6 items-center justify-center rounded-md bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
|
||||
<Activity className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
<span className="truncate text-xs font-medium text-foreground">
|
||||
<span className="truncate text-xs font-normal text-foreground">
|
||||
{t('running.title', 'Running now')}
|
||||
</span>
|
||||
</div>
|
||||
<span className="rounded-full bg-emerald-500/10 px-2 py-0.5 text-[11px] font-medium text-emerald-700 dark:text-emerald-300">
|
||||
<span className="rounded-full bg-emerald-500/10 px-2 py-0.5 text-[11px] font-normal text-emerald-700 dark:text-emerald-300">
|
||||
{runningSessionsCount}
|
||||
</span>
|
||||
</div>
|
||||
@@ -393,7 +393,7 @@ export default function SidebarContent({
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<Folder className="h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" />
|
||||
<span className="truncate text-sm font-medium text-foreground">
|
||||
<span className="truncate text-sm font-normal text-foreground">
|
||||
{project.displayName}
|
||||
</span>
|
||||
<span className="inline-flex items-center justify-center rounded-full bg-muted px-1 py-px text-center text-[7px] font-medium uppercase leading-none tracking-[0.02em] text-muted-foreground">
|
||||
@@ -446,7 +446,7 @@ export default function SidebarContent({
|
||||
<SessionProviderLogo provider={session.__provider} className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="truncate text-xs font-medium text-foreground">
|
||||
<span className="truncate text-xs font-normal text-foreground">
|
||||
{(typeof session.summary === 'string' && session.summary.trim().length > 0
|
||||
? session.summary
|
||||
: typeof session.name === 'string' && session.name.trim().length > 0
|
||||
@@ -482,7 +482,7 @@ export default function SidebarContent({
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<Folder className="h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" />
|
||||
<span className="truncate text-sm font-medium text-foreground">
|
||||
<span className="truncate text-sm font-normal text-foreground">
|
||||
{group.projectDisplayName}
|
||||
</span>
|
||||
{group.isProjectArchived && (
|
||||
@@ -511,7 +511,7 @@ export default function SidebarContent({
|
||||
<SessionProviderLogo provider={session.provider} className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="truncate text-xs font-medium text-foreground">
|
||||
<span className="truncate text-xs font-normal text-foreground">
|
||||
{session.sessionTitle}
|
||||
</span>
|
||||
{session.lastActivity && (
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function SidebarFooter({
|
||||
<span className="absolute -right-0.5 -top-0.5 h-1.5 w-1.5 animate-pulse rounded-full bg-blue-500" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<span className="block truncate text-sm font-medium text-blue-600 dark:text-blue-300">
|
||||
<span className="block truncate text-sm font-normal text-blue-600 dark:text-blue-300">
|
||||
{releaseInfo?.title || `v${latestVersion}`}
|
||||
</span>
|
||||
<span className="text-[10px] text-blue-500/70 dark:text-blue-400/60">
|
||||
@@ -73,7 +73,7 @@ export default function SidebarFooter({
|
||||
<span className="absolute -right-0.5 -top-0.5 h-1.5 w-1.5 animate-pulse rounded-full bg-blue-500" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 text-left">
|
||||
<span className="block truncate text-sm font-medium text-blue-600 dark:text-blue-300">
|
||||
<span className="block truncate text-sm font-normal text-blue-600 dark:text-blue-300">
|
||||
{releaseInfo?.title || `v${latestVersion}`}
|
||||
</span>
|
||||
<span className="text-xs text-blue-500/70 dark:text-blue-400/60">
|
||||
@@ -150,7 +150,7 @@ export default function SidebarFooter({
|
||||
<div className="flex h-7 w-7 items-center justify-center rounded-lg bg-background/80">
|
||||
<Bug className="h-4 w-4 text-muted-foreground" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-foreground">{t('actions.reportIssue')}</span>
|
||||
<span className="text-sm font-normal text-foreground">{t('actions.reportIssue')}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -165,7 +165,7 @@ export default function SidebarFooter({
|
||||
<div className="flex h-7 w-7 items-center justify-center rounded-lg bg-background/80">
|
||||
<DiscordIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-foreground">{t('actions.joinCommunity')}</span>
|
||||
<span className="text-sm font-normal text-foreground">{t('actions.joinCommunity')}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -178,7 +178,7 @@ export default function SidebarFooter({
|
||||
<div className="flex h-7 w-7 items-center justify-center rounded-lg bg-background/80">
|
||||
<Settings className="h-4 w-4 text-muted-foreground" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-foreground">{t('actions.settings')}</span>
|
||||
<span className="text-sm font-normal text-foreground">{t('actions.settings')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -167,7 +167,7 @@ export default function SidebarHeader({
|
||||
aria-label={t('search.runningTooltip', 'Running sessions')}
|
||||
title={t('search.runningTooltip', 'Running sessions')}
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-medium transition-all",
|
||||
"flex items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-normal transition-all",
|
||||
searchMode === 'running'
|
||||
? "bg-background shadow-sm text-foreground ring-1 ring-emerald-500/15"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
@@ -307,7 +307,7 @@ export default function SidebarHeader({
|
||||
aria-label={t('search.runningTooltip', 'Running sessions')}
|
||||
title={t('search.runningTooltip', 'Running sessions')}
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-medium transition-all",
|
||||
"flex items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-normal transition-all",
|
||||
searchMode === 'running'
|
||||
? "bg-background shadow-sm text-foreground ring-1 ring-emerald-500/15"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
|
||||
@@ -102,7 +102,7 @@ export default function TaskIndicator({
|
||||
title={indicatorConfig.title}
|
||||
>
|
||||
<Icon className={sizeClassNames[size]} />
|
||||
<span className="font-medium">{indicatorConfig.label}</span>
|
||||
<span className="font-normal">{indicatorConfig.label}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -91,4 +91,4 @@ export const ThemeProvider = ({ children }) => {
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -129,6 +129,8 @@
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user