mirror of
https://github.com/voson-wang/toon.git
synced 2026-01-29 15:24:10 +08:00
docs: add playground
This commit is contained in:
@@ -32,6 +32,10 @@ export default defineConfig({
|
|||||||
logo: '/favicon.svg',
|
logo: '/favicon.svg',
|
||||||
|
|
||||||
nav: [
|
nav: [
|
||||||
|
{
|
||||||
|
text: 'Playground',
|
||||||
|
link: '/playground',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: 'Guide',
|
text: 'Guide',
|
||||||
activeMatch: '^/guide/',
|
activeMatch: '^/guide/',
|
||||||
@@ -117,6 +121,7 @@ function sidebarPrimary(): DefaultTheme.SidebarItem[] {
|
|||||||
{
|
{
|
||||||
text: 'Tooling',
|
text: 'Tooling',
|
||||||
items: [
|
items: [
|
||||||
|
{ text: 'Playground', link: '/playground' },
|
||||||
{ text: 'CLI Reference', link: '/cli/' },
|
{ text: 'CLI Reference', link: '/cli/' },
|
||||||
{ text: 'Tools & Playgrounds', link: '/ecosystem/tools-and-playgrounds' },
|
{ text: 'Tools & Playgrounds', link: '/ecosystem/tools-and-playgrounds' },
|
||||||
],
|
],
|
||||||
|
|||||||
497
docs/.vitepress/theme/components/PlaygroundLayout.vue
Normal file
497
docs/.vitepress/theme/components/PlaygroundLayout.vue
Normal file
@@ -0,0 +1,497 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { Delimiter } from '../../../../packages/toon/src'
|
||||||
|
import { useClipboard, useDebounceFn } from '@vueuse/core'
|
||||||
|
import { compressToEncodedURIComponent, decompressFromEncodedURIComponent } from 'lz-string'
|
||||||
|
import { computed, onMounted, ref, shallowRef, watch } from 'vue'
|
||||||
|
import { DEFAULT_DELIMITER, encode } from '../../../../packages/toon/src'
|
||||||
|
import VPInput from './VPInput.vue'
|
||||||
|
|
||||||
|
interface PlaygroundState {
|
||||||
|
json: string
|
||||||
|
delimiter: Delimiter
|
||||||
|
indent: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const PRESETS = {
|
||||||
|
users: [
|
||||||
|
{ id: 1, name: 'Alice', role: 'admin' },
|
||||||
|
{ id: 2, name: 'Bob', role: 'user' },
|
||||||
|
],
|
||||||
|
config: {
|
||||||
|
app: { name: 'MyApp', version: '1.0.0' },
|
||||||
|
features: { darkMode: true, notifications: false },
|
||||||
|
},
|
||||||
|
products: [
|
||||||
|
{ sku: 'A1', name: 'Widget', price: 9.99, qty: 100 },
|
||||||
|
{ sku: 'B2', name: 'Gadget', price: 14.5, qty: 50 },
|
||||||
|
],
|
||||||
|
} as const
|
||||||
|
const DELIMITER_OPTIONS: { value: Delimiter, label: string }[] = [
|
||||||
|
{ value: ',', label: 'Comma (,)' },
|
||||||
|
{ value: '\t', label: 'Tab (\\t)' },
|
||||||
|
{ value: '|', label: 'Pipe (|)' },
|
||||||
|
]
|
||||||
|
const DEFAULT_JSON = JSON.stringify(PRESETS.users, undefined, 2)
|
||||||
|
|
||||||
|
const jsonInput = ref(DEFAULT_JSON)
|
||||||
|
const delimiter = ref<Delimiter>(DEFAULT_DELIMITER)
|
||||||
|
const indent = ref(2)
|
||||||
|
const hasCopiedUrl = ref(false)
|
||||||
|
|
||||||
|
const tokenizer = shallowRef<typeof import('gpt-tokenizer') | undefined>()
|
||||||
|
|
||||||
|
const encodingResult = computed(() => {
|
||||||
|
try {
|
||||||
|
const parsedInput = JSON.parse(jsonInput.value)
|
||||||
|
return {
|
||||||
|
output: encode(parsedInput, {
|
||||||
|
indent: indent.value,
|
||||||
|
delimiter: delimiter.value,
|
||||||
|
}),
|
||||||
|
error: undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
return {
|
||||||
|
output: '',
|
||||||
|
error: e instanceof Error ? e.message : 'Invalid JSON',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const toonOutput = computed(() => encodingResult.value.output)
|
||||||
|
const error = computed(() => encodingResult.value.error)
|
||||||
|
|
||||||
|
const jsonTokens = computed(() =>
|
||||||
|
tokenizer.value?.encode(jsonInput.value).length,
|
||||||
|
)
|
||||||
|
const toonTokens = computed(() =>
|
||||||
|
tokenizer.value && toonOutput.value ? tokenizer.value.encode(toonOutput.value).length : undefined,
|
||||||
|
)
|
||||||
|
const tokenSavings = computed(() => {
|
||||||
|
if (!jsonTokens.value || !toonTokens.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
const saved = jsonTokens.value - toonTokens.value
|
||||||
|
const percent = ((saved / jsonTokens.value) * 100).toFixed(1)
|
||||||
|
return { saved, percent }
|
||||||
|
})
|
||||||
|
|
||||||
|
const { copy, copied } = useClipboard({ source: toonOutput })
|
||||||
|
|
||||||
|
async function copyShareUrl() {
|
||||||
|
await navigator.clipboard.writeText(window.location.href)
|
||||||
|
hasCopiedUrl.value = true
|
||||||
|
setTimeout(() => (hasCopiedUrl.value = false), 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateUrl = useDebounceFn(() => {
|
||||||
|
if (typeof window === 'undefined')
|
||||||
|
return
|
||||||
|
|
||||||
|
const hash = encodeState()
|
||||||
|
window.history.replaceState(null, '', `#${hash}`)
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
watch([jsonInput, delimiter, indent], () => {
|
||||||
|
updateUrl()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadTokenizer()
|
||||||
|
|
||||||
|
const hash = window.location.hash.slice(1)
|
||||||
|
if (!hash)
|
||||||
|
return
|
||||||
|
|
||||||
|
const state = decodeState(hash)
|
||||||
|
if (state) {
|
||||||
|
jsonInput.value = state.json
|
||||||
|
delimiter.value = state.delimiter
|
||||||
|
indent.value = state.indent
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function encodeState() {
|
||||||
|
const state: PlaygroundState = {
|
||||||
|
json: jsonInput.value,
|
||||||
|
delimiter: delimiter.value,
|
||||||
|
indent: indent.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
return compressToEncodedURIComponent(JSON.stringify(state))
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeState(hash: string) {
|
||||||
|
try {
|
||||||
|
const decodedData = decompressFromEncodedURIComponent(hash)
|
||||||
|
if (decodedData)
|
||||||
|
return JSON.parse(decodedData) as PlaygroundState
|
||||||
|
}
|
||||||
|
catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPreset(name: keyof typeof PRESETS) {
|
||||||
|
jsonInput.value = JSON.stringify(PRESETS[name], undefined, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTokenizer() {
|
||||||
|
tokenizer.value ??= await import('gpt-tokenizer')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="playground">
|
||||||
|
<div class="playground-container">
|
||||||
|
<!-- Header -->
|
||||||
|
<header class="playground-header">
|
||||||
|
<h1>Playground</h1>
|
||||||
|
<p>Experiment with JSON to TOON encoding in real-time.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Options Bar -->
|
||||||
|
<div class="options-bar">
|
||||||
|
<VPInput id="delimiter" label="Delimiter">
|
||||||
|
<select id="delimiter" v-model="delimiter">
|
||||||
|
<option v-for="opt in DELIMITER_OPTIONS" :key="opt.value" :value="opt.value">
|
||||||
|
{{ opt.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</VPInput>
|
||||||
|
|
||||||
|
<VPInput id="indent" label="Indent">
|
||||||
|
<input
|
||||||
|
id="indent"
|
||||||
|
v-model.number="indent"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="8"
|
||||||
|
>
|
||||||
|
</VPInput>
|
||||||
|
|
||||||
|
<VPInput id="preset" label="Preset">
|
||||||
|
<select id="preset" @change="(e) => loadPreset((e.target as HTMLSelectElement).value as keyof typeof PRESETS)">
|
||||||
|
<option value="" disabled selected>
|
||||||
|
Load example...
|
||||||
|
</option>
|
||||||
|
<option value="users">
|
||||||
|
Users
|
||||||
|
</option>
|
||||||
|
<option value="config">
|
||||||
|
Config
|
||||||
|
</option>
|
||||||
|
<option value="products">
|
||||||
|
Products
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</VPInput>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="share-button"
|
||||||
|
:class="{ copied: hasCopiedUrl }"
|
||||||
|
:aria-label="hasCopiedUrl ? 'Link copied!' : 'Copy shareable URL'"
|
||||||
|
@click="copyShareUrl"
|
||||||
|
>
|
||||||
|
<span class="vpi-link" :class="{ check: hasCopiedUrl }" aria-hidden="true" />
|
||||||
|
{{ hasCopiedUrl ? 'Copied!' : 'Share' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Editor Container -->
|
||||||
|
<div class="editor-container">
|
||||||
|
<!-- JSON Input -->
|
||||||
|
<div class="editor-pane">
|
||||||
|
<div class="pane-header">
|
||||||
|
<span class="pane-title">JSON Input</span>
|
||||||
|
<span class="pane-stats">
|
||||||
|
<span>{{ jsonTokens ?? '...' }} tokens</span>
|
||||||
|
<span>{{ jsonInput.length }} chars</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
id="json-input"
|
||||||
|
v-model="jsonInput"
|
||||||
|
class="editor-textarea"
|
||||||
|
spellcheck="false"
|
||||||
|
aria-label="JSON input"
|
||||||
|
:aria-describedby="error ? 'json-error' : undefined"
|
||||||
|
:aria-invalid="!!error"
|
||||||
|
placeholder="Enter JSON here..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TOON Output -->
|
||||||
|
<div class="editor-pane">
|
||||||
|
<div class="pane-header">
|
||||||
|
<span class="pane-title">
|
||||||
|
TOON Output
|
||||||
|
<span v-if="tokenSavings && Number(tokenSavings.percent) > 0" class="savings-badge">
|
||||||
|
−{{ tokenSavings.percent }}%
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="pane-stats">
|
||||||
|
<span>{{ toonTokens ?? '...' }} tokens</span>
|
||||||
|
<span>{{ toonOutput.length }} chars</span>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="copy-button"
|
||||||
|
:disabled="!!error"
|
||||||
|
:aria-label="copied ? 'Copied to clipboard' : 'Copy to clipboard'"
|
||||||
|
:aria-pressed="copied"
|
||||||
|
@click="copy()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="editor-output">
|
||||||
|
<pre v-if="!error"><code>{{ toonOutput }}</code></pre>
|
||||||
|
<div v-else id="json-error" role="alert" class="error-message">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.playground {
|
||||||
|
padding: 32px 24px 96px;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: var(--vp-c-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.playground {
|
||||||
|
padding: 48px 32px 128px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.playground {
|
||||||
|
padding: 48px 32px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.playground-container {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playground-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playground-header h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
line-height: 40px;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.playground-header h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.playground-header p {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 28px;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--vp-c-bg-soft);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpi-link {
|
||||||
|
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71'/%3E%3Cpath d='M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71'/%3E%3C/svg%3E");
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
-webkit-mask: var(--icon) no-repeat;
|
||||||
|
mask: var(--icon) no-repeat;
|
||||||
|
-webkit-mask-size: 100% 100%;
|
||||||
|
mask-size: 100% 100%;
|
||||||
|
background-color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpi-link.check {
|
||||||
|
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 0 12px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
background: var(--vp-c-bg);
|
||||||
|
border: 1px solid var(--vp-c-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: border-color 0.25s, color 0.25s;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-button:hover {
|
||||||
|
border-color: var(--vp-c-brand-1);
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-button:focus-visible {
|
||||||
|
outline: 2px solid var(--vp-c-brand-1);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-button.copied {
|
||||||
|
border-color: var(--vp-c-green-1);
|
||||||
|
color: var(--vp-c-green-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.editor-container {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-pane {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--vp-c-bg-soft);
|
||||||
|
transition: border-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-pane:focus-within {
|
||||||
|
border-color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--vp-c-bg-alt);
|
||||||
|
border-bottom: 1px solid var(--vp-c-divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.savings-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-size: 0.625rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vp-c-green-1);
|
||||||
|
background: var(--vp-c-green-soft);
|
||||||
|
border-radius: 4px;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: var(--vp-icon-copy) center / 16px no-repeat;
|
||||||
|
border-radius: 4px;
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: opacity 0.25s, background-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button:hover:not(:disabled) {
|
||||||
|
opacity: 1;
|
||||||
|
background-color: var(--vp-c-default-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button:focus-visible {
|
||||||
|
outline: 2px solid var(--vp-c-brand-1);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button:disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button[aria-pressed="true"] {
|
||||||
|
background-image: var(--vp-icon-copied);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-textarea,
|
||||||
|
.editor-output {
|
||||||
|
flex: 1;
|
||||||
|
padding: 16px;
|
||||||
|
font-family: var(--vp-font-family-mono);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-textarea {
|
||||||
|
resize: none;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
background: var(--vp-c-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-output {
|
||||||
|
overflow: auto;
|
||||||
|
background: var(--vp-code-block-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-output pre {
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: var(--vp-c-danger-1);
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--vp-c-danger-soft);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-family: var(--vp-font-family-base);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
53
docs/.vitepress/theme/components/VPInput.vue
Normal file
53
docs/.vitepress/theme/components/VPInput.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
label: string
|
||||||
|
id: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="VPInput">
|
||||||
|
<label :for="id" class="label">{{ label }}</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPInput {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper :deep(select),
|
||||||
|
.input-wrapper :deep(input) {
|
||||||
|
padding: 0 10px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
background-color: var(--vp-c-bg);
|
||||||
|
border: 1px solid var(--vp-c-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: border-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper :deep(select):hover,
|
||||||
|
.input-wrapper :deep(input):hover,
|
||||||
|
.input-wrapper :deep(select):focus,
|
||||||
|
.input-wrapper :deep(input):focus {
|
||||||
|
border-color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper :deep(input[type="number"]) {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { Theme } from 'vitepress'
|
import type { Theme } from 'vitepress'
|
||||||
import CopyOrDownloadAsMarkdownButtons from 'vitepress-plugin-llms/vitepress-components/CopyOrDownloadAsMarkdownButtons.vue'
|
import CopyOrDownloadAsMarkdownButtons from 'vitepress-plugin-llms/vitepress-components/CopyOrDownloadAsMarkdownButtons.vue'
|
||||||
import DefaultTheme from 'vitepress/theme'
|
import DefaultTheme from 'vitepress/theme'
|
||||||
|
import PlaygroundLayout from './components/PlaygroundLayout.vue'
|
||||||
|
import VPInput from './components/VPInput.vue'
|
||||||
|
|
||||||
import './vars.css'
|
import './vars.css'
|
||||||
import './overrides.css'
|
import './overrides.css'
|
||||||
@@ -13,6 +15,8 @@ const config: Theme = {
|
|||||||
version: '3.0',
|
version: '3.0',
|
||||||
}
|
}
|
||||||
app.component('CopyOrDownloadAsMarkdownButtons', CopyOrDownloadAsMarkdownButtons)
|
app.component('CopyOrDownloadAsMarkdownButtons', CopyOrDownloadAsMarkdownButtons)
|
||||||
|
app.component('PlaygroundLayout', PlaygroundLayout)
|
||||||
|
app.component('VPInput', VPInput)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
# Tools & Playgrounds
|
# Tools & Playgrounds
|
||||||
|
|
||||||
Experiment with TOON format interactively using these community-built tools for token comparison, format conversion, and validation.
|
Experiment with TOON format interactively using these tools for token comparison, format conversion, and validation.
|
||||||
|
|
||||||
## Playgrounds
|
## Playgrounds
|
||||||
|
|
||||||
Experiment with TOON format interactively using these community-built tools for token comparison, format conversion, and validation:
|
### Official Playground
|
||||||
|
|
||||||
|
The [TOON Playground](/playground) lets you convert JSON to TOON in real-time, compare token counts, and share your experiments via URL.
|
||||||
|
|
||||||
|
### Community Playgrounds
|
||||||
|
|
||||||
- [Format Tokenization Playground](https://www.curiouslychase.com/playground/format-tokenization-exploration)
|
- [Format Tokenization Playground](https://www.curiouslychase.com/playground/format-tokenization-exploration)
|
||||||
- [TOON Tools](https://toontools.vercel.app/)
|
- [TOON Tools](https://toontools.vercel.app/)
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ hero:
|
|||||||
alt: TOON Logo
|
alt: TOON Logo
|
||||||
actions:
|
actions:
|
||||||
- theme: brand
|
- theme: brand
|
||||||
text: Get Started
|
text: What is TOON?
|
||||||
link: /guide/getting-started
|
link: /guide/getting-started
|
||||||
- theme: alt
|
- theme: alt
|
||||||
text: Benchmarks
|
text: Benchmarks
|
||||||
link: /guide/benchmarks
|
link: /guide/benchmarks
|
||||||
|
- theme: alt
|
||||||
|
text: Playground
|
||||||
|
link: /playground
|
||||||
- theme: alt
|
- theme: alt
|
||||||
text: CLI
|
text: CLI
|
||||||
link: /cli/
|
link: /cli/
|
||||||
- theme: alt
|
|
||||||
text: Spec v3.0
|
|
||||||
link: /reference/spec
|
|
||||||
|
|
||||||
features:
|
features:
|
||||||
- title: Token-Efficient & Accurate
|
- title: Token-Efficient & Accurate
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
"preview": "vitepress preview"
|
"preview": "vitepress preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@vueuse/core": "^14.1.0",
|
||||||
|
"gpt-tokenizer": "^3.4.0",
|
||||||
|
"lz-string": "^1.5.0",
|
||||||
"markdown-it-mathjax3": "^4.3.2",
|
"markdown-it-mathjax3": "^4.3.2",
|
||||||
"unocss": "^66.5.9",
|
"unocss": "^66.5.9",
|
||||||
"vitepress": "^1.6.4",
|
"vitepress": "^1.6.4",
|
||||||
|
|||||||
4
docs/playground.md
Normal file
4
docs/playground.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
layout: PlaygroundLayout
|
||||||
|
title: Playground
|
||||||
|
---
|
||||||
41
pnpm-lock.yaml
generated
41
pnpm-lock.yaml
generated
@@ -89,6 +89,15 @@ importers:
|
|||||||
|
|
||||||
docs:
|
docs:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@vueuse/core':
|
||||||
|
specifier: ^14.1.0
|
||||||
|
version: 14.1.0(vue@3.5.24(typescript@5.9.3))
|
||||||
|
gpt-tokenizer:
|
||||||
|
specifier: ^3.4.0
|
||||||
|
version: 3.4.0
|
||||||
|
lz-string:
|
||||||
|
specifier: ^1.5.0
|
||||||
|
version: 1.5.0
|
||||||
markdown-it-mathjax3:
|
markdown-it-mathjax3:
|
||||||
specifier: ^4.3.2
|
specifier: ^4.3.2
|
||||||
version: 4.3.2
|
version: 4.3.2
|
||||||
@@ -1442,6 +1451,11 @@ packages:
|
|||||||
'@vueuse/core@12.8.2':
|
'@vueuse/core@12.8.2':
|
||||||
resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==}
|
resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==}
|
||||||
|
|
||||||
|
'@vueuse/core@14.1.0':
|
||||||
|
resolution: {integrity: sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.5.0
|
||||||
|
|
||||||
'@vueuse/integrations@12.8.2':
|
'@vueuse/integrations@12.8.2':
|
||||||
resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==}
|
resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1486,9 +1500,17 @@ packages:
|
|||||||
'@vueuse/metadata@12.8.2':
|
'@vueuse/metadata@12.8.2':
|
||||||
resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==}
|
resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==}
|
||||||
|
|
||||||
|
'@vueuse/metadata@14.1.0':
|
||||||
|
resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==}
|
||||||
|
|
||||||
'@vueuse/shared@12.8.2':
|
'@vueuse/shared@12.8.2':
|
||||||
resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==}
|
resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==}
|
||||||
|
|
||||||
|
'@vueuse/shared@14.1.0':
|
||||||
|
resolution: {integrity: sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.5.0
|
||||||
|
|
||||||
'@xmldom/xmldom@0.9.8':
|
'@xmldom/xmldom@0.9.8':
|
||||||
resolution: {integrity: sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==}
|
resolution: {integrity: sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==}
|
||||||
engines: {node: '>=14.6'}
|
engines: {node: '>=14.6'}
|
||||||
@@ -2437,6 +2459,10 @@ packages:
|
|||||||
lru-cache@10.4.3:
|
lru-cache@10.4.3:
|
||||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||||
|
|
||||||
|
lz-string@1.5.0:
|
||||||
|
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
@@ -4829,6 +4855,13 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
|
'@vueuse/core@14.1.0(vue@3.5.24(typescript@5.9.3))':
|
||||||
|
dependencies:
|
||||||
|
'@types/web-bluetooth': 0.0.21
|
||||||
|
'@vueuse/metadata': 14.1.0
|
||||||
|
'@vueuse/shared': 14.1.0(vue@3.5.24(typescript@5.9.3))
|
||||||
|
vue: 3.5.24(typescript@5.9.3)
|
||||||
|
|
||||||
'@vueuse/integrations@12.8.2(change-case@5.4.4)(focus-trap@7.6.6)(typescript@5.9.3)':
|
'@vueuse/integrations@12.8.2(change-case@5.4.4)(focus-trap@7.6.6)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vueuse/core': 12.8.2(typescript@5.9.3)
|
'@vueuse/core': 12.8.2(typescript@5.9.3)
|
||||||
@@ -4842,12 +4875,18 @@ snapshots:
|
|||||||
|
|
||||||
'@vueuse/metadata@12.8.2': {}
|
'@vueuse/metadata@12.8.2': {}
|
||||||
|
|
||||||
|
'@vueuse/metadata@14.1.0': {}
|
||||||
|
|
||||||
'@vueuse/shared@12.8.2(typescript@5.9.3)':
|
'@vueuse/shared@12.8.2(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.5.24(typescript@5.9.3)
|
vue: 3.5.24(typescript@5.9.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
|
'@vueuse/shared@14.1.0(vue@3.5.24(typescript@5.9.3))':
|
||||||
|
dependencies:
|
||||||
|
vue: 3.5.24(typescript@5.9.3)
|
||||||
|
|
||||||
'@xmldom/xmldom@0.9.8': {}
|
'@xmldom/xmldom@0.9.8': {}
|
||||||
|
|
||||||
acorn-jsx@5.3.2(acorn@8.15.0):
|
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||||
@@ -5888,6 +5927,8 @@ snapshots:
|
|||||||
|
|
||||||
lru-cache@10.4.3: {}
|
lru-cache@10.4.3: {}
|
||||||
|
|
||||||
|
lz-string@1.5.0: {}
|
||||||
|
|
||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|||||||
Reference in New Issue
Block a user