refactor: improve seed source handling with additional type support, enhanced parsing logic, and text chunking optimization

This commit is contained in:
Shine1i 2026-02-23 19:29:54 +01:00
commit af00aeb9e4
8 changed files with 98 additions and 32 deletions

View file

@ -33,6 +33,7 @@ import {
TabsList,
TabsTrigger,
} from "@/components/ui/tabs";
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
import mammoth from "mammoth";
import { type ReactElement, useEffect, useMemo, useState } from "react";
import { extractText, getDocumentProxy } from "unpdf";
@ -60,6 +61,10 @@ const UNSTRUCTURED_ACCEPT = ".txt,.pdf,.docx";
const MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
const CHUNK_SIZE = 1200;
const CHUNK_OVERLAP = 200;
const UNSTRUCTURED_SPLITTER = new RecursiveCharacterTextSplitter({
chunkSize: CHUNK_SIZE,
chunkOverlap: CHUNK_OVERLAP,
});
type SeedDialogProps = {
config: SeedConfig;
@ -84,20 +89,12 @@ function stringifyCell(value: unknown): string {
}
}
function chunkText(input: string): string[] {
async function chunkText(input: string): Promise<string[]> {
const text = input.replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
if (!text) return [];
const chunks: string[] = [];
let cursor = 0;
const overlap = Math.max(0, Math.min(CHUNK_OVERLAP, CHUNK_SIZE - 1));
while (cursor < text.length) {
const end = Math.min(cursor + CHUNK_SIZE, text.length);
chunks.push(text.slice(cursor, end).trim());
if (end >= text.length) break;
cursor = Math.max(0, end - overlap);
}
return chunks.filter(Boolean);
const chunks = await UNSTRUCTURED_SPLITTER.splitText(text);
return chunks.map((chunk) => chunk.trim()).filter(Boolean);
}
async function fileToBase64Payload(file: File): Promise<string> {
@ -218,7 +215,7 @@ export function SeedDialog({ config, onUpdate }: SeedDialogProps): ReactElement
}
const text = await extractUnstructuredText(unstructuredFile);
const chunks = chunkText(text);
const chunks = await chunkText(text);
if (chunks.length === 0) {
throw new Error("No text found in file.");
}

View file

@ -300,12 +300,12 @@ export const useRecipeStudioStore = create<RecipeStudioState>((set, get) => ({
if (!existing) {
return buildAddedNodeState(state, "seed", type);
}
const nextSourceType: SeedSourceType =
type === "seed_local"
? "local"
: type === "seed_unstructured"
? "unstructured"
: "hf";
let nextSourceType: SeedSourceType = "hf";
if (type === "seed_local") {
nextSourceType = "local";
} else if (type === "seed_unstructured") {
nextSourceType = "unstructured";
}
const nextConfig = {
...existing,

View file

@ -4,6 +4,7 @@ import type {
LlmToolConfig,
NodeConfig,
RecipeProcessorConfig,
SeedSourceType,
} from "../../types";
import { buildEdges } from "./edges";
import { isRecord, parseJson, readString } from "./helpers";
@ -29,6 +30,7 @@ type RecipeInput = {
type UiInput = {
nodes?: unknown;
edges?: unknown;
seed_source_type?: unknown;
};
function parseProcessors(input: unknown): RecipeProcessorConfig[] {
@ -219,11 +221,18 @@ export function importRecipePayload(input: string): ImportResult {
const nameToId = new Map<string, string>();
let nextId = 1;
const uiSeedSourceTypeRaw = readString(ui?.seed_source_type);
const uiSeedSourceType: SeedSourceType | undefined =
uiSeedSourceTypeRaw === "hf" ||
uiSeedSourceTypeRaw === "local" ||
uiSeedSourceTypeRaw === "unstructured"
? uiSeedSourceTypeRaw
: undefined;
if (recipe.seed_config) {
const id = `n${nextId}`;
nextId += 1;
const seedConfig = parseSeedConfig(recipe.seed_config, id);
const seedConfig = parseSeedConfig(recipe.seed_config, id, uiSeedSourceType);
if (seedConfig) {
configs.push(seedConfig);
}

View file

@ -65,7 +65,7 @@ function parseSeedSettings(seedConfigRaw: unknown): Partial<SeedConfig> {
let hf_endpoint = "https://huggingface.co";
let hf_repo_id = "";
let local_file_name = "";
let unstructured_file_name = "";
const unstructured_file_name = "";
const sourceRaw = seedConfigRaw.source;
if (isRecord(sourceRaw)) {
const seedType = readString(sourceRaw.seed_type);
@ -127,12 +127,23 @@ function parseSeedSettings(seedConfigRaw: unknown): Partial<SeedConfig> {
export function parseSeedConfig(
seedConfigRaw: unknown,
id: string,
preferredSourceType?: SeedSourceType,
): SeedConfig | null {
if (!seedConfigRaw) {
return null;
}
const parsed = parseSeedSettings(seedConfigRaw);
let sourceType: SeedSourceType = "hf";
if (parsed.seed_source_type === "hf") {
sourceType = "hf";
} else if (preferredSourceType) {
sourceType = preferredSourceType;
} else if (parsed.seed_source_type) {
sourceType = parsed.seed_source_type;
}
return {
...makeDefaultSeedConfig(id),
...parseSeedSettings(seedConfigRaw), // payload-only fields override ui defaults
...parsed, // payload-only fields override ui defaults
seed_source_type: sourceType,
};
}

View file

@ -241,6 +241,7 @@ export function buildRecipePayload(
ui: {
nodes: uiNodes,
edges: uiEdges,
...(firstSeed && { seed_source_type: firstSeed.seed_source_type }),
},
},
};

View file

@ -30,6 +30,8 @@ export type RecipePayload = {
ui: {
nodes: { id: string; x: number; y: number }[];
edges: { from: string; to: string; type?: string }[];
// ui-only, used to preserve seed block mode across imports/refresh
seed_source_type?: "hf" | "local" | "unstructured";
};
};