Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions .claude/commands/import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# ST Card Import — CLI Orchestrator

Import SillyTavern character card files (PNG / CharX / ZIP) into the VibeApp system. Extracts apps from the card's character book and generates VibeApp code + mod scenario.

## Parameter Parsing

- `$ARGUMENTS` format: `{FilePath}`
- `FilePath`: path to a `.png`, `.charx`, or `.zip` file containing a SillyTavern character card

If `$ARGUMENTS` is empty, ask the user for the file path.

## Execution Protocol

### 1. Extract Card Data

Run the extraction script:

```bash
python3 .claude/scripts/extract-card.py "{FilePath}"
```

Capture the JSON output. If extraction fails, report the error and stop.

### 2. Analyze & Present Results

Parse the extraction output JSON. Display a structured summary to the user:

```
Card: {source} ({source_type})
Character: {character.name}
Description: {first 100 chars of character.description}...

Apps Found ({count}):
1. [{comment}] — keywords: {keywords} | format: {format} | tags: {tag_names}
2. ...

Lore Entries: {count}
Regex Scripts: {count}
```

### 3. User Selection

Ask the user which apps to generate using AskUserQuestion:
- Option 1: Generate all apps (recommended)
- Option 2: Select specific apps
- Option 3: Skip app generation (mod only)

If the user selects specific apps, present a multi-select list of extracted apps.

### 4. Generate Apps via Vibe Workflow

For each selected app, derive a VibeApp requirement from the card data:

#### 4.1 App Name Derivation

Convert the app's `comment` field to PascalCase for the VibeApp name:
- `"live stream"` → `LiveStream`
- `"social-feed"` → `SocialFeed`
- `"music app"` → `MusicApp`
- Chinese names: translate to English PascalCase

#### 4.2 Requirement Generation

Build a comprehensive requirement description from the extracted data:

```
A {format}-based app that provides {functional description based on keywords and tags}.

UI Features:
{For each tag: describe the UI element it represents}

Data Resources:
{For each resource list: describe what data it manages}

Content Format: {format type — xml tags / bracket notation / prose}

Regex Scripts (for reference):
{List relevant scripts that transform this app's output}
```

#### 4.3 Execute Vibe Workflow

For each app, execute the `/vibe` command:

```
/vibe {PascalCaseAppName} {GeneratedRequirement}
```

Process apps **sequentially** — each vibe workflow must complete before starting the next.

**Important**: Before starting each app, check if a VibeApp with that name already exists at `src/pages/{AppName}/`. If it does, ask the user whether to:
- Skip this app
- Overwrite (delete existing and regenerate)
- Use change mode (modify existing app)

### 5. Completion Report

```
═══════════════════════════════════════
ST Card Import Complete
═══════════════════════════════════════
Source: {filename} ({source_type})
Character: {character.name}

Apps Generated ({count}):
• {AppName1} → http://localhost:3000/{app-name-1}
• {AppName2} → http://localhost:3000/{app-name-2}
═══════════════════════════════════════
```

## Error Handling

- If extraction fails: report error, suggest checking file format
- If a vibe workflow fails for one app: log error, continue with remaining apps

## Notes

- The extraction script handles both PNG (ccv3/chara tEXt chunks) and CharX/ZIP (card.json) formats
- Apps are identified by character book entries containing `<rule S>` in their content
- The vibe workflow handles all code generation, architecture, and integration
- Lore entries are preserved as reference data but not directly used in app generation
135 changes: 135 additions & 0 deletions .claude/prompt_generate_mod_en.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions apps/webuiapps/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
},
"dependencies": {
"framer-motion": "^12.34.0",
"jszip": "^3.10.1",
"react-markdown": "^10.1.0",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1"
},
"devDependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.72",
"@vitest/coverage-istanbul": "^1.6.1",
"@vitest/coverage-v8": "^1.6.1",
"happy-dom": "^14.0.0",
Expand Down
5 changes: 3 additions & 2 deletions apps/webuiapps/src/components/ChatPanel/ModPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ interface ModPanelProps {
collection: ModCollection;
onSave: (collection: ModCollection) => void;
onClose: () => void;
initialEditId?: string;
}

const ModPanel: React.FC<ModPanelProps> = ({ collection, onSave, onClose }) => {
const ModPanel: React.FC<ModPanelProps> = ({ collection, onSave, onClose, initialEditId }) => {
const [col, setCol] = useState<ModCollection>(() => ({ ...collection }));
const [editingId, setEditingId] = useState<string | null>(null);
const [editingId, setEditingId] = useState<string | null>(initialEditId ?? null);

const mods = getModList(col);
const activeId = col.activeId;
Expand Down
35 changes: 34 additions & 1 deletion apps/webuiapps/src/components/ChatPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,22 @@ const ChatPanel: React.FC<{
const [suggestedReplies, setSuggestedReplies] = useState<string[]>([]);
const [showCharacterPanel, setShowCharacterPanel] = useState(false);
const [showModPanel, setShowModPanel] = useState(false);
const [initialEditModId, setInitialEditModId] = useState<string | undefined>();
const [currentEmotion, setCurrentEmotion] = useState<string | undefined>();

// Open mod editor when triggered from Shell (e.g. after card import mod generation)
useEffect(() => {
const handler = (e: Event) => {
const modId = (e as CustomEvent<{ modId: string }>).detail?.modId;
if (modId) {
setInitialEditModId(modId);
setShowModPanel(true);
}
};
window.addEventListener('open-mod-editor', handler);
return () => window.removeEventListener('open-mod-editor', handler);
}, []);

// Memories loaded for SP injection
const [memories, setMemories] = useState<MemoryEntry[]>([]);

Expand Down Expand Up @@ -540,6 +554,20 @@ const ChatPanel: React.FC<{
});
}, []);

// Listen for mod collection changes from Shell (e.g. after mod generation)
useEffect(() => {
const handler = (e: Event) => {
const col = (e as CustomEvent<ModCollection>).detail;
if (col) {
setModCollection(col);
const entry = getActiveModEntry(col);
setModManager(new ModManager(entry.config, entry.state));
}
};
window.addEventListener('mod-collection-changed', handler);
return () => window.removeEventListener('mod-collection-changed', handler);
}, []);

const handleClearHistory = useCallback(async () => {
await clearChatHistory(sessionPathRef.current);
seedPrologue();
Expand Down Expand Up @@ -1169,14 +1197,19 @@ const ChatPanel: React.FC<{
{showModPanel && (
<ModPanel
collection={modCollection}
initialEditId={initialEditModId}
onSave={(col) => {
setModCollection(col);
saveModCollection(col);
const entry = getActiveModEntry(col);
setModManager(new ModManager(entry.config, entry.state));
setShowModPanel(false);
setInitialEditModId(undefined);
}}
onClose={() => {
setShowModPanel(false);
setInitialEditModId(undefined);
}}
onClose={() => setShowModPanel(false)}
/>
)}
</>
Expand Down
Loading
Loading