Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion openless-all/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark" />
<title>OpenLess</title>
</head>
<body>
Expand Down
29 changes: 27 additions & 2 deletions openless-all/app/scripts/aura-skin-contract.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ assertUsesClassName(
'sample should accept className usage',
);

const [tokens, globalCss, shell, settingsModal, overview, settingsTabs, themeMode, stylePage, sourceFiles, remoteStyle] = await Promise.all([
const [tokens, globalCss, shell, settingsModal, overview, settingsTabs, themeMode, stylePage, sourceFiles, remoteStyle, selectLite, translationPage] = await Promise.all([
read('src/styles/tokens.css'),
read('src/styles/global.css'),
read('src/components/FloatingShell.tsx'),
Expand All @@ -197,6 +197,8 @@ const [tokens, globalCss, shell, settingsModal, overview, settingsTabs, themeMod
read('src/pages/Style.tsx'),
walkSourceFiles(srcRoot),
read('src-tauri/src/remote_server/assets/style.css'),
read('src/components/ui/SelectLite.tsx'),
read('src/pages/Translation.tsx'),
]);

assert.match(tokens, /--ol-shell-radius:/, 'tokens.css must define --ol-shell-radius');
Expand Down Expand Up @@ -363,6 +365,28 @@ assert.match(
'tokens.css must define --ol-capsule-confirm-ink in dark theme',
);

assert.match(tokens, /--ol-select-trigger-bg:/, 'tokens.css must define --ol-select-trigger-bg');
assert.match(tokens, /--ol-select-popover-bg:/, 'tokens.css must define --ol-select-popover-bg');
assert.match(
tokens,
/\[data-ol-theme='dark'\][\s\S]*--ol-select-popover-bg:/,
'tokens.css must define --ol-select-popover-bg in dark theme',
);

assert.match(selectLite, /--ol-select-trigger-bg/, 'SelectLite must use --ol-select-trigger-bg');
assert.match(selectLite, /--ol-select-popover-bg/, 'SelectLite must use --ol-select-popover-bg');
assert.match(selectLite, /--ol-select-option-hover-bg/, 'SelectLite must use --ol-select-option-hover-bg');
assert.doesNotMatch(
selectLite,
/rgba\(\s*252\s*,/,
'SelectLite must not hardcode light popover rgba backgrounds',
);
assert.doesNotMatch(
translationPage,
/background:\s*'#fff'/,
'Translation.tsx must not override SelectLite with hardcoded #fff background',
);

assert.match(globalCss, /\.ol-app-shell-bg\b/, 'global.css must expose .ol-app-shell-bg');
assert.match(globalCss, /\.ol-aura-panel\b/, 'global.css must expose .ol-aura-panel');
assert.doesNotMatch(globalCss, /@keyframes ol-aura-halo/, 'global.css must not add an animated halo');
Expand Down Expand Up @@ -433,7 +457,8 @@ const illegalCssStringPatterns = [
/background:\s*'var\([^)]+\)';/,
];

const forbiddenInlineInkBackground = /background:\s*'var\(--ol-ink\)'/;
const forbiddenInlineInkBackground =
/background:\s*'var\(--ol-ink\)'|background:\s*[^,\n;{]+?\?\s*'var\(--ol-ink\)'/;

const forbiddenBlueOnAccentCombo =
/background:[\s\S]{0,200}var\(--ol-blue\)[\s\S]{0,500}?color:[\s\S]{0,200}var\(--ol-on-accent\)|color:[\s\S]{0,200}var\(--ol-on-accent\)[\s\S]{0,500}?background:[\s\S]{0,200}var\(--ol-blue\)/;
Expand Down
20 changes: 20 additions & 0 deletions openless-all/app/src-tauri/src/remote_server/assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,26 @@
--safe-bottom: env(safe-area-inset-bottom, 0px);
}

[data-ol-theme='dark'] {
--bg: #0b0e13;
--surface: #141922;
--surface-2: #1a202b;
--line: rgba(255, 255, 255, 0.09);
--line-strong: rgba(255, 255, 255, 0.16);
--ink: #f4f7fb;
--ink-2: #d8dfeb;
--ink-3: rgba(244, 247, 251, 0.74);
--ink-4: rgba(244, 247, 251, 0.58);
--blue: #60a5fa;
--blue-hover: #3b82f6;
--blue-soft: rgba(96, 165, 250, 0.14);
--blue-ring: rgba(96, 165, 250, 0.30);
--on-accent: #f8fbff;
--accent-solid-bg: #2563eb;
--accent-solid-bg-hover: #3b82f6;
--accent-solid-ink: #f8fbff;
}

* {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
Expand Down
17 changes: 17 additions & 0 deletions openless-all/app/src-tauri/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ pub enum UpdateChannel {
Beta,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub enum ThemeMode {
#[default]
System,
Light,
Dark,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum InsertStatus {
Expand Down Expand Up @@ -721,6 +730,9 @@ pub struct UserPreferences {
/// 用户改用托盘菜单访问主窗口。默认 false 跟历史行为一致。
#[serde(default)]
pub start_minimized: bool,
/// UI theme: follow OS, force light, or force dark. Frontend applies via data-ol-theme.
#[serde(default)]
pub theme_mode: ThemeMode,
/// 流式输入:润色 SSE 一边到达一边逐字模拟键盘事件输出到当前焦点。开启后用户感知到
/// 的处理时延显著降低(润色 LLM 第一个 token 即开始落字)。
///
Expand Down Expand Up @@ -942,6 +954,8 @@ struct UserPreferencesWire {
polish_context_window_minutes: u32,
#[serde(default)]
start_minimized: bool,
#[serde(default)]
theme_mode: ThemeMode,
#[serde(default = "default_true")]
streaming_insert: bool,
#[serde(default)]
Expand Down Expand Up @@ -1034,6 +1048,7 @@ impl Default for UserPreferencesWire {
history_retention_days: prefs.history_retention_days,
polish_context_window_minutes: prefs.polish_context_window_minutes,
start_minimized: prefs.start_minimized,
theme_mode: prefs.theme_mode,
streaming_insert: prefs.streaming_insert,
streaming_insert_default_migrated: prefs.streaming_insert_default_migrated,
streaming_insert_save_clipboard: prefs.streaming_insert_save_clipboard,
Expand Down Expand Up @@ -1140,6 +1155,7 @@ impl<'de> Deserialize<'de> for UserPreferences {
history_retention_days: wire.history_retention_days,
polish_context_window_minutes: wire.polish_context_window_minutes,
start_minimized: wire.start_minimized,
theme_mode: wire.theme_mode,
streaming_insert,
streaming_insert_default_migrated: true,
streaming_insert_save_clipboard: wire.streaming_insert_save_clipboard,
Expand Down Expand Up @@ -1874,6 +1890,7 @@ impl Default for UserPreferences {
history_retention_days: default_history_retention_days(),
polish_context_window_minutes: default_polish_context_window_minutes(),
start_minimized: false,
theme_mode: ThemeMode::default(),
streaming_insert: true,
streaming_insert_default_migrated: true,
streaming_insert_save_clipboard: true,
Expand Down
26 changes: 10 additions & 16 deletions openless-all/app/src/components/Capsule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ interface CenterTextProps {
color?: string;
}

function CenterText({ os, kind, text, color = 'var(--ol-ink-3)' }: CenterTextProps) {
function CenterText({ os, kind, text, color = 'var(--ol-capsule-center-ink)' }: CenterTextProps) {
const metrics = getCapsulePillMetrics(os);
const layout = getCapsuleMessageLayout(os, kind);
return (
Expand Down Expand Up @@ -99,8 +99,6 @@ interface CircleButtonProps {
function CircleButton({ variant, enabled, onClick }: CircleButtonProps) {
const { t } = useTranslation();
const isCancel = variant === 'cancel';
// confirm 是主操作锚点,纯白;cancel 半透 + 自带 backdrop blur 跟 pill 拉开层级。
const useBackdrop = isCancel;
return (
<button
onClick={enabled ? onClick : undefined}
Expand All @@ -114,11 +112,9 @@ function CircleButton({ variant, enabled, onClick }: CircleButtonProps) {
width: 28,
height: 28,
borderRadius: 999,
background: isCancel ? 'rgba(255, 255, 255, 0.55)' : 'rgba(255, 255, 255, 0.92)',
backdropFilter: useBackdrop ? 'blur(12px) saturate(160%)' : 'none',
WebkitBackdropFilter: useBackdrop ? 'blur(12px) saturate(160%)' : 'none',
color: 'var(--ol-ink)',
border: '0.8px solid rgba(0, 0, 0, 0.08)',
background: isCancel ? 'var(--ol-capsule-btn-bg)' : 'var(--ol-capsule-btn-bg-confirm)',
color: 'var(--ol-capsule-btn-ink)',
border: '0.8px solid var(--ol-capsule-btn-border)',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
Expand Down Expand Up @@ -255,7 +251,7 @@ function Pill({ os, state, level, insertedChars, message, operating, onCancel, o
// 非 Linux 走假毛玻璃;Linux 禁用透明窗口后由 .ol-frost 平台规则退成不透明面。
// 不写 backdrop-filter —— webview 模糊不了透明窗口背后的桌面(Tauri 上游限制)。
<div
className="ol-frost"
className="ol-frost ol-capsule-pill"
style={{
display: 'inline-flex',
alignItems: 'center',
Expand All @@ -266,11 +262,9 @@ function Pill({ os, state, level, insertedChars, message, operating, onCancel, o
height: metrics.height,
boxSizing: metrics.boxSizing,
borderRadius: 999,
border: '1px solid rgba(255, 255, 255, 0.55)',
boxShadow: os === 'win'
? `0 10px 24px -14px rgba(0, 0, 0, ${(0.24 + ambient * 0.06).toFixed(3)}), 0 0 0 0.5px rgba(0, 0, 0, 0.08), inset 0 1px 0 0 rgba(255, 255, 255, 0.8)`
: `0 18px 50px -10px rgba(0, 0, 0, ${shadowAlpha.toFixed(3)}), 0 0 0 0.5px rgba(0, 0, 0, 0.08), inset 0 1px 0 0 rgba(255, 255, 255, 0.8)`,
color: 'var(--ol-ink)',
border: '1px solid var(--ol-capsule-pill-border)',
boxShadow: `${os === 'win' ? `0 10px 24px -14px rgba(0, 0, 0, ${(0.24 + ambient * 0.06).toFixed(3)})` : `0 18px 50px -10px rgba(0, 0, 0, ${shadowAlpha.toFixed(3)})`}, 0 0 0 0.5px rgba(0, 0, 0, 0.24), var(--ol-capsule-pill-inset)`,
color: 'var(--ol-capsule-center-ink)',
fontFamily: 'var(--ol-font-sans)',
transform: `scale(${scale.toFixed(4)})`,
transformOrigin: 'center',
Expand Down Expand Up @@ -450,10 +444,10 @@ export function Capsule() {
fontSize: 10.5,
fontWeight: 600,
color: 'var(--ol-blue)',
background: 'rgba(255, 255, 255, 0.78)',
background: 'var(--ol-capsule-badge-bg)',
backdropFilter: 'blur(20px) saturate(180%)',
WebkitBackdropFilter: 'blur(20px) saturate(180%)',
border: '0.5px solid rgba(37, 99, 235, 0.25)',
border: '0.5px solid var(--ol-capsule-badge-border)',
boxShadow: '0 4px 12px -4px rgba(37, 99, 235, 0.25), 0 0 0 0.5px rgba(0,0,0,0.04)',
letterSpacing: '0.02em',
whiteSpace: 'nowrap',
Expand Down
24 changes: 13 additions & 11 deletions openless-all/app/src/components/FloatingShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,10 @@ function FloatingShellBody({ os, initialTab, initialSettings }: { os: OS; initia

{/* Main shell — flush with the frosted backplate (no separate float). */}
<div
className="ol-app-shell-bg"
style={{
flex: 1, minHeight: 0,
display: 'flex',
background: 'transparent',
overflow: 'hidden',
position: 'relative',
zIndex: 1,
Expand All @@ -218,6 +218,7 @@ function FloatingShellBody({ os, initialTab, initialSettings }: { os: OS; initia
{/* Sidebar — desktop / wide only */}
{!mobile && (
<aside
className="ol-aura-sidebar"
style={{
width: 188,
flexShrink: 0,
Expand Down Expand Up @@ -346,16 +347,17 @@ function FloatingShellBody({ os, initialTab, initialSettings }: { os: OS; initia
{/* Main content — Linux 禁用透明窗口后使用不透明面;mobile 全宽无玻璃层。 */}
<div style={{ flex: 1, minWidth: 0, padding: mobile ? 0 : '4px 8px 8px 0', display: 'flex' }}>
<main
className="ol-console-main"
className="ol-console-main ol-aura-panel"
style={{
flex: 1, minWidth: 0,
overflow: 'hidden',
background: useOpaqueMain ? 'var(--ol-surface)' : 'rgba(255, 255, 255, 0.62)',
background: useOpaqueMain ? 'var(--ol-panel-bg)' : 'var(--ol-aura-surface)',
backdropFilter: useOpaqueMain ? 'none' : 'blur(18px) saturate(170%)',
WebkitBackdropFilter: useOpaqueMain ? 'none' : 'blur(18px) saturate(170%)',
borderRadius: mobile ? 0 : 'var(--ol-window-console-radius)',
border: mobile ? 'none' : '0.5px solid rgba(0,0,0,0.06)',
boxShadow: mobile ? 'none' : '0 1px 0 rgba(255,255,255,0.8) inset, 0 8px 24px -12px rgba(15,17,22,0.10), 0 2px 6px -2px rgba(15,17,22,0.06)',
borderRadius: 'var(--ol-r-lg)',
...(mobile ? { borderRadius: 0 } : {}),
border: mobile ? 'none' : '0.5px solid var(--ol-panel-border)',
boxShadow: mobile ? 'none' : 'var(--ol-panel-shadow)',
display: 'flex',
flexDirection: 'column',
}}
Expand Down Expand Up @@ -471,7 +473,7 @@ function FloatingShellBody({ os, initialTab, initialSettings }: { os: OS; initia
font-weight: 600;
}
.ol-nav-btn:not(.ol-nav-btn-active):hover {
background: rgba(0,0,0,0.04);
background: var(--ol-nav-hover-bg);
color: var(--ol-ink);
}
@keyframes ol-page-slide {
Expand Down Expand Up @@ -740,8 +742,8 @@ function ProviderSetupPrompt({ onLater, onOpenSettings }: { onLater: () => void;
padding: '0 14px',
borderRadius: 8,
border: 0,
background: 'var(--ol-ink)',
color: '#fff',
background: 'var(--ol-primary-solid-bg)',
color: 'var(--ol-primary-solid-ink)',
fontFamily: 'inherit',
fontSize: 12.5,
fontWeight: 500,
Expand Down Expand Up @@ -833,8 +835,8 @@ function HotkeyModeMigrationPrompt({ onLater, onOpenSettings }: { onLater: () =>
padding: '0 14px',
borderRadius: 8,
border: 0,
background: 'var(--ol-ink)',
color: '#fff',
background: 'var(--ol-primary-solid-bg)',
color: 'var(--ol-primary-solid-ink)',
fontFamily: 'inherit',
fontSize: 12.5,
fontWeight: 500,
Expand Down
8 changes: 4 additions & 4 deletions openless-all/app/src/components/Onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,8 @@ function PermissionStep({ index, title, desc, status, actionLabel, onAction, dis
fontFamily: 'inherit',
border: 0,
borderRadius: 8,
background: granted ? 'var(--ol-surface-2)' : 'var(--ol-ink)',
color: granted ? 'var(--ol-ink-3)' : '#fff',
background: granted ? 'var(--ol-surface-2)' : 'var(--ol-primary-solid-bg)',
color: granted ? 'var(--ol-ink-3)' : 'var(--ol-primary-solid-ink)',
cursor: disabled ? 'not-allowed' : 'default',
opacity: disabled && !granted ? 0.6 : 1,
transition: 'background 0.16s var(--ol-motion-quick), color 0.16s var(--ol-motion-quick), opacity 0.18s var(--ol-motion-soft), transform 0.12s var(--ol-motion-quick)',
Expand All @@ -581,8 +581,8 @@ const primaryButtonStyle = {
fontFamily: 'inherit',
border: 0,
borderRadius: 10,
background: 'var(--ol-ink)',
color: '#fff',
background: 'var(--ol-primary-solid-bg)',
color: 'var(--ol-primary-solid-ink)',
cursor: 'default',
} as const;

Expand Down
20 changes: 10 additions & 10 deletions openless-all/app/src/components/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
// 「设置」还要再面对第二个侧栏。现在拍平成单层 —— 通用 / 服务 / 隐私 / 高级 /
// 个性化 / 关于 六个 tab + 帮助外链组。每个 tab 的内容见 pages/settings/。
//
// 设计原则:每个可见控件都必须可用。没有后端支撑的占位(账号 / 主题切换 等)
// 不在此弹窗出现。
// 设计原则:每个可见控件都必须可用。

import { useLayoutEffect, useRef, useState, type CSSProperties } from 'react';
import { useTranslation } from 'react-i18next';
Expand All @@ -17,6 +16,7 @@ import { useMobileLayout } from '../lib/useMobileLayout';
import type { OS } from './WindowChrome';
import { GeneralTab, ServicesTab, PrivacyTab, AdvancedTab } from '../pages/settings/tabs';
import { AboutSection } from '../pages/settings/AboutSection';
import { chipSelectedStyle } from '../pages/settings/shared';

// 稳定 tab ID(与 i18n key `modal.sections.*` 一致)。
export type SettingsSectionId =
Expand Down Expand Up @@ -81,7 +81,7 @@ export function SettingsModal({ os: _os, onClose, initialSettingsSection }: Sett
style={{
position: mobile ? 'fixed' : 'absolute',
inset: 0,
background: mobile ? 'var(--ol-surface)' : 'rgba(15,17,22,0.32)',
background: mobile ? 'var(--ol-surface)' : 'var(--ol-overlay-bg)',
backdropFilter: mobile ? 'none' : 'blur(8px) saturate(140%)',
WebkitBackdropFilter: mobile ? 'none' : 'blur(8px) saturate(140%)',
display: 'flex',
Expand All @@ -93,6 +93,8 @@ export function SettingsModal({ os: _os, onClose, initialSettingsSection }: Sett
}}>

<div
className="ol-aura-settings"
data-ol-mobile={mobile ? 'true' : undefined}
onClick={(e) => e.stopPropagation()}
style={{
width: '100%',
Expand All @@ -101,8 +103,8 @@ export function SettingsModal({ os: _os, onClose, initialSettingsSection }: Sett
maxHeight: mobile ? undefined : 600,
background: 'var(--ol-surface)',
borderRadius: mobile ? 0 : 14,
border: mobile ? 'none' : '0.5px solid rgba(0,0,0,.08)',
boxShadow: mobile ? 'none' : '0 30px 80px -20px rgba(15,17,22,.35), 0 0 0 0.5px rgba(0,0,0,.06)',
border: mobile ? 'none' : '0.5px solid var(--ol-line)',
boxShadow: mobile ? 'none' : 'var(--ol-shadow-xl)',
display: 'flex',
flexDirection: mobile ? 'column' : 'row',
overflow: 'hidden',
Expand Down Expand Up @@ -151,7 +153,7 @@ export function SettingsModal({ os: _os, onClose, initialSettingsSection }: Sett
<aside
style={{
width: 200, flexShrink: 0,
background: 'rgba(247,247,250,0.7)',
background: 'var(--ol-settings-rail-bg)',
borderRight: '0.5px solid var(--ol-line-soft)',
padding: '18px 12px',
display: 'flex', flexDirection: 'column', gap: 14,
Expand All @@ -168,7 +170,7 @@ export function SettingsModal({ os: _os, onClose, initialSettingsSection }: Sett
right: 0,
top: pillRect.top,
height: pillRect.height,
background: '#fff',
background: 'var(--ol-segmented-active-bg)',
borderRadius: 8,
boxShadow: '0 1px 2px rgba(0,0,0,.05), 0 0 0 0.5px rgba(0,0,0,.06)',
transition: 'top 0.36s var(--ol-motion-spring), height 0.36s var(--ol-motion-spring)',
Expand Down Expand Up @@ -303,13 +305,11 @@ function mobileTabChipStyle(active: boolean): CSSProperties {
flexShrink: 0,
padding: '6px 12px',
borderRadius: 999,
border: active ? '0.5px solid var(--ol-ink)' : '0.5px solid var(--ol-line-strong)',
background: active ? 'var(--ol-ink)' : 'transparent',
color: active ? '#fff' : 'var(--ol-ink-3)',
fontFamily: 'inherit',
fontSize: 12,
fontWeight: active ? 600 : 500,
cursor: 'default',
...chipSelectedStyle(active),
};
}

Expand Down
Loading