mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 21:46:35 +00:00
feat: add text core tools and views
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import MetaList from "$lib/components/kit/MetaList.svelte";
|
import MetaList from "$lib/components/kit/MetaList.svelte";
|
||||||
import DownloadButton from "$lib/components/kit/ui/DownloadButton.svelte";
|
import DownloadButton from "$lib/components/kit/ui/DownloadButton.svelte";
|
||||||
|
import SchemaTextResult from "$lib/components/kit/SchemaTextResult.svelte";
|
||||||
|
import SchemaTextSource from "$lib/components/kit/SchemaTextSource.svelte";
|
||||||
import { toDataUrl } from "$lib/core/io";
|
import { toDataUrl } from "$lib/core/io";
|
||||||
import type { PixelImage } from "$lib/core/types";
|
import type { PixelImage } from "$lib/core/types";
|
||||||
import { Check, Sparkles, Upload } from "@lucide/svelte";
|
import { Check, Sparkles, Upload } from "@lucide/svelte";
|
||||||
@@ -8,24 +10,41 @@
|
|||||||
interface Props {
|
interface Props {
|
||||||
source: PixelImage | null;
|
source: PixelImage | null;
|
||||||
result: PixelImage | null;
|
result: PixelImage | null;
|
||||||
|
textSource?: string;
|
||||||
|
textResult?: string | null;
|
||||||
|
resultKind?: "image" | "text" | "verdict";
|
||||||
running: boolean;
|
running: boolean;
|
||||||
error: string;
|
error: string;
|
||||||
isGenerator?: boolean;
|
isGenerator?: boolean;
|
||||||
onupload: (file: File) => void;
|
onupload: (file: File) => void;
|
||||||
ongenerate?: () => void;
|
ongenerate?: () => void;
|
||||||
|
ontextsource?: (text: string) => void;
|
||||||
|
onrendertext?: () => void;
|
||||||
|
oncopytext?: () => void;
|
||||||
|
ondownloadtxt?: () => void;
|
||||||
ondownload: () => void;
|
ondownload: () => void;
|
||||||
}
|
}
|
||||||
let {
|
let {
|
||||||
source,
|
source,
|
||||||
result,
|
result,
|
||||||
|
textSource = "",
|
||||||
|
textResult = null,
|
||||||
|
resultKind = "image",
|
||||||
running,
|
running,
|
||||||
error,
|
error,
|
||||||
isGenerator = false,
|
isGenerator = false,
|
||||||
onupload,
|
onupload,
|
||||||
ongenerate,
|
ongenerate,
|
||||||
|
ontextsource,
|
||||||
|
onrendertext,
|
||||||
|
oncopytext,
|
||||||
|
ondownloadtxt,
|
||||||
ondownload,
|
ondownload,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
|
const isTextInput = $derived(ontextsource !== undefined && !isGenerator);
|
||||||
|
const isImageDownload = $derived(resultKind === "image" && Boolean(result));
|
||||||
|
|
||||||
let sourceUrl = $derived(source ? toDataUrl(source) : null);
|
let sourceUrl = $derived(source ? toDataUrl(source) : null);
|
||||||
let resultUrl = $derived(result ? toDataUrl(result) : null);
|
let resultUrl = $derived(result ? toDataUrl(result) : null);
|
||||||
</script>
|
</script>
|
||||||
@@ -44,7 +63,7 @@
|
|||||||
<Sparkles size={14} />
|
<Sparkles size={14} />
|
||||||
{running ? "Generating…" : "Generate"}
|
{running ? "Generating…" : "Generate"}
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else if !isTextInput}
|
||||||
<label class="upload">
|
<label class="upload">
|
||||||
<Upload size={14} /> Open image
|
<Upload size={14} /> Open image
|
||||||
<input
|
<input
|
||||||
@@ -57,7 +76,9 @@
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
<DownloadButton label="Download result" onclick={ondownload} />
|
{#if isImageDownload}
|
||||||
|
<DownloadButton label="Download result" onclick={ondownload} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -70,7 +91,16 @@
|
|||||||
<figure class="tile">
|
<figure class="tile">
|
||||||
<figcaption><span>SOURCE</span></figcaption>
|
<figcaption><span>SOURCE</span></figcaption>
|
||||||
<div class="canvas">
|
<div class="canvas">
|
||||||
{#if sourceUrl}
|
{#if isTextInput}
|
||||||
|
<div class="text-source-wrap">
|
||||||
|
<SchemaTextSource
|
||||||
|
value={textSource}
|
||||||
|
disabled={running}
|
||||||
|
oninput={ontextsource ?? (() => {})}
|
||||||
|
onrender={onrendertext ?? (() => {})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else if sourceUrl}
|
||||||
<img src={sourceUrl} alt="source" />
|
<img src={sourceUrl} alt="source" />
|
||||||
{:else}
|
{:else}
|
||||||
<span class="empty">choose an image</span>
|
<span class="empty">choose an image</span>
|
||||||
@@ -78,10 +108,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</figure>
|
</figure>
|
||||||
{/if}
|
{/if}
|
||||||
<figure class="tile" class:full={isGenerator}>
|
<figure class="tile" class:full={isGenerator || resultKind !== "image"}>
|
||||||
<figcaption><span>RESULT {running ? "…" : ""}</span></figcaption>
|
<figcaption><span>RESULT {running ? "…" : ""}</span></figcaption>
|
||||||
<div class="canvas checker">
|
<div class="canvas" class:checker={resultKind === "image"}>
|
||||||
{#if resultUrl}
|
{#if resultKind === "text" && textResult}
|
||||||
|
<div class="text-result-wrap">
|
||||||
|
<SchemaTextResult
|
||||||
|
value={textResult}
|
||||||
|
kind="text"
|
||||||
|
oncopy={oncopytext ?? (() => {})}
|
||||||
|
ondownload={ondownloadtxt ?? (() => {})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else if resultKind === "verdict" && textResult}
|
||||||
|
<div class="text-result-wrap">
|
||||||
|
<SchemaTextResult
|
||||||
|
value={textResult}
|
||||||
|
kind="verdict"
|
||||||
|
oncopy={oncopytext ?? (() => {})}
|
||||||
|
ondownload={ondownloadtxt ?? (() => {})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else if resultKind === "image" && resultUrl}
|
||||||
<img src={resultUrl} alt="result" />
|
<img src={resultUrl} alt="result" />
|
||||||
{:else if running}
|
{:else if running}
|
||||||
<Check size={22} />
|
<Check size={22} />
|
||||||
@@ -99,11 +147,25 @@
|
|||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
caption: "SOURCE",
|
caption: "SOURCE",
|
||||||
value: source ? `${source.width} × ${source.height} px` : "—",
|
value:
|
||||||
|
!isGenerator && !isTextInput
|
||||||
|
? source
|
||||||
|
? `${source.width} × ${source.height} px`
|
||||||
|
: "—"
|
||||||
|
: isTextInput
|
||||||
|
? "text"
|
||||||
|
: "—",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
caption: "RESULT",
|
caption: "RESULT",
|
||||||
value: result ? `${result.width} × ${result.height} px` : "—",
|
value:
|
||||||
|
resultKind === "image"
|
||||||
|
? result
|
||||||
|
? `${result.width} × ${result.height} px`
|
||||||
|
: "—"
|
||||||
|
: textResult
|
||||||
|
? "text"
|
||||||
|
: "—",
|
||||||
},
|
},
|
||||||
{ caption: "FORMAT", value: "PNG" },
|
{ caption: "FORMAT", value: "PNG" },
|
||||||
]}
|
]}
|
||||||
@@ -202,6 +264,12 @@
|
|||||||
.empty {
|
.empty {
|
||||||
font: var(--font-size-s) var(--font-mono);
|
font: var(--font-size-s) var(--font-mono);
|
||||||
}
|
}
|
||||||
|
.text-source-wrap,
|
||||||
|
.text-result-wrap {
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--space-l);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
.meta {
|
.meta {
|
||||||
margin-top: var(--space-l);
|
margin-top: var(--space-l);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Copy, Download, FileText } from "@lucide/svelte";
|
||||||
|
import IconButton from "./ui/IconButton.svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value: string;
|
||||||
|
kind: "text" | "verdict";
|
||||||
|
fileName?: string;
|
||||||
|
oncopy: () => void;
|
||||||
|
ondownload: () => void;
|
||||||
|
}
|
||||||
|
let {
|
||||||
|
value,
|
||||||
|
kind,
|
||||||
|
fileName = "result.txt",
|
||||||
|
oncopy,
|
||||||
|
ondownload,
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
const tone = $derived(
|
||||||
|
/^(yes|true)/i.test(value.trim())
|
||||||
|
? "success"
|
||||||
|
: /^(no|false)/i.test(value.trim())
|
||||||
|
? "danger"
|
||||||
|
: "info",
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if kind === "verdict"}
|
||||||
|
<div class="verdict">
|
||||||
|
<span class="verdict-label">RESULT</span>
|
||||||
|
<div class="badge badge-tone--{tone}" role="status">
|
||||||
|
<span class="verdict-text">{value}</span>
|
||||||
|
<IconButton icon={Copy} label="Copy" onclick={oncopy} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="text-result">
|
||||||
|
<div class="result-head">
|
||||||
|
<span class="result-label"><FileText size={14} /> {fileName}</span>
|
||||||
|
<div class="result-actions">
|
||||||
|
<IconButton icon={Copy} label="Copy" onclick={oncopy} />
|
||||||
|
<IconButton
|
||||||
|
icon={Download}
|
||||||
|
label="Download .txt"
|
||||||
|
onclick={ondownload}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<pre class="result-pre"><code>{value}</code></pre>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.text-result {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-m);
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.result-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-m);
|
||||||
|
}
|
||||||
|
.result-label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-m);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
letter-spacing: var(--space-text-l);
|
||||||
|
}
|
||||||
|
.result-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-s);
|
||||||
|
}
|
||||||
|
.result-pre {
|
||||||
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-height: var(--space-xxxl);
|
||||||
|
overflow: auto;
|
||||||
|
padding: var(--space-l) var(--space-m);
|
||||||
|
border: var(--size-border) solid var(--color-border);
|
||||||
|
border-radius: var(--radius-m);
|
||||||
|
background: var(--color-background);
|
||||||
|
color: var(--color-text);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
.verdict {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-m);
|
||||||
|
justify-items: start;
|
||||||
|
}
|
||||||
|
.verdict-label {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
letter-spacing: var(--space-text-l);
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-l);
|
||||||
|
padding: var(--space-m) var(--space-xl);
|
||||||
|
border: var(--size-border-thick) solid transparent;
|
||||||
|
border-radius: var(--radius-m);
|
||||||
|
font: var(--font-size-m) var(--font-mono);
|
||||||
|
}
|
||||||
|
.badge-tone--success {
|
||||||
|
background: var(--color-success-tint);
|
||||||
|
border-color: var(--color-success);
|
||||||
|
color: var(--color-success);
|
||||||
|
}
|
||||||
|
.badge-tone--danger {
|
||||||
|
background: var(--color-danger-tint);
|
||||||
|
border-color: var(--color-danger);
|
||||||
|
color: var(--color-danger);
|
||||||
|
}
|
||||||
|
.badge-tone--info {
|
||||||
|
background: var(--color-info-tint);
|
||||||
|
border-color: var(--color-info);
|
||||||
|
color: var(--color-info);
|
||||||
|
}
|
||||||
|
.verdict-text {
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ArrowUp, Upload } from "@lucide/svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value: string;
|
||||||
|
placeholder?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
oninput: (text: string) => void;
|
||||||
|
onrender: () => void;
|
||||||
|
onsample?: () => void;
|
||||||
|
}
|
||||||
|
let {
|
||||||
|
value,
|
||||||
|
placeholder = "Paste base64 / hex / bytes…",
|
||||||
|
disabled = false,
|
||||||
|
oninput,
|
||||||
|
onrender,
|
||||||
|
onsample,
|
||||||
|
}: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="text-source">
|
||||||
|
<textarea
|
||||||
|
spellcheck="false"
|
||||||
|
{placeholder}
|
||||||
|
rows="6"
|
||||||
|
{value}
|
||||||
|
oninput={(e) => oninput((e.target as HTMLTextAreaElement).value)}
|
||||||
|
onkeydown={(e) => {
|
||||||
|
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
onrender();
|
||||||
|
}
|
||||||
|
}}></textarea>
|
||||||
|
<div class="actions">
|
||||||
|
{#if onsample}
|
||||||
|
<button type="button" class="secondary" onclick={onsample}>
|
||||||
|
<Upload size={14} /> Try sample
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
<button type="button" class="render" onclick={onrender} {disabled}>
|
||||||
|
<ArrowUp size={14} /> Render text
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.text-source {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-m);
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: var(--space-brand);
|
||||||
|
max-height: var(--space-xxxl);
|
||||||
|
resize: vertical;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: var(--space-m);
|
||||||
|
border: var(--size-border) solid var(--color-border);
|
||||||
|
border-radius: var(--radius-m);
|
||||||
|
background: var(--color-background);
|
||||||
|
color: var(--color-text);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-main);
|
||||||
|
}
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-m);
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.render {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-m);
|
||||||
|
padding: var(--space-m) var(--space-l);
|
||||||
|
border: var(--size-border) solid var(--color-main);
|
||||||
|
border-radius: var(--radius-m);
|
||||||
|
background: var(--color-main);
|
||||||
|
color: var(--color-background);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.render:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.secondary {
|
||||||
|
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);
|
||||||
|
background: none;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.secondary:hover {
|
||||||
|
color: var(--color-text);
|
||||||
|
border-color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -4,7 +4,13 @@
|
|||||||
import { debounce } from "$lib/core/debounce";
|
import { debounce } from "$lib/core/debounce";
|
||||||
import { decodeFile, encode } from "$lib/core/io";
|
import { decodeFile, encode } from "$lib/core/io";
|
||||||
import type { PixelImage } from "$lib/core/types";
|
import type { PixelImage } from "$lib/core/types";
|
||||||
import { executeGenerate, executeStep } from "$lib/preview/executor";
|
import {
|
||||||
|
executeFromText,
|
||||||
|
executeGenerate,
|
||||||
|
executeStep,
|
||||||
|
executeTextToText,
|
||||||
|
executeToText,
|
||||||
|
} from "$lib/preview/executor";
|
||||||
import type { ToolEntry } from "$lib/registry-new";
|
import type { ToolEntry } from "$lib/registry-new";
|
||||||
import {
|
import {
|
||||||
defaultSchemaParams,
|
defaultSchemaParams,
|
||||||
@@ -22,10 +28,14 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isGenerator = $derived(Boolean(tool.generate && !tool.run));
|
const isGenerator = $derived(Boolean(tool.generate && !tool.run));
|
||||||
|
const inputMode = $derived(tool.input ?? "file");
|
||||||
|
const resultKind = $derived(tool.result ?? "image");
|
||||||
|
|
||||||
let values = $state<Record<string, unknown>>({});
|
let values = $state<Record<string, unknown>>({});
|
||||||
let source = $state<PixelImage | null>(null);
|
let source = $state<PixelImage | null>(null);
|
||||||
let result = $state<PixelImage | null>(null);
|
let result = $state<PixelImage | null>(null);
|
||||||
|
let textSource = $state("");
|
||||||
|
let textResult = $state<string | null>(null);
|
||||||
let running = $state(false);
|
let running = $state(false);
|
||||||
let error = $state("");
|
let error = $state("");
|
||||||
let started = $state(false);
|
let started = $state(false);
|
||||||
@@ -45,6 +55,7 @@
|
|||||||
function reset() {
|
function reset() {
|
||||||
values = schema ? defaultSchemaParams(schema) : {};
|
values = schema ? defaultSchemaParams(schema) : {};
|
||||||
result = null;
|
result = null;
|
||||||
|
textResult = null;
|
||||||
error = "";
|
error = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,12 +75,29 @@
|
|||||||
await runGenerate();
|
await runGenerate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!source) return;
|
|
||||||
error = "";
|
error = "";
|
||||||
running = true;
|
|
||||||
const params = sanitizeSchemaParams(schema, values);
|
const params = sanitizeSchemaParams(schema, values);
|
||||||
|
running = true;
|
||||||
try {
|
try {
|
||||||
result = await executeStep(tool, source, params);
|
if (inputMode === "text") {
|
||||||
|
if (!textSource.trim()) return;
|
||||||
|
if (tool.textToText) {
|
||||||
|
textResult = await executeTextToText(tool, textSource);
|
||||||
|
result = null;
|
||||||
|
} else {
|
||||||
|
result = await executeFromText(tool, textSource, params);
|
||||||
|
textResult = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!source) return;
|
||||||
|
if (resultKind !== "image" && tool.toText) {
|
||||||
|
textResult = await executeToText(tool, source, params);
|
||||||
|
result = null;
|
||||||
|
} else {
|
||||||
|
result = await executeStep(tool, source, params);
|
||||||
|
textResult = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = errorText(e);
|
error = errorText(e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -106,12 +134,32 @@
|
|||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function copyText() {
|
||||||
|
if (!textResult) return;
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(textResult);
|
||||||
|
} catch (e) {
|
||||||
|
error = errorText(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadText() {
|
||||||
|
if (!textResult) return;
|
||||||
|
const blob = new Blob([textResult], { type: "text/plain" });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = `${tool.id}.txt`;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
init();
|
init();
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (isGenerator || !source || !started) return;
|
if (inputMode === "text" || isGenerator || !source || !started) return;
|
||||||
void values;
|
void values;
|
||||||
debouncedRun();
|
debouncedRun();
|
||||||
return () => debouncedRun.cancel();
|
return () => debouncedRun.cancel();
|
||||||
@@ -146,11 +194,20 @@
|
|||||||
<SchemaPreview
|
<SchemaPreview
|
||||||
{source}
|
{source}
|
||||||
{result}
|
{result}
|
||||||
|
{textSource}
|
||||||
|
{textResult}
|
||||||
|
{resultKind}
|
||||||
{running}
|
{running}
|
||||||
{error}
|
{error}
|
||||||
{isGenerator}
|
{isGenerator}
|
||||||
onupload={handleFile}
|
onupload={handleFile}
|
||||||
ongenerate={runGenerate}
|
ongenerate={runGenerate}
|
||||||
|
ontextsource={(t) => {
|
||||||
|
textSource = t;
|
||||||
|
}}
|
||||||
|
onrendertext={run}
|
||||||
|
oncopytext={copyText}
|
||||||
|
ondownloadtxt={downloadText}
|
||||||
ondownload={download}
|
ondownload={download}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -55,6 +55,53 @@ export async function executeGenerate(
|
|||||||
return await tool.generate(params);
|
return await tool.generate(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MaybeTextRunnable = MaybeRunnable & {
|
||||||
|
runFromText?: (
|
||||||
|
text: string,
|
||||||
|
params: Record<string, unknown>,
|
||||||
|
) => Promise<PixelImage> | PixelImage;
|
||||||
|
toText?: (
|
||||||
|
img: PixelImage,
|
||||||
|
params: Record<string, unknown>,
|
||||||
|
) => Promise<string> | string;
|
||||||
|
textToText?: (text: string) => Promise<string> | string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Текст → изображение (text-source конвертеры). Выполняется напрямую. */
|
||||||
|
export async function executeFromText(
|
||||||
|
tool: MaybeTextRunnable,
|
||||||
|
text: string,
|
||||||
|
params: Record<string, unknown>,
|
||||||
|
): Promise<PixelImage> {
|
||||||
|
if (!tool.runFromText) {
|
||||||
|
throw new Error("errors.noTextInput");
|
||||||
|
}
|
||||||
|
return await tool.runFromText(text, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Изображение → текст (текстовые конвертеры и вердикты). */
|
||||||
|
export async function executeToText(
|
||||||
|
tool: MaybeTextRunnable,
|
||||||
|
img: PixelImage,
|
||||||
|
params: Record<string, unknown>,
|
||||||
|
): Promise<string> {
|
||||||
|
if (!tool.toText) {
|
||||||
|
throw new Error("errors.noTextResult");
|
||||||
|
}
|
||||||
|
return await tool.toText(img, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Текст → текст (напр. verify-is-png). */
|
||||||
|
export async function executeTextToText(
|
||||||
|
tool: MaybeTextRunnable,
|
||||||
|
text: string,
|
||||||
|
): Promise<string> {
|
||||||
|
if (!tool.textToText) {
|
||||||
|
throw new Error("errors.noTextResult");
|
||||||
|
}
|
||||||
|
return await tool.textToText(text);
|
||||||
|
}
|
||||||
|
|
||||||
async function runDirect(
|
async function runDirect(
|
||||||
tool: MaybeRunnable,
|
tool: MaybeRunnable,
|
||||||
img: PixelImage,
|
img: PixelImage,
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
export { executeGenerate, executeStep } from "./executor";
|
export {
|
||||||
|
executeFromText,
|
||||||
|
executeGenerate,
|
||||||
|
executeStep,
|
||||||
|
executeTextToText,
|
||||||
|
executeToText,
|
||||||
|
} from "./executor";
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ export type ToolEntry<P = Record<string, unknown>> = {
|
|||||||
schema: ToolSchema<P>;
|
schema: ToolSchema<P>;
|
||||||
/** Требует DOM (canvas/document); превью-executor запускает напрямую, не в worker. */
|
/** Требует DOM (canvas/document); превью-executor запускает напрямую, не в worker. */
|
||||||
domOnly?: boolean;
|
domOnly?: boolean;
|
||||||
|
/** Чем управляется инструмент: файлом (по умолчанию), текстом или ничем (генератор). */
|
||||||
|
input?: "file" | "text" | "none";
|
||||||
|
/** Тип результата: картинка (по умолчанию), большой текст или короткий вердикт. */
|
||||||
|
result?: "image" | "text" | "verdict";
|
||||||
/** Применение к входному изображению. Для чистых генераторов отсутствует. */
|
/** Применение к входному изображению. Для чистых генераторов отсутствует. */
|
||||||
run?(img: PixelImage, params: P): Promise<PixelImage> | PixelImage;
|
run?(img: PixelImage, params: P): Promise<PixelImage> | PixelImage;
|
||||||
generate?(params: P): Promise<PixelImage> | PixelImage;
|
generate?(params: P): Promise<PixelImage> | PixelImage;
|
||||||
|
|||||||
Reference in New Issue
Block a user