Chat with an LLM to edit a document. Configure your API key in Params, then try "summarize this", "translate to French", or "fix the grammar". Toggle Show Tool Calls to inspect the function calls.
Set your API provider and key here. Any OpenAI-compatible API that supports function calling will work.
| Provider | Default Model | API Key |
|---|---|---|
| OpenAI | gpt-5.2 | Required |
| Anthropic (Claude) | claude-sonnet-4-20250514 | Required |
| Groq | llama-3.3-70b-versatile | Required |
| Together AI | Llama-3.3-70B-Instruct-Turbo | Required |
| Mistral | mistral-small-latest | Required |
| OpenRouter | openai/gpt-4o | Required |
| Ollama (local) | llama3.1 | Not needed |
| LM Studio (local) | local-model | Not needed |
This prompt is sent to the LLM with every request. Edit it to change the assistant's behavior.
Settings are stored as cookies on your local machine (prefixed te_). Your API key is never sent anywhere except the API endpoint you configure.
This example connects two components — a QuikChat widget and a QuikdownEditor — through an LLM that uses function calling to bridge them. The user types natural-language commands in the chat; the LLM decides which editing operations to perform.
Seven tools are registered with the LLM via the OpenAI function calling API. The LLM decides which tools to call and in what order.
| Tool | Parameters | Description |
|---|---|---|
read_editor |
none | Returns the full markdown content of the editor. The LLM should always call this first before making changes. |
write_editor |
content (string) |
Replaces the entire editor content. Used for large rewrites, translations, or restructuring. |
replace_text |
find, replace (strings) |
Finds the first occurrence of an exact string and replaces it. Best for small, targeted edits. |
extract_text |
start_line, end_line (integers) |
Returns lines in range (1-based, inclusive) without modifying the document. Useful for quoting or analyzing a specific section. |
get_stats |
none | Returns JSON with characters, words, lines, and paragraphs counts. |
undo |
none | Undoes the last edit and returns the resulting content. |
redo |
none | Redoes a previously undone edit and returns the resulting content. |
| Parameter | Value | Why |
|---|---|---|
stream | false | Tool-call JSON must be parsed as complete objects — streaming would fragment them. |
temperature | 0.3 | Editing tasks benefit from more deterministic output. |
| Conversation history | Separate JS array | OpenAI tool calling requires tool_calls and tool_call_id fields in the message history, which historyGet() doesn't preserve. |
| Max tool-call rounds | 10 | Safety limit to prevent infinite loops if the LLM keeps calling tools. |
Steps 2–4 repeat if the LLM calls multiple tools (e.g. read_editor then write_editor). The loop runs up to 10 rounds per user message.
Streaming is disabled (stream: false) because tool-call JSON in the response must be parsed as complete objects. The loop works as follows:
async function handleUserInput(chatInstance, userInput) {
// 1. Show user message, disable input
chatInstance.messageAddNew(userInput, 'user', 'right');
// 2. Push to conversation history (separate from chat widget)
conversationMessages.push({ role: 'user', content: userInput });
// 3. Build messages = [system_prompt, ...conversationMessages]
const messages = [{ role: 'system', content: systemPrompt }, ...conversationMessages];
// 4. Tool-call loop (max 10 rounds)
for (let round = 0; round < 10; round++) {
const data = await fetch(baseUrl, {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + apiKey, ... },
body: JSON.stringify({ model, messages, tools, temperature: 0.3, stream: false })
}).then(r => r.json());
const choice = data.choices[0];
// If the model called tools, execute them and continue the loop
if (choice.message.tool_calls) {
for (const tc of choice.message.tool_calls) {
const result = executeTool(tc.function.name, JSON.parse(tc.function.arguments));
messages.push({ role: 'tool', tool_call_id: tc.id, content: result });
}
continue;
}
// Otherwise, display the final text response and break
chatInstance.messageAddNew(choice.message.content, 'bot', 'left');
break;
}
}
Each tool call and result is added to the chat with visible: false and tagged 'tool-call'. This keeps the conversation clean while preserving a full audit trail. The Show Tool Calls button toggles visibility using messageSetVisibleByTag():
// Add hidden tool-call message
chatInstance.messageAddFull({
content: 'Tool: read_editor\nResult: ...',
role: 'tool',
visible: false,
tags: ['tool-call']
});
// Toggle all tool-call messages on/off
chat.messageSetVisibleByTag('tool-call', true); // show
chat.messageSetVisibleByTag('tool-call', false); // hide
The conversation is tracked in a separate JavaScript array rather than using historyGet(). This is necessary because the OpenAI tool-calling protocol requires tool_calls objects on assistant messages and tool_call_id fields on tool result messages — metadata that QuikChat's history format doesn't capture.
| Component | Build | Role |
|---|---|---|
| QuikChat | quikchat-md.umd.min.js |
Chat widget with markdown rendering (LLM responses may contain markdown). Handles user input, message display, and tool-call visibility toggling. |
| QuikdownEditor | quikdown_edit.umd.min.js (CDN) |
Split-mode markdown editor. The LLM reads and writes its content via the getMarkdown() and setMarkdown() API. |
write_editorreplace_textget_statsextract_textwrite_editorundo