blob: 502212fb118dde3574a037d05028fe3e352a14a1 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
<script lang="ts">
import { untrack } from "svelte";
import { MAX_CHAT_LIMIT, MIN_CHAT_LIMIT } from "../../../core/chunks";
import { chatLimitChanged, parseChatLimit, type SaveChatLimit } from "../logic/view-model";
let {
chatLimit,
save,
}: {
/** The persisted chat limit (max loaded chunks per conversation). */
chatLimit: number;
save: SaveChatLimit;
} = $props();
// Seed from the prop; the $effect below re-seeds on external changes (a live
// apply from elsewhere) but only while the field is untouched, so an in-flight
// change can't clobber what the user typed.
let value = $state("");
let lastSeed = $state("");
let saving = $state(false);
let error = $state<string | null>(null);
let justSaved = $state(false);
let savedValue = $state<number | null>(null);
$effect(() => {
const incoming = String(chatLimit);
untrack(() => {
if (value === lastSeed) value = incoming;
lastSeed = incoming;
});
});
const dirty = $derived(chatLimitChanged(value, chatLimit));
async function handleSave() {
if (saving || !dirty) return;
const parsed = parseChatLimit(value);
if (!parsed.ok) {
error = parsed.error;
return;
}
saving = true;
error = null;
justSaved = false;
const result = await save(parsed.value);
saving = false;
if (result.ok) {
justSaved = true;
savedValue = result.chatLimit;
// Reflect the clamped / persisted value back into the input immediately
// (the prop will also re-assert it via the effect above).
value = String(result.chatLimit);
lastSeed = value;
} else {
error = result.error;
}
}
function onInput() {
justSaved = false;
error = null;
}
</script>
<div class="flex flex-col gap-1">
<span class="text-xs font-semibold uppercase opacity-60">Chat limit</span>
<div class="flex items-center gap-2">
<input
type="text"
inputmode="numeric"
class="input input-bordered input-sm w-full font-mono text-xs"
placeholder={String(chatLimit)}
bind:value
disabled={saving}
oninput={onInput}
onkeydown={(e) => {
if (e.key === "Enter") handleSave();
}}
aria-label="Chat limit"
/>
<button
type="button"
class="btn btn-primary btn-sm"
disabled={saving || !dirty}
onclick={handleSave}
>
{#if saving}
<span class="loading loading-spinner loading-xs"></span>
{:else}
Set
{/if}
</button>
</div>
{#if error}
<p class="text-xs text-error">{error}</p>
{:else if justSaved && !dirty}
<p class="text-xs text-success">Saved{savedValue !== null ? `: ${savedValue}.` : "."}</p>
{:else}
<p class="text-xs opacity-50">
Max loaded chunks per conversation ({MIN_CHAT_LIMIT}–{MAX_CHAT_LIMIT}). Lowering it unloads
older history; raise it and use "Show earlier" to page back in.
</p>
{/if}
</div>
|