/** * Frontend-side variable substitution for display during navigation. * * - [VAR:name] → replaced with variables[name] * - [USER_INPUT:prompt] → replaced with variables[prompt] * - [SAVE_AS:name] → removed from display */ export interface UnresolvedVariable { variableName: string token: string // e.g. "[VAR:server_name]" } export interface ResolveResult { text: string unresolvedVariables: UnresolvedVariable[] } /** * Resolve variables in text, replacing tokens with values. * Missing/empty values are replaced with "N/A". */ export function resolveVariables(text: string, variables: Record): string { // Replace [VAR:name] — empty/missing values show "N/A" let result = text.replace(/\[VAR:([^\]]+)\]/g, (_, name) => { const key = name.trim() const value = variables[key] return value && value.trim() ? value : 'N/A' }) // Replace [USER_INPUT:prompt] result = result.replace(/\[USER_INPUT:([^\]]+)\]/g, (_, prompt) => { const key = prompt.trim() const value = variables[key] return value && value.trim() ? value : 'N/A' }) // Remove [SAVE_AS:name] result = result.replace(/\[SAVE_AS:[^\]]+\]/g, '') return result } /** * Resolve variables but also return info about which variables are unresolved. * Unresolved variables are left as their original tokens in the text (not replaced with N/A). */ export function resolveVariablesWithStatus(text: string, variables: Record): ResolveResult { const unresolvedVariables: UnresolvedVariable[] = [] // Replace [VAR:name] — resolved values get substituted, unresolved stay as tokens let result = text.replace(/\[VAR:([^\]]+)\]/g, (match, name) => { const key = name.trim() const value = variables[key] if (value && value.trim()) { return value } unresolvedVariables.push({ variableName: key, token: match }) return match // Keep original token for inline prompt rendering }) // Replace [USER_INPUT:prompt] result = result.replace(/\[USER_INPUT:([^\]]+)\]/g, (match, prompt) => { const key = prompt.trim() const value = variables[key] if (value && value.trim()) { return value } unresolvedVariables.push({ variableName: key, token: match }) return match }) // Remove [SAVE_AS:name] result = result.replace(/\[SAVE_AS:[^\]]+\]/g, '') return { text: result, unresolvedVariables } } /** * Extract all [USER_INPUT:prompt] tokens from text. * Returns array of prompt strings. */ export function extractUserInputPrompts(text: string): string[] { const prompts: string[] = [] const re = /\[USER_INPUT:([^\]]+)\]/g let match while ((match = re.exec(text)) !== null) { const prompt = match[1].trim() if (!prompts.includes(prompt)) { prompts.push(prompt) } } return prompts } /** * Check if text contains any variable tokens. */ export function hasVariableTokens(text: string): boolean { return /\[(USER_INPUT|VAR|SAVE_AS):[^\]]+\]/.test(text) }