mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
feat: add info\warning badge, update size range workflow
This commit is contained in:
@@ -13,11 +13,25 @@
|
||||
label: string;
|
||||
value: unknown;
|
||||
spec: FieldSpec;
|
||||
/** Размеры входа шага: потолок осей для maxFromSource-полей. */
|
||||
sourceDims?: Dimension;
|
||||
onchange?: (value: Dimension, axis?: "width" | "height" | "both") => void;
|
||||
}
|
||||
let { label, value, spec, onchange }: Props = $props();
|
||||
let {
|
||||
label,
|
||||
value,
|
||||
spec,
|
||||
sourceDims = undefined,
|
||||
onchange,
|
||||
}: Props = $props();
|
||||
|
||||
const sp = $derived(spec as DimensionSpec);
|
||||
const maxW = $derived(
|
||||
sp.maxFromSource && sourceDims ? sourceDims.width : sp.max,
|
||||
);
|
||||
const maxH = $derived(
|
||||
sp.maxFromSource && sourceDims ? sourceDims.height : sp.max,
|
||||
);
|
||||
const current = $derived.by(() => {
|
||||
const v = value as Partial<Dimension> | undefined;
|
||||
return {
|
||||
@@ -49,7 +63,7 @@
|
||||
<input
|
||||
type="number"
|
||||
min={sp.min}
|
||||
max={sp.max}
|
||||
max={maxW}
|
||||
value={current.width}
|
||||
oninput={(e) =>
|
||||
setAxis("width", Number((e.target as HTMLInputElement).value))}
|
||||
@@ -60,7 +74,7 @@
|
||||
<input
|
||||
type="number"
|
||||
min={sp.min}
|
||||
max={sp.max}
|
||||
max={maxH}
|
||||
value={current.height}
|
||||
oninput={(e) =>
|
||||
setAxis("height", Number((e.target as HTMLInputElement).value))}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { InputMode, ResultKind } from "$lib/registry";
|
||||
import { RefreshCw } from "@lucide/svelte";
|
||||
import { t } from "$lib/i18n/t";
|
||||
import Badge from "$lib/components/ui/Badge.svelte";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
interface Props {
|
||||
@@ -11,6 +12,9 @@
|
||||
loading?: boolean;
|
||||
parts?: number;
|
||||
partsDims?: string;
|
||||
dims?: string;
|
||||
note?: string;
|
||||
noteTone?: "warning" | "info";
|
||||
}
|
||||
let {
|
||||
label,
|
||||
@@ -19,24 +23,31 @@
|
||||
loading = false,
|
||||
parts = undefined,
|
||||
partsDims = undefined,
|
||||
dims = undefined,
|
||||
note = undefined,
|
||||
noteTone = "warning",
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<figure class="tile">
|
||||
<figcaption>
|
||||
<span>
|
||||
{label}
|
||||
{#if parts !== undefined}
|
||||
<span class="count">
|
||||
{parts}
|
||||
{t("resultCard.parts")}{#if partsDims}
|
||||
· {partsDims}{/if}
|
||||
</span>
|
||||
{/if}
|
||||
<span class="tile-label">
|
||||
<span
|
||||
>{label}{#if parts !== undefined}<span class="dims"
|
||||
>: {parts}
|
||||
{t("resultCard.parts")}{#if partsDims}
|
||||
· {partsDims}{/if}</span
|
||||
>{:else if dims}<span class="dims">: {dims}</span>{/if}</span
|
||||
>
|
||||
{#if loading}
|
||||
<RefreshCw class="rotating" size="12" />
|
||||
{/if}
|
||||
</span>
|
||||
<span class="tile-note">
|
||||
{#if note}
|
||||
<Badge tone={noteTone} label={note} />
|
||||
{/if}
|
||||
</span>
|
||||
</figcaption>
|
||||
<div class="canvas" class:checker={mode === "image"}>
|
||||
{@render children()}
|
||||
@@ -47,7 +58,10 @@
|
||||
.tile figcaption {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-m);
|
||||
/* Резерв под бейдж: строка не прыгает, когда заметка появляется. */
|
||||
min-height: var(--space-xxl);
|
||||
margin-bottom: var(--space-m);
|
||||
font: var(--font-size-s) var(--font-mono);
|
||||
text-transform: uppercase;
|
||||
@@ -56,8 +70,18 @@
|
||||
animation: rotate var(--duration-l) linear infinite;
|
||||
}
|
||||
}
|
||||
.count {
|
||||
margin-left: var(--space-m);
|
||||
.tile-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-m);
|
||||
}
|
||||
/* Место под заметки результата: бейджи-предупреждения. */
|
||||
.tile-note {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-m);
|
||||
}
|
||||
.dims {
|
||||
color: var(--color-main);
|
||||
}
|
||||
.canvas {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type {
|
||||
Dimension,
|
||||
FieldSpec,
|
||||
FieldSpecKind,
|
||||
ToolSchema,
|
||||
} from "$lib/registry-schema";
|
||||
import { resolveLayoutGroups } from "$lib/registry-schema";
|
||||
import { effectiveMax, resolveLayoutGroups } from "$lib/registry-schema";
|
||||
import { fieldLabel, groupLabel } from "$lib/i18n/schema-tool-strings";
|
||||
import { t } from "$lib/i18n/t";
|
||||
import { RotateCcw } from "@lucide/svelte";
|
||||
@@ -30,6 +31,7 @@
|
||||
spec: FieldSpec;
|
||||
toolId: string;
|
||||
fieldId: string;
|
||||
sourceDims?: Dimension;
|
||||
onchange?: (value: unknown, axis?: "width" | "height" | "both") => void;
|
||||
}
|
||||
|
||||
@@ -54,6 +56,8 @@
|
||||
schema: ToolSchema<Record<string, unknown>>;
|
||||
values: Record<string, unknown>;
|
||||
toolId: string;
|
||||
/** Размеры входа шага: для maxFromSource-полей (crop и т.п.). */
|
||||
sourceDims?: Dimension;
|
||||
onchange: (
|
||||
id: string,
|
||||
value: unknown,
|
||||
@@ -61,9 +65,23 @@
|
||||
) => void;
|
||||
onreset: () => void;
|
||||
}
|
||||
let { schema, values, toolId, onchange, onreset }: Props = $props();
|
||||
let {
|
||||
schema,
|
||||
values,
|
||||
toolId,
|
||||
sourceDims = undefined,
|
||||
onchange,
|
||||
onreset,
|
||||
}: Props = $props();
|
||||
|
||||
const layoutGroups = $derived(resolveLayoutGroups(schema));
|
||||
|
||||
function effectiveSpec(spec: FieldSpec): FieldSpec {
|
||||
if (sourceDims && spec.kind === "number" && spec.maxFromSource) {
|
||||
return { ...spec, max: effectiveMax(spec, sourceDims) };
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each layoutGroups as group (group.key)}
|
||||
@@ -81,9 +99,10 @@
|
||||
<Control
|
||||
label={fieldLabel(schema.fields[id], id)}
|
||||
value={values[id]}
|
||||
spec={schema.fields[id].spec}
|
||||
spec={effectiveSpec(schema.fields[id].spec)}
|
||||
{toolId}
|
||||
fieldId={id}
|
||||
{sourceDims}
|
||||
onchange={(v, axis) => onchange(id, v, axis)}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
<script lang="ts">
|
||||
import MetaList from "$lib/components/display/MetaList.svelte";
|
||||
import { buildSchemaPreviewModel } from "./schema-preview-model";
|
||||
import { hasPreviewResult } from "./schema-preview-model";
|
||||
import SchemaActions from "./SchemaActions.svelte";
|
||||
import SchemaResultTile from "./SchemaResultTile.svelte";
|
||||
import SchemaSourceTile from "./SchemaSourceTile.svelte";
|
||||
import type { PixelImage } from "$lib/core/types";
|
||||
import type { OutputMime } from "$lib/core/io";
|
||||
import { t } from "$lib/i18n/t";
|
||||
import type { FileResult, InputMode, ResultKind } from "$lib/registry";
|
||||
import type {
|
||||
FileResult,
|
||||
InputMode,
|
||||
ResultKind,
|
||||
ResultNote,
|
||||
} from "$lib/registry";
|
||||
|
||||
interface Props {
|
||||
inputMode: InputMode;
|
||||
@@ -15,6 +19,7 @@
|
||||
toolId: string;
|
||||
source: PixelImage | null;
|
||||
result: PixelImage | null;
|
||||
resultNote?: ResultNote | null;
|
||||
fileResult?: FileResult | null;
|
||||
textSource?: string;
|
||||
textResult?: string | null;
|
||||
@@ -39,6 +44,7 @@
|
||||
toolId,
|
||||
source,
|
||||
result,
|
||||
resultNote = null,
|
||||
fileResult = null,
|
||||
textSource = "",
|
||||
textResult = null,
|
||||
@@ -58,11 +64,8 @@
|
||||
onquality,
|
||||
}: Props = $props();
|
||||
|
||||
const model = $derived(
|
||||
buildSchemaPreviewModel(
|
||||
{ inputMode, resultKind, source, result, fileResult, textResult },
|
||||
t,
|
||||
),
|
||||
const hasResult = $derived(
|
||||
hasPreviewResult({ inputMode, resultKind, result, fileResult, textResult }),
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -71,7 +74,7 @@
|
||||
<SchemaActions
|
||||
{inputMode}
|
||||
{resultKind}
|
||||
canDownload={model.hasResult}
|
||||
canDownload={hasResult}
|
||||
{running}
|
||||
{format}
|
||||
{quality}
|
||||
@@ -100,6 +103,7 @@
|
||||
<SchemaResultTile
|
||||
{resultKind}
|
||||
{result}
|
||||
{resultNote}
|
||||
{fileResult}
|
||||
{textResult}
|
||||
{textVars}
|
||||
@@ -110,15 +114,6 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="meta">
|
||||
<MetaList
|
||||
items={[
|
||||
{ caption: t("sourceCard.source"), value: model.sourceValue },
|
||||
{ caption: t("resultCard.result"), value: model.resultValue },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.panel-head {
|
||||
display: flex;
|
||||
@@ -145,9 +140,6 @@
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-l);
|
||||
}
|
||||
.meta {
|
||||
margin-top: var(--space-l);
|
||||
}
|
||||
@media (--bp-tablet) {
|
||||
.pair {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { toDataUrl } from "$lib/core/io";
|
||||
import type { PixelImage } from "$lib/core/types";
|
||||
import { t } from "$lib/i18n/t";
|
||||
import type { FileResult, ResultKind } from "$lib/registry";
|
||||
import type { FileResult, ResultKind, ResultNote } from "$lib/registry";
|
||||
import { Check } from "@lucide/svelte";
|
||||
import SchemaTextResult from "./SchemaTextResult.svelte";
|
||||
import PreviewTile from "./PreviewTile.svelte";
|
||||
@@ -10,6 +10,7 @@
|
||||
interface Props {
|
||||
resultKind?: ResultKind;
|
||||
result: PixelImage | null;
|
||||
resultNote?: ResultNote | null;
|
||||
fileResult?: FileResult | null;
|
||||
textResult?: string | null;
|
||||
textVars?: Record<string, string | number>;
|
||||
@@ -21,6 +22,7 @@
|
||||
let {
|
||||
resultKind = "image",
|
||||
result,
|
||||
resultNote = null,
|
||||
fileResult = null,
|
||||
textResult = null,
|
||||
textVars = undefined,
|
||||
@@ -31,6 +33,11 @@
|
||||
}: Props = $props();
|
||||
|
||||
const resultUrl = $derived(result ? toDataUrl(result) : null);
|
||||
const resultDims = $derived(
|
||||
resultKind === "image" && result
|
||||
? `${result.width} × ${result.height}`
|
||||
: undefined,
|
||||
);
|
||||
const partUrls = $derived(
|
||||
resultKind === "files" && fileResult
|
||||
? fileResult.files.map((file) => ({
|
||||
@@ -57,6 +64,9 @@
|
||||
loading={running}
|
||||
{parts}
|
||||
{partsDims}
|
||||
dims={resultDims}
|
||||
note={resultNote ? t(resultNote.key) : undefined}
|
||||
noteTone={resultNote?.tone ?? "warning"}
|
||||
>
|
||||
<div
|
||||
class="canvas"
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
}: Props = $props();
|
||||
|
||||
const sourceUrl = $derived(source ? toDataUrl(source) : null);
|
||||
const sourceDims = $derived(
|
||||
source ? `${source.width} × ${source.height}` : undefined,
|
||||
);
|
||||
|
||||
let dragging = $state(false);
|
||||
let input = $state<HTMLInputElement | null>(null);
|
||||
@@ -63,7 +66,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<PreviewTile label={t("sourceCard.source")} viewMode={mode}>
|
||||
<PreviewTile label={t("sourceCard.source")} viewMode={mode} dims={sourceDims}>
|
||||
{#if mode === "text"}
|
||||
<div class="text-source-wrap">
|
||||
<SchemaTextSource
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
} from "$lib/registry";
|
||||
import {
|
||||
applySourceDefaults,
|
||||
clampSourceAwareMaxes,
|
||||
defaultSchemaParams,
|
||||
withAspectLock,
|
||||
type Dimension,
|
||||
@@ -105,6 +106,11 @@
|
||||
// он — единый источник качества; dropdown редактирует его же.
|
||||
const qualityParamId = $derived(lastTool.output?.qualityParamId);
|
||||
const lastParams = $derived(steps.at(-1)?.params);
|
||||
const resultNote = $derived(
|
||||
result && lastTool.resultNote
|
||||
? lastTool.resultNote(lastParams ?? {}, result)
|
||||
: null,
|
||||
);
|
||||
const currentQuality = $derived.by(() => {
|
||||
const setting = outputFormatByMime(format).settings?.quality;
|
||||
if (!setting) return undefined;
|
||||
@@ -205,7 +211,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
steps = steps.with(index, { ...step, params: next });
|
||||
const clamped = dims ? clampSourceAwareMaxes(stepSchema, next, dims) : next;
|
||||
steps = steps.with(index, { ...step, params: clamped });
|
||||
}
|
||||
|
||||
function resetStep(index: number) {
|
||||
@@ -249,11 +256,15 @@
|
||||
if (first && firstSchema) {
|
||||
steps = steps.with(0, {
|
||||
...first,
|
||||
params: applySourceDefaults(
|
||||
params: clampSourceAwareMaxes(
|
||||
firstSchema,
|
||||
first.params,
|
||||
{ source: decoded },
|
||||
touchedFor(first.key),
|
||||
applySourceDefaults(
|
||||
firstSchema,
|
||||
first.params,
|
||||
{ source: decoded },
|
||||
touchedFor(first.key),
|
||||
),
|
||||
decoded,
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -309,7 +320,7 @@
|
||||
const base = toDisplayError(e);
|
||||
const msg = base.kind === "i18n" ? t(base.key, base.vars) : base.text;
|
||||
throw new Error(
|
||||
t("steps.stepError", {
|
||||
t("toolPage.stepError", {
|
||||
n: i + 1,
|
||||
title: stepTitle(stepTool.id),
|
||||
msg,
|
||||
@@ -468,6 +479,7 @@
|
||||
schema={toolSchemaOf(step)}
|
||||
values={step.params}
|
||||
toolId={step.id}
|
||||
sourceDims={i === 0 ? (source ?? undefined) : stepDims[i - 1]}
|
||||
onchange={(id, v, axis) => setStepValue(i, id, v, axis)}
|
||||
onreset={() => resetStep(i)}
|
||||
/>
|
||||
@@ -483,6 +495,7 @@
|
||||
toolId={lastTool.id}
|
||||
{source}
|
||||
{result}
|
||||
{resultNote}
|
||||
{fileResult}
|
||||
{textSource}
|
||||
{textResult}
|
||||
|
||||
@@ -1,107 +1,47 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { makeImage } from "../../core/test-helpers";
|
||||
import {
|
||||
buildSchemaPreviewModel,
|
||||
type PreviewTranslate,
|
||||
hasPreviewResult,
|
||||
type SchemaPreviewModelInput,
|
||||
} from "./schema-preview-model";
|
||||
|
||||
const image = makeImage(2, 3, [
|
||||
[1, 2, 3, 255],
|
||||
[4, 5, 6, 255],
|
||||
[7, 8, 9, 255],
|
||||
[10, 11, 12, 255],
|
||||
[13, 14, 15, 255],
|
||||
[16, 17, 18, 255],
|
||||
]);
|
||||
const image = { width: 2, height: 3, data: new Uint8ClampedArray(24) };
|
||||
|
||||
const translate: PreviewTranslate = (key) => {
|
||||
if (key === "resultCard.parts") return "parts";
|
||||
if (key === "textInput.heading") return "Text";
|
||||
return key;
|
||||
};
|
||||
|
||||
function input(
|
||||
overrides: Partial<Parameters<typeof buildSchemaPreviewModel>[0]> = {},
|
||||
) {
|
||||
function input(over: Partial<SchemaPreviewModelInput> = {}) {
|
||||
return {
|
||||
inputMode: "image" as const,
|
||||
resultKind: "image" as const,
|
||||
source: null,
|
||||
inputMode: "image",
|
||||
resultKind: "image",
|
||||
result: null,
|
||||
fileResult: null,
|
||||
textResult: null,
|
||||
...overrides,
|
||||
};
|
||||
...over,
|
||||
} satisfies SchemaPreviewModelInput;
|
||||
}
|
||||
|
||||
describe("buildSchemaPreviewModel", () => {
|
||||
it("formats source values for every input mode", () => {
|
||||
expect(
|
||||
buildSchemaPreviewModel(input({ inputMode: "none" }), translate),
|
||||
).toMatchObject({ sourceValue: "—" });
|
||||
expect(
|
||||
buildSchemaPreviewModel(input({ inputMode: "text" }), translate),
|
||||
).toMatchObject({ sourceValue: "Text" });
|
||||
expect(
|
||||
buildSchemaPreviewModel(
|
||||
input({ inputMode: "image", source: image }),
|
||||
translate,
|
||||
),
|
||||
).toMatchObject({ sourceValue: "2 × 3 px" });
|
||||
describe("hasPreviewResult", () => {
|
||||
it("image: результат только при наличии картинки", () => {
|
||||
expect(hasPreviewResult(input({ result: image }))).toBe(true);
|
||||
expect(hasPreviewResult(input())).toBe(false);
|
||||
});
|
||||
|
||||
it("formats image results and enables download only with a result", () => {
|
||||
expect(
|
||||
buildSchemaPreviewModel(input({ result: image }), translate),
|
||||
).toEqual({
|
||||
sourceValue: "—",
|
||||
resultValue: "2 × 3 px",
|
||||
hasResult: true,
|
||||
});
|
||||
expect(buildSchemaPreviewModel(input(), translate)).toMatchObject({
|
||||
resultValue: "—",
|
||||
hasResult: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("formats file results as ZIP and rejects empty file results", () => {
|
||||
it("files: результат только при непустом наборе", () => {
|
||||
const fileResult = { files: [{ name: "part.png", image }] };
|
||||
expect(hasPreviewResult(input({ resultKind: "files", fileResult }))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
buildSchemaPreviewModel(
|
||||
input({ resultKind: "files", fileResult }),
|
||||
translate,
|
||||
),
|
||||
).toEqual({
|
||||
sourceValue: "—",
|
||||
resultValue: "1 parts",
|
||||
hasResult: true,
|
||||
});
|
||||
expect(
|
||||
buildSchemaPreviewModel(
|
||||
hasPreviewResult(
|
||||
input({ resultKind: "files", fileResult: { files: [] } }),
|
||||
translate,
|
||||
),
|
||||
).toMatchObject({ resultValue: "—", hasResult: false });
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it.each(["text", "verdict"] as const)(
|
||||
"uses text result presence for %s output",
|
||||
"%s: результат по наличию текста",
|
||||
(resultKind) => {
|
||||
expect(
|
||||
buildSchemaPreviewModel(
|
||||
input({ resultKind, textResult: "done" }),
|
||||
translate,
|
||||
),
|
||||
).toMatchObject({
|
||||
resultValue: "Text",
|
||||
hasResult: true,
|
||||
});
|
||||
expect(
|
||||
buildSchemaPreviewModel(input({ resultKind }), translate),
|
||||
).toMatchObject({
|
||||
resultValue: "—",
|
||||
hasResult: false,
|
||||
});
|
||||
expect(hasPreviewResult(input({ resultKind, textResult: "done" }))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(hasPreviewResult(input({ resultKind }))).toBe(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,58 +1,20 @@
|
||||
import type { PixelImage } from "$lib/core/types";
|
||||
import type { FileResult, InputMode, ResultKind } from "$lib/registry";
|
||||
|
||||
export type PreviewTranslate = (key: string) => string;
|
||||
|
||||
export type SchemaPreviewModelInput = {
|
||||
inputMode: InputMode;
|
||||
resultKind: ResultKind;
|
||||
source: PixelImage | null;
|
||||
result: PixelImage | null;
|
||||
fileResult: FileResult | null;
|
||||
textResult: string | null;
|
||||
};
|
||||
|
||||
export type SchemaPreviewModel = {
|
||||
sourceValue: string;
|
||||
resultValue: string;
|
||||
hasResult: boolean;
|
||||
};
|
||||
|
||||
export function buildSchemaPreviewModel(
|
||||
input: SchemaPreviewModelInput,
|
||||
translate: PreviewTranslate,
|
||||
): SchemaPreviewModel {
|
||||
const { inputMode, resultKind, source, result, fileResult, textResult } =
|
||||
input;
|
||||
const sourceValue =
|
||||
inputMode === "none"
|
||||
? "—"
|
||||
: inputMode === "text"
|
||||
? translate("textInput.heading")
|
||||
: source
|
||||
? `${source.width} × ${source.height} px`
|
||||
: "—";
|
||||
const hasFiles = Boolean(fileResult && fileResult.files.length > 0);
|
||||
const resultValue =
|
||||
resultKind === "image"
|
||||
? result
|
||||
? `${result.width} × ${result.height} px`
|
||||
: "—"
|
||||
: resultKind === "files"
|
||||
? hasFiles
|
||||
? `${fileResult?.files.length} ${translate("resultCard.parts")}`
|
||||
: "—"
|
||||
: textResult
|
||||
? translate("textInput.heading")
|
||||
: "—";
|
||||
return {
|
||||
sourceValue,
|
||||
resultValue,
|
||||
hasResult:
|
||||
resultKind === "image"
|
||||
? Boolean(result)
|
||||
: resultKind === "files"
|
||||
? hasFiles
|
||||
: Boolean(textResult),
|
||||
};
|
||||
/** Можно ли скачать результат: он есть и он не пустой. */
|
||||
export function hasPreviewResult(input: SchemaPreviewModelInput): boolean {
|
||||
const { resultKind, result, fileResult, textResult } = input;
|
||||
if (resultKind === "image") return Boolean(result);
|
||||
if (resultKind === "files") {
|
||||
return Boolean(fileResult && fileResult.files.length > 0);
|
||||
}
|
||||
return Boolean(textResult);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-s);
|
||||
line-height: normal;
|
||||
line-height: 1;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: bold;
|
||||
|
||||
Reference in New Issue
Block a user