refactor: change dark theme color
This commit is contained in:
+76
-11
@@ -11,7 +11,7 @@
|
|||||||
* emits a `capture:overlay` event with the screenshot payload so the
|
* emits a `capture:overlay` event with the screenshot payload so the
|
||||||
* frontend knows when (and what) to render.
|
* frontend knows when (and what) to render.
|
||||||
*/
|
*/
|
||||||
import { onMounted, onUnmounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import SettingsView from './views/SettingsView.vue'
|
import SettingsView from './views/SettingsView.vue'
|
||||||
import Toast from './components/Toast.vue'
|
import Toast from './components/Toast.vue'
|
||||||
import CaptureOverlay from './views/CaptureOverlay.vue'
|
import CaptureOverlay from './views/CaptureOverlay.vue'
|
||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
SaveRegionToRemote,
|
SaveRegionToRemote,
|
||||||
SummarizeRegion,
|
SummarizeRegion,
|
||||||
CancelRegion,
|
CancelRegion,
|
||||||
|
GetConfig,
|
||||||
} from '../wailsjs/go/main/App'
|
} from '../wailsjs/go/main/App'
|
||||||
|
|
||||||
type HotkeyStatus =
|
type HotkeyStatus =
|
||||||
@@ -35,6 +36,25 @@ const hotkeyStatus = ref<HotkeyStatus>({ state: 'unknown' })
|
|||||||
type Mode = 'settings' | 'overlay'
|
type Mode = 'settings' | 'overlay'
|
||||||
const mode = ref<Mode>('settings')
|
const mode = ref<Mode>('settings')
|
||||||
|
|
||||||
|
type ThemeMode = 'auto' | 'light' | 'dark'
|
||||||
|
const themeMode = ref<ThemeMode>('auto')
|
||||||
|
const appThemeClass = computed(() => `theme-${themeMode.value}`)
|
||||||
|
|
||||||
|
function normalizeTheme(theme: unknown): ThemeMode {
|
||||||
|
return theme === 'light' || theme === 'dark' || theme === 'auto'
|
||||||
|
? theme
|
||||||
|
: 'auto'
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadThemePreference() {
|
||||||
|
try {
|
||||||
|
const cfg = await GetConfig()
|
||||||
|
themeMode.value = normalizeTheme((cfg as any)?.theme)
|
||||||
|
} catch {
|
||||||
|
themeMode.value = 'auto'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// `SettingsTab` is hoisted to the App shell so the sidebar (which lives
|
// `SettingsTab` is hoisted to the App shell so the sidebar (which lives
|
||||||
// here) and the inner SettingsView (which renders the matching card) can
|
// here) and the inner SettingsView (which renders the matching card) can
|
||||||
// share a single source of truth without an event bus.
|
// share a single source of truth without an event bus.
|
||||||
@@ -224,6 +244,7 @@ async function onOverlayCancel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
void loadThemePreference()
|
||||||
EventsOn('capture:start', () => {
|
EventsOn('capture:start', () => {
|
||||||
capturing.value = true
|
capturing.value = true
|
||||||
})
|
})
|
||||||
@@ -286,7 +307,7 @@ onUnmounted(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Settings mode: standard desktop window; macOS title bar is native. -->
|
<!-- Settings mode: standard desktop window; macOS title bar is native. -->
|
||||||
<div v-else class="app-root">
|
<div v-else class="app-root" :class="appThemeClass">
|
||||||
<div v-if="hotkeyStatus.state === 'error'" class="permission-banner">
|
<div v-if="hotkeyStatus.state === 'error'" class="permission-banner">
|
||||||
<div>
|
<div>
|
||||||
<strong>Global hotkey is not active.</strong>
|
<strong>Global hotkey is not active.</strong>
|
||||||
@@ -312,7 +333,11 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
<section class="content">
|
<section class="content">
|
||||||
<SettingsView :tab="activeTab" />
|
<SettingsView
|
||||||
|
:tab="activeTab"
|
||||||
|
:theme="themeMode"
|
||||||
|
@theme-change="themeMode = normalizeTheme($event)"
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@@ -344,6 +369,15 @@ onUnmounted(() => {
|
|||||||
color: #111827;
|
color: #111827;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
.app-root.theme-light {
|
||||||
|
color-scheme: light;
|
||||||
|
}
|
||||||
|
.app-root.theme-dark {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
.app-root.theme-auto {
|
||||||
|
color-scheme: light dark;
|
||||||
|
}
|
||||||
|
|
||||||
.status-pill {
|
.status-pill {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@@ -498,33 +532,64 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-root.theme-dark {
|
||||||
|
background: #1c1d22;
|
||||||
|
color: #e5e7eb;
|
||||||
|
}
|
||||||
|
.app-root.theme-dark .titlebar {
|
||||||
|
background: rgba(28, 29, 34, 0.7);
|
||||||
|
border-bottom-color: #2c2f36;
|
||||||
|
}
|
||||||
|
.app-root.theme-dark .titlebar-title {
|
||||||
|
color: #f3f4f6;
|
||||||
|
}
|
||||||
|
.app-root.theme-dark .sidebar {
|
||||||
|
background: rgba(0, 0, 0, 0.15);
|
||||||
|
border-right-color: #2c2f36;
|
||||||
|
}
|
||||||
|
.app-root.theme-dark .sidebar-item {
|
||||||
|
color: #d1d5db;
|
||||||
|
}
|
||||||
|
.app-root.theme-dark .sidebar-item:hover:not(.active) {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
.app-root.theme-dark .sidebar-item.active {
|
||||||
|
background: rgba(59, 130, 246, 0.18);
|
||||||
|
color: #93c5fd;
|
||||||
|
}
|
||||||
|
.app-root.theme-dark .permission-banner {
|
||||||
|
background: rgba(120, 53, 15, 0.3);
|
||||||
|
border-color: rgba(253, 186, 116, 0.4);
|
||||||
|
color: #fed7aa;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.app-root {
|
.app-root.theme-auto {
|
||||||
background: #1c1d22;
|
background: #1c1d22;
|
||||||
color: #e5e7eb;
|
color: #e5e7eb;
|
||||||
}
|
}
|
||||||
.titlebar {
|
.app-root.theme-auto .titlebar {
|
||||||
background: rgba(28, 29, 34, 0.7);
|
background: rgba(28, 29, 34, 0.7);
|
||||||
border-bottom-color: #2c2f36;
|
border-bottom-color: #2c2f36;
|
||||||
}
|
}
|
||||||
.titlebar-title {
|
.app-root.theme-auto .titlebar-title {
|
||||||
color: #f3f4f6;
|
color: #f3f4f6;
|
||||||
}
|
}
|
||||||
.sidebar {
|
.app-root.theme-auto .sidebar {
|
||||||
background: rgba(0, 0, 0, 0.15);
|
background: rgba(0, 0, 0, 0.15);
|
||||||
border-right-color: #2c2f36;
|
border-right-color: #2c2f36;
|
||||||
}
|
}
|
||||||
.sidebar-item {
|
.app-root.theme-auto .sidebar-item {
|
||||||
color: #d1d5db;
|
color: #d1d5db;
|
||||||
}
|
}
|
||||||
.sidebar-item:hover:not(.active) {
|
.app-root.theme-auto .sidebar-item:hover:not(.active) {
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
.sidebar-item.active {
|
.app-root.theme-auto .sidebar-item.active {
|
||||||
background: rgba(59, 130, 246, 0.18);
|
background: rgba(59, 130, 246, 0.18);
|
||||||
color: #93c5fd;
|
color: #93c5fd;
|
||||||
}
|
}
|
||||||
.permission-banner {
|
.app-root.theme-auto .permission-banner {
|
||||||
background: rgba(120, 53, 15, 0.3);
|
background: rgba(120, 53, 15, 0.3);
|
||||||
border-color: rgba(253, 186, 116, 0.4);
|
border-color: rgba(253, 186, 116, 0.4);
|
||||||
color: #fed7aa;
|
color: #fed7aa;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
*
|
*
|
||||||
* State flow: load() pulls config from Go on mount, save() pushes back.
|
* State flow: load() pulls config from Go on mount, save() pushes back.
|
||||||
*/
|
*/
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import {
|
import {
|
||||||
GetConfig,
|
GetConfig,
|
||||||
SaveConfig,
|
SaveConfig,
|
||||||
@@ -24,9 +24,15 @@ import {
|
|||||||
// Tab discriminator shared with the App shell. Kept as a string union so
|
// Tab discriminator shared with the App shell. Kept as a string union so
|
||||||
// the parent can pass the value without importing a type from this view.
|
// the parent can pass the value without importing a type from this view.
|
||||||
type TabId = 'general' | 's3' | 'ssh' | 'llm'
|
type TabId = 'general' | 's3' | 'ssh' | 'llm'
|
||||||
|
type ThemeMode = 'auto' | 'light' | 'dark'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tab: TabId
|
tab: TabId
|
||||||
|
theme: ThemeMode
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'theme-change', theme: ThemeMode): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
interface S3Config {
|
interface S3Config {
|
||||||
@@ -70,6 +76,7 @@ interface LLMConfig {
|
|||||||
|
|
||||||
interface AppConfig {
|
interface AppConfig {
|
||||||
hotkey: string
|
hotkey: string
|
||||||
|
theme: ThemeMode
|
||||||
s3: S3Config
|
s3: S3Config
|
||||||
ssh: SSHConfig
|
ssh: SSHConfig
|
||||||
llm: LLMConfig
|
llm: LLMConfig
|
||||||
@@ -81,6 +88,12 @@ const llmProviderOptions = [
|
|||||||
{ id: 'openai', label: 'ChatGPT / OpenAI-compatible' },
|
{ id: 'openai', label: 'ChatGPT / OpenAI-compatible' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const themeOptions: Array<{ id: ThemeMode; label: string }> = [
|
||||||
|
{ id: 'light', label: '浅色' },
|
||||||
|
{ id: 'dark', label: '深色' },
|
||||||
|
{ id: 'auto', label: '自动' },
|
||||||
|
]
|
||||||
|
|
||||||
const DEFAULT_SUMMARY_PROMPT = `你是一个截图内容总结助手。请阅读截图,并用中文输出一段适合直接粘贴到聊天、工单、Issue 或 PR 中的说明。
|
const DEFAULT_SUMMARY_PROMPT = `你是一个截图内容总结助手。请阅读截图,并用中文输出一段适合直接粘贴到聊天、工单、Issue 或 PR 中的说明。
|
||||||
|
|
||||||
要求:
|
要求:
|
||||||
@@ -97,6 +110,7 @@ const DEFAULT_SUMMARY_PROMPT = `你是一个截图内容总结助手。请阅读
|
|||||||
function defaultConfig(): AppConfig {
|
function defaultConfig(): AppConfig {
|
||||||
return {
|
return {
|
||||||
hotkey: 'cmd+shift+a',
|
hotkey: 'cmd+shift+a',
|
||||||
|
theme: 'auto',
|
||||||
s3: {
|
s3: {
|
||||||
endpoint: '',
|
endpoint: '',
|
||||||
region: 'us-east-1',
|
region: 'us-east-1',
|
||||||
@@ -184,6 +198,12 @@ const activeLLMProvider = computed(() => {
|
|||||||
return config.value.llm.providers[id]
|
return config.value.llm.providers[id]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function normalizeTheme(theme: unknown): ThemeMode {
|
||||||
|
return theme === 'light' || theme === 'dark' || theme === 'auto'
|
||||||
|
? theme
|
||||||
|
: 'auto'
|
||||||
|
}
|
||||||
|
|
||||||
function flash(kind: 'ok' | 'err', text: string) {
|
function flash(kind: 'ok' | 'err', text: string) {
|
||||||
message.value = { kind, text }
|
message.value = { kind, text }
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
@@ -205,7 +225,9 @@ async function load() {
|
|||||||
...defaultConfig().llm.providers,
|
...defaultConfig().llm.providers,
|
||||||
...((cfg as any).llm?.providers ?? {}),
|
...((cfg as any).llm?.providers ?? {}),
|
||||||
}
|
}
|
||||||
|
merged.theme = normalizeTheme((cfg as any).theme)
|
||||||
config.value = merged
|
config.value = merged
|
||||||
|
emit('theme-change', config.value.theme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,11 +271,16 @@ async function manualCapture() {
|
|||||||
await CaptureNow()
|
await CaptureNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => config.value.theme,
|
||||||
|
(theme) => emit('theme-change', normalizeTheme(theme))
|
||||||
|
)
|
||||||
|
|
||||||
onMounted(load)
|
onMounted(load)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="settings">
|
<div class="settings" :class="`theme-${props.theme}`">
|
||||||
<header class="hero">
|
<header class="hero">
|
||||||
<div>
|
<div>
|
||||||
<h1>SnapGo</h1>
|
<h1>SnapGo</h1>
|
||||||
@@ -265,17 +292,31 @@ onMounted(load)
|
|||||||
<button class="btn primary" @click="manualCapture">Capture now</button>
|
<button class="btn primary" @click="manualCapture">Capture now</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- General tab — only the shortcut configuration lives here. -->
|
<!-- General tab — shortcut and appearance configuration. -->
|
||||||
<section v-if="props.tab === 'general'" class="card">
|
<section v-if="props.tab === 'general'" class="card">
|
||||||
<h2>Shortcut</h2>
|
<h2>General</h2>
|
||||||
<label class="field">
|
<div class="grid">
|
||||||
<span>Global hotkey</span>
|
<label class="field">
|
||||||
<input
|
<span>Global hotkey</span>
|
||||||
v-model="config.hotkey"
|
<input
|
||||||
placeholder="cmd+shift+a"
|
v-model="config.hotkey"
|
||||||
spellcheck="false"
|
placeholder="cmd+shift+a"
|
||||||
/>
|
spellcheck="false"
|
||||||
</label>
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>主题选择</span>
|
||||||
|
<select v-model="config.theme">
|
||||||
|
<option
|
||||||
|
v-for="themeOption in themeOptions"
|
||||||
|
:key="themeOption.id"
|
||||||
|
:value="themeOption.id"
|
||||||
|
>
|
||||||
|
{{ themeOption.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<p class="hint">
|
<p class="hint">
|
||||||
Tokens (case-insensitive, "+"-separated): cmd / ctrl / option / shift +
|
Tokens (case-insensitive, "+"-separated): cmd / ctrl / option / shift +
|
||||||
a–z or 0–9. Example: <code>cmd+shift+a</code>.
|
a–z or 0–9. Example: <code>cmd+shift+a</code>.
|
||||||
@@ -564,6 +605,15 @@ onMounted(load)
|
|||||||
padding: 28px 32px 60px;
|
padding: 28px 32px 60px;
|
||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
|
.settings.theme-light {
|
||||||
|
color-scheme: light;
|
||||||
|
}
|
||||||
|
.settings.theme-dark {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
.settings.theme-auto {
|
||||||
|
color-scheme: light dark;
|
||||||
|
}
|
||||||
.hero {
|
.hero {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -779,51 +829,100 @@ kbd {
|
|||||||
.fade-leave-to {
|
.fade-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
.settings.theme-dark {
|
||||||
|
color: #e5e7eb;
|
||||||
|
}
|
||||||
|
.settings.theme-dark .card {
|
||||||
|
background: #1f2937;
|
||||||
|
border-color: #374151;
|
||||||
|
}
|
||||||
|
.settings.theme-dark .field span {
|
||||||
|
color: #d1d5db;
|
||||||
|
}
|
||||||
|
.settings.theme-dark .field input[type='text'],
|
||||||
|
.settings.theme-dark .field input[type='number'],
|
||||||
|
.settings.theme-dark .field input:not([type='checkbox']),
|
||||||
|
.settings.theme-dark .field select,
|
||||||
|
.settings.theme-dark .field textarea {
|
||||||
|
background: #111827;
|
||||||
|
border-color: #374151;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.settings.theme-dark .field input::placeholder,
|
||||||
|
.settings.theme-dark .field textarea::placeholder {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
.settings.theme-dark .field input:disabled {
|
||||||
|
background: #1f2937;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
.settings.theme-dark .prefixed-input {
|
||||||
|
background: #111827;
|
||||||
|
border-color: #374151;
|
||||||
|
}
|
||||||
|
.settings.theme-dark .input-prefix {
|
||||||
|
background: #1f2937;
|
||||||
|
border-right-color: #374151;
|
||||||
|
color: #d1d5db;
|
||||||
|
}
|
||||||
|
.settings.theme-dark .btn:not(.primary) {
|
||||||
|
background: #1f2937;
|
||||||
|
border-color: #374151;
|
||||||
|
color: #e5e7eb;
|
||||||
|
}
|
||||||
|
.settings.theme-dark .btn:not(.primary):hover:not(:disabled) {
|
||||||
|
background: #374151;
|
||||||
|
}
|
||||||
|
.settings.theme-dark .subtitle {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.settings {
|
.settings.theme-auto {
|
||||||
color: #e5e7eb;
|
color: #e5e7eb;
|
||||||
}
|
}
|
||||||
.card {
|
.settings.theme-auto .card {
|
||||||
background: #1f2937;
|
background: #1f2937;
|
||||||
border-color: #374151;
|
border-color: #374151;
|
||||||
}
|
}
|
||||||
.field input,
|
.settings.theme-auto .field span {
|
||||||
.field textarea {
|
color: #d1d5db;
|
||||||
|
}
|
||||||
|
.settings.theme-auto .field input[type='text'],
|
||||||
|
.settings.theme-auto .field input[type='number'],
|
||||||
|
.settings.theme-auto .field input:not([type='checkbox']),
|
||||||
|
.settings.theme-auto .field select,
|
||||||
|
.settings.theme-auto .field textarea {
|
||||||
background: #111827;
|
background: #111827;
|
||||||
border-color: #374151;
|
border-color: #374151;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.field select {
|
.settings.theme-auto .field input::placeholder,
|
||||||
background: #111827;
|
.settings.theme-auto .field textarea::placeholder {
|
||||||
border-color: #374151;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.field input::placeholder,
|
|
||||||
.field textarea::placeholder {
|
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
}
|
}
|
||||||
.field input:disabled {
|
.settings.theme-auto .field input:disabled {
|
||||||
background: #1f2937;
|
background: #1f2937;
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
}
|
}
|
||||||
.prefixed-input {
|
.settings.theme-auto .prefixed-input {
|
||||||
background: #111827;
|
background: #111827;
|
||||||
border-color: #374151;
|
border-color: #374151;
|
||||||
}
|
}
|
||||||
.input-prefix {
|
.settings.theme-auto .input-prefix {
|
||||||
background: #1f2937;
|
background: #1f2937;
|
||||||
border-right-color: #374151;
|
border-right-color: #374151;
|
||||||
color: #d1d5db;
|
color: #d1d5db;
|
||||||
}
|
}
|
||||||
.btn {
|
.settings.theme-auto .btn:not(.primary) {
|
||||||
background: #1f2937;
|
background: #1f2937;
|
||||||
border-color: #374151;
|
border-color: #374151;
|
||||||
color: #e5e7eb;
|
color: #e5e7eb;
|
||||||
}
|
}
|
||||||
.btn:hover:not(:disabled) {
|
.settings.theme-auto .btn:not(.primary):hover:not(:disabled) {
|
||||||
background: #374151;
|
background: #374151;
|
||||||
}
|
}
|
||||||
.subtitle {
|
.settings.theme-auto .subtitle {
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ export namespace domain {
|
|||||||
}
|
}
|
||||||
export class AppConfig {
|
export class AppConfig {
|
||||||
hotkey: string;
|
hotkey: string;
|
||||||
|
theme: string;
|
||||||
s3: S3Config;
|
s3: S3Config;
|
||||||
ssh: SSHConfig;
|
ssh: SSHConfig;
|
||||||
llm: LLMConfig;
|
llm: LLMConfig;
|
||||||
@@ -178,6 +179,7 @@ export namespace domain {
|
|||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.hotkey = source["hotkey"];
|
this.hotkey = source["hotkey"];
|
||||||
|
this.theme = source["theme"];
|
||||||
this.s3 = this.convertValues(source["s3"], S3Config);
|
this.s3 = this.convertValues(source["s3"], S3Config);
|
||||||
this.ssh = this.convertValues(source["ssh"], SSHConfig);
|
this.ssh = this.convertValues(source["ssh"], SSHConfig);
|
||||||
this.llm = this.convertValues(source["llm"], LLMConfig);
|
this.llm = this.convertValues(source["llm"], LLMConfig);
|
||||||
|
|||||||
@@ -117,6 +117,13 @@ const (
|
|||||||
LLMProviderOpenAI = "openai"
|
LLMProviderOpenAI = "openai"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Theme preference identifiers stored in AppConfig.Theme.
|
||||||
|
const (
|
||||||
|
ThemeAuto = "auto"
|
||||||
|
ThemeLight = "light"
|
||||||
|
ThemeDark = "dark"
|
||||||
|
)
|
||||||
|
|
||||||
const DefaultSummaryPrompt = `你是一个截图内容总结助手。请阅读截图,并用中文输出一段适合直接粘贴到聊天、工单、Issue 或 PR 中的说明。
|
const DefaultSummaryPrompt = `你是一个截图内容总结助手。请阅读截图,并用中文输出一段适合直接粘贴到聊天、工单、Issue 或 PR 中的说明。
|
||||||
|
|
||||||
要求:
|
要求:
|
||||||
@@ -141,6 +148,9 @@ type AppConfig struct {
|
|||||||
// in the infrastructure hotkey adapter.
|
// in the infrastructure hotkey adapter.
|
||||||
Hotkey string `json:"hotkey"`
|
Hotkey string `json:"hotkey"`
|
||||||
|
|
||||||
|
// Theme controls the settings UI appearance: "auto", "light", or "dark".
|
||||||
|
Theme string `json:"theme"`
|
||||||
|
|
||||||
// S3 holds the active S3-compatible storage configuration.
|
// S3 holds the active S3-compatible storage configuration.
|
||||||
S3 S3Config `json:"s3"`
|
S3 S3Config `json:"s3"`
|
||||||
|
|
||||||
@@ -156,6 +166,7 @@ type AppConfig struct {
|
|||||||
func DefaultAppConfig() AppConfig {
|
func DefaultAppConfig() AppConfig {
|
||||||
cfg := AppConfig{
|
cfg := AppConfig{
|
||||||
Hotkey: "cmd+shift+a",
|
Hotkey: "cmd+shift+a",
|
||||||
|
Theme: ThemeAuto,
|
||||||
S3: S3Config{
|
S3: S3Config{
|
||||||
PathPrefix: "snapgo/",
|
PathPrefix: "snapgo/",
|
||||||
UsePathStyle: true,
|
UsePathStyle: true,
|
||||||
@@ -214,6 +225,9 @@ func (c *AppConfig) Normalize() {
|
|||||||
if c.Hotkey == "" {
|
if c.Hotkey == "" {
|
||||||
c.Hotkey = "cmd+shift+a"
|
c.Hotkey = "cmd+shift+a"
|
||||||
}
|
}
|
||||||
|
if c.Theme != ThemeLight && c.Theme != ThemeDark && c.Theme != ThemeAuto {
|
||||||
|
c.Theme = ThemeAuto
|
||||||
|
}
|
||||||
if c.S3.PathPrefix == "" {
|
if c.S3.PathPrefix == "" {
|
||||||
c.S3.PathPrefix = "snapgo/"
|
c.S3.PathPrefix = "snapgo/"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user