diff --git a/web/src/lib/i18n/en.ts b/web/src/lib/i18n/en.ts index 1d25e42..cb9dbf3 100644 --- a/web/src/lib/i18n/en.ts +++ b/web/src/lib/i18n/en.ts @@ -112,6 +112,8 @@ export const en: Dict = { }, errors: { noImageRun: "This tool does not process images", + sourceRequired: "Upload an image first", + textRequired: "Enter text first", workerFailed: "Worker execution failed", workerUnavailable: "Worker is unavailable", notFound: "Tool not found", diff --git a/web/src/lib/i18n/ru.ts b/web/src/lib/i18n/ru.ts index 5d12f55..35e05f8 100644 --- a/web/src/lib/i18n/ru.ts +++ b/web/src/lib/i18n/ru.ts @@ -113,6 +113,8 @@ export const ru: Dict = { }, errors: { noImageRun: "Этот инструмент не обрабатывает изображения", + sourceRequired: "Сначала загрузите изображение", + textRequired: "Сначала введите текст", workerFailed: "Ошибка исполнения в воркере", workerUnavailable: "Воркер недоступен", notFound: "Инструмент не найден", diff --git a/web/src/lib/registry-new/types.ts b/web/src/lib/registry-new/types.ts index 4b5b6f4..c041568 100644 --- a/web/src/lib/registry-new/types.ts +++ b/web/src/lib/registry-new/types.ts @@ -2,6 +2,7 @@ import type { CategoryId } from "../preview/categories"; import type { ToolSchema } from "../registry-schema"; import type { PixelImage } from "../core/types"; import type { OutputMime } from "../core/io"; +import { ToolError } from "../core/errors"; /** Формат скачивания, отличный от PNG (bmp/jpeg/webp). */ export interface OutputFormat { @@ -10,10 +11,37 @@ export interface OutputFormat { qualityParamId?: string; } +/** Чем управляется инструмент. `image` — требуется входное изображение. */ +export const INPUT_MODES = { + image: "image", + text: "text", + none: "none", +} as const; +export type InputMode = (typeof INPUT_MODES)[keyof typeof INPUT_MODES]; + +/** Тип результата: картинка (по умолчанию), большой текст или короткий вердикт. */ +export const RESULT_KINDS = { + image: "image", + text: "text", + verdict: "verdict", +} as const; +export type ResultKind = (typeof RESULT_KINDS)[keyof typeof RESULT_KINDS]; + +/** Единый вход инструмента. `source`/`text` присутствуют строго по `input`. */ +export interface ToolContext
{ + params: P; + source?: PixelImage; + text?: string; +} + +export type ToolResult = PixelImage | string; + /** * Инструмент нового registry: полностью типизирован на `Params`, схема — - * обязательна (единый источник дефолтов/валидации/UI). Старого `ParamDef[]` - * нет — это отдельная система от старого UI. + * обязательна (единый источник дефолтов/валидации/UI). У каждого инструмента + * ровно один метод `run(ctx)`; контракт входа декларируется через `input` + * (обязательное поле), а результат — через `result`. Отдельных методов + * `generate`/`runFromText`/`toText`/`textToText` нет. */ export type ToolEntry
> = { id: string; @@ -21,20 +49,55 @@ export type ToolEntry
> = { description: string; category: CategoryId; schema: ToolSchema
; + /** Чем управляется инструмент: входным изображением, текстом или ничем. */ + input: InputMode; + /** Тип результата: картинка (по умолчанию), большой текст или короткий вердикт. */ + result?: ResultKind; + /** Единственный метод исполнения — и для генераторов, и для трансформаторов. */ + run(ctx: ToolContext
): Promise (ctx: ToolContext ): PixelImage {
+ if (!ctx.source) {
+ throw new ToolError("errors.sourceRequired");
+ }
+ return ctx.source;
+}
+
+/** Гарантирует наличие текстового входа для text-инструментов. */
+export function requireText (ctx: ToolContext ): string {
+ if (!ctx.text?.trim()) {
+ throw new ToolError("errors.textRequired");
+ }
+ return ctx.text;
+}
+
+/**
+ * Обёртка для image-to-image инструментов: подставляет `src` и `params`
+ * из контекста. Позволяет оставить тело инструмента в виде `(img, p) => …`.
+ */
+export function imgTool (
+ fn: (img: PixelImage, params: P) => ToolResult,
+): (ctx: ToolContext ) => ToolResult {
+ return (ctx) => fn(requireSource(ctx), ctx.params);
+}
+
+/** Обёртка для генераторов: инструменту нужны только параметры. */
+export function genTool (
+ fn: (params: P) => ToolResult,
+): (ctx: ToolContext ) => ToolResult {
+ return (ctx) => fn(ctx.params);
+}
+
+/** Обёртка для text-to-image инструментов: подставляет `text` и `params`. */
+export function textGen (
+ fn: (text: string, params: P) => ToolResult,
+): (ctx: ToolContext ) => ToolResult {
+ return (ctx) => fn(requireText(ctx), ctx.params);
+}
\ No newline at end of file