saniti-vue 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +19 -0
  2. package/package.json +57 -0
  3. package/src/components/AgentConfig.vue +518 -0
  4. package/src/components/ArrowLink.vue +117 -0
  5. package/src/components/Avatar.vue +98 -0
  6. package/src/components/AvatarGroup.vue +58 -0
  7. package/src/components/Badge.vue +86 -0
  8. package/src/components/Banner.vue +225 -0
  9. package/src/components/Breadcrumbs.vue +190 -0
  10. package/src/components/Button.vue +242 -0
  11. package/src/components/Callout.vue +162 -0
  12. package/src/components/Card.vue +167 -0
  13. package/src/components/Checkbox.vue +147 -0
  14. package/src/components/CodeBlock.vue +221 -0
  15. package/src/components/Combobox.vue +387 -0
  16. package/src/components/CommandPalette.vue +539 -0
  17. package/src/components/ContextMenu.vue +113 -0
  18. package/src/components/CopiedToss.vue +25 -0
  19. package/src/components/DatePicker.vue +144 -0
  20. package/src/components/DateRangePicker.vue +148 -0
  21. package/src/components/DiffViewer.vue +379 -0
  22. package/src/components/DistributionBar.vue +134 -0
  23. package/src/components/Divider.vue +98 -0
  24. package/src/components/DropdownMenu.vue +137 -0
  25. package/src/components/EmptyState.vue +107 -0
  26. package/src/components/Field.vue +135 -0
  27. package/src/components/Glide.vue +20 -0
  28. package/src/components/Input.vue +227 -0
  29. package/src/components/Kbd.vue +69 -0
  30. package/src/components/KeyValue.vue +203 -0
  31. package/src/components/MenuBar.vue +221 -0
  32. package/src/components/Modal.vue +179 -0
  33. package/src/components/MultiSelect.vue +593 -0
  34. package/src/components/NumberInput.vue +390 -0
  35. package/src/components/Odometer.vue +131 -0
  36. package/src/components/PageHeader.vue +140 -0
  37. package/src/components/Pagination.vue +175 -0
  38. package/src/components/Popover.vue +116 -0
  39. package/src/components/ProgressBar.vue +113 -0
  40. package/src/components/RadioGroup.vue +161 -0
  41. package/src/components/ScrollArea.vue +132 -0
  42. package/src/components/Select.vue +340 -0
  43. package/src/components/SideNav.vue +347 -0
  44. package/src/components/Skeleton.vue +81 -0
  45. package/src/components/Slider.vue +323 -0
  46. package/src/components/Spinner.vue +32 -0
  47. package/src/components/Switch.vue +146 -0
  48. package/src/components/Table.vue +278 -0
  49. package/src/components/Tabs.vue +252 -0
  50. package/src/components/TagInput.vue +496 -0
  51. package/src/components/Textarea.vue +200 -0
  52. package/src/components/Toaster.vue +196 -0
  53. package/src/components/ToggleGroup.vue +211 -0
  54. package/src/components/Tooltip.vue +87 -0
  55. package/src/components/Tree.vue +284 -0
  56. package/src/components/TreeBranch.vue +97 -0
  57. package/src/components/calendar.css +354 -0
  58. package/src/composables/toast.js +57 -0
  59. package/src/composables/tooltipChain.js +12 -0
  60. package/src/composables/useClickOutside.js +11 -0
  61. package/src/composables/useDateValue.js +31 -0
  62. package/src/composables/useGlide.js +77 -0
  63. package/src/composables/useHeldLoading.js +34 -0
  64. package/src/composables/useListKeys.js +12 -0
  65. package/src/composables/usePolarity.js +12 -0
  66. package/src/histoire-setup.js +18 -0
  67. package/src/icons.js +89 -0
  68. package/src/index.js +60 -0
  69. package/src/style.css +753 -0
  70. package/src/syntax.js +63 -0
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # saniti-vue
2
+
3
+ A Vue 3 component library. Components ship as ESM source and are compiled by the consumer's Vite/Vue toolchain.
4
+
5
+ ## usage
6
+
7
+ ```js
8
+ import "saniti-vue/style.css"
9
+ import { Button } from "saniti-vue"
10
+ ```
11
+
12
+ ## development
13
+
14
+ ```sh
15
+ npm install
16
+ npm run dev
17
+ ```
18
+
19
+ `npm run dev` opens the Histoire showcase. Stories live in `stories/*.story.vue`, components in `src/components/`, tokens in `src/style.css`.
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "saniti-vue",
3
+ "version": "0.1.0",
4
+ "description": "A Vue 3 component library.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/nvms/saniti-vue.git"
10
+ },
11
+ "main": "./src/index.js",
12
+ "module": "./src/index.js",
13
+ "exports": {
14
+ ".": "./src/index.js",
15
+ "./style.css": "./src/style.css"
16
+ },
17
+ "files": [
18
+ "src"
19
+ ],
20
+ "sideEffects": [
21
+ "*.css",
22
+ "*.vue",
23
+ "src/icons.js",
24
+ "stories/icons.js"
25
+ ],
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "scripts": {
30
+ "predev": "node scripts/gen-icons.mjs",
31
+ "dev": "histoire dev",
32
+ "prebuild": "node scripts/gen-icons.mjs",
33
+ "build": "histoire build",
34
+ "preview": "histoire preview",
35
+ "gen:icons": "node scripts/gen-icons.mjs"
36
+ },
37
+ "peerDependencies": {
38
+ "vue": "^3.4"
39
+ },
40
+ "dependencies": {
41
+ "@floating-ui/vue": "^1.1.6",
42
+ "@iconify/vue": "^5.0.1",
43
+ "@internationalized/date": "^3.12.3",
44
+ "diff": "^9.0.0",
45
+ "prismjs": "^1.30.0",
46
+ "reka-ui": "^2.9.7"
47
+ },
48
+ "devDependencies": {
49
+ "@histoire/plugin-vue": "^0.17.17",
50
+ "@iconify-json/lucide": "^1.2.116",
51
+ "@iconify/utils": "^3.1.4",
52
+ "@vitejs/plugin-vue": "^5.2.1",
53
+ "histoire": "^0.17.17",
54
+ "vite": "^5.4.0",
55
+ "vue": "^3.5.35"
56
+ }
57
+ }
@@ -0,0 +1,518 @@
1
+ <script>
2
+ const clone = (v) => JSON.parse(JSON.stringify(v))
3
+
4
+ export const defaults = () => ({
5
+ name: "",
6
+ handle: "",
7
+ description: "",
8
+ enabled: true,
9
+ model: { id: null, temperature: 0.7, effort: "auto", compactAt: 80, compaction: "" },
10
+ instructions: "",
11
+ tools: [],
12
+ toolChoice: "auto",
13
+ parallelTools: true,
14
+ maxToolCalls: 10,
15
+ delegates: [],
16
+ output: { type: "text", schema: "" },
17
+ limits: { maxTurns: 20, timeout: 120, budget: 1 },
18
+ })
19
+ </script>
20
+
21
+ <script setup>
22
+ import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue"
23
+ import Input from "./Input.vue"
24
+ import Textarea from "./Textarea.vue"
25
+ import Combobox from "./Combobox.vue"
26
+ import MultiSelect from "./MultiSelect.vue"
27
+ import NumberInput from "./NumberInput.vue"
28
+ import Slider from "./Slider.vue"
29
+ import Switch from "./Switch.vue"
30
+ import Checkbox from "./Checkbox.vue"
31
+ import RadioGroup from "./RadioGroup.vue"
32
+ import Field from "./Field.vue"
33
+ import Badge from "./Badge.vue"
34
+ import KeyValue from "./KeyValue.vue"
35
+ import Divider from "./Divider.vue"
36
+
37
+ const props = defineProps({
38
+ modelValue: { type: Object, required: true },
39
+ catalog: { type: Object, default: () => ({ models: [], tools: [], agents: [] }) },
40
+ })
41
+ const emit = defineEmits(["update:modelValue"])
42
+
43
+
44
+ const agent = reactive({ ...defaults(), ...clone(props.modelValue) })
45
+ let serialized = JSON.stringify(agent)
46
+
47
+ watch(
48
+ agent,
49
+ (v) => {
50
+ serialized = JSON.stringify(v)
51
+ emit("update:modelValue", clone(v))
52
+ },
53
+ { deep: true },
54
+ )
55
+ watch(
56
+ () => props.modelValue,
57
+ (v) => {
58
+ if (JSON.stringify(v) === serialized) return
59
+ Object.assign(agent, defaults(), clone(v))
60
+ },
61
+ { deep: true },
62
+ )
63
+
64
+ const modelOptions = computed(() => {
65
+ const groups = new Map()
66
+ for (const m of props.catalog.models) {
67
+ if (!groups.has(m.provider)) groups.set(m.provider, [])
68
+ groups.get(m.provider).push({ value: m.id, label: m.label ?? m.id, meta: m.id })
69
+ }
70
+ return [...groups].map(([label, options]) => ({ label, options }))
71
+ })
72
+ const selectedModel = computed(() => props.catalog.models.find((m) => m.id === agent.model.id) ?? null)
73
+ const modelMeta = computed(() =>
74
+ selectedModel.value
75
+ ? [
76
+ { label: "Context", value: selectedModel.value.context, mono: true },
77
+ { label: "Input", value: selectedModel.value.input, mono: true },
78
+ { label: "Output", value: selectedModel.value.output, mono: true },
79
+ ]
80
+ : [],
81
+ )
82
+ const effortLabels = { none: "Off", minimal: "Minimal", low: "Low", medium: "Medium", high: "High", xhigh: "Extra high", max: "Max" }
83
+ const effortOptions = computed(() => [
84
+ { value: "auto", label: "Auto" },
85
+ ...(selectedModel.value?.efforts ?? ["low", "medium", "high"]).map((e) => ({ value: e, label: effortLabels[e] ?? e })),
86
+ ])
87
+ watch(effortOptions, (opts) => {
88
+ if (!opts.some((o) => o.value === agent.model.effort)) agent.model.effort = "auto"
89
+ })
90
+ const compactTokens = computed(() => {
91
+ const window = selectedModel.value?.contextTokens
92
+ if (!window) return ""
93
+ const n = Math.round((window * agent.model.compactAt) / 100)
94
+ return n >= 1_000_000 ? `${(n / 1_000_000).toFixed(n % 1_000_000 ? 1 : 0)}M tokens` : `${Math.round(n / 1000)}K tokens`
95
+ })
96
+
97
+ const chars = computed(() => agent.instructions.length)
98
+ const tokens = computed(() => Math.round(chars.value / 4))
99
+ const variables = computed(() => [...new Set([...agent.instructions.matchAll(/\{\{\s*([\w.]+)\s*\}\}/g)].map((m) => m[1]))])
100
+ const fmt = (n) => n.toLocaleString("en-US")
101
+ const braces = (v) => "{{" + v + "}}"
102
+
103
+ const toolGroups = computed(() => {
104
+ const groups = new Map()
105
+ for (const t of props.catalog.tools) {
106
+ if (!groups.has(t.source)) groups.set(t.source, [])
107
+ groups.get(t.source).push(t)
108
+ }
109
+ return [...groups].map(([source, tools]) => ({ source, tools }))
110
+ })
111
+ const toolEntry = (id) => agent.tools.find((t) => t.id === id)
112
+ const isEnabled = (id) => Boolean(toolEntry(id))
113
+ const setEnabled = (id, on) => {
114
+ if (on && !isEnabled(id)) agent.tools.push({ id, approval: false })
115
+ if (!on) agent.tools = agent.tools.filter((t) => t.id !== id)
116
+ }
117
+ const enabledIn = (group) => group.tools.filter((t) => isEnabled(t.id)).length
118
+ const enabledCount = computed(() => agent.tools.length)
119
+
120
+ const agentOptions = computed(() =>
121
+ props.catalog.agents.filter((a) => a.id !== props.modelValue.id).map((a) => ({ value: a.id, label: a.name })),
122
+ )
123
+
124
+ const sections = computed(() => [
125
+ { key: "identity", title: "Identity", state: agent.enabled ? "enabled" : "paused" },
126
+ { key: "model", title: "Model", state: selectedModel.value?.label ?? "none" },
127
+ { key: "instructions", title: "Instructions", state: tokens.value ? `~${fmt(tokens.value)} tok` : "empty" },
128
+ { key: "tools", title: "Tools", state: String(enabledCount.value) },
129
+ { key: "delegation", title: "Delegation", state: String(agent.delegates.length) },
130
+ { key: "output", title: "Output", state: agent.output.type },
131
+ { key: "limits", title: "Limits", state: "" },
132
+ ])
133
+
134
+ const root = ref(null)
135
+ const active = ref("identity")
136
+ const sectionEls = new Map()
137
+ const bindSection = (key) => (el) => (el ? sectionEls.set(key, el) : sectionEls.delete(key))
138
+ let pinned = false
139
+ let pinTimer = null
140
+ const unpin = () => {
141
+ pinned = false
142
+ clearTimeout(pinTimer)
143
+ }
144
+ const jump = (key) => {
145
+ active.value = key
146
+ pinned = true
147
+ clearTimeout(pinTimer)
148
+ pinTimer = setTimeout(unpin, 1000)
149
+ sectionEls.get(key)?.scrollIntoView({ behavior: "smooth", block: "start" })
150
+ }
151
+
152
+ let observer = null
153
+ onMounted(() => {
154
+ observer = new IntersectionObserver(
155
+ (entries) => {
156
+ if (pinned) return
157
+ const hit = entries.filter((e) => e.isIntersecting).sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0]
158
+ if (hit) active.value = hit.target.dataset.section
159
+ },
160
+ { rootMargin: "-20% 0px -70% 0px" },
161
+ )
162
+ for (const el of sectionEls.values()) observer.observe(el)
163
+ document.addEventListener("scrollend", unpin, true)
164
+ })
165
+ onBeforeUnmount(() => {
166
+ observer?.disconnect()
167
+ document.removeEventListener("scrollend", unpin, true)
168
+ clearTimeout(pinTimer)
169
+ })
170
+
171
+ const toolChoiceOptions = [
172
+ { value: "auto", label: "Auto" },
173
+ { value: "required", label: "Required" },
174
+ { value: "none", label: "None" },
175
+ ]
176
+ const outputOptions = [
177
+ { value: "text", label: "Text" },
178
+ { value: "json", label: "JSON" },
179
+ ]
180
+ </script>
181
+
182
+ <template>
183
+ <div ref="root" class="sv-agent">
184
+ <nav class="sv-agent__rail">
185
+ <button
186
+ v-for="(s, i) in sections"
187
+ :key="s.key"
188
+ type="button"
189
+ class="sv-agent__rail-item"
190
+ :class="{ 'is-active': active === s.key }"
191
+ @click="jump(s.key)"
192
+ >
193
+ <span class="sv-agent__rail-index">{{ String(i + 1).padStart(2, "0") }}</span>
194
+ <span class="sv-agent__rail-title">{{ s.title }}</span>
195
+ <span v-if="s.state" class="sv-agent__rail-state">{{ s.state }}</span>
196
+ </button>
197
+ </nav>
198
+
199
+ <div class="sv-agent__body">
200
+ <section :ref="bindSection('identity')" data-section="identity" class="sv-agent__section">
201
+ <Divider :label="`01 Identity`" align="start" spacing="none" class="sv-agent__divider" />
202
+ <div class="sv-agent__grid">
203
+ <Switch v-model="agent.enabled" size="sm" class="sv-agent__toggle">Enabled</Switch>
204
+ <Field label="Name" size="md" v-slot="{ id }">
205
+ <Input :id="id" v-model="agent.name" size="md" placeholder="Triage" />
206
+ </Field>
207
+ <Field label="Handle" size="md" v-slot="{ id }">
208
+ <Input :id="id" v-model="agent.handle" size="md" icon="lucide:at-sign" placeholder="triage" class="sv-agent__mono" />
209
+ </Field>
210
+ <Field label="Description" size="md" class="sv-agent__span" v-slot="{ id }">
211
+ <Textarea :id="id" v-model="agent.description" size="md" :rows="2" :max-rows="4" placeholder="What this agent is for" />
212
+ </Field>
213
+ </div>
214
+ </section>
215
+
216
+ <section :ref="bindSection('model')" data-section="model" class="sv-agent__section">
217
+ <Divider :label="`02 Model`" align="start" spacing="none" class="sv-agent__divider" />
218
+ <div class="sv-agent__grid">
219
+ <Field label="Model" size="md" class="sv-agent__span" v-slot="{ id }">
220
+ <Combobox :id="id" v-model="agent.model.id" :options="modelOptions" size="md" placeholder="Search models" :clearable="false" />
221
+ </Field>
222
+ <KeyValue v-if="modelMeta.length" :items="modelMeta" layout="stack" :columns="3" compact :dividers="false" class="sv-agent__span" />
223
+ <Field label="Temperature" size="md" v-slot="{ id }">
224
+ <Slider :id="id" v-model="agent.model.temperature" :min="0" :max="1" :step="0.05" show-value :format="(v) => v.toFixed(2)" :marks="[0, 0.5, 1]" />
225
+ </Field>
226
+ <Field label="Reasoning" hint="How much the model thinks before answering; auto lets it decide per request" size="md" class="sv-agent__span">
227
+ <RadioGroup v-model="agent.model.effort" :options="effortOptions" orientation="horizontal" size="sm" />
228
+ </Field>
229
+ <Field label="Compact at" hint="Share of the context window at which the thread is summarised before the next turn" size="md" class="sv-agent__span">
230
+ <template v-if="compactTokens" #aside>{{ compactTokens }}</template>
231
+ <template #default="{ id }">
232
+ <Slider :id="id" v-model="agent.model.compactAt" :min="50" :max="95" :step="5" show-value :format="(v) => `${v}%`" :marks="[50, 75, 95]" />
233
+ </template>
234
+ </Field>
235
+ <Field label="Compaction instructions" size="md" class="sv-agent__span" v-slot="{ id }">
236
+ <Textarea :id="id" v-model="agent.model.compaction" size="md" :rows="4" :max-rows="12" placeholder="Keep every ticket id and any commitment made to the customer" class="sv-agent__prompt" />
237
+ </Field>
238
+ </div>
239
+ </section>
240
+
241
+ <section :ref="bindSection('instructions')" data-section="instructions" class="sv-agent__section">
242
+ <Divider :label="`03 Instructions`" align="start" spacing="none" class="sv-agent__divider" />
243
+ <Textarea v-model="agent.instructions" size="md" :rows="10" :max-rows="28" placeholder="You are..." class="sv-agent__prompt" />
244
+ <div class="sv-agent__prompt-foot">
245
+ <div class="sv-agent__vars">
246
+ <Badge v-for="v in variables" :key="v" mono class="sv-agent__var">{{ braces(v) }}</Badge>
247
+ </div>
248
+ <span class="sv-agent__count">{{ fmt(chars) }} chars, ~{{ fmt(tokens) }} tokens</span>
249
+ </div>
250
+ </section>
251
+
252
+ <section :ref="bindSection('tools')" data-section="tools" class="sv-agent__section">
253
+ <Divider :label="`04 Tools`" align="start" spacing="none" class="sv-agent__divider" />
254
+ <div v-for="group in toolGroups" :key="group.source" class="sv-agent__tool-group">
255
+ <div class="sv-agent__tool-source">
256
+ <span>{{ group.source }}</span>
257
+ <span class="sv-agent__count">{{ enabledIn(group) }} of {{ group.tools.length }}</span>
258
+ <span v-if="enabledIn(group)" class="sv-agent__tool-source-end">Approval</span>
259
+ </div>
260
+ <div
261
+ v-for="tool in group.tools"
262
+ :key="tool.id"
263
+ class="sv-agent__tool"
264
+ :class="{ 'is-on': isEnabled(tool.id) }"
265
+ >
266
+ <Checkbox :model-value="isEnabled(tool.id)" size="sm" @update:model-value="setEnabled(tool.id, $event)" class="sv-agent__tool-check">
267
+ <span class="sv-agent__tool-name">{{ tool.name }}</span>
268
+ <span class="sv-agent__tool-id">{{ tool.id }}</span>
269
+ </Checkbox>
270
+ <span class="sv-agent__tool-desc">{{ tool.description }}</span>
271
+ <Switch v-if="isEnabled(tool.id)" v-model="toolEntry(tool.id).approval" size="sm" :aria-label="`Approval for ${tool.name}`" class="sv-agent__tool-approval" />
272
+ </div>
273
+ </div>
274
+ <div class="sv-agent__grid sv-agent__grid--tight">
275
+ <Field label="Tool choice" size="md">
276
+ <RadioGroup v-model="agent.toolChoice" :options="toolChoiceOptions" orientation="horizontal" size="sm" />
277
+ </Field>
278
+ <Field label="Max tool calls per turn" size="md" v-slot="{ id }">
279
+ <NumberInput :id="id" v-model="agent.maxToolCalls" size="md" :min="1" :max="100" />
280
+ </Field>
281
+ <Switch v-model="agent.parallelTools" size="sm" class="sv-agent__toggle">Parallel tool calls</Switch>
282
+ </div>
283
+ </section>
284
+
285
+ <section :ref="bindSection('delegation')" data-section="delegation" class="sv-agent__section">
286
+ <Divider :label="`05 Delegation`" align="start" spacing="none" class="sv-agent__divider" />
287
+ <Field label="Hands off to" size="md" v-slot="{ id }">
288
+ <MultiSelect :id="id" v-model="agent.delegates" :options="agentOptions" size="md" placeholder="Agents" />
289
+ </Field>
290
+ </section>
291
+
292
+ <section :ref="bindSection('output')" data-section="output" class="sv-agent__section">
293
+ <Divider :label="`06 Output`" align="start" spacing="none" class="sv-agent__divider" />
294
+ <div class="sv-agent__grid">
295
+ <Field label="Format" size="md">
296
+ <RadioGroup v-model="agent.output.type" :options="outputOptions" orientation="horizontal" size="sm" />
297
+ </Field>
298
+ <Field v-if="agent.output.type === 'json'" label="Schema" size="md" class="sv-agent__span" v-slot="{ id }">
299
+ <Textarea :id="id" v-model="agent.output.schema" size="md" :rows="6" :max-rows="20" placeholder='{ "type": "object" }' class="sv-agent__code" />
300
+ </Field>
301
+ </div>
302
+ </section>
303
+
304
+ <section :ref="bindSection('limits')" data-section="limits" class="sv-agent__section">
305
+ <Divider :label="`07 Limits`" align="start" spacing="none" class="sv-agent__divider" />
306
+ <div class="sv-agent__grid sv-agent__grid--3">
307
+ <Field label="Max turns" hint="Model calls in one run before the agent stops and reports" size="md" v-slot="{ id }">
308
+ <NumberInput :id="id" v-model="agent.limits.maxTurns" size="md" :min="1" :max="500" suffix="turns" />
309
+ </Field>
310
+ <Field label="Timeout" hint="Wall-clock limit for one run, tool calls included" size="md" v-slot="{ id }">
311
+ <NumberInput :id="id" v-model="agent.limits.timeout" size="md" :min="5" :max="3600" :step="5" suffix="s" />
312
+ </Field>
313
+ <Field label="Budget per run" hint="Token spend at the model's prices at which the run is cut off" size="md" v-slot="{ id }">
314
+ <NumberInput :id="id" v-model="agent.limits.budget" size="md" :min="0.05" :max="1000" :step="0.25" :precision="2" prefix="$" />
315
+ </Field>
316
+ </div>
317
+ </section>
318
+ </div>
319
+ </div>
320
+ </template>
321
+
322
+ <style scoped>
323
+ .sv-agent {
324
+ display: grid;
325
+ grid-template-columns: 220px minmax(0, 1fr);
326
+ gap: var(--sv-space-xxl);
327
+ align-items: start;
328
+ color: var(--sv-text);
329
+ }
330
+
331
+ .sv-agent__rail {
332
+ position: sticky;
333
+ top: var(--sv-space-lg);
334
+ display: flex;
335
+ flex-direction: column;
336
+ gap: var(--sv-space-xs);
337
+ }
338
+ .sv-agent__rail-item {
339
+ display: grid;
340
+ grid-template-columns: 22px minmax(0, 1fr);
341
+ grid-template-rows: auto auto;
342
+ align-items: baseline;
343
+ column-gap: var(--sv-space-xs);
344
+ row-gap: 2px;
345
+ padding: var(--sv-space-xs) var(--sv-space-sm);
346
+ border: 0;
347
+ border-radius: var(--sv-radius-app-sm);
348
+ font: inherit;
349
+ font-size: 13px;
350
+ text-align: left;
351
+ color: var(--sv-text-body);
352
+ background: none;
353
+ cursor: pointer;
354
+ transition: background-color var(--sv-dur) var(--sv-ease), color var(--sv-dur) var(--sv-ease);
355
+ }
356
+ .sv-agent__rail-item:hover {
357
+ color: var(--sv-text);
358
+ background: var(--sv-item-hover);
359
+ }
360
+ .sv-agent__rail-item.is-active {
361
+ color: var(--sv-text);
362
+ background: var(--sv-item-active);
363
+ }
364
+ .sv-agent__rail-index,
365
+ .sv-agent__count,
366
+ .sv-agent__rail-state,
367
+ .sv-agent__tool-source,
368
+ .sv-agent__tool-id {
369
+ font-family: var(--sv-font-mono);
370
+ font-size: 11px;
371
+ letter-spacing: 0.02em;
372
+ color: var(--sv-text-mute);
373
+ }
374
+ .sv-agent__rail-index {
375
+ grid-row: 1;
376
+ }
377
+ .sv-agent__rail-title {
378
+ grid-column: 2;
379
+ white-space: nowrap;
380
+ }
381
+ .sv-agent__rail-state {
382
+ grid-column: 2;
383
+ overflow: hidden;
384
+ text-overflow: ellipsis;
385
+ white-space: nowrap;
386
+ }
387
+ .sv-agent__rail-item.is-active .sv-agent__rail-index {
388
+ color: var(--sv-brand);
389
+ }
390
+
391
+ .sv-agent__body {
392
+ max-width: 720px;
393
+ min-width: 0;
394
+ }
395
+ .sv-agent__section {
396
+ display: flex;
397
+ flex-direction: column;
398
+ gap: var(--sv-space-lg);
399
+ scroll-margin-top: var(--sv-space-lg);
400
+ }
401
+ .sv-agent__section + .sv-agent__section {
402
+ margin-top: var(--sv-space-xxl);
403
+ }
404
+ .sv-agent__prompt-foot {
405
+ display: flex;
406
+ align-items: center;
407
+ justify-content: space-between;
408
+ gap: var(--sv-space-md);
409
+ min-height: 20px;
410
+ }
411
+ .sv-agent__var {
412
+ text-transform: none;
413
+ }
414
+
415
+ .sv-agent__grid {
416
+ display: grid;
417
+ grid-template-columns: repeat(2, minmax(0, 1fr));
418
+ gap: var(--sv-space-md) var(--sv-space-lg);
419
+ align-items: start;
420
+ }
421
+ .sv-agent__grid--3 {
422
+ grid-template-columns: repeat(3, minmax(0, 1fr));
423
+ }
424
+ .sv-agent__grid--tight {
425
+ padding-top: var(--sv-space-xs);
426
+ }
427
+ .sv-agent__span {
428
+ grid-column: 1 / -1;
429
+ }
430
+ .sv-agent__toggle {
431
+ grid-column: 1 / -1;
432
+ }
433
+ .sv-agent__mono :deep(input) {
434
+ font-family: var(--sv-font-mono);
435
+ font-size: 12.5px;
436
+ }
437
+
438
+ .sv-agent__prompt :deep(textarea) {
439
+ padding: var(--sv-space-sm) var(--sv-space-md);
440
+ line-height: 1.6;
441
+ }
442
+ .sv-agent__code :deep(textarea) {
443
+ font-family: var(--sv-font-mono);
444
+ font-size: 12.5px;
445
+ line-height: 1.55;
446
+ }
447
+ .sv-agent__vars {
448
+ display: flex;
449
+ flex-wrap: wrap;
450
+ gap: var(--sv-space-xs);
451
+ }
452
+
453
+ .sv-agent__tool-group {
454
+ display: flex;
455
+ flex-direction: column;
456
+ }
457
+ .sv-agent__tool-source {
458
+ display: flex;
459
+ gap: var(--sv-space-sm);
460
+ padding: 0 0 var(--sv-space-xs);
461
+ text-transform: uppercase;
462
+ letter-spacing: 0.04em;
463
+ }
464
+ .sv-agent__tool-source-end {
465
+ margin-left: auto;
466
+ }
467
+ .sv-agent__tool {
468
+ display: grid;
469
+ grid-template-columns: minmax(0, 1fr) auto;
470
+ align-items: center;
471
+ gap: 2px var(--sv-space-md);
472
+ padding: var(--sv-space-sm) 0;
473
+ border-top: 1px solid var(--sv-line);
474
+ }
475
+ .sv-agent__tool:last-child {
476
+ border-bottom: 1px solid var(--sv-line);
477
+ }
478
+ .sv-agent__tool-check {
479
+ min-width: 0;
480
+ }
481
+ .sv-agent__tool-name {
482
+ font-weight: 500;
483
+ }
484
+ .sv-agent__tool-id {
485
+ margin-left: var(--sv-space-xs);
486
+ }
487
+ .sv-agent__tool-desc {
488
+ grid-column: 1;
489
+ padding-left: calc(16px + var(--sv-space-sm));
490
+ font-size: 12.5px;
491
+ color: var(--sv-text-mute);
492
+ }
493
+ .sv-agent__tool.is-on .sv-agent__tool-desc {
494
+ color: var(--sv-text-body);
495
+ }
496
+ .sv-agent__tool-approval {
497
+ grid-column: 2;
498
+ grid-row: 1 / span 2;
499
+ }
500
+
501
+ @media (max-width: 720px) {
502
+ .sv-agent {
503
+ grid-template-columns: minmax(0, 1fr);
504
+ }
505
+ .sv-agent__rail {
506
+ position: static;
507
+ flex-direction: row;
508
+ flex-wrap: wrap;
509
+ }
510
+ .sv-agent__rail-state {
511
+ display: none;
512
+ }
513
+ .sv-agent__grid,
514
+ .sv-agent__grid--3 {
515
+ grid-template-columns: minmax(0, 1fr);
516
+ }
517
+ }
518
+ </style>