Download src/lib/JsonEditor/JsonEditor.svelte from githubtailscaleduplicity342/chat-template-playground: direct link, hf CLI and curl.
- Browser
- Download file 7.81 kB
-
https://huggingface.co/spaces/githubtailscaleduplicity342/chat-template-playground/resolve/main/src/lib/JsonEditor/JsonEditor.svelte
- Command line
-
hf download hf://spaces/githubtailscaleduplicity342/chat-template-playground/src/lib/JsonEditor/JsonEditor.svelte
-
curl -L -o JsonEditor.svelte https://huggingface.co/spaces/githubtailscaleduplicity342/chat-template-playground/resolve/main/src/lib/JsonEditor/JsonEditor.svelte
7.81 kB
| <script lang="ts"> | |
| import CodeMirror from '$lib/CodeMirror/CodeMirror.svelte'; | |
| import { EditorView, lineNumbers } from '@codemirror/view'; | |
| import CopyButton from '$lib/CopyButton/CopyButton.svelte'; | |
| import { javascript } from '@codemirror/lang-javascript'; | |
| import { linter, lintGutter, type Diagnostic } from '@codemirror/lint'; | |
| import LineWrapButton from '$lib/LineWrapButton/LineWrapButton.svelte'; | |
| import JSON5 from 'json5'; | |
| import type { FormattedChatTemplate } from '../ChatTemplateViewer/types'; | |
| import { getExampleHelloWorld } from '$lib/example-inputs/helloWorld'; | |
| import { onMount } from 'svelte'; | |
| import { getExampleToolUsage } from '$lib/example-inputs/toolUsage'; | |
| import { foldGutter } from '@codemirror/language'; | |
| import { getExampleReasoning } from '$lib/example-inputs/reasoning'; | |
| import { tooltip } from '$lib/utils/tooltip'; | |
| import IconRestart from '$lib/Icons/IconRestart.svelte'; | |
| import { createEventDispatcher } from 'svelte'; | |
| import { page } from '$app/stores'; | |
| import { transformInput } from '$lib/utils/transformInput'; | |
| export let content: Record<string, unknown> = {}; | |
| export let error = ''; | |
| export let selectedTemplate: FormattedChatTemplate | undefined = undefined; | |
| export let selectedExampleInputId = ''; | |
| const dispatch = createEventDispatcher<{ exampleChange: void }>(); | |
| let value = JSON5.stringify(content, null, 2); | |
| let wrapLines = true; | |
| let exampleInputs: { id: string; label: string; content: Record<string, unknown> }[] = []; | |
| let exampleValue = ''; | |
| async function handleUpdateEditor(e: CustomEvent{ | |
| const currentCode = e.detail; | |
| try { | |
| content = JSON5.parse(currentCode); | |
| value = currentCode; | |
| error = ''; | |
| } catch (e) { | |
| console.error(e); | |
| error = 'Error in input JSON'; | |
| } | |
| } | |
| function jsonLinter() { | |
| return (view: EditorView): Diagnostic[] => { | |
| const diagnostics: Diagnostic[] = []; | |
| const text = view.state.doc.toString(); | |
| try { | |
| // Attempt to parse the JSON5 | |
| JSON5.parse(text); | |
| } catch (e) { | |
| let pos = 0; | |
| let errorMessage = ''; | |
| if (e && typeof e === 'object') { | |
| errorMessage = e.message || ''; | |
| // Prefer JSON5 error properties if available | |
| const line = e.lineNumber; | |
| const column = e.columnNumber; | |
| if (typeof line === 'number' && typeof column === 'number') { | |
| // Convert line/column to character offset | |
| let runningPos = 0; | |
| let currentLine = 1; | |
| for (let i = 0; i < text.length; i++) { | |
| if (currentLine === line && runningPos === column - 1) { | |
| pos = i; | |
| break; | |
| } | |
| if (text[i] === '\n') { | |
| currentLine++; | |
| runningPos = 0; | |
| } else { | |
| runningPos++; | |
| } | |
| } | |
| } else { | |
| // Fallback: try to extract from message | |
| const match = /at position (\d+)/.exec(errorMessage); | |
| if (match) { | |
| pos = parseInt(match[1], 10); | |
| } else { | |
| const lineMatch = /line (\d+) column (\d+)/.exec(errorMessage); | |
| if (lineMatch) { | |
| const l = parseInt(lineMatch[1], 10); | |
| const c = parseInt(lineMatch[2], 10); | |
| let runningPos = 0; | |
| let currentLine = 1; | |
| for (let i = 0; i < text.length; i++) { | |
| if (currentLine === l && runningPos === c - 1) { | |
| pos = i; | |
| break; | |
| } | |
| if (text[i] === '\n') { | |
| currentLine++; | |
| runningPos = 0; | |
| } else { | |
| runningPos++; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| diagnostics.push({ | |
| from: Math.max(0, pos - 1), | |
| to: Math.min(text.length, pos + 1), | |
| severity: 'error', | |
| message: `JSON Error: ${errorMessage}` | |
| }); | |
| } | |
| return diagnostics; | |
| }; | |
| } | |
| function handleExampleInputChange(e: Event) { | |
| const target = e.target as HTMLSelectElement; | |
| const selectedId = target.value; | |
| const selectedExampleInput = exampleInputs.find( | |
| (exampleInput) => exampleInput.id === selectedId | |
| ); | |
| if (selectedExampleInput) { | |
| selectedExampleInputId = selectedId; | |
| content = selectedExampleInput.content; | |
| value = JSON5.stringify(content, null, 2); | |
| exampleValue = value; | |
| dispatch('exampleChange'); | |
| } | |
| } | |
| onMount(() => { | |
| if (selectedTemplate) { | |
| const exampleHelloWorld = getExampleHelloWorld(selectedTemplate.template); | |
| if (exampleHelloWorld) { | |
| exampleInputs = [ | |
| ...exampleInputs, | |
| { | |
| id: 'hello-world', | |
| label: 'hello world example', | |
| content: transformInput(exampleHelloWorld, selectedTemplate.template) | |
| } | |
| ]; | |
| } | |
| const exampleReasoning = getExampleReasoning(selectedTemplate.template); | |
| if (exampleReasoning) { | |
| exampleInputs = [ | |
| ...exampleInputs, | |
| { | |
| id: 'reasoning', | |
| label: 'reasoning example', | |
| content: transformInput(exampleReasoning, selectedTemplate.template) | |
| } | |
| ]; | |
| } | |
| const exampleToolUsage = getExampleToolUsage(selectedTemplate.template); | |
| if (exampleToolUsage) { | |
| exampleInputs = [ | |
| ...exampleInputs, | |
| { | |
| id: 'tool-usage', | |
| label: 'tool usage example', | |
| content: transformInput(exampleToolUsage, selectedTemplate.template) | |
| } | |
| ]; | |
| } | |
| const exampleFromQuery = $page.url.searchParams.get('example'); | |
| if (exampleFromQuery) { | |
| const exampleInput = exampleInputs.find( | |
| (exampleInput) => exampleInput.id === exampleFromQuery | |
| ); | |
| if (exampleInput) { | |
| content = exampleInput.content; | |
| value = JSON5.stringify(content, null, 2); | |
| exampleValue = value; | |
| selectedExampleInputId = exampleInput.id; | |
| return; | |
| } | |
| } | |
| content = exampleInputs.at(-1)?.content ?? {}; | |
| selectedExampleInputId = exampleInputs.at(-1)?.id ?? ''; | |
| value = JSON5.stringify(content, null, 2); | |
| exampleValue = value; | |
| } | |
| }); | |
| </script> | |
| <div class="h-full overflow-scroll bg-white dark:bg-gray-900"> | |
| <div class="sticky top-0 z-10 bg-white dark:bg-gray-900"> | |
| <div | |
| class="flex items-center gap-x-2 border-b border-gray-200 px-3 py-1.5 dark:border-gray-700" | |
| > | |
| <span class="hidden text-sm font-semibold text-gray-800 sm:inline dark:text-gray-200" | |
| >JSON Input</span | |
| > | |
| {#if exampleInputs.length > 1} | |
| <select | |
| class="rounded border border-gray-200 bg-white px-1 py-0.5 text-xs text-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400" | |
| on:change={handleExampleInputChange} | |
| > | |
| {#each exampleInputs as exampleInput} | |
| <option value={exampleInput.id} selected={exampleInput.id === selectedExampleInputId} | |
| >{exampleInput.label}</option | |
| > | |
| {/each} | |
| </select> | |
| {/if} | |
| <div class="ml-auto flex items-center gap-x-2"> | |
| <!-- reset button --> | |
| {#if exampleValue && exampleValue !== value} | |
| <button | |
| class="relative inline-flex h-6! cursor-pointer items-center justify-center rounded-md border border-gray-200 bg-white p-0! px-1.5! text-sm focus:outline-hidden dark:border-gray-700 dark:bg-gray-800 dark:text-white [&_svg]:text-base!" | |
| type="button" | |
| on:click={() => { | |
| value = exampleValue; | |
| }} | |
| use:tooltip={'Reset example to original'} | |
| ><IconRestart classNames="dark:text-gray-200!" /> | |
| <span class="ml-1 hidden text-sm select-none sm:inline dark:text-gray-200!"> | |
| Reset | |
| </span> | |
| </button> | |
| {/if} | |
| <CopyButton | |
| label="Copy" | |
| {value} | |
| style="button-clear" | |
| classNames="h-6! [&_svg]:text-[0.7rem]! px-1.5! text-black! dark:text-gray-200!" | |
| /> | |
| <LineWrapButton | |
| style="button-clear" | |
| bind:wrapLines | |
| classNames="[&_svg]:text-xs! size-6! p-0!" | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| <CodeMirror | |
| {value} | |
| on:change={handleUpdateEditor} | |
| extensions={[ | |
| lineNumbers(), | |
| javascript({ jsx: false, typescript: false }), | |
| linter(jsonLinter()), | |
| lintGutter(), | |
| foldGutter(), | |
| ...[wrapLines ? [EditorView.lineWrapping] : []] | |
| ]} | |
| /> | |
| </div> | |