From 8efdf32d4b6e444116bae3da91d94868d2c11738 Mon Sep 17 00:00:00 2001 From: Failerko Date: Sun, 29 Mar 2026 04:08:31 +0200 Subject: [PATCH 01/14] feat: add template context group mapping with per-group variable definitions Co-Authored-By: Claude Opus 4.6 (1M context) --- .../services/templates/templateContextMap.ts | 840 ++++++++++++++++++ 1 file changed, 840 insertions(+) create mode 100644 src/lib/services/templates/templateContextMap.ts diff --git a/src/lib/services/templates/templateContextMap.ts b/src/lib/services/templates/templateContextMap.ts new file mode 100644 index 00000000..78ebfc46 --- /dev/null +++ b/src/lib/services/templates/templateContextMap.ts @@ -0,0 +1,840 @@ +/** + * Template Context Map + * + * Maps each templateId to a context group name and defines the variables + * available in each group. Replaces the flat VariableRegistry with a + * structured, per-group approach that reflects what data each family of + * templates actually receives at render time. + */ + +import type { VariableDefinition, VariableFieldInfo } from './types' + +// --------------------------------------------------------------------------- +// Context group names +// --------------------------------------------------------------------------- + +export type ContextGroupName = + | 'promptContext' + | 'timelineFillAnswer' + | 'wizard' + | 'vault' + | 'lore' + | 'import' + | 'portrait' + | 'translateWizard' + +// --------------------------------------------------------------------------- +// Display group (semantic UI grouping) +// --------------------------------------------------------------------------- + +export interface DisplayGroup { + /** Human-readable group label */ + label: string + /** Variable names in this group */ + variables: string[] +} + +// --------------------------------------------------------------------------- +// Template -> context group mapping +// --------------------------------------------------------------------------- + +const TEMPLATE_GROUP_MAP: Record = { + // promptContext + 'adventure': 'promptContext', + 'creative-writing': 'promptContext', + 'classifier': 'promptContext', + 'suggestions': 'promptContext', + 'action-choices': 'promptContext', + 'style-reviewer': 'promptContext', + 'chapter-analysis': 'promptContext', + 'chapter-summarization': 'promptContext', + 'agentic-retrieval': 'promptContext', + 'timeline-fill': 'promptContext', + 'tier3-entry-selection': 'promptContext', + 'image-prompt-analysis': 'promptContext', + 'image-prompt-analysis-reference': 'promptContext', + 'background-image-prompt-analysis': 'promptContext', + 'translate-narration': 'promptContext', + 'translate-input': 'promptContext', + 'translate-ui': 'promptContext', + 'translate-suggestions': 'promptContext', + 'translate-action-choices': 'promptContext', + + // timelineFillAnswer + 'timeline-fill-answer': 'timelineFillAnswer', + + // wizard + 'setting-expansion': 'wizard', + 'setting-refinement': 'wizard', + 'character-elaboration': 'wizard', + 'character-refinement': 'wizard', + 'protagonist-generation': 'wizard', + 'supporting-characters': 'wizard', + 'opening-generation-adventure': 'wizard', + 'opening-generation-creative': 'wizard', + 'opening-refinement-adventure': 'wizard', + 'opening-refinement-creative': 'wizard', + + // vault + 'interactive-lorebook': 'vault', + + // lore + 'lore-management': 'lore', + + // import + 'character-card-import': 'import', + 'vault-character-import': 'import', + 'lorebook-classifier': 'import', + + // portrait + 'image-portrait-generation': 'portrait', + + // translateWizard + 'translate-wizard-content': 'translateWizard', +} + +// --------------------------------------------------------------------------- +// Helper to build VariableDefinition concisely +// --------------------------------------------------------------------------- + +function v( + name: string, + type: VariableDefinition['type'], + description: string, + opts?: { + required?: boolean + category?: VariableDefinition['category'] + enumValues?: string[] + infoFields?: VariableFieldInfo[] + }, +): VariableDefinition { + return { + name, + type, + category: opts?.category ?? 'runtime', + description, + required: opts?.required ?? false, + ...(opts?.enumValues ? { enumValues: opts.enumValues } : {}), + ...(opts?.infoFields ? { infoFields: opts.infoFields } : {}), + } +} + +// --------------------------------------------------------------------------- +// Shared infoFields used across multiple groups +// --------------------------------------------------------------------------- + +const storyEntryFields: VariableFieldInfo[] = [ + { name: 'type', type: 'string', description: 'user_action or narration' }, + { name: 'content', type: 'string', description: 'Entry text' }, +] + +const storyEntryRawFields: VariableFieldInfo[] = [ + { name: 'type', type: 'string', description: 'Entry type' }, + { name: 'content', type: 'string', description: 'Entry text' }, + { name: 'id', type: 'string', description: 'Entry ID' }, +] + +const characterFields: VariableFieldInfo[] = [ + { name: 'name', type: 'string', description: 'Character name' }, + { name: 'relationship', type: 'string', description: 'e.g. companion, rival, ally (nullable)' }, + { name: 'description', type: 'string', description: 'Character description (nullable)' }, + { name: 'traits', type: 'string[]', description: 'Personality traits' }, + { name: 'appearance', type: 'string[]', description: 'Visual appearance details' }, + { name: 'status', type: 'string', description: 'active, inactive, or deceased' }, +] + +const locationFields: VariableFieldInfo[] = [ + { name: 'name', type: 'string', description: 'Location name' }, + { name: 'description', type: 'string', description: 'Location description' }, +] + +const itemFields: VariableFieldInfo[] = [ + { name: 'name', type: 'string', description: 'Item name' }, + { name: 'description', type: 'string', description: 'Item description (nullable)' }, + { name: 'quantity', type: 'number', description: 'Quantity held' }, + { name: 'equipped', type: 'boolean', description: 'Whether equipped' }, + { name: 'location', type: 'string', description: 'Item location' }, +] + +const storyBeatFields: VariableFieldInfo[] = [ + { name: 'title', type: 'string', description: 'Beat title' }, + { name: 'description', type: 'string', description: 'Beat description' }, + { name: 'type', type: 'string', description: 'e.g. discovery, conflict, quest' }, + { name: 'status', type: 'string', description: 'e.g. active, completed, failed' }, +] + +const chapterFields: VariableFieldInfo[] = [ + { name: 'number', type: 'number', description: 'Chapter number' }, + { name: 'title', type: 'string', description: 'Chapter title' }, + { name: 'summary', type: 'string', description: 'Chapter summary text' }, + { name: 'startTime', type: 'string', description: 'Formatted time or null' }, + { name: 'endTime', type: 'string', description: 'Formatted time or null' }, + { name: 'characters', type: 'string[]', description: 'Character names' }, + { name: 'locations', type: 'string[]', description: 'Location names' }, + { name: 'emotionalTone', type: 'string', description: 'Emotional tone' }, +] + +const lorebookEntryFields: VariableFieldInfo[] = [ + { name: 'name', type: 'string', description: 'Entry name' }, + { name: 'type', type: 'string', description: 'e.g. character, location, item, faction' }, + { name: 'description', type: 'string', description: 'Entry description' }, + { name: 'tier', type: 'number', description: 'Retrieval tier 1-3 (omitted for wizard)' }, + { name: 'disposition', type: 'string', description: 'Current disposition (character-only, optional)' }, + { name: 'hiddenInfo', type: 'string', description: 'Hidden lore (optional)' }, +] + +// --------------------------------------------------------------------------- +// promptContext variable definitions +// --------------------------------------------------------------------------- + +const PROMPT_CONTEXT_VARS: VariableDefinition[] = [ + // Story Config + v('mode', 'enum', 'Story mode', { + required: true, + category: 'system', + enumValues: ['adventure', 'creative-writing'], + }), + v('pov', 'enum', 'Point of view', { + required: true, + category: 'system', + enumValues: ['first', 'second', 'third'], + }), + v('tense', 'enum', 'Narrative tense', { + required: true, + category: 'system', + enumValues: ['past', 'present'], + }), + v('protagonistName', 'text', 'Name of the main character', { + required: true, + category: 'system', + }), + v('protagonistDescription', 'text', 'Physical/personality description of the protagonist', { + category: 'system', + }), + v('genre', 'text', 'Story genre', { category: 'system' }), + v('settingDescription', 'text', 'World/setting description', { category: 'system' }), + v('tone', 'text', 'Story tone/mood', { category: 'system' }), + v('themes', 'array', 'Story themes', { category: 'system' }), + + // Story Content + v('storyEntries', 'array', 'Story entries (type + content pairs)', { + infoFields: storyEntryFields, + }), + v('storyEntriesRaw', 'array', 'Full StoryEntry objects (unfiltered)', { + infoFields: storyEntryRawFields, + }), + v('storyEntriesVisible', 'array', 'Visible story entries (filtered)', { + infoFields: storyEntryFields, + }), + v('storyEntriesVisibleRaw', 'array', 'Visible story entries (full objects)', { + infoFields: storyEntryRawFields, + }), + v('userInput', 'text', 'Current user input text'), + v('userActionOriginal', 'text', 'Original user action before processing'), + v('lastNarrativeEntry', 'object', 'Most recent narrative entry', { + infoFields: [ + { name: 'type', type: 'string', description: 'Always narration' }, + { name: 'content', type: 'string', description: 'Narration text' }, + ], + }), + + // Entity Data + v('characters', 'array', 'Story characters', { infoFields: characterFields }), + v('locations', 'array', 'Known story locations', { infoFields: locationFields }), + v('items', 'array', 'Player inventory items', { infoFields: itemFields }), + v('storyBeats', 'array', 'Active story beats/threads', { infoFields: storyBeatFields }), + v('chapters', 'array', 'Story chapters', { infoFields: chapterFields }), + v('lorebookEntries', 'array', 'Lorebook entries', { infoFields: lorebookEntryFields }), + + // World State + v('relevantWorldState', 'object', 'Tiered world state from EntryInjector', { + infoFields: [ + { name: 'characters', type: 'array', description: 'Tiered characters with visual descriptors' }, + { name: 'inventory', type: 'array', description: 'Player inventory items' }, + { name: 'storyBeats', type: 'array', description: 'Active story threads' }, + { name: 'locations', type: 'array', description: 'Known locations' }, + { name: 'relevantItems', type: 'array', description: 'Tier 2/3 relevant items' }, + { name: 'relatedStoryBeats', type: 'array', description: 'Tier 2/3 related beats' }, + ], + }), + + // Entry Selection + v('loreEntriesForTier3', 'array', 'Lorebook entries for tier-3 LLM selection'), + v('worldStateForTier3', 'array', 'World state candidates for tier-3 selection'), + + // Settings + v('userSettings', 'object', 'User and story settings', { + infoFields: [ + { name: 'visualProseMode', type: 'boolean', description: 'Whether visual prose mode is enabled' }, + { + name: 'classifier.maxEntries', + type: 'number', + description: 'Max entries for classifier', + }, + { + name: 'retieval.maxStoryEntries', + type: 'number', + description: 'Max story entries for retrieval', + }, + { + name: 'agenticRetrieval.recentEntriesCount', + type: 'number', + description: 'Recent entries count', + }, + { + name: 'agenticRetrieval.maxChapters', + type: 'number', + description: 'Max chapters per retrieval', + }, + { + name: 'agenticRetrieval.summaryCharLimit', + type: 'number', + description: 'Summary character limit', + }, + { + name: 'agenticRetrieval.maxLorebookEntries', + type: 'number', + description: 'Max lorebook entries', + }, + { name: 'memoryConfig', type: 'object', description: 'Memory configuration' }, + { name: 'lorebookConfig', type: 'object', description: 'Lorebook limits settings' }, + { + name: 'imageGeneration.inlineImageMode', + type: 'boolean', + description: 'Inline image mode enabled', + }, + { + name: 'imageGeneration.referenceMode', + type: 'boolean', + description: 'Reference mode enabled', + }, + { + name: 'imageGeneration.maxImages', + type: 'number', + description: 'Max images per message', + }, + { + name: 'imageGeneration.stylePrompt', + type: 'string', + description: 'Style prompt for image generation', + }, + { + name: 'translationSettings.targetLanguage.code', + type: 'string', + description: 'Target language code', + }, + { + name: 'translationSettings.targetLanguage.name', + type: 'string', + description: 'Target language name', + }, + { + name: 'translationSettings.sourceLanguage.code', + type: 'string', + description: 'Source language code', + }, + { + name: 'translationSettings.sourceLanguage.name', + type: 'string', + description: 'Source language name', + }, + ], + }), + + // Generation Results + v('styleReview', 'object', 'Style analysis result', { + infoFields: [ + { name: 'phrases', type: 'array', description: 'Array of PhraseAnalysis objects' }, + { name: 'phrases[].phrase', type: 'string', description: 'The overused phrase' }, + { name: 'phrases[].frequency', type: 'number', description: 'Times used' }, + { name: 'phrases[].severity', type: 'string', description: 'low, medium, or high' }, + { name: 'phrases[].alternatives', type: 'string[]', description: 'Suggested replacements' }, + { name: 'overallAssessment', type: 'string', description: 'Overall style assessment' }, + { name: 'reviewedEntryCount', type: 'number', description: 'Entries analyzed' }, + ], + }), + v('retrievalResult', 'object', 'Lorebook retrieval results'), + v('classificationResult', 'object', 'World state classification results'), + v('narrativeResult', 'object', 'Narrative generation result', { + infoFields: [ + { name: 'content', type: 'string', description: 'Generated narrative text' }, + ], + }), + v('translationResult', 'object', 'Translation results'), + + // Memory + v('chapterAnalysis', 'object', 'Chapter analysis context', { + infoFields: [ + { name: 'result', type: 'object', description: 'Chapter analysis result' }, + { name: 'protectedEntryCount', type: 'number', description: 'Protected entry count' }, + { name: 'analysisEntries', type: 'array', description: 'Entries for analysis (StoryEntry[])' }, + { name: 'chapterEntries', type: 'array', description: 'Entries within chapter (StoryEntry[])' }, + ], + }), + v('lastChapterEndIndex', 'number', 'Index of last chapter end'), + + // Pack Variables + v('packVariables', 'object', 'Pack runtime variable values', { + infoFields: [ + { + name: 'runtimeVariables', + type: 'object', + description: 'Runtime variables grouped by entity type', + }, + ], + }), + + // Translation Data + v('suggestionsToTranslate', 'array', 'Suggestions for translation'), + v('actionChoicesToTranslate', 'array', 'Action choices for translation'), + v('uiElementsToTranslate', 'array', 'UI elements for translation'), + + // Time + v('timeTracker', 'object', 'In-story time tracking'), +] + +// --------------------------------------------------------------------------- +// timelineFillAnswer variable definitions +// (all promptContext vars + extras) +// --------------------------------------------------------------------------- + +const TIMELINE_FILL_ANSWER_EXTRA_VARS: VariableDefinition[] = [ + v('answerChapters', 'array', 'Chapters with optional embedded entries', { + infoFields: [ + { name: 'number', type: 'number', description: 'Chapter number' }, + { name: 'title', type: 'string', description: 'Chapter title' }, + { name: 'summary', type: 'string', description: 'Chapter summary' }, + { name: 'entries', type: 'array', description: 'Optional story entries within chapter' }, + ], + }), + v('query', 'text', 'Query for timeline fill answer'), +] + +const TIMELINE_FILL_ANSWER_VARS: VariableDefinition[] = [ + ...PROMPT_CONTEXT_VARS, + ...TIMELINE_FILL_ANSWER_EXTRA_VARS, +] + +// --------------------------------------------------------------------------- +// wizard variable definitions +// --------------------------------------------------------------------------- + +const WIZARD_VARS: VariableDefinition[] = [ + // Story Setup (scalars) + v('genreLabel', 'text', 'Genre label for display'), + v('seed', 'text', 'Seed text or idea for generation'), + v('customInstruction', 'text', 'Custom user instruction'), + v('toneInstruction', 'text', 'Tone instruction text'), + v('settingInstruction', 'text', 'Setting instruction text'), + v('settingContext', 'text', 'Setting context text'), + v('settingName', 'text', 'Setting name'), + v('settingDescription', 'text', 'Setting description'), + v('settingAtmosphere', 'text', 'Setting atmosphere and mood'), + v('settingThemesText', 'text', 'Setting themes as text'), + v('count', 'number', 'Number of items to generate'), + v('title', 'text', 'Title for generation'), + v('atmosphere', 'text', 'Atmosphere description'), + v('openingGuidance', 'text', 'Guidance for opening generation'), + v('tenseInstruction', 'text', 'Tense instruction text'), + v('mode', 'enum', 'Story mode', { + enumValues: ['adventure', 'creative-writing'], + }), + v('pov', 'enum', 'Point of view', { + enumValues: ['first', 'second', 'third'], + }), + v('tone', 'text', 'Story tone/mood'), + v('protagonistName', 'text', 'Protagonist name'), + v('protagonistDescription', 'text', 'Protagonist description'), + + // Setting (objects) + v('currentSetting', 'object', 'Current setting data', { + infoFields: [ + { name: 'name', type: 'string', description: 'Setting name' }, + { name: 'description', type: 'string', description: 'Setting description' }, + { name: 'atmosphere', type: 'string', description: 'Atmosphere and mood' }, + { name: 'themes', type: 'string[]', description: 'Thematic elements' }, + { name: 'potentialConflicts', type: 'string[]', description: 'Potential story conflicts' }, + { + name: 'keyLocations', + type: 'array', + description: 'Key locations ({name, description})', + }, + ], + }), + + // Character (objects) + v('characterInput', 'object', 'Character input for elaboration', { + infoFields: [ + { name: 'name', type: 'string', description: 'Character name' }, + { name: 'description', type: 'string', description: 'Physical description' }, + { name: 'background', type: 'string', description: 'Backstory' }, + { name: 'motivation', type: 'string', description: 'Goals' }, + { name: 'traits', type: 'string[]', description: 'Personality traits' }, + ], + }), + v('currentCharacter', 'object', 'Current character data for refinement', { + infoFields: [ + { name: 'name', type: 'string', description: 'Character name' }, + { name: 'description', type: 'string', description: 'Physical description' }, + { name: 'background', type: 'string', description: 'Backstory' }, + { name: 'motivation', type: 'string', description: 'Goals' }, + { name: 'traits', type: 'string[]', description: 'Personality traits' }, + { name: 'appearance', type: 'string', description: 'Detailed appearance' }, + ], + }), + v('protagonist', 'object', 'Protagonist data', { + infoFields: [ + { name: 'name', type: 'string', description: 'Protagonist name' }, + { name: 'description', type: 'string', description: 'Description' }, + { name: 'motivation', type: 'string', description: 'Motivation' }, + ], + }), + + // Opening (object) + v('currentOpening', 'object', 'Current opening data for refinement', { + infoFields: [ + { name: 'title', type: 'string', description: 'Opening title' }, + { name: 'scene', type: 'string', description: 'Opening scene prose' }, + { + name: 'initialLocation', + type: 'object', + description: 'Starting location ({name, description})', + }, + ], + }), + + // Lorebook + v('lorebookEntries', 'array', 'Lorebook entries', { infoFields: lorebookEntryFields }), + + // Supporting Characters + v('supportingCharacters', 'array', 'Supporting characters', { + infoFields: [ + { name: 'name', type: 'string', description: 'Character name' }, + { name: 'role', type: 'string', description: 'Role (ally, antagonist, etc.)' }, + { name: 'description', type: 'string', description: 'Character description' }, + { name: 'relationship', type: 'string', description: 'Relationship to protagonist' }, + { name: 'traits', type: 'string[]', description: 'Personality traits' }, + ], + }), +] + +// --------------------------------------------------------------------------- +// vault variable definitions +// --------------------------------------------------------------------------- + +const VAULT_VARS: VariableDefinition[] = [ + v('characterCount', 'number', 'Number of vault characters'), + v('lorebookCount', 'number', 'Number of lorebook entries'), + v('totalEntryCount', 'number', 'Total number of vault entries'), + v('scenarioCount', 'number', 'Number of scenarios'), + v('focusedEntity', 'object', 'Currently focused vault entity', { + infoFields: [ + { name: 'entityType', type: 'string', description: 'Entity type' }, + { name: 'entityName', type: 'string', description: 'Entity name' }, + { name: 'entityId', type: 'string', description: 'Entity ID' }, + ], + }), +] + +// --------------------------------------------------------------------------- +// lore variable definitions +// --------------------------------------------------------------------------- + +const LORE_VARS: VariableDefinition[] = [ + v('loreEntries', 'array', 'Lorebook entries for management', { + infoFields: [ + { name: 'name', type: 'string', description: 'Entry name' }, + { name: 'type', type: 'string', description: 'Entry type' }, + { name: 'description', type: 'string', description: 'Entry description' }, + { name: 'state', type: 'string', description: 'Current dynamic state (optional)' }, + ], + }), + v('loreChapters', 'array', 'Chapters for lore context', { + infoFields: [ + { name: 'number', type: 'number', description: 'Chapter number' }, + { name: 'title', type: 'string', description: 'Chapter title' }, + { name: 'summary', type: 'string', description: 'Chapter summary' }, + ], + }), +] + +// --------------------------------------------------------------------------- +// import variable definitions +// --------------------------------------------------------------------------- + +const IMPORT_VARS: VariableDefinition[] = [ + v('genre', 'text', 'Genre for import context'), + v('title', 'text', 'Title of imported content'), + v('cardContent', 'text', 'Raw character card content'), + v('entriesJson', 'text', 'Entries as JSON string'), +] + +// --------------------------------------------------------------------------- +// portrait variable definitions +// --------------------------------------------------------------------------- + +const PORTRAIT_VARS: VariableDefinition[] = [ + v('imageStylePrompt', 'text', 'Style prompt for portrait generation'), + v('visualDescriptors', 'object', 'Character visual descriptors', { + infoFields: [ + { name: 'face', type: 'string', description: 'Skin tone, facial features, expression' }, + { name: 'hair', type: 'string', description: 'Color, length, style' }, + { name: 'eyes', type: 'string', description: 'Color, shape, notable features' }, + { name: 'build', type: 'string', description: 'Height, body type, posture' }, + { name: 'clothing', type: 'string', description: 'Full outfit description' }, + { name: 'accessories', type: 'string', description: 'Jewelry, weapons, bags' }, + { name: 'distinguishing', type: 'string', description: 'Scars, tattoos, birthmarks' }, + ], + }), +] + +// --------------------------------------------------------------------------- +// translateWizard variable definitions +// --------------------------------------------------------------------------- + +const TRANSLATE_WIZARD_VARS: VariableDefinition[] = [ + v('targetLanguage', 'text', 'Target language for translation'), + v('content', 'text', 'Content to translate'), +] + +// --------------------------------------------------------------------------- +// Group name -> variable definitions +// --------------------------------------------------------------------------- + +const GROUP_VARIABLES: Record = { + promptContext: PROMPT_CONTEXT_VARS, + timelineFillAnswer: TIMELINE_FILL_ANSWER_VARS, + wizard: WIZARD_VARS, + vault: VAULT_VARS, + lore: LORE_VARS, + import: IMPORT_VARS, + portrait: PORTRAIT_VARS, + translateWizard: TRANSLATE_WIZARD_VARS, +} + +// --------------------------------------------------------------------------- +// Display groups per context group +// --------------------------------------------------------------------------- + +const PROMPT_CONTEXT_DISPLAY_GROUPS: DisplayGroup[] = [ + { + label: 'Story Config', + variables: [ + 'mode', + 'pov', + 'tense', + 'protagonistName', + 'protagonistDescription', + 'genre', + 'settingDescription', + 'tone', + 'themes', + ], + }, + { + label: 'Story Content', + variables: [ + 'storyEntries', + 'storyEntriesRaw', + 'storyEntriesVisible', + 'storyEntriesVisibleRaw', + 'userInput', + 'userActionOriginal', + 'lastNarrativeEntry', + ], + }, + { + label: 'Entities', + variables: [ + 'characters', + 'locations', + 'items', + 'storyBeats', + 'chapters', + 'lorebookEntries', + ], + }, + { + label: 'World State', + variables: ['relevantWorldState'], + }, + { + label: 'Entry Selection', + variables: ['loreEntriesForTier3', 'worldStateForTier3'], + }, + { + label: 'Settings', + variables: ['userSettings'], + }, + { + label: 'Generation Results', + variables: [ + 'styleReview', + 'retrievalResult', + 'classificationResult', + 'narrativeResult', + 'translationResult', + ], + }, + { + label: 'Memory', + variables: ['chapterAnalysis', 'lastChapterEndIndex'], + }, + { + label: 'Pack Variables', + variables: ['packVariables'], + }, + { + label: 'Translation Data', + variables: ['suggestionsToTranslate', 'actionChoicesToTranslate', 'uiElementsToTranslate'], + }, + { + label: 'Time', + variables: ['timeTracker'], + }, +] + +const TIMELINE_FILL_ANSWER_DISPLAY_GROUPS: DisplayGroup[] = [ + ...PROMPT_CONTEXT_DISPLAY_GROUPS, + { + label: 'Timeline Fill', + variables: ['answerChapters', 'query'], + }, +] + +const WIZARD_DISPLAY_GROUPS: DisplayGroup[] = [ + { + label: 'Story Setup', + variables: [ + 'genreLabel', + 'seed', + 'customInstruction', + 'toneInstruction', + 'settingInstruction', + 'settingContext', + 'settingName', + 'settingDescription', + 'settingAtmosphere', + 'settingThemesText', + 'count', + 'title', + 'atmosphere', + 'openingGuidance', + 'tenseInstruction', + 'mode', + 'pov', + 'tone', + 'protagonistName', + 'protagonistDescription', + ], + }, + { + label: 'Setting', + variables: ['currentSetting'], + }, + { + label: 'Character', + variables: ['characterInput', 'currentCharacter', 'protagonist', 'supportingCharacters'], + }, + { + label: 'Opening', + variables: ['currentOpening'], + }, + { + label: 'Lorebook', + variables: ['lorebookEntries'], + }, +] + +const VAULT_DISPLAY_GROUPS: DisplayGroup[] = [ + { + label: 'Vault', + variables: [ + 'characterCount', + 'lorebookCount', + 'totalEntryCount', + 'scenarioCount', + 'focusedEntity', + ], + }, +] + +const LORE_DISPLAY_GROUPS: DisplayGroup[] = [ + { + label: 'Lore', + variables: ['loreEntries', 'loreChapters'], + }, +] + +const IMPORT_DISPLAY_GROUPS: DisplayGroup[] = [ + { + label: 'Import', + variables: ['genre', 'title', 'cardContent', 'entriesJson'], + }, +] + +const PORTRAIT_DISPLAY_GROUPS: DisplayGroup[] = [ + { + label: 'Portrait', + variables: ['imageStylePrompt', 'visualDescriptors'], + }, +] + +const TRANSLATE_WIZARD_DISPLAY_GROUPS: DisplayGroup[] = [ + { + label: 'Translate Wizard', + variables: ['targetLanguage', 'content'], + }, +] + +const GROUP_DISPLAY_GROUPS: Record = { + promptContext: PROMPT_CONTEXT_DISPLAY_GROUPS, + timelineFillAnswer: TIMELINE_FILL_ANSWER_DISPLAY_GROUPS, + wizard: WIZARD_DISPLAY_GROUPS, + vault: VAULT_DISPLAY_GROUPS, + lore: LORE_DISPLAY_GROUPS, + import: IMPORT_DISPLAY_GROUPS, + portrait: PORTRAIT_DISPLAY_GROUPS, + translateWizard: TRANSLATE_WIZARD_DISPLAY_GROUPS, +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +/** + * Returns the context group name for a given template ID, + * or null if the template is not mapped. + */ +export function getContextGroup(templateId: string): ContextGroupName | null { + return TEMPLATE_GROUP_MAP[templateId] ?? null +} + +/** + * Returns all variable definitions available for a given template ID. + * Returns an empty array if the template is not mapped. + */ +export function getVariablesForTemplate(templateId: string): VariableDefinition[] { + const group = getContextGroup(templateId) + if (!group) return [] + return GROUP_VARIABLES[group] +} + +/** + * Returns semantic display groups for a given template ID. + * Each group contains a label and the variable names in that group. + * Returns an empty array if the template is not mapped. + */ +export function getDisplayGroupsForTemplate(templateId: string): DisplayGroup[] { + const group = getContextGroup(templateId) + if (!group) return [] + return GROUP_DISPLAY_GROUPS[group] +} + +/** + * Returns a flat list of variable names available for a given template ID. + * Convenience wrapper around getVariablesForTemplate(). + */ +export function getVariableNamesForTemplate(templateId: string): string[] { + return getVariablesForTemplate(templateId).map((v) => v.name) +} From 90acce0fa51832d8670594e1ae95824e02520f6a Mon Sep 17 00:00:00 2001 From: Failerko Date: Sun, 29 Mar 2026 04:24:11 +0200 Subject: [PATCH 02/14] feat: add template context group mapping with per-group variable definitions Co-Authored-By: Claude Opus 4.6 (1M context) --- src/lib/services/templates/templateContextMap.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/services/templates/templateContextMap.ts b/src/lib/services/templates/templateContextMap.ts index 78ebfc46..86e6a8f8 100644 --- a/src/lib/services/templates/templateContextMap.ts +++ b/src/lib/services/templates/templateContextMap.ts @@ -178,11 +178,15 @@ const lorebookEntryFields: VariableFieldInfo[] = [ { name: 'name', type: 'string', description: 'Entry name' }, { name: 'type', type: 'string', description: 'e.g. character, location, item, faction' }, { name: 'description', type: 'string', description: 'Entry description' }, - { name: 'tier', type: 'number', description: 'Retrieval tier 1-3 (omitted for wizard)' }, + { name: 'tier', type: 'number', description: 'Retrieval tier 1-3 (optional)' }, { name: 'disposition', type: 'string', description: 'Current disposition (character-only, optional)' }, { name: 'hiddenInfo', type: 'string', description: 'Hidden lore (optional)' }, ] +const lorebookEntryWizardFields: VariableFieldInfo[] = lorebookEntryFields.filter( + (f) => f.name !== 'tier', +) + // --------------------------------------------------------------------------- // promptContext variable definitions // --------------------------------------------------------------------------- @@ -504,7 +508,7 @@ const WIZARD_VARS: VariableDefinition[] = [ }), // Lorebook - v('lorebookEntries', 'array', 'Lorebook entries', { infoFields: lorebookEntryFields }), + v('lorebookEntries', 'array', 'Lorebook entries', { infoFields: lorebookEntryWizardFields }), // Supporting Characters v('supportingCharacters', 'array', 'Supporting characters', { @@ -836,5 +840,5 @@ export function getDisplayGroupsForTemplate(templateId: string): DisplayGroup[] * Convenience wrapper around getVariablesForTemplate(). */ export function getVariableNamesForTemplate(templateId: string): string[] { - return getVariablesForTemplate(templateId).map((v) => v.name) + return getVariablesForTemplate(templateId).map((def) => def.name) } From e3b0dc857e49bc6a9e840c8e83c10d9638de7da1 Mon Sep 17 00:00:00 2001 From: Failerko Date: Sun, 29 Mar 2026 04:28:22 +0200 Subject: [PATCH 03/14] refactor: restructure sample context per template group with correct nesting Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/vault/prompts/sampleContext.ts | 1105 ++++++++--------- 1 file changed, 488 insertions(+), 617 deletions(-) diff --git a/src/lib/components/vault/prompts/sampleContext.ts b/src/lib/components/vault/prompts/sampleContext.ts index 159303f3..ee7fe429 100644 --- a/src/lib/components/vault/prompts/sampleContext.ts +++ b/src/lib/components/vault/prompts/sampleContext.ts @@ -1,254 +1,271 @@ /** * Sample values for template preview rendering. - * Used by both TemplatePreview (rendering) and TestVariablesModal (pre-filling inputs). + * Organized per context group to match actual runtime shapes. + * + * Used by: + * - TemplatePreview.svelte (rendering previews) + * - TemplateEditor.svelte (tooltip example values) + * - PromptPackEditor.svelte (test value defaults) */ -export const systemSamples: Record = { - protagonistName: 'Aria', - currentLocation: 'The Whispering Woods', - storyTime: 'Year 1, Day 15, 14:30', - genre: 'Fantasy', - tone: 'Mysterious', - settingDescription: 'A vast magical realm where ancient forests conceal forgotten ruins.', - themes: 'courage, discovery, friendship', +import { getContextGroup, type ContextGroupName } from '$lib/services/templates/templateContextMap' +import type { TemplateContext } from '$lib/services/templates/types' + +// --------------------------------------------------------------------------- +// Reusable sample data fragments +// --------------------------------------------------------------------------- + +const sampleCharacters = [ + { + name: 'Theron', + relationship: 'companion', + description: 'A seasoned ranger who has guided Aria through the Whispering Woods.', + traits: ['loyal', 'perceptive', 'cautious'], + appearance: ['rugged leather armor', 'shortbow', 'weathered cloak'], + tier: 1, + status: 'active', + }, + { + name: 'Lyra', + relationship: 'rival', + description: 'A rival mage who seeks the same ancient artifacts as Aria.', + traits: ['ambitious', 'cunning', 'talented'], + appearance: ['dark robes', 'silver staff', 'cold eyes'], + tier: 2, + status: 'active', + }, +] + +const sampleLocations = [ + { + name: 'Crystal Caverns', + description: 'Shimmering caverns of magical crystal beneath the mountains.', + visited: false, + tier: 2, + }, + { + name: 'Thornhold Castle', + description: 'Ancient fortress occupied by the Order of the Iron Veil.', + visited: true, + tier: 3, + }, +] + +const sampleItems = [ + { + name: 'Enchanted Compass', + description: 'Points toward hidden magical sources.', + quantity: 1, + equipped: false, + }, + { + name: 'Shadow Cloak', + description: 'Grants partial invisibility in dim light.', + quantity: 1, + equipped: true, + }, + { + name: 'Healing Potion', + description: 'Restores 2d4+2 health points.', + quantity: 2, + equipped: false, + }, +] + +const sampleStoryBeats = [ + { + title: 'Discovered the Hidden Map', + description: 'Found a map leading to the Crystal Caverns.', + type: 'discovery', + status: 'completed', + }, + { + title: 'Confrontation with Lyra', + description: 'Lyra demands the compass at the forest edge.', + type: 'conflict', + status: 'active', + }, +] + +const sampleChapters = [ + { + number: 1, + title: 'Into the Woods', + summary: 'Aria entered the Whispering Woods and encountered her first magical creature.', + startTime: 'Year 1, Day 1', + endTime: 'Year 1, Day 3', + characters: ['Aria', 'Theron'], + locations: ['The Whispering Woods'], + emotionalTone: 'curious', + }, + { + number: 2, + title: 'The Map Revealed', + summary: 'Aria discovered the hidden map leading to the Crystal Caverns.', + startTime: 'Year 1, Day 4', + endTime: 'Year 1, Day 10', + characters: ['Aria', 'Theron', 'Elder Maren'], + locations: ['The Whispering Woods', 'Crystal Caverns entrance'], + emotionalTone: 'hopeful', + }, +] + +const sampleLorebookEntries = [ + { + name: 'The Whispering Woods', + type: 'location', + description: 'Ancient forest where trees carry memories of the world.', + tier: 1, + }, + { + name: 'The Moonstone Pendant', + type: 'item', + description: 'A pendant that glows under moonlight, said to reveal hidden paths.', + tier: 1, + }, + { + name: 'Elder Maren', + type: 'character', + description: 'A wandering sage who guards forgotten knowledge.', + tier: 2, + disposition: 'cautiously helpful', + }, +] + +const sampleStoryEntries = [ + { type: 'user_action', content: 'I draw my sword and step into the shadows.' }, + { + type: 'narration', + content: + 'The forest falls silent as Aria moves into the darkness, her blade catching the last slivers of moonlight.', + }, + { type: 'user_action', content: 'I whisper to Theron to flank left.' }, +] + +const sampleWorldStateRelevantItems = [ + { + name: 'Enchanted Compass', + description: 'Points toward the nearest ley line convergence', + tier: 2, + }, + { + name: 'Faded Map Fragment', + description: 'Partial map of the Thornwood labyrinth', + tier: 3, + }, +] + +const sampleWorldStateRelatedBeats = [ + { + title: 'The Missing Apprentice', + description: 'Rumors of a sorcerer apprentice who vanished near the old ruins', + type: 'quest', + status: 'active', + tier: 2, + }, + { + title: 'Harvest Festival Preparations', + description: 'The village is preparing for the annual harvest celebration', + type: 'discovery', + status: 'active', + tier: 3, + }, +] + +const sampleVisualDescriptors = { + face: 'angular features, sharp cheekbones', + hair: 'silver, flowing to shoulders', + eyes: 'violet, luminous', + build: 'athletic, graceful', + clothing: 'leather armor with arcane engravings', + accessories: 'enchanted compass on a chain', + distinguishing: 'scar across left cheek', +} + +// --------------------------------------------------------------------------- +// promptContext samples — matches StoryPromptContext shape +// --------------------------------------------------------------------------- + +const PROMPT_CONTEXT_SAMPLES: TemplateContext = { + // Story Config (flat scalars) mode: 'adventure', pov: 'second', tense: 'present', -} - -export const runtimeSamples: Record = { - // Narrative Service - recentContent: '[Recent story content would appear here...]', - visualProseMode: 'false', - inlineImageMode: 'false', - - // Classifier Service - entityCounts: 'Characters: 3, Locations: 5, Items: 4', - currentTimeInfo: 'Year 1, Day 15, 14:30', - inputLabel: 'Player Action', - userAction: 'I want to explore the ancient ruins to the north.', - narrativeResponse: '[The narrative response text...]', - existingLocations: 'The Whispering Woods, Crystal Caverns, Thornhold Castle', - existingItems: 'Enchanted Compass, Shadow Cloak, Moonstone Pendant', - storyBeatTypes: 'discovery, encounter, revelation, conflict, resolution', - itemLocationOptions: 'inventory, equipped, location, npc', - defaultItemLocation: 'location', - - // Memory Service - firstValidId: '1', - lastValidId: '25', - maxChaptersPerRetrieval: '3', - - // Suggestions Service - activeThreads: 'Finding the lost artifact, Resolving the conflict with Lyra', - - // Action Choices Service - npcsPresent: 'Theron the Ranger, Old Sage Maren', - inventory: 'Enchanted Compass, Shadow Cloak, Healing Potion x2', - activeQuests: 'Find the Moonstone Pendant, Explore the Crystal Caverns', + protagonistName: 'Aria', protagonistDescription: 'A young woman with silver hair and violet eyes', - povInstruction: 'Write in second person perspective.', - lengthInstruction: 'Write 2-3 paragraphs.', + genre: 'Fantasy', + settingDescription: 'A vast magical realm where ancient forests conceal forgotten ruins.', + tone: 'Mysterious', + themes: ['courage', 'discovery', 'friendship'], - // Shared / Common + // Story Content + storyEntries: sampleStoryEntries, + storyEntriesRaw: sampleStoryEntries.map((e, i) => ({ ...e, id: `entry-${i}` })), + storyEntriesVisible: sampleStoryEntries, + storyEntriesVisibleRaw: sampleStoryEntries.map((e, i) => ({ ...e, id: `entry-${i}` })), userInput: 'I want to explore the ancient ruins to the north.', + userActionOriginal: 'I want to explore the ancient ruins to the north.', + lastNarrativeEntry: { + type: 'narration', + content: + 'The forest falls silent as Aria moves into the darkness, her blade catching the last slivers of moonlight.', + }, - // Style Reviewer - passageCount: '5', - - // Agentic Retrieval - chaptersCount: '8', - chapterList: '[Formatted chapter list for retrieval...]', - entriesCount: '15', - entryList: '[Formatted lorebook entry list...]', - - // Timeline Fill - query: 'What happened between the forest encounter and arriving at the castle?', - - // Translation - targetLanguage: 'Spanish', - sourceLanguage: 'English', - content: '[Content to translate or process...]', - elementsJson: '[JSON of UI elements for translation...]', - suggestionsJson: '[JSON of suggestions for translation...]', - choicesJson: '[JSON of action choices for translation...]', - - // Image Services - imageStylePrompt: '[Style prompt for image generation...]', - maxImages: '3', - translatedNarrativeBlock: '[Translated narrative for image analysis...]', - - // Wizard Service - genreLabel: 'Fantasy', - seed: 'A world where magic flows through ancient ley lines...', - customInstruction: 'Make it feel epic and mysterious.', - toneInstruction: 'Maintain a mysterious and wonder-filled tone.', - settingInstruction: 'Set in a high fantasy world with elemental magic.', - settingContext: 'A high fantasy world with elemental magic and feudal kingdoms.', - settingName: 'The Shattered Realms', - count: '3', - title: 'Echoes of the Forgotten', - atmosphere: 'A sense of ancient mystery pervades the land...', - tenseInstruction: 'Write in present tense.', - openingGuidance: 'Begin with the protagonist arriving at the forest edge.', - settingAtmosphere: 'Eerie and mystical, where the air hums with ancient power.', - settingThemesText: 'magic, discovery, redemption', - cardContent: '[Character card content for import...]', - lorebookName: 'The Shattered Realms Lore', - entriesJson: '[Lorebook entries JSON for vault import...]', - entryCount: '12', - - // Interactive Lorebook (scalar counts) - characterCount: '12', - lorebookCount: '3', - totalEntryCount: '47', - scenarioCount: '5', - lastNarrationIndex: '2', - - // Interactive Lorebook (chat) - userMessage: 'Tell me about the Crystal Caverns.', - conversationHistory: '[Conversation history for interactive lorebook...]', - - // Agentic Retrieval Output - agenticReasoning: - '[Agent reasoning: selected entries for current context based on scene relevance and character proximity...]', - agenticChapterSummary: - '[Chapter facts: protagonist learned about the ancient prophecy in chapter 3, encountered Elder Maren in chapter 5...]', -} + // Entity Data + characters: sampleCharacters, + locations: sampleLocations, + items: sampleItems, + storyBeats: sampleStoryBeats, + chapters: sampleChapters, + lorebookEntries: sampleLorebookEntries, + + // World State (nested) + relevantWorldState: { + characters: sampleCharacters, + inventory: sampleItems, + storyBeats: sampleStoryBeats, + locations: sampleLocations, + relevantItems: sampleWorldStateRelevantItems, + relatedStoryBeats: sampleWorldStateRelatedBeats, + }, -/** - * Structured sample arrays for the 9 new context-layer array variables. - * Uses the Aventura fantasy world (Aria, Theron, Lyra, Whispering Woods, Crystal Caverns). - */ -export const structuredSamples: Record = { - worldStateCharacters: [ - { - name: 'Theron', - relationship: 'companion', - description: 'A seasoned ranger who has guided Aria through the Whispering Woods.', - traits: ['loyal', 'perceptive', 'cautious'], - appearance: ['rugged leather armor', 'shortbow', 'weathered cloak'], - tier: 1, - status: 'active', + // Entry Selection + loreEntriesForTier3: sampleLorebookEntries, + worldStateForTier3: sampleCharacters, + + // Settings (nested) + userSettings: { + visualProseMode: false, + classifier: { maxEntries: 20 }, + retieval: { maxStoryEntries: 50 }, + agenticRetrieval: { + recentEntriesCount: 5, + maxChapters: 3, + summaryCharLimit: 500, + maxLorebookEntries: 10, + }, + memoryConfig: { + chapterLength: 20, + maxProtectedEntries: 5, + }, + lorebookConfig: { + maxTier1Entries: 5, + maxTier2Entries: 10, + maxTier3Entries: 3, + }, + imageGeneration: { + inlineImageMode: false, + referenceMode: false, + maxImages: 3, + stylePrompt: '[Style prompt for image generation...]', + }, + translationSettings: { + targetLanguage: { name: 'Spanish', code: 'es' }, + sourceLanguage: { name: 'English', code: 'en' }, }, - { - name: 'Lyra', - relationship: 'rival', - description: 'A rival mage who seeks the same ancient artifacts as Aria.', - traits: ['ambitious', 'cunning', 'talented'], - appearance: ['dark robes', 'silver staff', 'cold eyes'], - tier: 2, - status: 'active', - }, - ], - worldStateInventory: [ - { - name: 'Enchanted Compass', - description: 'Points toward hidden magical sources.', - quantity: 1, - equipped: false, - }, - { - name: 'Shadow Cloak', - description: 'Grants partial invisibility in dim light.', - quantity: 1, - equipped: true, - }, - { - name: 'Healing Potion', - description: 'Restores 2d4+2 health points.', - quantity: 2, - equipped: false, - }, - ], - worldStateBeats: [ - { - title: 'Discovered the Hidden Map', - description: 'Found a map leading to the Crystal Caverns.', - type: 'discovery', - status: 'completed', - }, - { - title: 'Confrontation with Lyra', - description: 'Lyra demands the compass at the forest edge.', - type: 'conflict', - status: 'active', - }, - ], - worldStateLocations: [ - { - name: 'Crystal Caverns', - description: 'Shimmering caverns of magical crystal beneath the mountains.', - visited: false, - tier: 2, - }, - { - name: 'Thornhold Castle', - description: 'Ancient fortress occupied by the Order of the Iron Veil.', - visited: true, - tier: 3, - }, - ], - lorebookEntries: [ - { - name: 'The Whispering Woods', - type: 'location', - description: 'Ancient forest where trees carry memories of the world.', - tier: 1, - }, - { - name: 'The Moonstone Pendant', - type: 'item', - description: 'A pendant that glows under moonlight, said to reveal hidden paths.', - tier: 1, - }, - { - name: 'Elder Maren', - type: 'character', - description: 'A wandering sage who guards forgotten knowledge.', - tier: 2, - disposition: 'cautiously helpful', - }, - ], - chapters: [ - { - number: 1, - title: 'Into the Woods', - summary: 'Aria entered the Whispering Woods and encountered her first magical creature.', - startTime: 'Year 1, Day 1', - endTime: 'Year 1, Day 3', - characters: ['Aria', 'Theron'], - locations: ['The Whispering Woods'], - emotionalTone: 'curious', - }, - { - number: 2, - title: 'The Map Revealed', - summary: 'Aria discovered the hidden map leading to the Crystal Caverns.', - startTime: 'Year 1, Day 4', - endTime: 'Year 1, Day 10', - characters: ['Aria', 'Theron', 'Elder Maren'], - locations: ['The Whispering Woods', 'Crystal Caverns entrance'], - emotionalTone: 'hopeful', - }, - ], - timelineFill: [ - { - query: 'What happened between the forest encounter and arriving at the caverns?', - answer: - 'Aria and Theron tracked a faint magical signal through three days of dense forest, camping near the Silver Creek.', - chapterNumbers: [1, 2], - }, - ], - storyEntries: [ - { type: 'user_action', content: 'I draw my sword and step into the shadows.' }, - { - type: 'narration', - content: - 'The forest falls silent as Aria moves into the darkness, her blade catching the last slivers of moonlight.', - }, - { type: 'user_action', content: 'I whisper to Theron to flank left.' }, - ], + }, + + // Generation Results styleReview: { phrases: [ { @@ -267,278 +284,90 @@ export const structuredSamples: Record = { overallAssessment: 'Writing is clear but relies on sudden-transition clich\u00e9s.', reviewedEntryCount: 12, }, - worldStateRelevantItems: [ - { - name: 'Enchanted Compass', - description: 'Points toward the nearest ley line convergence', - tier: 2, - }, - { - name: 'Faded Map Fragment', - description: 'Partial map of the Thornwood labyrinth', - tier: 3, - }, - ], - worldStateRelatedBeats: [ - { - title: 'The Missing Apprentice', - description: 'Rumors of a sorcerer apprentice who vanished near the old ruins', - type: 'quest', - status: 'active', - tier: 2, - }, - { - title: 'Harvest Festival Preparations', - description: 'The village is preparing for the annual harvest celebration', - type: 'discovery', - status: 'active', - tier: 3, - }, - ], - currentLocationObject: { - name: 'The Silver Stag Inn', - description: - 'A cozy tavern at the crossroads, known for its warm hearth and loose-lipped travelers', + retrievalResult: null, + classificationResult: null, + narrativeResult: { + content: '[The narrative response text...]', }, + translationResult: null, + + // Memory + chapterAnalysis: { + analysisEntries: sampleStoryEntries, + chapterEntries: [ + { type: 'narration', content: 'Aria and Theron entered the Crystal Caverns at dawn.' }, + { type: 'user_action', content: 'I run my hand along the glowing crystal wall.' }, + { + type: 'narration', + content: + 'The crystals hum under your touch, resonating with a frequency you feel in your bones.', + }, + ], + }, + lastChapterEndIndex: 25, - // Style Reviewer - passages: [ - { - content: - 'Aria stepped into the clearing, her breath catching as the moonlight spilled across the ancient stone altar.', - entryId: 'entry-001', - }, - { - content: - 'The forest whispered around them — a language older than words, older than the kingdom itself.', - entryId: 'entry-002', - }, - { - content: - "Theron's hand tightened on his bow. 'Something moved out there,' he said, his voice barely audible.", - entryId: 'entry-003', - }, - ], - - // Entry Retrieval (Tier 3) - availableEntries: [ - { - name: 'The Whispering Woods', - type: 'location', - description: 'Ancient forest where trees carry memories of the world.', - keywords: 'forest, ancient, magic, trees, memory', - }, - { - name: 'The Moonstone Pendant', - type: 'item', - description: 'A pendant that glows under moonlight, said to reveal hidden paths.', - keywords: 'pendant, moonstone, glow, path', - }, - { - name: 'Elder Maren', - type: 'character', - description: 'A wandering sage who guards forgotten knowledge.', - }, - ], - - // Agentic Retrieval (recentEntries) - recentEntries: [ - { - type: 'narration', - content: 'The lantern flickered as shadows crept along the walls.', - }, - { - type: 'user_action', - content: 'I cautiously step forward, hand on my sword.', - }, - ], - - // Agentic Retrieval Output (agenticSelectedEntries) - agenticSelectedEntries: [ - { - name: 'The Moonstone Pendant', - type: 'item', - description: 'A pendant that glows under moonlight, said to reveal hidden paths.', - }, - { - name: 'Elder Maren', - type: 'character', - description: 'A wandering sage who guards forgotten knowledge.', - }, - ], - - // Classifier (chatHistory — Omit + timeStart) - chatHistory: [ - { - type: 'user_action', - content: 'I examine the Moonstone Pendant carefully.', - timeStart: 'Y1D15 13:45', - metadata: null, - }, - { - type: 'narration', - content: 'The pendant pulses with a soft blue light in your hands.', - timeStart: 'Y1D15 13:45', - metadata: null, - }, - { - type: 'user_action', - content: 'I ask Theron if he has ever seen anything like this.', - timeStart: 'Y1D15 13:50', - metadata: null, - }, - ], - - // Chapter Analysis (messagesInRange — same shape as storyEntries) - messagesInRange: [ - { type: 'user_action', content: 'I draw my sword and step into the shadows.' }, - { - type: 'narration', - content: - 'The forest falls silent as Aria moves into the darkness, her blade catching the last slivers of moonlight.', - }, - { type: 'user_action', content: 'I whisper to Theron to flank left.' }, - ], - - // Chapter Summarization - chapterEntries: [ - { type: 'narration', content: 'Aria and Theron entered the Crystal Caverns at dawn.' }, - { type: 'user_action', content: 'I run my hand along the glowing crystal wall.' }, - { - type: 'narration', - content: - 'The crystals hum under your touch, resonating with a frequency you feel in your bones.', - }, - ], - - // Chapter Summarization (previousChapters — same shape as chapters) - previousChapters: [ - { - number: 1, - title: 'Into the Woods', - summary: 'Aria entered the Whispering Woods and encountered her first magical creature.', - startTime: 'Year 1, Day 1', - endTime: 'Year 1, Day 3', - characters: ['Aria', 'Theron'], - locations: ['The Whispering Woods'], - emotionalTone: 'curious', + // Pack Variables + packVariables: { + runtimeVariables: { + character: [ + { + variableName: 'loyalty', + variableType: 'number', + minValue: 0, + maxValue: 100, + defaultValue: '50', + description: 'Character loyalty level toward the protagonist', + }, + { + variableName: 'mood', + variableType: 'enum', + enumOptions: [{ value: 'happy' }, { value: 'neutral' }, { value: 'hostile' }], + defaultValue: 'neutral', + description: 'Current emotional state', + }, + ], + item: [ + { + variableName: 'durability', + variableType: 'number', + minValue: 0, + maxValue: 100, + defaultValue: undefined, + description: 'Item condition as a percentage', + }, + ], }, - ], + }, - // Lore Management - loreEntries: [ - { - name: 'The Whispering Woods', - type: 'location', - description: 'Ancient forest where trees carry memories of the world.', - state: 'Partially explored — southern trails mapped by Aria and Theron.', - }, - { - name: 'The Moonstone Pendant', - type: 'item', - description: 'A pendant that glows under moonlight, said to reveal hidden paths.', - }, - { - name: 'Elder Maren', - type: 'character', - description: 'A wandering sage who guards forgotten knowledge.', - state: 'Last seen at the Crystal Caverns entrance, offering cryptic guidance.', - }, + // Translation Data + suggestionsToTranslate: [ + { text: 'Explore the ruins' }, + { text: 'Talk to the stranger' }, ], - - // Lore Management (loreChapters) - loreChapters: [ - { - number: 1, - title: 'Into the Woods', - summary: 'Aria entered the Whispering Woods and encountered her first magical creature.', - }, - { - number: 2, - title: 'The Map Revealed', - summary: 'Aria discovered the hidden map leading to the Crystal Caverns.', - }, - { - number: 3, - title: 'Crystal Dawn', - summary: 'Aria and Theron reached the Crystal Caverns entrance and met Elder Maren.', - }, + actionChoicesToTranslate: [ + { text: 'Draw your sword', description: 'Prepare for combat' }, + { text: 'Attempt to sneak past', description: 'Use stealth' }, ], - - // Agentic Retrieval (agenticChapters — same shape as loreChapters) - agenticChapters: [ - { - number: 1, - title: 'Into the Woods', - summary: 'Aria entered the Whispering Woods and encountered her first magical creature.', - }, - { - number: 2, - title: 'The Map Revealed', - summary: 'Aria discovered the hidden map leading to the Crystal Caverns.', - }, - { - number: 3, - title: 'Crystal Dawn', - summary: 'Aria and Theron reached the Crystal Caverns entrance and met Elder Maren.', - }, + uiElementsToTranslate: [ + { key: 'continue', text: 'Continue' }, + { key: 'undo', text: 'Undo' }, ], - // Agentic Retrieval (agenticEntries — name and type only) - agenticEntries: [ - { name: 'The Whispering Woods', type: 'location' }, - { name: 'The Moonstone Pendant', type: 'item' }, - { name: 'Elder Maren', type: 'character' }, - ], + // Time + timeTracker: { + years: 1, + days: 15, + hours: 14, + minutes: 30, + }, +} - // Image Analysis (sceneCharacters — mixed portrait state) - sceneCharacters: [ - { - name: 'Theron', - relationship: 'companion', - description: 'A seasoned ranger who has guided Aria through the Whispering Woods.', - traits: ['loyal', 'perceptive', 'cautious'], - status: 'active', - portrait: null, - visualDescriptors: { - face: 'weathered, strong jaw, high cheekbones', - hair: 'dark brown, shoulder-length', - eyes: 'amber, watchful', - build: 'lean and athletic', - clothing: 'worn leather armor, forest-green cloak', - accessories: 'shortbow, quiver of black-fletched arrows', - distinguishing: 'faint scar above left brow', - }, - }, - { - name: 'Lyra', - relationship: 'rival', - description: 'A rival mage who seeks the same ancient artifacts as Aria.', - traits: ['ambitious', 'cunning', 'talented'], - status: 'active', - portrait: 'portrait://lyra.png', - visualDescriptors: { - face: 'sharp features, pale complexion', - hair: 'raven black, pulled back tightly', - eyes: 'ice blue, calculating', - build: 'slender', - clothing: 'dark robes with silver trim', - accessories: 'silver staff', - distinguishing: '', - }, - }, - { - name: 'Elder Maren', - relationship: 'neutral', - description: 'A wandering sage who guards forgotten knowledge.', - traits: ['wise', 'cryptic', 'ancient'], - status: 'active', - portrait: null, - visualDescriptors: {}, - }, - ], +// --------------------------------------------------------------------------- +// timelineFillAnswer samples — promptContext + extras +// --------------------------------------------------------------------------- - // Timeline Fill Answer (answerChapters — dual-mode) +const TIMELINE_FILL_ANSWER_SAMPLES: TemplateContext = { + ...PROMPT_CONTEXT_SAMPLES, answerChapters: [ { number: 1, @@ -561,65 +390,37 @@ export const structuredSamples: Record = { summary: 'Aria discovered the hidden map leading to the Crystal Caverns.', }, ], + query: 'What happened between the forest encounter and arriving at the castle?', +} - // Background Image (narrationEntries — all type narration) - narrationEntries: [ - { - type: 'narration', - content: - 'The Crystal Caverns stretch endlessly before you, each surface faceted like a gemstone.', - }, - { - type: 'narration', - content: - 'Pale blue light pulses from deep within the cave, casting long shadows across the floor.', - }, - { - type: 'narration', - content: - 'A low hum fills the air — the crystals are singing, resonating with some ancient frequency.', - }, - ], +// --------------------------------------------------------------------------- +// wizard samples +// --------------------------------------------------------------------------- - // Classifier (characters — same shape as worldStateCharacters) - characters: [ - { - name: 'Theron', - relationship: 'companion', - description: 'A seasoned ranger who has guided Aria through the Whispering Woods.', - traits: ['loyal', 'perceptive', 'cautious'], - appearance: ['rugged leather armor', 'shortbow', 'weathered cloak'], - tier: 1, - status: 'active', - }, - { - name: 'Lyra', - relationship: 'rival', - description: 'A rival mage who seeks the same ancient artifacts as Aria.', - traits: ['ambitious', 'cunning', 'talented'], - appearance: ['dark robes', 'silver staff', 'cold eyes'], - tier: 2, - status: 'active', - }, - ], - - // Classifier (storyBeats — same shape as worldStateBeats) - storyBeats: [ - { - title: 'Discovered the Hidden Map', - description: 'Found a map leading to the Crystal Caverns.', - type: 'discovery', - status: 'completed', - }, - { - title: 'Confrontation with Lyra', - description: 'Lyra demands the compass at the forest edge.', - type: 'conflict', - status: 'active', - }, - ], +const WIZARD_SAMPLES: TemplateContext = { + // Story Setup (scalars) + genreLabel: 'Fantasy', + seed: 'A world where magic flows through ancient ley lines...', + customInstruction: 'Make it feel epic and mysterious.', + toneInstruction: 'Maintain a mysterious and wonder-filled tone.', + settingInstruction: 'Set in a high fantasy world with elemental magic.', + settingContext: 'A high fantasy world with elemental magic and feudal kingdoms.', + settingName: 'The Shattered Realms', + settingDescription: 'A vast magical realm where ancient forests conceal forgotten ruins.', + settingAtmosphere: 'Eerie and mystical, where the air hums with ancient power.', + settingThemesText: 'magic, discovery, redemption', + count: 3, + title: 'Echoes of the Forgotten', + atmosphere: 'A sense of ancient mystery pervades the land...', + tenseInstruction: 'Write in present tense.', + openingGuidance: 'Begin with the protagonist arriving at the forest edge.', + mode: 'adventure', + pov: 'second', + tone: 'Mysterious', + protagonistName: 'Aria', + protagonistDescription: 'A young woman with silver hair and violet eyes', - // Wizard Service (structured objects replacing scalar strings) + // Setting (object) currentSetting: { name: 'The Whispering Woods', description: 'An ancient forest where trees carry memories of the world.', @@ -630,6 +431,15 @@ export const structuredSamples: Record = { { name: 'Elder Tree', description: 'A massive ancient oak at the heart of the woods.' }, ], }, + + // Character (objects) + characterInput: { + name: 'Aria', + description: 'A young woman with silver hair and violet eyes', + background: 'Raised in the hidden village of Thornhollow...', + motivation: '', + traits: [], + }, currentCharacter: { name: 'Aria', description: 'A young woman with silver hair and violet eyes', @@ -638,18 +448,26 @@ export const structuredSamples: Record = { traits: ['curious', 'brave', 'compassionate'], appearance: "Silver hair, violet eyes, slender build, wears a traveler's cloak", }, - characterInput: { - name: 'Aria', - description: 'A young woman with silver hair and violet eyes', - background: 'Raised in the hidden village of Thornhollow...', - motivation: '', - traits: [], - }, protagonist: { name: 'Kael', description: 'A brave warrior with a troubled past', motivation: 'To protect the realm from the encroaching darkness', }, + + // Opening (object) + currentOpening: { + title: 'The Journey Begins', + scene: 'The morning mist clings to the tree line as the sun crests the distant hills...', + initialLocation: { + name: 'Forest Edge', + description: 'Where the ancient trees begin and civilization ends.', + }, + }, + + // Lorebook + lorebookEntries: sampleLorebookEntries, + + // Supporting Characters supportingCharacters: [ { name: 'Theron', @@ -666,68 +484,121 @@ export const structuredSamples: Record = { traits: ['ambitious', 'clever'], }, ], - currentOpening: { - title: 'The Journey Begins', - scene: 'The morning mist clings to the tree line as the sun crests the distant hills...', - initialLocation: { - name: 'Forest Edge', - description: 'Where the ancient trees begin and civilization ends.', - }, - }, +} - // Classifier (runtimeVariables - grouped by entity type) - runtimeVariables: { - character: [ - { - variableName: 'loyalty', - variableType: 'number', - minValue: 0, - maxValue: 100, - defaultValue: '50', - description: 'Character loyalty level toward the protagonist', - }, - { - variableName: 'mood', - variableType: 'enum', - enumOptions: [{ value: 'happy' }, { value: 'neutral' }, { value: 'hostile' }], - defaultValue: 'neutral', - description: 'Current emotional state', - }, - ], - item: [ - { - variableName: 'durability', - variableType: 'number', - minValue: 0, - maxValue: 100, - defaultValue: undefined, - description: 'Item condition as a percentage', - }, - ], - }, +// --------------------------------------------------------------------------- +// vault samples +// --------------------------------------------------------------------------- - // Interactive Lorebook (focusedEntity object) +const VAULT_SAMPLES: TemplateContext = { + characterCount: 12, + lorebookCount: 3, + totalEntryCount: 47, + scenarioCount: 5, focusedEntity: { entityType: 'character', entityName: 'Theron', entityId: 'char-001', }, +} - // Image Portrait (visualDescriptors object) - visualDescriptors: { - face: 'angular features, sharp cheekbones', - hair: 'silver, flowing to shoulders', - eyes: 'violet, luminous', - build: 'athletic, graceful', - clothing: 'leather armor with arcane engravings', - accessories: 'enchanted compass on a chain', - distinguishing: 'scar across left cheek', - }, +// --------------------------------------------------------------------------- +// lore samples +// --------------------------------------------------------------------------- + +const LORE_SAMPLES: TemplateContext = { + loreEntries: [ + { + name: 'The Whispering Woods', + type: 'location', + description: 'Ancient forest where trees carry memories of the world.', + state: 'Partially explored — southern trails mapped by Aria and Theron.', + }, + { + name: 'The Moonstone Pendant', + type: 'item', + description: 'A pendant that glows under moonlight, said to reveal hidden paths.', + }, + { + name: 'Elder Maren', + type: 'character', + description: 'A wandering sage who guards forgotten knowledge.', + state: 'Last seen at the Crystal Caverns entrance, offering cryptic guidance.', + }, + ], + loreChapters: [ + { + number: 1, + title: 'Into the Woods', + summary: 'Aria entered the Whispering Woods and encountered her first magical creature.', + }, + { + number: 2, + title: 'The Map Revealed', + summary: 'Aria discovered the hidden map leading to the Crystal Caverns.', + }, + { + number: 3, + title: 'Crystal Dawn', + summary: 'Aria and Theron reached the Crystal Caverns entrance and met Elder Maren.', + }, + ], +} + +// --------------------------------------------------------------------------- +// import samples +// --------------------------------------------------------------------------- + +const IMPORT_SAMPLES: TemplateContext = { + genre: 'Fantasy', + title: 'Echoes of the Forgotten', + cardContent: '[Character card content for import...]', + entriesJson: '[Lorebook entries JSON for vault import...]', +} + +// --------------------------------------------------------------------------- +// portrait samples +// --------------------------------------------------------------------------- + +const PORTRAIT_SAMPLES: TemplateContext = { + imageStylePrompt: '[Style prompt for image generation...]', + visualDescriptors: sampleVisualDescriptors, } -/** All sample values combined — system strings, runtime strings, and structured arrays */ -export const allSamples: Record = { - ...systemSamples, - ...runtimeSamples, - ...structuredSamples, +// --------------------------------------------------------------------------- +// translateWizard samples +// --------------------------------------------------------------------------- + +const TRANSLATE_WIZARD_SAMPLES: TemplateContext = { + targetLanguage: 'Spanish', + content: '[Content to translate or process...]', +} + +// --------------------------------------------------------------------------- +// Group name -> sample data +// --------------------------------------------------------------------------- + +const GROUP_SAMPLES: Record = { + promptContext: PROMPT_CONTEXT_SAMPLES, + timelineFillAnswer: TIMELINE_FILL_ANSWER_SAMPLES, + wizard: WIZARD_SAMPLES, + vault: VAULT_SAMPLES, + lore: LORE_SAMPLES, + import: IMPORT_SAMPLES, + portrait: PORTRAIT_SAMPLES, + translateWizard: TRANSLATE_WIZARD_SAMPLES, +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +/** + * Returns sample context data for a given template ID. + * Returns an empty object if the template has no mapped context group. + */ +export function getSamplesForTemplate(templateId: string): TemplateContext { + const group = getContextGroup(templateId) + if (!group) return {} + return GROUP_SAMPLES[group] } From 3918dc264156f1687e9457a891dfe0509f27405a Mon Sep 17 00:00:00 2001 From: Failerko Date: Sun, 29 Mar 2026 04:34:35 +0200 Subject: [PATCH 04/14] refactor: update all consumers to use scoped template context map Switch validator, TemplateEditor, VariablePalette, TemplatePreview, TestVariablesModal, and PromptPackEditor from the old flat variableRegistry to the new per-template-group APIs (getVariablesForTemplate, getDisplayGroupsForTemplate, getSamplesForTemplate). Also update sampleContext.test.ts to use per-template samples instead of the removed allSamples export. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../vault/prompts/PromptPackEditor.svelte | 13 +- .../vault/prompts/TemplateEditor.svelte | 13 +- .../vault/prompts/TemplatePreview.svelte | 22 +- .../vault/prompts/TestVariablesModal.svelte | 225 +++--------------- .../vault/prompts/VariablePalette.svelte | 54 +++-- .../vault/prompts/sampleContext.test.ts | 10 +- src/lib/services/templates/validator.ts | 6 +- 7 files changed, 100 insertions(+), 243 deletions(-) diff --git a/src/lib/components/vault/prompts/PromptPackEditor.svelte b/src/lib/components/vault/prompts/PromptPackEditor.svelte index 025e4396..759b67af 100644 --- a/src/lib/components/vault/prompts/PromptPackEditor.svelte +++ b/src/lib/components/vault/prompts/PromptPackEditor.svelte @@ -1,7 +1,7 @@ @@ -301,29 +173,30 @@
- - {#if filteredSystem.length > 0} - + {#each filteredGroups as group (group.label)} +
- System - {filteredSystem.length} + {group.label} + {group.variables.length}
- {#each filteredSystem as v (v.name)} + {#each group.variables as v (v.name)} {@render varInput(v.name, v.description, v.type, v.enumValues)} {/each}
- {/if} + {/each} {#if filteredCustom.length > 0} @@ -354,32 +227,6 @@
{/if} - - - {#each filteredRuntimeGroups as group (group.name)} - - -
- - {group.name} - {group.variables.length} -
-
- -
- {#each group.variables as v (v.name)} - {@render varInput(v.name, v.description, v.type, v.enumValues)} - {/each} -
-
-
- {/each}
diff --git a/src/lib/components/vault/prompts/VariablePalette.svelte b/src/lib/components/vault/prompts/VariablePalette.svelte index 4e289f1c..5d2c1998 100644 --- a/src/lib/components/vault/prompts/VariablePalette.svelte +++ b/src/lib/components/vault/prompts/VariablePalette.svelte @@ -1,22 +1,35 @@