feat: add single tool schema rendering
This commit is contained in:
@@ -0,0 +1,134 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { RotateCcw } from "@lucide/svelte";
|
||||||
|
import type { ToolSchema } from "$lib/registry-schema";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
schema: ToolSchema<Record<string, unknown>>;
|
||||||
|
values: Record<string, unknown>;
|
||||||
|
onchange: (id: string, value: unknown) => void;
|
||||||
|
onreset: () => void;
|
||||||
|
}
|
||||||
|
let { schema, values, onchange, onreset }: Props = $props();
|
||||||
|
|
||||||
|
function labelOf(id: string): string {
|
||||||
|
return id
|
||||||
|
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
||||||
|
.replace(/[_-]+/g, " ")
|
||||||
|
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each Object.entries(schema.fields) as [id, field] (id)}
|
||||||
|
{#if field.spec.kind === "number" || field.spec.kind === "slider"}
|
||||||
|
<label class="control">
|
||||||
|
<span>
|
||||||
|
{labelOf(id)}
|
||||||
|
<output>{String(values[id] ?? field.spec.default)}</output>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={field.spec.min ?? 0}
|
||||||
|
max={field.spec.max ?? 100}
|
||||||
|
step={field.spec.step ?? 1}
|
||||||
|
value={Number(values[id] ?? field.spec.default)}
|
||||||
|
oninput={(e) =>
|
||||||
|
onchange(id, Number((e.target as HTMLInputElement).value))}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
{:else if field.spec.kind === "color"}
|
||||||
|
<label class="control color-control">
|
||||||
|
<span>{labelOf(id)}</span>
|
||||||
|
<span class="color-row">
|
||||||
|
<span
|
||||||
|
class="swatch"
|
||||||
|
style="background:{String(values[id] ?? field.spec.default)}"
|
||||||
|
></span>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
value={String(values[id] ?? field.spec.default)}
|
||||||
|
oninput={(e) => onchange(id, (e.target as HTMLInputElement).value)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<div class="panel-foot">
|
||||||
|
<button class="reset-btn" onclick={onreset}>
|
||||||
|
<RotateCcw size={14} /> Reset
|
||||||
|
</button>
|
||||||
|
<span class="auto-note"><i></i> updates automatically</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.control {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-m);
|
||||||
|
margin-bottom: var(--space-xl);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
letter-spacing: var(--space-text-m);
|
||||||
|
}
|
||||||
|
.control > span {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.control output {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
input[type="range"] {
|
||||||
|
width: 100%;
|
||||||
|
accent-color: var(--color-main);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
.color-control > span:first-child {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.color-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-m);
|
||||||
|
}
|
||||||
|
.swatch {
|
||||||
|
width: var(--space-xxl);
|
||||||
|
height: var(--space-xxl);
|
||||||
|
border-radius: var(--radius-s);
|
||||||
|
border: var(--size-border) solid var(--color-border);
|
||||||
|
}
|
||||||
|
input[type="color"] {
|
||||||
|
width: 100%;
|
||||||
|
height: var(--space-xxl);
|
||||||
|
border: var(--size-border) solid var(--color-border);
|
||||||
|
border-radius: var(--radius-s);
|
||||||
|
background: var(--color-background);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.panel-foot {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-m);
|
||||||
|
margin-top: var(--space-xl);
|
||||||
|
padding-top: var(--space-l);
|
||||||
|
border-top: var(--size-border) solid var(--color-border);
|
||||||
|
}
|
||||||
|
.reset-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-m);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.reset-btn:hover {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
.auto-note i {
|
||||||
|
width: var(--size-border-thick);
|
||||||
|
height: var(--size-border-thick);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--color-success);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Check, Upload } from "@lucide/svelte";
|
||||||
|
import { toDataUrl } from "$lib/core/io";
|
||||||
|
import type { PixelImage } from "$lib/core/types";
|
||||||
|
import MetaList from "$lib/components/kit/MetaList.svelte";
|
||||||
|
import DownloadButton from "$lib/components/kit/ui/DownloadButton.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
source: PixelImage | null;
|
||||||
|
result: PixelImage | null;
|
||||||
|
running: boolean;
|
||||||
|
error: string;
|
||||||
|
onupload: (file: File) => void;
|
||||||
|
ondownload: () => void;
|
||||||
|
}
|
||||||
|
let { source, result, running, error, onupload, ondownload }: Props =
|
||||||
|
$props();
|
||||||
|
|
||||||
|
let sourceUrl = $derived(source ? toDataUrl(source) : null);
|
||||||
|
let resultUrl = $derived(result ? toDataUrl(result) : null);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="panel-head">
|
||||||
|
<span class="label">SOURCE / RESULT</span>
|
||||||
|
<div class="head-actions">
|
||||||
|
<label class="upload">
|
||||||
|
<Upload size={14} /> Open image
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onchange={(e) => {
|
||||||
|
const f = (e.target as HTMLInputElement).files?.[0];
|
||||||
|
if (f) onupload(f);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<DownloadButton label="Download result" onclick={ondownload} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if error}
|
||||||
|
<p class="error" role="alert">{error}</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="pair">
|
||||||
|
<figure class="tile">
|
||||||
|
<figcaption><span>SOURCE</span></figcaption>
|
||||||
|
<div class="canvas">
|
||||||
|
{#if sourceUrl}
|
||||||
|
<img src={sourceUrl} alt="source" />
|
||||||
|
{:else}
|
||||||
|
<span class="empty">choose an image</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</figure>
|
||||||
|
<figure class="tile">
|
||||||
|
<figcaption><span>RESULT {running ? "…" : ""}</span></figcaption>
|
||||||
|
<div class="canvas checker">
|
||||||
|
{#if resultUrl}
|
||||||
|
<img src={resultUrl} alt="result" />
|
||||||
|
{:else if running}
|
||||||
|
<Check size={22} />
|
||||||
|
{:else}
|
||||||
|
<span class="empty">no result yet</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="meta">
|
||||||
|
<MetaList
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
caption: "SOURCE",
|
||||||
|
value: source ? `${source.width} × ${source.height} px` : "—",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
caption: "RESULT",
|
||||||
|
value: result ? `${result.width} × ${result.height} px` : "—",
|
||||||
|
},
|
||||||
|
{ caption: "FORMAT", value: "PNG" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.panel-head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: var(--space-m);
|
||||||
|
padding-bottom: var(--space-l);
|
||||||
|
border-bottom: var(--size-border) solid var(--color-border);
|
||||||
|
margin-bottom: var(--space-xl);
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
letter-spacing: var(--space-text-l);
|
||||||
|
color: var(--color-main);
|
||||||
|
}
|
||||||
|
.head-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-l);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.upload {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-m);
|
||||||
|
padding: var(--space-m) var(--space-l);
|
||||||
|
border: var(--size-border) solid var(--color-border);
|
||||||
|
border-radius: var(--radius-m);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.upload input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
margin: 0 0 var(--space-l);
|
||||||
|
color: var(--color-danger);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
}
|
||||||
|
.pair {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: var(--space-l);
|
||||||
|
}
|
||||||
|
.tile figcaption {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-m);
|
||||||
|
margin-bottom: var(--space-m);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
.canvas {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: clamp(var(--space-brand), 30vh, 60vh);
|
||||||
|
border: var(--size-border) solid var(--color-border);
|
||||||
|
border-radius: var(--radius-m);
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--color-background-muted);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
.canvas img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.canvas.checker {
|
||||||
|
background: repeating-conic-gradient(
|
||||||
|
var(--color-checker-main) 0 25%,
|
||||||
|
var(--color-checker-alt) 0 50%
|
||||||
|
)
|
||||||
|
50% / 28px 28px;
|
||||||
|
}
|
||||||
|
.empty {
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
margin-top: var(--space-l);
|
||||||
|
}
|
||||||
|
@media (max-width: var(--bp-tablet)) {
|
||||||
|
.pair {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { decodeFile, encode } from "$lib/core/io";
|
||||||
|
import { debounce } from "$lib/core/debounce";
|
||||||
|
import type { PixelImage } from "$lib/core/types";
|
||||||
|
import {
|
||||||
|
defaultSchemaParams,
|
||||||
|
sanitizeSchemaParams,
|
||||||
|
type ToolSchema,
|
||||||
|
} from "$lib/registry-schema";
|
||||||
|
import { executeStep } from "$lib/tools/executor";
|
||||||
|
import type { ToolEntry } from "$lib/registry";
|
||||||
|
import SchemaFields from "$lib/components/kit/SchemaFields.svelte";
|
||||||
|
import SchemaPreview from "$lib/components/kit/SchemaPreview.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
tool: ToolEntry;
|
||||||
|
}
|
||||||
|
let { tool }: Props = $props();
|
||||||
|
|
||||||
|
const schema = $derived(
|
||||||
|
tool.schema as ToolSchema<Record<string, unknown>> | undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
let values = $state<Record<string, unknown>>({});
|
||||||
|
let source = $state<PixelImage | null>(null);
|
||||||
|
let result = $state<PixelImage | null>(null);
|
||||||
|
let running = $state(false);
|
||||||
|
let error = $state("");
|
||||||
|
let started = $state(false);
|
||||||
|
|
||||||
|
const debouncedRun = debounce(() => run(), 200);
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
if (!schema || started) return;
|
||||||
|
started = true;
|
||||||
|
values = defaultSchemaParams(schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValue(id: string, value: unknown) {
|
||||||
|
values = { ...values, [id]: value };
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
values = schema ? defaultSchemaParams(schema) : {};
|
||||||
|
result = null;
|
||||||
|
error = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleFile(file: File) {
|
||||||
|
error = "";
|
||||||
|
try {
|
||||||
|
source = await decodeFile(file);
|
||||||
|
} catch (e) {
|
||||||
|
error = errorText(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
if (!schema || !tool.run || !source) return;
|
||||||
|
running = true;
|
||||||
|
error = "";
|
||||||
|
const params = sanitizeSchemaParams(schema, values);
|
||||||
|
try {
|
||||||
|
result = await executeStep(tool, source, params);
|
||||||
|
} catch (e) {
|
||||||
|
error = errorText(e);
|
||||||
|
} finally {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function download() {
|
||||||
|
if (!result) return;
|
||||||
|
const blob = await encode(result, "image/png");
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = "with-border.png";
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!source || !started) return;
|
||||||
|
void values;
|
||||||
|
debouncedRun();
|
||||||
|
return () => debouncedRun.cancel();
|
||||||
|
});
|
||||||
|
|
||||||
|
function errorText(e: unknown): string {
|
||||||
|
return e instanceof Error ? e.message : String(e);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if schema}
|
||||||
|
<div class="schema-tool">
|
||||||
|
<header class="header">
|
||||||
|
<div class="title-block">
|
||||||
|
<span class="eyebrow">PNG PROCESSING <span>/</span> SINGLE TOOL</span>
|
||||||
|
<h1>{tool.title}</h1>
|
||||||
|
<p class="lede">{tool.description}</p>
|
||||||
|
</div>
|
||||||
|
<span class="status"><i></i> LIVE PREVIEW</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="workspace">
|
||||||
|
<section class="panel settings">
|
||||||
|
<div class="panel-head">
|
||||||
|
<span class="label">TOOL SETTINGS</span>
|
||||||
|
<strong>Configure output</strong>
|
||||||
|
</div>
|
||||||
|
<SchemaFields {schema} {values} onchange={setValue} onreset={reset} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="panel">
|
||||||
|
<SchemaPreview
|
||||||
|
{source}
|
||||||
|
{result}
|
||||||
|
{running}
|
||||||
|
{error}
|
||||||
|
onupload={handleFile}
|
||||||
|
ondownload={download}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p class="no-schema">This tool has no schema yet.</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.schema-tool {
|
||||||
|
padding: calc(var(--space-xxxl) + var(--space-l))
|
||||||
|
clamp(var(--space-m), 4vw, var(--space-xxxl));
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-l);
|
||||||
|
margin-bottom: var(--space-xxxl);
|
||||||
|
}
|
||||||
|
.title-block h1 {
|
||||||
|
margin: var(--space-s) 0 0;
|
||||||
|
font-size: clamp(var(--font-size-xl), 4vw, var(--font-size-2xl));
|
||||||
|
line-height: 1.1;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
.eyebrow,
|
||||||
|
.label,
|
||||||
|
.status {
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
letter-spacing: var(--space-text-l);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
.eyebrow span,
|
||||||
|
.label {
|
||||||
|
color: var(--color-main);
|
||||||
|
}
|
||||||
|
.lede {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: var(--space-m) 0 0;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-m);
|
||||||
|
align-self: flex-start;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.status i {
|
||||||
|
width: var(--size-border-thick);
|
||||||
|
height: var(--size-border-thick);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--color-success);
|
||||||
|
}
|
||||||
|
.workspace {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(260px, 1fr) minmax(0, 2fr);
|
||||||
|
gap: var(--space-xxl);
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
.panel {
|
||||||
|
border: var(--size-border) solid var(--color-border);
|
||||||
|
border-radius: var(--radius-m);
|
||||||
|
background: var(--color-panel);
|
||||||
|
padding: var(--space-xl);
|
||||||
|
}
|
||||||
|
.panel-head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: var(--space-m);
|
||||||
|
padding-bottom: var(--space-l);
|
||||||
|
border-bottom: var(--size-border) solid var(--color-border);
|
||||||
|
margin-bottom: var(--space-xl);
|
||||||
|
}
|
||||||
|
.panel-head strong {
|
||||||
|
font-size: var(--font-size-l);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
.no-schema {
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
}
|
||||||
|
@media (max-width: var(--bp-tablet)) {
|
||||||
|
.workspace {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export function debounce(
|
||||||
|
fn: () => void,
|
||||||
|
ms: number,
|
||||||
|
): { (): void; cancel: () => void } {
|
||||||
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
|
||||||
|
const debounced = () => {
|
||||||
|
if (timer !== undefined) clearTimeout(timer);
|
||||||
|
timer = setTimeout(() => fn(), ms);
|
||||||
|
};
|
||||||
|
|
||||||
|
debounced.cancel = () => {
|
||||||
|
if (timer !== undefined) clearTimeout(timer);
|
||||||
|
};
|
||||||
|
|
||||||
|
return debounced;
|
||||||
|
}
|
||||||
@@ -144,9 +144,15 @@
|
|||||||
--font-size-s: 12px;
|
--font-size-s: 12px;
|
||||||
--font-size-m: 14px;
|
--font-size-m: 14px;
|
||||||
--font-size-l: 16px;
|
--font-size-l: 16px;
|
||||||
|
--font-size-xl: 24px;
|
||||||
|
--font-size-2xl: 32px;
|
||||||
|
|
||||||
--radius-s: 0px;
|
--radius-s: 0px;
|
||||||
--radius-m: 4px;
|
--radius-m: 4px;
|
||||||
|
|
||||||
|
--bp-mobile: 640px;
|
||||||
|
--bp-tablet: 800px;
|
||||||
|
--bp-desktop: 1100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getTool } from "$lib/registry";
|
import { getTool } from "$lib/registry";
|
||||||
import ToolView from "$lib/components/kit/ToolView.svelte";
|
import ToolView from "$lib/components/kit/ToolView.svelte";
|
||||||
|
import SchemaToolView from "$lib/components/kit/SchemaToolView.svelte";
|
||||||
import EmptyState from "$lib/components/kit/EmptyState.svelte";
|
import EmptyState from "$lib/components/kit/EmptyState.svelte";
|
||||||
import { SlidersHorizontal as ToolIcon } from "@lucide/svelte";
|
import { SlidersHorizontal as ToolIcon } from "@lucide/svelte";
|
||||||
|
|
||||||
@@ -25,6 +26,8 @@
|
|||||||
icon={ToolIcon}
|
icon={ToolIcon}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{:else if tool.schema}
|
||||||
|
<SchemaToolView {tool} />
|
||||||
{:else}
|
{:else}
|
||||||
<ToolView {tool} />
|
<ToolView {tool} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user