Skip to content

Commit 1b7bea0

Browse files
committed
refac
1 parent 0bbf9a4 commit 1b7bea0

17 files changed

Lines changed: 816 additions & 238 deletions

File tree

cptr/frontend/src/lib/apis/memory.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export type MemorySettings = {
1818
enabled: boolean;
1919
tool_enabled: boolean;
2020
background_review_enabled: boolean;
21+
'background_review.model'?: string | null;
2122
review_interval_turns: number;
2223
user_char_limit: number;
2324
workspace_char_limit: number;
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
<script lang="ts">
2+
import { onMount } from 'svelte';
3+
import { toast } from 'svelte-sonner';
4+
import { getAdminConfig, updateConfig } from '$lib/apis/admin';
5+
import { t } from '$lib/i18n';
6+
import Spinner from '$lib/components/common/Spinner.svelte';
7+
import ToggleSwitch from '$lib/components/common/ToggleSwitch.svelte';
8+
import ModelSelector from '$lib/components/common/ModelSelector.svelte';
9+
10+
let loading = $state(true);
11+
let saving = $state(false);
12+
let titleGenerationEnabled = $state(true);
13+
let titleGenerationModel = $state<string | null>(null);
14+
let contextCompactionModel = $state<string | null>(null);
15+
let toolApprovalReviewModel = $state<string | null>(null);
16+
let compactTokenThreshold = $state(80000);
17+
18+
onMount(async () => {
19+
try {
20+
const config = await getAdminConfig();
21+
titleGenerationEnabled =
22+
config['chat.title_generation.enabled'] !== false &&
23+
config['chat.title_generation.enabled'] !== 'false';
24+
titleGenerationModel =
25+
typeof config['chat.title_generation.model'] === 'string'
26+
? config['chat.title_generation.model']
27+
: null;
28+
contextCompactionModel =
29+
typeof config['chat.context_compaction.model'] === 'string'
30+
? config['chat.context_compaction.model']
31+
: null;
32+
toolApprovalReviewModel =
33+
typeof config['tool_approval.review.model'] === 'string'
34+
? config['tool_approval.review.model']
35+
: null;
36+
compactTokenThreshold = Number(config['chat.compact_token_threshold']) || 80000;
37+
} catch {
38+
toast.error($t('admin.failedToLoadConfig'));
39+
} finally {
40+
loading = false;
41+
}
42+
});
43+
44+
async function save() {
45+
saving = true;
46+
try {
47+
await updateConfig({
48+
'chat.title_generation.enabled': titleGenerationEnabled,
49+
'chat.title_generation.model': titleGenerationModel,
50+
'chat.context_compaction.model': contextCompactionModel,
51+
'tool_approval.review.model': toolApprovalReviewModel,
52+
'chat.compact_token_threshold': Math.max(10000, Number(compactTokenThreshold) || 80000)
53+
});
54+
toast.success($t('settings.saved'));
55+
} catch {
56+
toast.error($t('admin.failedToSave'));
57+
} finally {
58+
saving = false;
59+
}
60+
}
61+
</script>
62+
63+
<div class="flex flex-col h-full">
64+
{#if loading}
65+
<div class="flex justify-center py-8"><Spinner size={16} /></div>
66+
{:else}
67+
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
68+
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$t('admin.chat')}</h2>
69+
70+
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2">
71+
{$t('admin.chatTitles')}
72+
</h3>
73+
<div class="flex flex-col gap-2.5">
74+
<label class="flex items-center justify-between gap-3 cursor-pointer">
75+
<div class="min-w-0">
76+
<div class="text-xs text-gray-600 dark:text-gray-400">
77+
{$t('admin.chatTitleGeneration')}
78+
</div>
79+
<div class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
80+
{$t('admin.chatTitleGenerationHint')}
81+
</div>
82+
</div>
83+
<ToggleSwitch
84+
value={titleGenerationEnabled}
85+
onchange={(value) => {
86+
titleGenerationEnabled = value;
87+
}}
88+
/>
89+
</label>
90+
91+
{#if titleGenerationEnabled}
92+
<div class="flex items-center justify-between gap-3">
93+
<div class="min-w-0">
94+
<div class="text-xs text-gray-600 dark:text-gray-400">
95+
{$t('admin.chatTitleModel')}
96+
</div>
97+
<div class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
98+
{$t('admin.chatTitleModelHint')}
99+
</div>
100+
</div>
101+
<div class="shrink-0">
102+
<ModelSelector
103+
bind:selectedModel={titleGenerationModel}
104+
nullable
105+
nullLabel={$t('modelSelector.currentModel')}
106+
preferAbove={false}
107+
/>
108+
</div>
109+
</div>
110+
{/if}
111+
</div>
112+
113+
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2 mt-5">
114+
{$t('admin.contextCompaction')}
115+
</h3>
116+
<div class="flex flex-col gap-2.5">
117+
<div class="flex items-center justify-between gap-3">
118+
<div class="min-w-0">
119+
<div class="text-xs text-gray-600 dark:text-gray-400">
120+
{$t('admin.contextSummaryModel')}
121+
</div>
122+
<div class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
123+
{$t('admin.contextSummaryModelHint')}
124+
</div>
125+
</div>
126+
<div class="shrink-0">
127+
<ModelSelector
128+
bind:selectedModel={contextCompactionModel}
129+
nullable
130+
nullLabel={$t('modelSelector.currentModel')}
131+
preferAbove={false}
132+
/>
133+
</div>
134+
</div>
135+
<div>
136+
<label class="text-xs text-gray-600 dark:text-gray-400" for="compact-threshold">
137+
{$t('admin.compactTokenThreshold')}
138+
</label>
139+
<div class="flex items-center gap-1.5 mt-1">
140+
<input
141+
id="compact-threshold"
142+
type="number"
143+
bind:value={compactTokenThreshold}
144+
min="10000"
145+
max="1000000"
146+
step="10000"
147+
class="w-24 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
148+
/>
149+
<span class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
150+
{$t('admin.compactTokenThresholdUnit')}
151+
</span>
152+
</div>
153+
<p class="text-[0.6875rem] text-gray-400 dark:text-gray-600 mt-0.5">
154+
{$t('admin.compactTokenThresholdHint')}
155+
</p>
156+
</div>
157+
</div>
158+
159+
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2 mt-5">
160+
{$t('admin.toolApproval')}
161+
</h3>
162+
<div class="flex items-center justify-between gap-3">
163+
<div class="min-w-0">
164+
<div class="text-xs text-gray-600 dark:text-gray-400">
165+
{$t('admin.toolApprovalReviewModel')}
166+
</div>
167+
<div class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
168+
{$t('admin.toolApprovalReviewModelHint')}
169+
</div>
170+
</div>
171+
<div class="shrink-0">
172+
<ModelSelector
173+
bind:selectedModel={toolApprovalReviewModel}
174+
nullable
175+
nullLabel={$t('modelSelector.currentModel')}
176+
preferAbove={false}
177+
/>
178+
</div>
179+
</div>
180+
</div>
181+
182+
<div class="shrink-0 pt-3 flex justify-end">
183+
<button
184+
class="text-[0.8125rem] text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors duration-100 disabled:opacity-50"
185+
disabled={saving}
186+
onclick={save}
187+
>
188+
{saving ? $t('settings.saving') : $t('settings.save')}
189+
</button>
190+
</div>
191+
{/if}
192+
</div>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<script lang="ts">
2+
import { onMount } from 'svelte';
3+
import { toast } from 'svelte-sonner';
4+
import { getAdminConfig, updateConfig } from '$lib/apis/admin';
5+
import { t } from '$lib/i18n';
6+
import Spinner from '$lib/components/common/Spinner.svelte';
7+
import ModelSelector from '$lib/components/common/ModelSelector.svelte';
8+
9+
let loading = $state(true);
10+
let saving = $state(false);
11+
let commitMessageModel = $state<string | null>(null);
12+
13+
onMount(async () => {
14+
try {
15+
const config = await getAdminConfig();
16+
commitMessageModel =
17+
typeof config['git.commit_message_generation.model'] === 'string'
18+
? config['git.commit_message_generation.model']
19+
: null;
20+
} catch {
21+
toast.error($t('admin.failedToLoadConfig'));
22+
} finally {
23+
loading = false;
24+
}
25+
});
26+
27+
async function save() {
28+
saving = true;
29+
try {
30+
await updateConfig({ 'git.commit_message_generation.model': commitMessageModel });
31+
toast.success($t('settings.saved'));
32+
} catch {
33+
toast.error($t('admin.failedToSave'));
34+
} finally {
35+
saving = false;
36+
}
37+
}
38+
</script>
39+
40+
<div class="flex flex-col h-full">
41+
{#if loading}
42+
<div class="flex justify-center py-8"><Spinner size={16} /></div>
43+
{:else}
44+
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
45+
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$t('admin.git')}</h2>
46+
47+
<div class="flex items-center justify-between gap-3">
48+
<div class="min-w-0">
49+
<div class="text-xs text-gray-600 dark:text-gray-400">
50+
{$t('admin.gitCommitMessageModel')}
51+
</div>
52+
<div class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
53+
{$t('admin.gitCommitMessageModelHint')}
54+
</div>
55+
</div>
56+
<div class="shrink-0">
57+
<ModelSelector
58+
bind:selectedModel={commitMessageModel}
59+
nullable
60+
nullLabel={$t('modelSelector.defaultModel')}
61+
preferAbove={false}
62+
/>
63+
</div>
64+
</div>
65+
</div>
66+
67+
<div class="shrink-0 pt-3 flex justify-end">
68+
<button
69+
class="text-[0.8125rem] text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors duration-100 disabled:opacity-50"
70+
disabled={saving}
71+
onclick={save}
72+
>
73+
{saving ? $t('settings.saving') : $t('settings.save')}
74+
</button>
75+
</div>
76+
{/if}
77+
</div>

cptr/frontend/src/lib/components/Admin/Models.svelte

Lines changed: 13 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,6 @@
4848
// Default model
4949
let defaultModelId = $state('');
5050
51-
// Context compaction
52-
let compactTokenThreshold = $state(80000);
53-
let compactDirty = $state(false);
54-
5551
const TEMPLATE_VARIABLES = [
5652
{ name: 'CPTR_CONTEXT', desc: 'Runtime, machine, workspace, and tool context' },
5753
{ name: 'RUNTIME_ENV', desc: 'Runtime environment (host or container)' },
@@ -117,7 +113,7 @@ Workspace: {{WORKSPACE_NAME}}
117113
Files:
118114
{{FILE_TREE}}`;
119115
120-
let hasDirty = $derived(globalDirty || compactDirty || models.some((m) => m.dirty));
116+
let hasDirty = $derived(globalDirty || models.some((m) => m.dirty));
121117
122118
function parseRows(config: ModelConfigEntry | undefined): ParamRow[] {
123119
const rp = config?.params?.request_params;
@@ -256,10 +252,9 @@ Files:
256252
onMount(async () => {
257253
await loadModelConfig();
258254
259-
// Load admin config (default model, context compaction)
255+
// Load default model
260256
try {
261257
const adminCfg = await getAdminConfig();
262-
compactTokenThreshold = Number(adminCfg['chat.compact_token_threshold']) || 80000;
263258
defaultModelId =
264259
typeof adminCfg['chat.default_model'] === 'string' ? adminCfg['chat.default_model'] : '';
265260
} catch {}
@@ -349,12 +344,7 @@ Files:
349344
globalDirty = false;
350345
models.forEach((m) => (m.dirty = false));
351346
352-
// Save default model
353-
await updateConfig({
354-
'chat.compact_token_threshold': compactTokenThreshold,
355-
'chat.default_model': defaultModelId
356-
});
357-
compactDirty = false;
347+
await updateConfig({ 'chat.default_model': defaultModelId });
358348
359349
toast.success($t('settings.saved'));
360350
} catch {
@@ -552,43 +542,17 @@ Files:
552542
</button>
553543
</div>
554544

555-
<!-- Default model -->
556-
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2">
557-
{$t('models.defaultModel')}
558-
</h3>
559-
<div class="mb-1">
560-
<ModelSelector bind:selectedModel={defaultModelId} preferAbove={false} align="start" />
561-
</div>
562-
<p class="text-[0.6875rem] text-gray-400 dark:text-gray-600 mb-5">
563-
{$t('models.defaultModelHint')}
564-
</p>
565-
566-
<!-- Context compaction -->
567-
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2">{$t('admin.contextCompaction')}</h3>
568-
569-
<div class="flex flex-col gap-2.5 mb-5">
570-
<div>
571-
<label class="text-xs text-gray-600 dark:text-gray-400" for="compact-threshold"
572-
>{$t('admin.compactTokenThreshold')}</label
573-
>
574-
<div class="flex items-center gap-1.5 mt-1">
575-
<input
576-
id="compact-threshold"
577-
type="number"
578-
bind:value={compactTokenThreshold}
579-
oninput={() => (compactDirty = true)}
580-
min="10000"
581-
max="1000000"
582-
step="10000"
583-
class="w-24 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
584-
/>
585-
<span class="text-[0.6875rem] text-gray-400 dark:text-gray-600"
586-
>{$t('admin.compactTokenThresholdUnit')}</span
587-
>
545+
<div class="flex items-center justify-between gap-3 mb-1">
546+
<div class="min-w-0">
547+
<h3 class="text-xs text-gray-600 dark:text-gray-400">
548+
{$t('models.defaultModel')}
549+
</h3>
550+
<div class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
551+
{$t('models.defaultModelHint')}
588552
</div>
589-
<p class="text-[0.6875rem] text-gray-400 dark:text-gray-600 mt-0.5">
590-
{$t('admin.compactTokenThresholdHint')}
591-
</p>
553+
</div>
554+
<div class="shrink-0">
555+
<ModelSelector bind:selectedModel={defaultModelId} preferAbove={false} />
592556
</div>
593557
</div>
594558

0 commit comments

Comments
 (0)