diff --git a/app/src/components/flows/canvas/nodeConfig/__tests__/dedupFields.test.tsx b/app/src/components/flows/canvas/nodeConfig/__tests__/dedupFields.test.tsx new file mode 100644 index 000000000..7f969852e --- /dev/null +++ b/app/src/components/flows/canvas/nodeConfig/__tests__/dedupFields.test.tsx @@ -0,0 +1,37 @@ +/** + * Behavior tests for the `dedup` node config form (issue #5263). Covers the + * single `key` field: it renders and emits a `config.key` patch as the + * `=`-bindable expression is typed. `useT()` falls back to the bundled + * English map with no provider mounted (same convention as the sibling + * `memoryFields.test.tsx`). + */ +import { fireEvent, render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import { DedupForm } from '../dedupFields'; + +function renderDedupForm(config: Record = {}) { + const onChange = vi.fn(); + render(); + return { onChange }; +} + +describe('DedupForm', () => { + it('renders the key field', () => { + renderDedupForm(); + expect(screen.getByTestId('node-config-dedup-key')).toBeInTheDocument(); + }); + + it('seeds the key field from the existing config', () => { + renderDedupForm({ key: '=item.id' }); + expect(screen.getByTestId('node-config-dedup-key')).toHaveValue('=item.id'); + }); + + it('emits a key patch as the expression is typed', () => { + const { onChange } = renderDedupForm(); + fireEvent.change(screen.getByTestId('node-config-dedup-key'), { + target: { value: '=item.id' }, + }); + expect(onChange).toHaveBeenLastCalledWith({ key: '=item.id' }); + }); +}); diff --git a/app/src/components/flows/canvas/nodeConfig/dedupFields.tsx b/app/src/components/flows/canvas/nodeConfig/dedupFields.tsx new file mode 100644 index 000000000..dc3f8a25e --- /dev/null +++ b/app/src/components/flows/canvas/nodeConfig/dedupFields.tsx @@ -0,0 +1,44 @@ +/** + * `dedup` node config form (issue #5263 — 14th tinyflows `NodeKind`). Skips + * items already seen, keyed by a stable per-item `=`-expression evaluated + * against each item (e.g. `=item.id`) — the engine tracks which keys have + * already passed through this node and drops repeats. + * + * Field keys mirror what the engine actually reads at runtime: + * - `key` — `=`-bindable, required. The only config field this node has. + */ +import { useT } from '../../../../lib/i18n/I18nContext'; +import { configString, ExpressionField } from './nodeConfigFields'; +import type { UpstreamExpressionOption } from './upstreamOptions'; + +/** + * Deliberately narrower than `NodeConfigFormProps` (`nodeConfigForms.tsx`) — + * this form needs no `connections` (dedup nodes don't use the credential + * picker), so it isn't imported here just to keep the shape identical. A + * component typed against this subset is still assignable into + * `NODE_CONFIG_FORMS`'s `NodeConfigForm` slot (the fuller prop type has + * every property this one requires). + */ +export interface DedupFormProps { + config: Record; + onChange: (patch: Record) => void; + upstreamOptions?: UpstreamExpressionOption[]; +} + +export function DedupForm({ config, onChange, upstreamOptions }: DedupFormProps) { + const { t } = useT(); + + return ( +
+ onChange({ key: v })} + placeholder="=item.id" + upstreamOptions={upstreamOptions} + testId="node-config-dedup-key" + /> +
+ ); +} diff --git a/app/src/components/flows/canvas/nodeConfig/nodeConfigForms.tsx b/app/src/components/flows/canvas/nodeConfig/nodeConfigForms.tsx index d10ccfb46..ad09e088a 100644 --- a/app/src/components/flows/canvas/nodeConfig/nodeConfigForms.tsx +++ b/app/src/components/flows/canvas/nodeConfig/nodeConfigForms.tsx @@ -16,6 +16,7 @@ * - `trigger` → `trigger_kind` + kind-specific (`schedule`, `toolkit`/`trigger_slug`) * - `memory` → `operation` + operation-specific (`scope`/`query`/`flavour`/`key`/`value`/ * `limit`/`min_score`), see `memoryFields.tsx` + * - `dedup` → `key` (an `=`-bindable per-item id expression), see `dedupFields.tsx` */ import createDebug from 'debug'; import { useEffect, useState } from 'react'; @@ -32,6 +33,7 @@ import { ComposioTriggerField, fetchActionSchema, } from './composioFields'; +import { DedupForm } from './dedupFields'; import { MemoryForm } from './memoryFields'; import { NATIVE_TOOL_PREFIX, NativeToolField } from './nativeToolFields'; import { @@ -496,4 +498,5 @@ export const NODE_CONFIG_FORMS: Partial> = { transform: TransformForm, code: CodeForm, memory: MemoryForm, + dedup: DedupForm, }; diff --git a/app/src/lib/flows/nodeKindMeta.ts b/app/src/lib/flows/nodeKindMeta.ts index a88ca529b..9016fca20 100644 --- a/app/src/lib/flows/nodeKindMeta.ts +++ b/app/src/lib/flows/nodeKindMeta.ts @@ -1,5 +1,5 @@ /** - * Per-kind visual metadata for the 13 tinyflows `NodeKind`s, shared by the + * Per-kind visual metadata for the 14 tinyflows `NodeKind`s, shared by the * canvas node renderer (`FlowNodeComponent`) and the editable canvas's node * palette (`NodePalette`). Kept dependency-free (no React) so both a rendered * ``-bearing card and a plain palette button can pull the same @@ -8,7 +8,7 @@ * Colors cycle through the four CSS-variable-backed semantic ramps * (primary/sage/amber/coral) that support Tailwind's `/opacity` modifiers in * this codebase (see `tailwind.config.js`) so light/dark theming comes for - * free; with 13 kinds and 4 ramps some kinds share a color family — the emoji + * free; with 14 kinds and 4 ramps some kinds share a color family — the emoji * + name remain the primary distinguishers. */ import type { NodeKind } from './types'; @@ -29,12 +29,16 @@ interface NodeKindMeta { } /** - * The 13 `NodeKind`s in the order they should appear in the palette. Trigger + * The 14 `NodeKind`s in the order they should appear in the palette. Trigger * leads (every graph needs exactly one); the rest follow the logical grouping * of the `tinyflows::model::NodeKind` enum. `memory` (issue #5226) is - * appended last — the design doc (`08-memory-node.md`) sequences it as the - * 13th kind deliberately, so it trails `sub_workflow` here too rather than - * being interleaved with the other `actions`-group kinds. + * appended after `sub_workflow` — the design doc (`08-memory-node.md`) + * sequences it as the 13th kind deliberately, so it trails `sub_workflow` + * here too rather than being interleaved with the other `actions`-group + * kinds. `dedup` (issue #5263) is the 14th kind and is appended last in turn + * — it renders in the `logic` group (alongside `condition`/`split_out`/ + * `merge`) regardless of its position here, since {@link PALETTE_ENTRIES_BY_GROUP} + * filters by group rather than relying on interleaved array order. */ const NODE_KINDS: NodeKind[] = [ 'trigger', @@ -50,6 +54,7 @@ const NODE_KINDS: NodeKind[] = [ 'output_parser', 'sub_workflow', 'memory', + 'dedup', ]; /** Per-kind emoji + border/chip color + palette group. See the module doc. */ @@ -70,6 +75,10 @@ const NODE_KIND_META: Record = { split_out: { emoji: '📤', color: 'sage', group: 'logic' }, transform: { emoji: '♻️', color: 'primary', group: 'logic' }, output_parser: { emoji: '📋', color: 'amber', group: 'logic' }, + // Skips items already seen, keyed by a stable per-item `=`-expression — a + // "logic" node like its neighbours (`condition`/`split_out`/`merge`): it + // routes/filters the item stream rather than calling out or reading state. + dedup: { emoji: '🪪', color: 'coral', group: 'logic' }, }; /** Palette group render order. */ diff --git a/app/src/lib/flows/nodeSummary.test.ts b/app/src/lib/flows/nodeSummary.test.ts index 83d864581..63e1db9f8 100644 --- a/app/src/lib/flows/nodeSummary.test.ts +++ b/app/src/lib/flows/nodeSummary.test.ts @@ -72,4 +72,9 @@ describe('describeNode', () => { ); expect(describeNode('memory', { operation: 'search' })).toBe('Searches memory'); }); + + it('describes dedup by its key expression, and falls back generically when unset', () => { + expect(describeNode('dedup', { key: '=item.id' })).toBe('Skips items already seen by =item.id'); + expect(describeNode('dedup', {})).toBe('Skips items already processed'); + }); }); diff --git a/app/src/lib/flows/nodeSummary.ts b/app/src/lib/flows/nodeSummary.ts index c39378817..d012a7389 100644 --- a/app/src/lib/flows/nodeSummary.ts +++ b/app/src/lib/flows/nodeSummary.ts @@ -104,6 +104,12 @@ export function describeNode( // recall return `Recalls memory${scoped}`; } + case 'dedup': { + const key = str(config, 'key'); + return key + ? `Skips items already seen by ${truncate(key, 40)}` + : 'Skips items already processed'; + } default: return ''; } diff --git a/app/src/lib/flows/types.ts b/app/src/lib/flows/types.ts index 704eca20d..4392a0ad3 100644 --- a/app/src/lib/flows/types.ts +++ b/app/src/lib/flows/types.ts @@ -36,7 +36,7 @@ export interface Position { } /** - * The 13 node kinds `tinyflows` currently defines (`tinyflows::model::NodeKind`). + * The 14 node kinds `tinyflows` currently defines (`tinyflows::model::NodeKind`). * Wire values are `snake_case` (`#[serde(rename_all = "snake_case")]`). * * `memory` (issue #5226) is the 13th kind — declarative, in-graph memory @@ -48,6 +48,11 @@ export interface Position { * (`nodeConfig/memoryFields.tsx`) reads/writes the known keys * (`operation`, `scope`, `query`, `flavour`, `key`, `value`, `limit`, * `min_score`) directly off that bag, same as `condition`/`switch`/etc. + * + * `dedup` (issue #5263) is the 14th kind — skips items already seen, keyed + * by a stable per-item `=`-expression (e.g. `=item.id`). Same free-form + * config bag pattern: its one field, `key`, is read/written directly by + * `nodeConfig/dedupFields.tsx`. */ export type NodeKind = | 'trigger' @@ -62,7 +67,8 @@ export type NodeKind = | 'transform' | 'output_parser' | 'sub_workflow' - | 'memory'; + | 'memory' + | 'dedup'; /** * A named connection point on a node. Mirrors `tinyflows::model::Port`. diff --git a/app/src/lib/i18n/ar.ts b/app/src/lib/i18n/ar.ts index 64041679f..3232a6ac9 100644 --- a/app/src/lib/i18n/ar.ts +++ b/app/src/lib/i18n/ar.ts @@ -4093,6 +4093,7 @@ const messages: TranslationMap = { 'flows.nodeKind.output_parser': 'محلل المخرجات', 'flows.nodeKind.sub_workflow': 'سير عمل فرعي', 'flows.nodeKind.memory': 'الذاكرة', + 'flows.nodeKind.dedup': 'إزالة التكرار', 'flows.palette.title': 'العقد', 'flows.palette.addNode': 'إضافة عقدة {kind}', 'flows.editor.save': 'حفظ', @@ -4258,6 +4259,9 @@ const messages: TranslationMap = { 'flows.nodeConfig.memory.limitHint': 'أقصى عدد من النتائج.', 'flows.nodeConfig.memory.minScoreLabel': 'الحد الأدنى للنتيجة', 'flows.nodeConfig.memory.minScoreHint': 'عتبة الصلة من 0 إلى 1.', + 'flows.nodeConfig.dedup.keyLabel': 'المفتاح', + 'flows.nodeConfig.dedup.keyHint': + 'تعبير معرف ثابت لكل عنصر، مثل =item.id. يتم تخطي العناصر التي شوهد مفتاحها من قبل.', 'flows.enableApproval.title': 'هل تسمح لسير العمل هذا بالتنفيذ؟', 'flows.enableApproval.intro': diff --git a/app/src/lib/i18n/bn.ts b/app/src/lib/i18n/bn.ts index 9064ee349..2b97f4551 100644 --- a/app/src/lib/i18n/bn.ts +++ b/app/src/lib/i18n/bn.ts @@ -4195,6 +4195,7 @@ const messages: TranslationMap = { 'flows.nodeKind.output_parser': 'আউটপুট পার্সার', 'flows.nodeKind.sub_workflow': 'সাব-ওয়ার্কফ্লো', 'flows.nodeKind.memory': 'মেমরি', + 'flows.nodeKind.dedup': 'ডুপ্লিকেট অপসারণ', 'flows.palette.title': 'নোড', 'flows.palette.addNode': '{kind} নোড যোগ করুন', 'flows.editor.save': 'সংরক্ষণ করুন', @@ -4365,6 +4366,9 @@ const messages: TranslationMap = { 'flows.nodeConfig.memory.limitHint': 'সর্বোচ্চ ফলাফল সংখ্যা।', 'flows.nodeConfig.memory.minScoreLabel': 'সর্বনিম্ন স্কোর', 'flows.nodeConfig.memory.minScoreHint': '০ থেকে ১ পর্যন্ত প্রাসঙ্গিকতার থ্রেশহোল্ড।', + 'flows.nodeConfig.dedup.keyLabel': 'কী', + 'flows.nodeConfig.dedup.keyHint': + 'প্রতিটি আইটেমের জন্য একটি স্থিতিশীল আইডি এক্সপ্রেশন, যেমন =item.id। যেসব আইটেমের কী আগে দেখা গেছে সেগুলো এড়িয়ে যাওয়া হয়।', 'flows.enableApproval.title': 'এই ওয়ার্কফ্লোকে কাজ করার অনুমতি দেবেন?', 'flows.enableApproval.intro': diff --git a/app/src/lib/i18n/de.ts b/app/src/lib/i18n/de.ts index 19eadf752..49823f453 100644 --- a/app/src/lib/i18n/de.ts +++ b/app/src/lib/i18n/de.ts @@ -4313,6 +4313,7 @@ const messages: TranslationMap = { 'flows.nodeKind.output_parser': 'Ausgabe-Parser', 'flows.nodeKind.sub_workflow': 'Unter-Workflow', 'flows.nodeKind.memory': 'Gedächtnis', + 'flows.nodeKind.dedup': 'Deduplizierung', 'flows.palette.title': 'Knoten', 'flows.palette.addNode': '{kind}-Knoten hinzufügen', 'flows.editor.save': 'Speichern', @@ -4488,6 +4489,9 @@ const messages: TranslationMap = { 'flows.nodeConfig.memory.limitHint': 'Maximale Anzahl an Ergebnissen.', 'flows.nodeConfig.memory.minScoreLabel': 'Mindestbewertung', 'flows.nodeConfig.memory.minScoreHint': 'Relevanzschwelle von 0 bis 1.', + 'flows.nodeConfig.dedup.keyLabel': 'Schlüssel', + 'flows.nodeConfig.dedup.keyHint': + 'Ein stabiler Id-Ausdruck pro Element, z. B. =item.id. Elemente mit einem bereits gesehenen Schlüssel werden übersprungen.', 'flows.enableApproval.title': 'Darf dieser Workflow Aktionen ausführen?', 'flows.enableApproval.intro': diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts index 2bf26e395..342ca4ee4 100644 --- a/app/src/lib/i18n/en.ts +++ b/app/src/lib/i18n/en.ts @@ -4767,6 +4767,7 @@ const en: TranslationMap = { 'flows.nodeKind.output_parser': 'Output parser', 'flows.nodeKind.sub_workflow': 'Sub-workflow', 'flows.nodeKind.memory': 'Memory', + 'flows.nodeKind.dedup': 'Dedup', // ── Editable Workflow Canvas (issue B5b.2 / Phase 3a): the node palette // and editor toolbar layered on top of the read-only canvas above. @@ -4950,6 +4951,10 @@ const en: TranslationMap = { 'flows.nodeConfig.memory.limitHint': 'Maximum number of results.', 'flows.nodeConfig.memory.minScoreLabel': 'Minimum score', 'flows.nodeConfig.memory.minScoreHint': 'Relevance threshold from 0 to 1.', + // `dedup` node (issue #5263): skips items already seen by a stable per-item key. + 'flows.nodeConfig.dedup.keyLabel': 'Key', + 'flows.nodeConfig.dedup.keyHint': + 'A stable per-item id expression, e.g. =item.id. Items with a key already seen are skipped.', // Phase 4a "New workflow" chooser + Phase 4c templates gallery. The chooser // offers scratch / template / describe; the gallery lists the curated diff --git a/app/src/lib/i18n/es.ts b/app/src/lib/i18n/es.ts index 6ad47b1d8..8925fb90d 100644 --- a/app/src/lib/i18n/es.ts +++ b/app/src/lib/i18n/es.ts @@ -4264,6 +4264,7 @@ const messages: TranslationMap = { 'flows.nodeKind.output_parser': 'Analizador de salida', 'flows.nodeKind.sub_workflow': 'Subflujo de trabajo', 'flows.nodeKind.memory': 'Memoria', + 'flows.nodeKind.dedup': 'Deduplicación', 'flows.palette.title': 'Nodos', 'flows.palette.addNode': 'Añadir nodo {kind}', 'flows.editor.save': 'Guardar', @@ -4438,6 +4439,9 @@ const messages: TranslationMap = { 'flows.nodeConfig.memory.limitHint': 'Número máximo de resultados.', 'flows.nodeConfig.memory.minScoreLabel': 'Puntuación mínima', 'flows.nodeConfig.memory.minScoreHint': 'Umbral de relevancia de 0 a 1.', + 'flows.nodeConfig.dedup.keyLabel': 'Clave', + 'flows.nodeConfig.dedup.keyHint': + 'Una expresión de id estable por elemento, p. ej. =item.id. Los elementos con una clave ya vista se omiten.', 'flows.enableApproval.title': '¿Permitir que este flujo de trabajo actúe?', 'flows.enableApproval.intro': diff --git a/app/src/lib/i18n/fr.ts b/app/src/lib/i18n/fr.ts index b1c8d7bee..08522edd2 100644 --- a/app/src/lib/i18n/fr.ts +++ b/app/src/lib/i18n/fr.ts @@ -4292,6 +4292,7 @@ const messages: TranslationMap = { 'flows.nodeKind.output_parser': 'Analyseur de sortie', 'flows.nodeKind.sub_workflow': 'Sous-workflow', 'flows.nodeKind.memory': 'Mémoire', + 'flows.nodeKind.dedup': 'Déduplication', 'flows.palette.title': 'Nœuds', 'flows.palette.addNode': 'Ajouter un nœud {kind}', 'flows.editor.save': 'Enregistrer', @@ -4468,6 +4469,9 @@ const messages: TranslationMap = { 'flows.nodeConfig.memory.limitHint': 'Nombre maximal de résultats.', 'flows.nodeConfig.memory.minScoreLabel': 'Score minimal', 'flows.nodeConfig.memory.minScoreHint': 'Seuil de pertinence de 0 à 1.', + 'flows.nodeConfig.dedup.keyLabel': 'Clé', + 'flows.nodeConfig.dedup.keyHint': + "Une expression d'identifiant stable par élément, ex. =item.id. Les éléments dont la clé a déjà été vue sont ignorés.", 'flows.enableApproval.title': 'Autoriser ce workflow à agir ?', 'flows.enableApproval.intro': diff --git a/app/src/lib/i18n/hi.ts b/app/src/lib/i18n/hi.ts index 7aee4eca1..b01b40842 100644 --- a/app/src/lib/i18n/hi.ts +++ b/app/src/lib/i18n/hi.ts @@ -4192,6 +4192,7 @@ const messages: TranslationMap = { 'flows.nodeKind.output_parser': 'आउटपुट पार्सर', 'flows.nodeKind.sub_workflow': 'सब-वर्कफ़्लो', 'flows.nodeKind.memory': 'मेमोरी', + 'flows.nodeKind.dedup': 'डुप्लिकेट हटाएं', 'flows.palette.title': 'नोड', 'flows.palette.addNode': '{kind} नोड जोड़ें', 'flows.editor.save': 'सहेजें', @@ -4362,6 +4363,9 @@ const messages: TranslationMap = { 'flows.nodeConfig.memory.limitHint': 'अधिकतम परिणामों की संख्या।', 'flows.nodeConfig.memory.minScoreLabel': 'न्यूनतम स्कोर', 'flows.nodeConfig.memory.minScoreHint': '0 से 1 तक की प्रासंगिकता सीमा।', + 'flows.nodeConfig.dedup.keyLabel': 'कुंजी', + 'flows.nodeConfig.dedup.keyHint': + 'प्रति आइटम एक स्थिर आईडी एक्सप्रेशन, जैसे =item.id। जिन आइटम की कुंजी पहले देखी जा चुकी है उन्हें छोड़ दिया जाता है।', 'flows.enableApproval.title': 'क्या इस वर्कफ़्लो को काम करने की अनुमति दें?', 'flows.enableApproval.intro': diff --git a/app/src/lib/i18n/id.ts b/app/src/lib/i18n/id.ts index 6f2f60411..c583042dd 100644 --- a/app/src/lib/i18n/id.ts +++ b/app/src/lib/i18n/id.ts @@ -4208,6 +4208,7 @@ const messages: TranslationMap = { 'flows.nodeKind.output_parser': 'Pengurai keluaran', 'flows.nodeKind.sub_workflow': 'Sub-alur kerja', 'flows.nodeKind.memory': 'Memori', + 'flows.nodeKind.dedup': 'Hapus duplikat', 'flows.palette.title': 'Simpul', 'flows.palette.addNode': 'Tambah simpul {kind}', 'flows.editor.save': 'Simpan', @@ -4380,6 +4381,9 @@ const messages: TranslationMap = { 'flows.nodeConfig.memory.limitHint': 'Jumlah maksimum hasil.', 'flows.nodeConfig.memory.minScoreLabel': 'Skor minimum', 'flows.nodeConfig.memory.minScoreHint': 'Ambang batas relevansi dari 0 hingga 1.', + 'flows.nodeConfig.dedup.keyLabel': 'Kunci', + 'flows.nodeConfig.dedup.keyHint': + 'Ekspresi id stabil per item, mis. =item.id. Item dengan kunci yang sudah pernah dilihat akan dilewati.', 'flows.enableApproval.title': 'Izinkan alur kerja ini bertindak?', 'flows.enableApproval.intro': diff --git a/app/src/lib/i18n/it.ts b/app/src/lib/i18n/it.ts index 23902f498..333ad9088 100644 --- a/app/src/lib/i18n/it.ts +++ b/app/src/lib/i18n/it.ts @@ -4260,6 +4260,7 @@ const messages: TranslationMap = { 'flows.nodeKind.output_parser': 'Analizzatore di output', 'flows.nodeKind.sub_workflow': 'Sotto-flusso di lavoro', 'flows.nodeKind.memory': 'Memoria', + 'flows.nodeKind.dedup': 'Deduplicazione', 'flows.palette.title': 'Nodi', 'flows.palette.addNode': 'Aggiungi nodo {kind}', 'flows.editor.save': 'Salva', @@ -4436,6 +4437,9 @@ const messages: TranslationMap = { 'flows.nodeConfig.memory.limitHint': 'Numero massimo di risultati.', 'flows.nodeConfig.memory.minScoreLabel': 'Punteggio minimo', 'flows.nodeConfig.memory.minScoreHint': 'Soglia di rilevanza da 0 a 1.', + 'flows.nodeConfig.dedup.keyLabel': 'Chiave', + 'flows.nodeConfig.dedup.keyHint': + "Un'espressione id stabile per elemento, es. =item.id. Gli elementi con una chiave già vista vengono ignorati.", 'flows.enableApproval.title': 'Consentire a questo workflow di agire?', 'flows.enableApproval.intro': diff --git a/app/src/lib/i18n/ko.ts b/app/src/lib/i18n/ko.ts index 108e8cb40..6f9315b7d 100644 --- a/app/src/lib/i18n/ko.ts +++ b/app/src/lib/i18n/ko.ts @@ -4145,6 +4145,7 @@ const messages: TranslationMap = { 'flows.nodeKind.output_parser': '출력 파서', 'flows.nodeKind.sub_workflow': '하위 워크플로', 'flows.nodeKind.memory': '메모리', + 'flows.nodeKind.dedup': '중복 제거', 'flows.palette.title': '노드', 'flows.palette.addNode': '{kind} 노드 추가', 'flows.editor.save': '저장', @@ -4313,6 +4314,9 @@ const messages: TranslationMap = { 'flows.nodeConfig.memory.limitHint': '최대 결과 수입니다.', 'flows.nodeConfig.memory.minScoreLabel': '최소 점수', 'flows.nodeConfig.memory.minScoreHint': '0에서 1 사이의 관련성 임곗값입니다.', + 'flows.nodeConfig.dedup.keyLabel': '키', + 'flows.nodeConfig.dedup.keyHint': + '항목별 안정적인 id 표현식입니다. 예: =item.id. 이미 본 키를 가진 항목은 건너뜁니다.', 'flows.enableApproval.title': '이 워크플로가 작업을 수행하도록 허용할까요?', 'flows.enableApproval.intro': diff --git a/app/src/lib/i18n/pl.ts b/app/src/lib/i18n/pl.ts index b67ae0586..a2c2cf4c3 100644 --- a/app/src/lib/i18n/pl.ts +++ b/app/src/lib/i18n/pl.ts @@ -4249,6 +4249,7 @@ const messages: TranslationMap = { 'flows.nodeKind.output_parser': 'Parser wyjścia', 'flows.nodeKind.sub_workflow': 'Podprzepływ pracy', 'flows.nodeKind.memory': 'Pamięć', + 'flows.nodeKind.dedup': 'Deduplikacja', 'flows.palette.title': 'Węzły', 'flows.palette.addNode': 'Dodaj węzeł {kind}', 'flows.editor.save': 'Zapisz', @@ -4422,6 +4423,9 @@ const messages: TranslationMap = { 'flows.nodeConfig.memory.limitHint': 'Maksymalna liczba wyników.', 'flows.nodeConfig.memory.minScoreLabel': 'Minimalny wynik', 'flows.nodeConfig.memory.minScoreHint': 'Próg trafności od 0 do 1.', + 'flows.nodeConfig.dedup.keyLabel': 'Klucz', + 'flows.nodeConfig.dedup.keyHint': + 'Stabilne wyrażenie identyfikatora dla każdego elementu, np. =item.id. Elementy z już widzianym kluczem są pomijane.', 'flows.enableApproval.title': 'Zezwolić temu przepływowi pracy na działanie?', 'flows.enableApproval.intro': diff --git a/app/src/lib/i18n/pt.ts b/app/src/lib/i18n/pt.ts index 75bebb4bb..320b22c41 100644 --- a/app/src/lib/i18n/pt.ts +++ b/app/src/lib/i18n/pt.ts @@ -4253,6 +4253,7 @@ const messages: TranslationMap = { 'flows.nodeKind.output_parser': 'Analisador de saída', 'flows.nodeKind.sub_workflow': 'Subfluxo de trabalho', 'flows.nodeKind.memory': 'Memória', + 'flows.nodeKind.dedup': 'Deduplicação', 'flows.palette.title': 'Nós', 'flows.palette.addNode': 'Adicionar nó {kind}', 'flows.editor.save': 'Salvar', @@ -4426,6 +4427,9 @@ const messages: TranslationMap = { 'flows.nodeConfig.memory.limitHint': 'Número máximo de resultados.', 'flows.nodeConfig.memory.minScoreLabel': 'Pontuação mínima', 'flows.nodeConfig.memory.minScoreHint': 'Limite de relevância de 0 a 1.', + 'flows.nodeConfig.dedup.keyLabel': 'Chave', + 'flows.nodeConfig.dedup.keyHint': + 'Uma expressão de id estável por item, ex.: =item.id. Itens com uma chave já vista são ignorados.', 'flows.enableApproval.title': 'Permitir que este fluxo de trabalho aja?', 'flows.enableApproval.intro': diff --git a/app/src/lib/i18n/ru.ts b/app/src/lib/i18n/ru.ts index 18af023f5..05f46b567 100644 --- a/app/src/lib/i18n/ru.ts +++ b/app/src/lib/i18n/ru.ts @@ -4231,6 +4231,7 @@ const messages: TranslationMap = { 'flows.nodeKind.output_parser': 'Парсер вывода', 'flows.nodeKind.sub_workflow': 'Подпроцесс', 'flows.nodeKind.memory': 'Память', + 'flows.nodeKind.dedup': 'Дедупликация', 'flows.palette.title': 'Узлы', 'flows.palette.addNode': 'Добавить узел {kind}', 'flows.editor.save': 'Сохранить', @@ -4406,6 +4407,9 @@ const messages: TranslationMap = { 'flows.nodeConfig.memory.limitHint': 'Максимальное количество результатов.', 'flows.nodeConfig.memory.minScoreLabel': 'Минимальная оценка', 'flows.nodeConfig.memory.minScoreHint': 'Порог релевантности от 0 до 1.', + 'flows.nodeConfig.dedup.keyLabel': 'Ключ', + 'flows.nodeConfig.dedup.keyHint': + 'Стабильное выражение id для каждого элемента, напр. =item.id. Элементы с уже встреченным ключом пропускаются.', 'flows.enableApproval.title': 'Разрешить этому рабочему процессу действовать?', 'flows.enableApproval.intro': diff --git a/app/src/lib/i18n/zh-CN.ts b/app/src/lib/i18n/zh-CN.ts index 164c74747..13ae5076d 100644 --- a/app/src/lib/i18n/zh-CN.ts +++ b/app/src/lib/i18n/zh-CN.ts @@ -3973,6 +3973,7 @@ const messages: TranslationMap = { 'flows.nodeKind.output_parser': '输出解析器', 'flows.nodeKind.sub_workflow': '子工作流', 'flows.nodeKind.memory': '记忆', + 'flows.nodeKind.dedup': '去重', 'flows.palette.title': '节点', 'flows.palette.addNode': '添加{kind}节点', 'flows.editor.save': '保存', @@ -4134,6 +4135,9 @@ const messages: TranslationMap = { 'flows.nodeConfig.memory.limitHint': '返回结果的最大数量。', 'flows.nodeConfig.memory.minScoreLabel': '最低分数', 'flows.nodeConfig.memory.minScoreHint': '相关性阈值,范围为 0 到 1。', + 'flows.nodeConfig.dedup.keyLabel': '键', + 'flows.nodeConfig.dedup.keyHint': + '每个项目的稳定 id 表达式,例如 =item.id。已出现过该键的项目将被跳过。', 'flows.enableApproval.title': '允许此工作流执行操作吗?', 'flows.enableApproval.intro': '此工作流需要您对以下操作的许可。批准仅适用于此工作流。',