Files
Claudia-rust/src/index.html
matt f31e3fb28a feat: add v2 character card import/export
- Expanded Character struct with all v2 spec fields (description, scenario, mes_example, post_history_instructions, alternate_greetings, character_book, tags, creator, character_version, creator_notes, extensions)
- Created CharacterCardV2 serialization structs following spec at github.com/malfoyslastname/character-card-spec-v2
- Implemented PNG metadata utilities:
  * read_character_card_from_png() - extracts and decodes character data from PNG tEXt chunks
  * write_character_card_to_png() - embeds character data into PNG files
  * create_placeholder_png() - generates gradient placeholder images for avatarless characters
- Added Tauri commands:
  * import_character_card - opens file picker, imports PNG with automatic name conflict handling
  * export_character_card - exports character as v2 PNG card with embedded metadata
- Added Import/Export buttons to character settings UI
- Full backward compatibility with existing characters using serde defaults
- Added dependencies: png 0.17, base64 0.21, image 0.24
2025-10-14 08:19:57 -07:00

240 lines
9.3 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="styles.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Claudia</title>
<script src="https://cdn.jsdelivr.net/npm/marked@11.1.1/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script type="module" src="/main.js" defer></script>
</head>
<body>
<div class="app-container">
<header class="app-header">
<div class="header-content">
<div class="character-display">
<div class="avatar-circle"></div>
<span id="character-header-name"></span>
</div>
<div class="character-controls">
<div class="select-wrapper">
<select id="character-select" class="character-select"></select>
</div>
<button id="new-character-btn" class="icon-btn" title="New Character">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</button>
</div>
<div class="header-controls">
<button id="clear-btn" class="icon-btn" title="Clear conversation">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M3 4h10M6 4V3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v1M5 4v8a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>
</button>
<button id="settings-btn" class="icon-btn" title="Settings">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<circle cx="8" cy="8" r="2" stroke="currentColor" stroke-width="1.5"/>
<path d="M14 8c0-.5-.1-1-.3-1.4l1.2-.7-1-1.7-1.2.7c-.6-.6-1.3-1-2.1-1.2V2h-2v1.7c-.8.2-1.5.6-2.1 1.2L5.3 4.2l-1 1.7 1.2.7C5.1 7 5 7.5 5 8s.1 1 .3 1.4l-1.2.7 1 1.7 1.2-.7c.6.6 1.3 1 2.1 1.2V14h2v-1.7c.8-.2 1.5-.6 2.1-1.2l1.2.7 1-1.7-1.2-.7c.2-.4.3-.9.3-1.4z" stroke="currentColor" stroke-width="1.5" fill="none"/>
</svg>
</button>
</div>
</div>
</header>
<main class="chat-container" id="chat-view">
<div id="messages" class="messages-list">
<div class="message assistant">
<div class="message-content">
<p>Configure API settings to get started.</p>
</div>
</div>
</div>
</main>
<div class="settings-panel" id="settings-panel" style="display: none;">
<div class="settings-header">
<h2>Settings</h2>
<button id="close-settings-btn" class="icon-btn">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<line x1="4" y1="4" x2="12" y2="12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
<line x1="12" y1="4" x2="4" y2="12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</button>
</div>
<div class="settings-tabs">
<button class="tab-btn active" data-tab="api">API</button>
<button class="tab-btn" data-tab="character">Character</button>
</div>
<div id="api-tab" class="tab-content active">
<form id="settings-form" class="settings-form">
<div class="form-group">
<label for="api-base-url">Base URL</label>
<input
type="text"
id="api-base-url"
placeholder="https://api.anthropic.com"
required
/>
</div>
<div class="form-group">
<label for="api-key">API Key</label>
<input
type="password"
id="api-key"
placeholder="sk-ant-..."
required
/>
</div>
<button type="button" id="validate-btn" class="btn-secondary">
Validate
</button>
<div id="models-group" class="form-group" style="display: none;">
<label for="model-select">Model</label>
<select id="model-select" required>
<option value="">Select a model</option>
</select>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="stream-toggle" />
Enable streaming responses (real-time token display)
</label>
</div>
<div id="validation-message" class="validation-message"></div>
<button type="submit" id="save-settings-btn" class="btn-primary" disabled>
Save Configuration
</button>
</form>
</div>
<div id="character-tab" class="tab-content">
<form id="character-form" class="settings-form">
<div class="form-group">
<label for="character-settings-select">Select Character</label>
<select id="character-settings-select"></select>
</div>
<div class="form-group">
<label for="character-name">Character Name</label>
<input
type="text"
id="character-name"
placeholder="Assistant"
required
/>
</div>
<div class="form-group">
<label for="character-avatar">Avatar (Optional)</label>
<div class="avatar-upload">
<div id="avatar-preview" class="avatar-preview">
<div class="avatar-circle-large"></div>
</div>
<input
type="file"
id="character-avatar"
accept="image/png,image/jpeg,image/jpg,image/webp"
style="display: none;"
/>
<button type="button" id="upload-avatar-btn" class="btn-secondary">
Choose Image
</button>
<button type="button" id="remove-avatar-btn" class="btn-secondary" style="display: none;">
Remove
</button>
</div>
</div>
<div class="form-group">
<label for="character-system-prompt">System Prompt</label>
<textarea
id="character-system-prompt"
placeholder="You are a helpful AI assistant..."
rows="6"
required
></textarea>
</div>
<div class="form-group">
<label for="character-greeting">Greeting (Optional)</label>
<textarea
id="character-greeting"
placeholder="Hello! How can I help you today?"
rows="2"
></textarea>
</div>
<div class="form-group">
<label for="character-personality">Personality Tags (Optional)</label>
<input
type="text"
id="character-personality"
placeholder="helpful, friendly, knowledgeable"
/>
</div>
<div id="character-message" class="validation-message"></div>
<button type="submit" id="save-character-btn" class="btn-primary">
Save Character
</button>
<button type="button" id="delete-character-btn" class="btn-danger">
Delete Character
</button>
<div class="form-group" style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border-color);">
<label>Character Card Import/Export</label>
<div style="display: flex; gap: 0.5rem;">
<button type="button" id="import-character-btn" class="btn-secondary" style="flex: 1;">
Import v2 Card
</button>
<button type="button" id="export-character-btn" class="btn-secondary" style="flex: 1;">
Export v2 Card
</button>
</div>
</div>
</form>
</div>
</div>
<footer class="input-container">
<form id="chat-form" class="chat-form">
<textarea
id="message-input"
placeholder="Ask me anything..."
rows="1"
autocomplete="off"
></textarea>
<button type="submit" id="send-btn" class="send-btn">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M3 10L17 3L13 10L17 17L3 10Z" fill="currentColor"/>
</svg>
</button>
</form>
<div class="status-bar">
<span id="status-text" class="status-text">Ready</span>
</div>
</footer>
</div>
<!-- Avatar zoom modal -->
<div id="avatar-modal" class="avatar-modal" style="display: none;">
<div class="avatar-modal-overlay"></div>
<div class="avatar-modal-content">
<img id="avatar-modal-img" src="" alt="Avatar" />
</div>
</div>
</body>
</html>