blob: bd8b87098498e798dae32d7e404ff35a4a77661b (
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
|
<script lang="ts">
import { untrack } from "svelte";
import { cwdChanged, normalizeCwd, type SaveCwd } from "../logic/view-model";
let {
cwd,
canEdit,
save,
}: {
/** The active conversation's persisted cwd, or null when unset. */
cwd: string | null;
/** Whether a real conversation is focused (a draft can't persist a cwd yet). */
canEdit: boolean;
save: SaveCwd;
} = $props();
// Start empty; the $effect below seeds from the (async-loaded) cwd prop. (Reading
// the prop directly into initial $state would only capture its first value.)
let value = $state("");
let lastSeed = $state("");
let saving = $state(false);
let error = $state<string | null>(null);
let justSaved = $state(false);
// Seed the input from the persisted cwd (it loads async). Only reseed while the
// field is untouched, so an in-flight load can't clobber what the user typed.
// Re-mounted per conversation, so there is no cross-tab bleed.
$effect(() => {
const incoming = cwd ?? "";
untrack(() => {
if (value === lastSeed) value = incoming;
lastSeed = incoming;
});
});
const dirty = $derived(cwdChanged(value, cwd));
async function handleSave() {
if (saving || !canEdit || !dirty) return;
saving = true;
error = null;
justSaved = false;
const result = await save(normalizeCwd(value));
saving = false;
if (result === null) return;
if (result.ok) {
justSaved = true;
} 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">Working directory</span>
<div class="flex items-center gap-2">
<input
type="text"
class="input input-bordered input-sm w-full font-mono text-xs"
placeholder={canEdit ? "/abs/path/to/project" : "Open a conversation first"}
bind:value
disabled={!canEdit || saving}
oninput={onInput}
onkeydown={(e) => {
if (e.key === "Enter") handleSave();
}}
aria-label="Working directory"
/>
<button
type="button"
class="btn btn-primary btn-sm"
disabled={!canEdit || saving || !dirty}
onclick={handleSave}
>
{#if saving}
<span class="loading loading-spinner loading-xs"></span>
{:else}
Set
{/if}
</button>
</div>
{#if !canEdit}
<p class="text-xs opacity-60">Start or open a conversation to set its working directory.</p>
{:else if error}
<p class="text-xs text-error">{error}</p>
{:else if justSaved && !dirty}
<p class="text-xs text-success">Saved.</p>
{:else}
<p class="text-xs opacity-50">Defaults each turn's cwd; drives the language servers below.</p>
{/if}
</div>
|