6666 let responsePromise = $state ();
6767 let modelName = $state (" Gemini" );
6868 let template =
69- $state (` You modify a spreadsheet by executing Markdown JavaScript code blocks. You follow these rules exactly:
70- - You NEVER use the tool calling API or function calling API
71- - You always output JavaScript that will be executed
72- - You always refer to the \` llmToolFunctions\` object within that code
69+ $state (` You modify a spreadsheet by executing JavaScript code. You follow these rules exactly:
7370- You always query for information (unless you are adding a new sheet)
74- - You try to do as many queries as possible in each block
75- - You always make row and column values start from 0
71+ - You try to collate as many queries as possible in each tool call
72+ - You always make row and column values start from 0 (R0C0 is the top left corner of the sheet)
7673- You try to use formatting functions (like \` BOLD\` and \` DOLLARS\` ) whenever possible
74+ - You pass string arguments wrapped in quotes like \` =BOLD("Title")\`
7775
7876- First, you plan
7977 - Then you revise the plan to combine as many steps as possible
8886 - If the user gives new instructions, make a new plan
8987
9088
91- You can run the following JavaScript functions in code blocks :
89+ You can run the following JavaScript functions:
9290- \$ {Object.entries(llmToolFunctions).map(([name, f]) => {
9391 const args = f.toString().replaceAll("\\ n", " ").replaceAll(/ */g, " ").match(/\\ ([^)]*\\ )/)?.[0] ?? "";
9492 return "llmToolFunctions." + name + args + " " + (f.description ?? "");
@@ -116,75 +114,14 @@ Formula function definitions have access to a \`this\` object with:
116114- this.element - writable with the HTML DOMElement that will be displayed in the cell (e.g., buttons, checkboxes, canvas, SVG, etc.)
117115- this.style - writable with the CSS style string for the containing \` <td>\`
118116
119- You add any formula functions you use if they do not already exist.
120-
121-
122- Example:
123-
124- User:
125- Find the receipt items that contain seafood and make them red.
126-
127- Model:
128- Plan:
129- - I should learn about the current sheets through a query.
130- - I should find the items with seafood using another query.
131- - I should check if there is already a formula to make items red.
132- - If not, I should add one.
133- - I should modify the formulas for the seafood item cells to wrap them in calls to the new red formula.
134-
135- Thought: I should learn more about the current sheets using a query.
136-
137- \`\`\` javascript
138- // Query the current sheets
139- const sheets = llmToolFunctions.getSheets();
140- llmToolFunctions.query("Sheets", sheets);
141- // Query the first rows of each sheet to learn about the columns
142- let firstRows = {};
143- sheets.forEach((sheet, sheetIndex) => {
144- firstRows[sheet.name] = new Array(sheet.cols).fill().map(
145- (_, i) => llmToolFunctions.getCell(sheetIndex, 0, i)
146- )
147- });
148- llmToolFunctions.query("First rows", firstRows);
149- \`\`\`
150-
151- User:
152- Sheets: [{"name": "Sheet 1", "rows": 10, "cols": 3}, {"name": "Receipt", "rows": 6, "cols": 2}]
153- First rows: {"Sheet 1": [{}, {}, {}], "Receipt": [{"formula": "=BOLD(\\\\ "Item\\\\ ")", "value": "Item"}, {"formula": "=BOLD(\\\\ "Cost\\\\ ")", "value": "Cost"}]}
154-
155- Model:
156- Thought: I should find the cells that might contain seafood. I now know that I can identify them by the "Item" column.
157- Thought: I can also query the formulas at the same time to check and see if there is already a formula to make items red.
158-
159- \`\`\` javascript
160- llmToolFunctions.query("Items", new Array(6).fill().map(
161- (_, i) => llmToolFunctions.getCell(1, i, 0)
162- ));
163- llmToolFunctions.query("Formulas", llmToolFunctions.getFormulaFunctionsList());
164- \`\`\`
165-
166- User:
167- Items: [{"formula": "=BOLD(\\\\ "Items\\\\ ")", "value": "Items"}, {"formula": "shrimp", "value": "shrimp"}, {"formula": "chicken", "value": "chicken"}, ... ]
168- Formulas: [ "abs", "acos", ..., "average", "rand", "slider", "bold", "center", "dollars", "sparkbars", "checkbox" ]
169-
170- Model:
171- Thought: Since there is no formula to make items red, I should add one. And I can make the seafood item cells red to complete my task.
172-
173- \`\`\` javascript
174- llmToolFunctions.addFunction(\`
175- functions.red = function (s) {
176- this.style += "color: red;"
177- return s;
178- }
179- \` );
180- llmToolFunctions.setCellFormula(1, 1, 0, \` =RED(\\\$ {llmToolFunctions.getCell(1, 1, 0).formula})\` )
181- llmToolFunctions.setCellFormula(1, 4, 0, \` =RED(\\\$ {llmToolFunctions.getCell(1, 4, 0).formula})\` )
182- \`\`\`
183- ` );
117+ You add any formula functions you use if they do not already exist.` );
184118
185119 let conversation = $state ([
186120 { role: " system" , text: " " },
187- { role: " user" , text: " " },
121+ {
122+ role: " user" ,
123+ text: " " ,
124+ },
188125 ]);
189126
190127 // Need to call eval in a separate function from derived.by to ensure globals
@@ -211,55 +148,60 @@ llmToolFunctions.setCellFormula(1, 4, 0, \`=RED(\\\${llmToolFunctions.getCell(1,
211148 conversation = conversationSlice;
212149 responsePromise = llmModels[modelName]
213150 .request(conversationSlice)
214- .then((parts) =>
215- parts.map((part) => {
216- if (
217- part.match(/^` ` ` ` *( *javascript *)?\n /) &&
218- part.endsWith("\n ` ` ` " )
219- ) {
220- return {
221- role: " model" ,
222- code: part
223- .replaceAll(/(^````*( *javascript *)?\n )|(\n ````*$)/g, " " )
224- .trim(),
225- };
226- } else {
227- return {
228- role: " model" ,
229- text: part,
230- };
231- }
232- }),
233- )
234151 .then((parts) => {
235152 conversation = conversation.concat(parts);
236153 });
237154 }
238155
239156 function execute(llmCode, codeIndex) {
157+ const { log, warn, error } = console;
240158 llmToolFunctions.globals = globals;
241-
242- llmToolFunctions.query = (name, value) => {
159+ llmToolFunctions.query = (value, name) => {
243160 let userResponse;
244161 let i = codeIndex;
245162 for (
246163 userResponse = conversation[++i];
247164 i < conversation.length && userResponse.role != "user";
248165 userResponse = conversation[++i]
249166 ) {}
250- if (userResponse.text.length && !userResponse.text.endsWith(" \n" )) {
251- userResponse.text += " \n" ;
167+ let response = "";
168+ if (name) {
169+ response += ` ${name}: ` ;
170+ }
171+ response += JSON.stringify(value);
172+ if (!userResponse?.response) {
173+ conversation.splice(i, 0, { role: "user", response });
174+ } else {
175+ userResponse.response += "\n " + response;
252176 }
253- userResponse.text += `${name}: ${JSON.stringify(value)}`;
254177 };
178+ console.log = (value) => {
179+ log(value);
180+ llmToolFunctions.query(value, "LOG");
181+ };
182+ console.warn = (value) => {
183+ warn(value);
184+ llmToolFunctions.query(value, "WARN");
185+ };
186+ console.error = (value) => {
187+ error(value);
188+ llmToolFunctions.query(value, "ERROR");
189+ };
190+
191+ try {
192+ eval(
193+ llmCode +
194+ // Allows user code to show up in the devtools debugger as "llm-code.js"
195+ "\n //# sourceURL=llm-code.js",
196+ );
197+ } catch (e) {
198+ console.error(e?.message ?? e?.toString() ?? e);
199+ }
255200
256- // TODO: Display the error
257- eval(
258- llmCode +
259- // Allows user code to show up in the devtools debugger as " llm- code .js "
260- " \n// # sourceURL=llm-code.js",
261- );
262201 delete llmToolFunctions.globals;
202+ console.log = log;
203+ console.warn = warn;
204+ console.error = error;
263205 }
264206
265207 function scrollIntoView(e) {
@@ -321,21 +263,37 @@ llmToolFunctions.setCellFormula(1, 4, 0, \`=RED(\\\${llmToolFunctions.getCell(1,
321263 <div style="margin-left: 10%;">
322264 <Details open>
323265 {#snippet summary()}User{/snippet}
324- < textarea
325- onkeydown= {(e ) => {
326- if (
327- e .key .toLocaleLowerCase () == " enter" &&
328- (e .ctrlKey || e .metaKey )
329- ) {
330- e .target .blur ();
331- submit (conversation .slice (0 , i + 1 ));
332- }
333- }}
334- placeholder= {i == 1
335- ? " Make a comprehensive budget spreadsheet for a 25 year old living in Manhattan and making $75k per year"
336- : " " }
337- bind: value= {part .text }
338- >< / textarea>
266+ {#if part.response}
267+ <CodeEditor
268+ onkeydown={(e) => {
269+ if (
270+ e.key.toLocaleLowerCase() == "enter" &&
271+ (e.ctrlKey || e.metaKey)
272+ ) {
273+ e.target.blur();
274+ submit(conversation.slice(0, i + 1));
275+ }
276+ }}
277+ bind:code={part.response}
278+ style="min-height: 10em; resize: vertical;"
279+ ></CodeEditor>
280+ {:else}
281+ <textarea
282+ onkeydown={(e) => {
283+ if (
284+ e.key.toLocaleLowerCase() == "enter" &&
285+ (e.ctrlKey || e.metaKey)
286+ ) {
287+ e.target.blur();
288+ submit(conversation.slice(0, i + 1));
289+ }
290+ }}
291+ placeholder={i == 1
292+ ? "Make a comprehensive budget spreadsheet for a 25 year old living in Manhattan and making $75k per year"
293+ : ""}
294+ bind:value={part.text}
295+ ></textarea>
296+ {/if}
339297 <div class="buttons">
340298 <Button onclick={() => submit(conversation.slice(0, i + 1))}
341299 >Submit</Button
0 commit comments