Spaces:
Sleeping
Sleeping
| /** | |
| * NLI Engine — Sends user instructions + subtitle context to Claude, | |
| * receives structured tool calls back. | |
| */ | |
| import { getClaudeClient } from "../../ai/claude-client"; | |
| import { NLI_TOOLS } from "./tools"; | |
| import type { SubtitleData } from "../event-bus"; | |
| export interface NliRequest { | |
| instruction: string; | |
| subtitles: SubtitleData[]; | |
| fps: number; | |
| sourceLanguage?: string; | |
| targetLanguage?: string; | |
| clientId?: string; | |
| maxCPL?: number; | |
| } | |
| export interface NliToolCall { | |
| toolName: string; | |
| input: Record<string, unknown>; | |
| } | |
| export interface NliResult { | |
| toolCalls: NliToolCall[]; | |
| reasoning: string; | |
| } | |
| const SYSTEM_PROMPT = `You are the NLI (Natural Language Interface) engine for GTS-X, a professional subtitle editor. You receive editing instructions in natural language and translate them into precise tool calls that modify subtitles. | |
| You have access to the complete subtitle file as context. Analyze ALL subtitles when the instruction is contextual (e.g., character relationships, tone adjustments, consistency fixes). | |
| IMPORTANT RULES: | |
| - For deterministic operations (timecode shifts, find/replace, FPS conversion), use the appropriate deterministic tool. Do NOT use edit_subtitles for these. | |
| - For semantic/contextual operations (tone adjustment, translation improvement, character voice), use edit_subtitles with the COMPLETE new text for each modified subtitle. | |
| - When editing text, preserve subtitle formatting (line breaks for multi-line subtitles, tags). | |
| - When fixing CPL violations, keep the meaning while shortening. Split into two lines if needed (use \\n for line breaks). | |
| - Always provide a reason for each edit when using edit_subtitles. | |
| - You may call multiple tools in one response if the instruction requires it.`; | |
| function formatSubtitlesForContext(subtitles: SubtitleData[]): string { | |
| return subtitles | |
| .map( | |
| (s, i) => | |
| `[${i}] ${s.speaker ? `(${s.speaker}) ` : ""}${s.tcIn} → ${s.tcOut} | ${s.text.replace(/\n/g, " / ")}`, | |
| ) | |
| .join("\n"); | |
| } | |
| export async function planNliExecution( | |
| request: NliRequest, | |
| ): Promise<NliResult> { | |
| const { client } = getClaudeClient(); | |
| const contextParts: string[] = [ | |
| `FPS: ${request.fps}`, | |
| ]; | |
| if (request.sourceLanguage) | |
| contextParts.push(`Source: ${request.sourceLanguage}`); | |
| if (request.targetLanguage) | |
| contextParts.push(`Target: ${request.targetLanguage}`); | |
| if (request.clientId) contextParts.push(`Client: ${request.clientId}`); | |
| if (request.maxCPL) contextParts.push(`Max CPL: ${request.maxCPL}`); | |
| const userMessage = `${contextParts.join(" | ")} | |
| Total subtitles: ${request.subtitles.length} | |
| SUBTITLES: | |
| ${formatSubtitlesForContext(request.subtitles)} | |
| INSTRUCTION: ${request.instruction}`; | |
| const response = await client.messages.create({ | |
| model: "claude-sonnet-4-20250514", | |
| max_tokens: 8192, | |
| system: SYSTEM_PROMPT, | |
| tools: NLI_TOOLS, | |
| messages: [{ role: "user", content: userMessage }], | |
| }); | |
| // Extract tool calls and reasoning from response | |
| const toolCalls: NliToolCall[] = []; | |
| let reasoning = ""; | |
| for (const block of response.content) { | |
| if (block.type === "text") { | |
| reasoning += block.text; | |
| } else if (block.type === "tool_use") { | |
| toolCalls.push({ | |
| toolName: block.name, | |
| input: block.input as Record<string, unknown>, | |
| }); | |
| } | |
| } | |
| return { toolCalls, reasoning }; | |
| } | |