Writingway2/main.html

475 lines
No EOL
28 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Writingway 2.0</title>
<!-- Note: This file should be named main.html and opened via start.bat -->
<script src="https://cdn.jsdelivr.net/npm/dexie@3.2.4/dist/dexie.min.js"></script>
<script defer src="src/prompts.js"></script>
<script defer src="src/generation.js"></script>
<script defer src="src/ai.js"></script>
<script defer src="src/save-utils.js"></script>
<script defer src="src/app.js"></script>
<script defer src="src/compendium.js"></script>
<!-- Local vendor Alpine (use a real alpine.min.js here for offline runs) -->
<script defer src="src/vendor/alpine.min.js"></script>
<link rel="stylesheet" href="src/styles.css">
</head>
<body>
<div x-data="app" x-init="init()">
<!-- debug banner removed -->
<!-- Main App -->
<template x-if="currentProject && !showModelLoading">
<div class="app-container">
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-header">
<h1>Writingway</h1>
<p>AI-Powered Creative Writing</p>
</div>
<!-- AI Status -->
<div class="ai-status">
<div class="ai-status-item" style="cursor:pointer;" @click="showAISettings = true">
<div class="status-dot" :class="aiStatus"></div>
<span x-text="aiStatusText"></span>
</div>
</div>
<div class="project-info" x-data="{ openMenu:false }" style="position:relative;">
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
<h2 x-text="currentProject.name" style="margin:0;padding-right:8px;"></h2>
<div style="display:flex;align-items:center;gap:6px;">
<button class="btn btn-secondary" @click.stop="showCodexPanel = true"
title="Codex">Codex</button>
<button class="menu-button" @click.stop="openMenu = !openMenu"
title="Project menu">⋯</button>
<div x-show="openMenu" x-cloak @click.away="openMenu = false" x-transition
class="action-menu">
<button
@click.stop="showRenameProjectModal = true; renameProjectName = currentProject.name; openMenu=false">Rename</button>
<div style="border-top:1px solid var(--border);margin:6px 0"></div>
<div style="max-height:200px;overflow:auto;margin-bottom:6px;">
<strong
style="display:block;padding:6px 10px;color:var(--text-secondary);font-size:12px;">Switch
project</strong>
<template x-for="p in projects" :key="p.id">
<button @click.stop="selectProject(p.id); openMenu=false"
style="padding-left:10px;">
<span x-text="p.name"></span>
</button>
</template>
</div>
<div style="border-top:1px solid var(--border);margin:6px 0"></div>
<button @click.stop="showNewProjectModal = true; openMenu=false">New
project</button>
</div>
</div>
</div>
<div class="word-count" x-text="`${totalWords} words`" style="margin-top:6px;"></div>
</div>
<div class="scene-list">
<template x-for="chapter in chapters" :key="chapter.id">
<div class="chapter-item">
<div class="chapter-header" style="align-items:center;gap:8px;"
x-data="{ openMenu:false }">
<div
style="flex:1;display:flex;justify-content:space-between;align-items:center;position:relative;">
<div @click="chapter.expanded = !chapter.expanded; currentChapter = chapter"
style="flex:1;cursor:pointer;display:flex;align-items:center;gap:8px;">
<div style="flex:1;" x-text="chapter.title"></div>
<div
style="font-size:12px;color:var(--text-secondary);display:flex;align-items:center;gap:6px;">
<span x-text="`${chapter.scenes ? chapter.scenes.length : 0}`"></span>
<span class="caret" :class="{ 'rotated': chapter.expanded }">▾</span>
</div>
</div>
<div class="item-actions" style="margin-left:8px;">
<button class="menu-button" @click.stop="openMenu = !openMenu" title="More">
⋯
</button>
<div x-show="openMenu" x-cloak @click.away="openMenu = false" x-transition
class="action-menu">
<button @click.stop="moveChapterUp(chapter.id); openMenu=false">Move
chapter up</button>
<button @click.stop="moveChapterDown(chapter.id); openMenu=false">Move
chapter down</button>
<button @click.stop="deleteChapter(chapter.id); openMenu=false">Delete
chapter</button>
</div>
</div>
</div>
</div>
<div class="chapter-scenes" x-show="chapter.expanded">
<template x-for="scene in chapter.scenes" :key="scene.id">
<div class="scene-item"
:class="{ 'active': currentScene && currentScene.id === scene.id }"
@click="loadScene(scene.id)"
style="display:flex;justify-content:space-between;align-items:center;">
<div>
<div class="scene-title" x-text="scene.title"></div>
<div class="scene-meta" x-text="`${scene.wordCount || 0} words`"></div>
</div>
<div class="item-actions" style="margin-left:12px;"
x-data="{ openMenu:false }">
<button class="menu-button" @click.stop="openMenu = !openMenu"
title="More">
⋯
</button>
<div x-show="openMenu" x-cloak @click.away="openMenu = false"
x-transition class="action-menu">
<div class="menu-row">
<label
style="font-size:12px;color:var(--text-secondary);flex:1;">Move
to
<select class="move-select"
@change.stop="moveSceneToChapter(scene.id, $event.target.value); openMenu=false;">
<option value="" disabled selected>Choose chapter
</option>
<template x-for="ch in chapters" :key="ch.id">
<option :value="ch.id"
:selected="ch.id === scene.chapterId"
x-text="ch.title"></option>
</template>
</select>
</label>
</div>
<button @click.stop="moveSceneUp(scene.id); openMenu=false">Move
up</button>
<button @click.stop="moveSceneDown(scene.id); openMenu=false">Move
down</button>
<button @click.stop="deleteScene(scene.id); openMenu=false">Delete
scene</button>
</div>
</div>
</div>
</template>
</div>
</div>
</template>
</div>
<div style="display:flex;flex-direction:column;padding:10px;gap:8px;">
<button class="add-scene-btn" @click="openNewSceneModal()">
+ New Scene
</button>
<button class="add-scene-btn" @click="openNewChapterModal()">
+ New Chapter
</button>
</div>
</div>
<!-- Editor -->
<div class="editor-container">
<div class="editor-header">
<div class="editor-title" x-text="currentScene ? currentScene.title : 'No scene selected'">
</div>
<div class="editor-stats">
<span x-text="`${currentSceneWords} words`"></span>
<span class="save-indicator" :class="{ 'saving': isSaving }" x-text="saveStatus"></span>
</div>
</div>
<div class="editor-main">
<template x-if="currentScene">
<textarea class="editor-textarea" x-model="currentScene.content" @input="onEditorChange"
placeholder="Start writing your scene here..."></textarea>
</template>
<template x-if="!currentScene">
<div class="welcome-screen">
<div class="welcome-content">
<h2>Ready to Write</h2>
<p>Select a scene from the sidebar or create a new one to get started.</p>
</div>
</div>
</template>
</div>
<!-- Beat Panel -->
<div class="beat-panel">
<h3>Generate from Beat</h3>
<div class="beat-input-group">
<textarea class="beat-input" x-model="beatInput"
placeholder="Describe what happens next (e.g., 'She grabs her bag and leaves the apartment. She senses tension in the corridor.')"
rows="2"></textarea>
<div style="display:flex;flex-direction:column;gap:8px;align-items:flex-start;">
<div style="display:flex;gap:8px;align-items:center;">
<button class="generate-btn" @click="generateFromBeat"
:disabled="!beatInput || isGenerating || aiStatus !== 'ready'">
<span x-text="isGenerating ? 'Generating...' : 'Generate'"></span>
</button>
<button class="btn btn-secondary" @click="showSceneOptions = !showSceneOptions"
title="Scene options">
⚙️
</button>
</div>
<div class="scene-options" x-show="showSceneOptions"
style="background:var(--bg-tertiary);padding:10px;border-radius:6px;border:1px solid var(--border);width:320px;">
<div style="display:flex;flex-direction:column;gap:8px;">
<label style="font-size:12px;color:var(--text-secondary);">POV CHARACTER
<input type="text" x-model="povCharacter" placeholder="e.g., Alice"
style="width:100%;margin-top:6px;padding:8px;background:var(--bg-primary);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);">
</label>
<label style="font-size:12px;color:var(--text-secondary);">POV
<select x-model="pov"
style="width:100%;margin-top:6px;padding:8px;background:var(--bg-primary);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);">
<option>3rd person limited</option>
<option>1st person</option>
<option>omniscient</option>
</select>
</label>
<label style="font-size:12px;color:var(--text-secondary);">TENSE
<select x-model="tense"
style="width:100%;margin-top:6px;padding:8px;background:var(--bg-primary);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);">
<option value="past">past tense</option>
<option value="present">present tense</option>
</select>
</label>
<div style="display:flex;gap:8px;margin-top:6px;">
<button class="btn btn-secondary"
@click="showPromptsPanel = true">Prompts</button>
</div>
</div>
</div>
</div>
</div>
<div class="context-info">
<span x-show="aiStatus === 'ready'">✓ AI ready • Will use current scene as context</span>
<span x-show="aiStatus === 'loading'">⏳ Loading model...</span>
<span x-show="aiStatus === 'error'">⚠️ Model not loaded (using placeholder text for
now)</span>
</div>
<!-- Generation actions (accept/retry/discard) -->
<div style="position:relative;padding:12px 30px 20px;">
<div x-show="showGenActions" x-cloak
style="position:absolute;right:30px;top:-12px;display:flex;gap:8px;">
<button class="btn btn-primary" @click="acceptGeneration">Accept</button>
<button class="btn btn-secondary" @click="retryGeneration">Retry</button>
<button class="btn btn-secondary" @click="discardGeneration">Discard</button>
</div>
</div>
</div>
</div>
</div>
</template>
<!-- Model Loading Screen -->
<template x-if="showModelLoading">
<div class="welcome-screen">
<div class="welcome-content">
<h2>Setting Up AI</h2>
<p x-text="loadingMessage"></p>
<div class="loading-bar">
<div class="loading-bar-fill" :style="`width: ${loadingProgress}%`"></div>
</div>
<p style="margin-top: 10px; font-size: 14px;" x-text="`${loadingProgress}%`"></p>
<div class="note" x-show="loadingProgress < 100">
This may take a minute on first run while the AI engine initializes...
</div>
</div>
</div>
</template>
<!-- Welcome Screen (No Project) -->
<template x-if="!currentProject && !showModelLoading">
<div class="welcome-screen">
<div class="welcome-content">
<h2>Welcome to Writingway 2.0</h2>
<p>A free, open-source creative writing tool with AI assistance. Everything runs locally on your
computer.</p>
<button class="btn btn-primary" @click="showNewProjectModal = true">
Create Your First Project
</button>
<div class="note">
<strong>Note:</strong> AI integration is currently being set up. The app will work without AI,
and generation features will use placeholder text until the local model is fully configured.
</div>
</div>
</div>
</template>
<!-- New Project Modal -->
<template x-if="showNewProjectModal">
<div class="modal-overlay" @click.self="showNewProjectModal = false">
<div class="modal">
<h3>New Project</h3>
<input type="text" x-model="newProjectName" placeholder="Project name (e.g., 'My Novel')"
@keydown.enter="createProject">
<div class="modal-buttons">
<button class="btn btn-secondary" @click="showNewProjectModal = false">Cancel</button>
<button class="btn btn-primary" @click="createProject">Create</button>
</div>
</div>
</div>
</template>
<!-- Rename Project Modal -->
<template x-if="showRenameProjectModal">
<div class="modal-overlay" @click.self="showRenameProjectModal = false">
<div class="modal">
<h3>Rename Project</h3>
<input type="text" x-model="renameProjectName" placeholder="Project name"
@keydown.enter="renameCurrentProject">
<div class="modal-buttons">
<button class="btn btn-secondary" @click="showRenameProjectModal = false">Cancel</button>
<button class="btn btn-primary" @click="renameCurrentProject">Rename</button>
</div>
</div>
</div>
</template>
<!-- AI Settings Modal (placeholder) -->
<template x-if="showAISettings">
<div class="modal-overlay" @click.self="showAISettings = false">
<div class="modal">
<h3>AI Provider Settings</h3>
<p style="color:var(--text-secondary);">Configure AI providers and API keys here (placeholder).</p>
<div style="margin-top:12px;">
<label style="font-size:12px;color:var(--text-secondary);">Provider
<input type="text" placeholder="e.g., local / remote"
style="width:100%;margin-top:6px;padding:8px;background:var(--bg-primary);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);">
</label>
</div>
<div class="modal-buttons" style="margin-top:18px;">
<button class="btn btn-secondary" @click="showAISettings = false">Close</button>
<button class="btn btn-primary" @click="showAISettings = false">Save</button>
</div>
</div>
</div>
</template>
<!-- Prompts Slide Panel -->
<div class="slide-panel-right" :class="{ 'open': showPromptsPanel }" x-cloak aria-hidden="true">
<div class="slide-panel-header">
<h3 style="margin:0;">Prompts</h3>
<div>
<button class="btn btn-secondary" @click="showPromptsPanel = false">Close</button>
</div>
</div>
<div class="slide-panel-body">
<div style="display:flex;gap:12px;height:100%;">
<div style="width:40%;overflow:auto;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
<strong style="color:var(--text-primary);">Categories</strong>
<button class="btn btn-secondary" @click="createPrompt('prose')">+ New</button>
</div>
<template x-for="cat in promptCategories" :key="cat">
<div style="margin-bottom:12px;">
<div style="display:flex;justify-content:space-between;align-items:center;cursor:pointer;"
@click="promptCollapsed[cat] = !promptCollapsed[cat]">
<div style="text-transform:capitalize;font-weight:700;color:var(--text-primary);"
x-text="cat"></div>
<div style="font-size:12px;color:var(--text-secondary);"
x-text="`${(prompts.filter(p => p.category === cat) || []).length}`"></div>
</div>
<div x-show="!promptCollapsed[cat]" x-cloak style="margin-top:6px;">
<template x-for="pr in prompts.filter(p => p.category === cat)" :key="pr.id">
<div @click="openPrompt(pr.id)"
:class="{ 'active': currentPrompt && currentPrompt.id === pr.id }"
style="padding:8px;border-radius:6px;background:var(--bg-tertiary);margin-bottom:6px;cursor:pointer;">
<div style="font-weight:600;color:var(--text-primary);" x-text="pr.title">
</div>
<div style="font-size:11px;color:var(--text-secondary);"
x-text="new Date(pr.modified).toLocaleString()"></div>
</div>
</template>
</div>
</div>
</template>
</div>
<div style="flex:1;display:flex;flex-direction:column;">
<div style="flex:0 0 auto;margin-bottom:6px;">
<div x-show="!currentPrompt" style="color:var(--text-secondary);">Select a prompt or create
a new one.</div>
<div x-show="currentPrompt">
<input type="text" x-model="currentPrompt.title"
style="width:100%;padding:8px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text-primary);border-radius:6px;margin-bottom:6px;">
</div>
</div>
<div style="flex:1;overflow:auto;">
<textarea x-model="promptEditorContent"
style="width:100%;height:100%;padding:8px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text-primary);border-radius:6px;"></textarea>
</div>
<div style="flex:0 0 auto;display:flex;gap:8px;margin-top:8px;">
<button class="btn btn-primary" @click="savePrompt" :disabled="!currentPrompt">Save</button>
<button class="btn btn-secondary" @click="deletePrompt(currentPrompt.id)"
x-show="currentPrompt">Delete</button>
</div>
</div>
</div>
</div>
</div>
<!-- Codex Slide Panel -->
<div class="slide-panel-right" :class="{ 'open': showCodexPanel }" x-cloak aria-hidden="true">
<div class="slide-panel-header">
<h3 style="margin:0;">Codex</h3>
<div>
<button class="btn btn-secondary" @click="showCodexPanel = false">Close</button>
</div>
</div>
<div class="slide-panel-body">
<p style="color:var(--text-secondary);">Story bible placeholder. Add character bios and world notes here
later.</p>
<textarea
style="width:100%;height:100%;padding:8px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text-primary);border-radius:6px;">Codex notes go here.</textarea>
</div>
</div>
<!-- New Scene Modal -->
<template x-if="showNewSceneModal">
<div class="modal-overlay" @click.self="showNewSceneModal = false">
<div class="modal">
<h3>New Scene</h3>
<input type="text" x-model="newSceneName" placeholder="Scene name (e.g., 'Opening')"
@keydown.enter="createScene">
<div class="modal-buttons">
<button class="btn btn-secondary" @click="showNewSceneModal = false">Cancel</button>
<button class="btn btn-primary" @click="createScene">Create</button>
</div>
</div>
</div>
</template>
<!-- New Chapter Modal -->
<template x-if="showNewChapterModal">
<div class="modal-overlay" @click.self="showNewChapterModal = false">
<div class="modal">
<h3>New Chapter</h3>
<input type="text" x-model="newChapterName" placeholder="Chapter name (e.g., 'Chapter 1')"
@keydown.enter="createChapter">
<div class="modal-buttons">
<button class="btn btn-secondary" @click="showNewChapterModal = false">Cancel</button>
<button class="btn btn-primary" @click="createChapter">Create</button>
</div>
</div>
</div>
</template>
</div>
</body>
</html>