Skip to content

Commit a82cab7

Browse files
committed
Improve LLM UI
1 parent 79ca424 commit a82cab7

File tree

3 files changed

+65
-33
lines changed

3 files changed

+65
-33
lines changed

src/Llm.svelte

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414
white-space: pre-line;
1515
padding: 0.25em;
1616
min-height: 5em;
17-
height: 5em;
1817
flex-grow: 1;
1918
flex-shrink: 1;
19+
}
20+
21+
textarea {
2022
resize: vertical;
23+
height: 8em;
2124
}
2225
23-
input[type="password"] {
26+
input {
2427
border: 1px solid var(--fg-color);
2528
padding: 0.25em;
2629
}
@@ -41,7 +44,6 @@
4144
justify-content: flex-end;
4245
align-items: center;
4346
gap: 1em;
44-
margin-top: -0.75em;
4547
}
4648
</style>
4749

@@ -53,6 +55,7 @@
5355
// May appear unused, but actually used during evals
5456
import { llmToolFunctions, llmModels } from "./llm.svelte.js";
5557
import { functions as formulaFunctions } from "./formula-functions.svelte";
58+
import CodeEditor from "./CodeEditor.svelte";
5659
5760
let { globals } = $props();
5861
let response = $state();
@@ -86,7 +89,6 @@ Available spreadsheets:
8689
).join('\\n')
8790
}
8891
`);
89-
let llmCode = $state("");
9092
9193
// Need to call eval in a separate function from derived.by to ensure globals,
9294
// functions, and prompt are in-scope
@@ -103,14 +105,24 @@ Available spreadsheets:
103105
});
104106
105107
async function submit() {
106-
response = llmModels[modelName].request(prompt, systemPrompt, {
107-
apiKey: llmModels[modelName].apiKey,
108-
});
109-
// TODO: Fix race condition if the button is pushed multiple times
110-
llmCode = await response;
108+
response = llmModels[modelName]
109+
.request(prompt, systemPrompt)
110+
.then((parts) =>
111+
parts.map((part) => {
112+
if (part.startsWith("```") && part.endsWith("```")) {
113+
return {
114+
code: part
115+
.replaceAll(/(^````*( *javascript *)?\n)|(\n````*$)/g, "")
116+
.trim(),
117+
};
118+
} else {
119+
return part;
120+
}
121+
}),
122+
);
111123
}
112124
113-
function execute() {
125+
function execute(llmCode) {
114126
llmToolFunctions.globals = globals;
115127
eval(
116128
llmCode +
@@ -143,6 +155,10 @@ Available spreadsheets:
143155
<!-- <Button>Save</Button> -->
144156
</div>
145157
</label>
158+
<label>
159+
Model
160+
<input type="text" bind:value={llmModels[modelName].model} />
161+
</label>
146162
</Details>
147163
148164
<Details>
@@ -165,18 +181,35 @@ Available spreadsheets:
165181
placeholder="Make a simple budget spreadsheet template"
166182
bind:value={prompt}
167183
></textarea>
184+
<div class="buttons" style="margin-top: 0.5em;">
185+
<Button onclick={submit}>Submit</Button>
186+
</div>
168187
</div>
169188
170-
<div class="buttons"><Button onclick={submit}>Submit</Button></div>
171-
172189
{#if response}
173190
<h1>LLM Response</h1>
174191
{#await response}
175192
<p>Loading...</p>
176-
{:then}
177-
<textarea bind:value={llmCode} style:min-height="10em" style:flex-grow="2"
178-
></textarea>
179-
<div class="buttons"><Button onclick={execute}>Execute</Button></div>
193+
{:then r}
194+
<div style="gap: 0.25em;">
195+
{#each r as part, i}
196+
{#if part.code}
197+
<Details open>
198+
{#snippet summary()}Code{/snippet}
199+
<CodeEditor bind:code={r[i].code} style="min-height: 10em"
200+
></CodeEditor>
201+
<div class="buttons">
202+
<Button onclick={() => execute(r[i].code)}>Execute</Button>
203+
</div>
204+
</Details>
205+
{:else}
206+
<Details open>
207+
{#snippet summary()}Text{/snippet}
208+
<pre class="message">{part}</pre>
209+
</Details>
210+
{/if}
211+
{/each}
212+
</div>
180213
<!-- TODO: Add error display if evaluated code throws -->
181214
{:catch e}
182215
<p>Error: {e?.message ?? e}</p>

src/keyboard.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ export const keybindings = {
6060
"Shift+:": "Toggle Code Editor",
6161
"Ctrl+s": "Toggle Save and Load",
6262
"Meta+s": "Toggle Save and Load",
63+
"Ctrl+i": "Toggle LLM",
64+
"Meta+i": "Toggle LLM",
6365
g: "Go To",
6466
"Shift+g": "Go to Bottom",
6567
"Ctrl+z": "Undo",
@@ -245,6 +247,10 @@ export const actions = {
245247
}
246248
},
247249

250+
"Toggle LLM": (e, globals) => {
251+
globals.llmOpen = !globals.llmOpen;
252+
},
253+
248254
"Toggle Save and Load": (e, globals) => {
249255
globals.imageOpen = !globals.imageOpen;
250256
},

src/llm.svelte.js

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export const llmToolFunctions = $state({});
33
llmToolFunctions.newSheet = function (name, cells) {
44
const cols = Math.max(...cells.map((row) => row.length));
55
this.globals?.addSheet(name, cells.length, cols, (i, j) =>
6-
cells[i][j]?.toString(),
6+
cells[i]?.[j]?.toString(),
77
);
88
};
99
llmToolFunctions.newSheet.description =
@@ -31,10 +31,10 @@ llmToolFunctions.setCellFormula = function (sheetIndex, row, col, formula) {
3131
export const llmModels = $state({});
3232

3333
llmModels.Gemini = {
34-
async request(prompt, systemPrompt, { apiKey }) {
34+
model: "gemini-2.5-pro-exp-03-25",
35+
async request(prompt, systemPrompt) {
3536
const response = await fetch(
36-
`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro-exp-03-25:generateContent?key=${apiKey}`,
37-
// `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`,
37+
`https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent?key=${this.apiKey}`,
3838
{
3939
method: "POST",
4040
headers: {
@@ -44,15 +44,11 @@ llmModels.Gemini = {
4444
system_instruction: {
4545
parts: systemPrompt.split("\n\n").map((s) => ({ text: s.trim() })),
4646
},
47-
generationConfig: {
48-
response_mime_type: "application/json",
49-
response_schema: {
50-
type: "OBJECT",
51-
properties: {
52-
code: { type: "STRING" },
53-
},
47+
tools: [
48+
{
49+
google_search: {},
5450
},
55-
},
51+
],
5652
contents: [
5753
{
5854
role: "user",
@@ -67,16 +63,13 @@ llmModels.Gemini = {
6763
},
6864
)
6965
.then((r) => {
70-
if (!r.ok) throw new Error(r.statusText);
66+
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
7167
return r.json();
7268
})
7369
.catch((e) => {
7470
console.error(e);
7571
throw e;
7672
});
77-
const j = response?.candidates?.[0]?.content?.parts
78-
?.map(({ text }) => text)
79-
.join("\n\n");
80-
return JSON.parse(j).code;
73+
return response?.candidates?.[0]?.content?.parts?.map(({ text }) => text);
8174
},
8275
};

0 commit comments

Comments
 (0)