Skip to content

Commit a19a258

Browse files
committed
Novel editor working
Signed-off-by: Alex Mikhalev <alex@metacortex.engineer>
1 parent c810b5f commit a19a258

15 files changed

Lines changed: 504 additions & 94 deletions

desktop/src/lib/Chat/Chat.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,7 +1103,7 @@ function _toggleSessionList() {
11031103

11041104
<div class="field has-addons chat-input">
11051105
<div class="control is-expanded">
1106-
<textarea class="textarea" rows="3" bind:value={input} on:keydown={_handleKeydown} placeholder="Type your message and press Enter..." data-testid="chat-input" />
1106+
<textarea class="textarea" rows="3" bind:value={input} on:keydown={_handleKeydown} placeholder="Type your message and press Enter..." data-testid="chat-input"></textarea>
11071107
</div>
11081108
<div class="control">
11091109
<button class="button is-primary" on:click={sendMessage} disabled={sending || !input.trim()} data-testid="send-message-button">
@@ -1330,7 +1330,7 @@ function _toggleSessionList() {
13301330
<!-- Debug Request Modal -->
13311331
{#if _showDebugRequest}
13321332
<div class="modal is-active">
1333-
<div class="modal-background" on:click={() => _showDebugRequest = false}></div>
1333+
<div class="modal-background" on:click={() => _showDebugRequest = false} role="button" tabindex="0" aria-label="Close debug request modal"></div>
13341334
<div class="modal-card">
13351335
<header class="modal-card-head">
13361336
<p class="modal-card-title">
@@ -1371,7 +1371,7 @@ function _toggleSessionList() {
13711371
<!-- Debug Response Modal -->
13721372
{#if _showDebugResponse}
13731373
<div class="modal is-active">
1374-
<div class="modal-background" on:click={() => _showDebugResponse = false}></div>
1374+
<div class="modal-background" on:click={() => _showDebugResponse = false} role="button" tabindex="0" aria-label="Close debug response modal"></div>
13751375
<div class="modal-card">
13761376
<header class="modal-card-head">
13771377
<p class="modal-card-title">

desktop/src/lib/ConfigJsonEditor.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@ onMount(() => {
5151
<i>The best editing experience is to configure Atomic Server, in the meantime use editor below. You will need to refresh page via Command R or Ctrl-R to see changes</i>
5252
</p>
5353
<div class="editor">
54-
<textarea class="textarea" rows="20" bind:value={_content.json} on:change={() => _handleChange(_content)} style="font-family: monospace;" />
54+
<textarea class="textarea" rows="20" bind:value={_content.json} on:change={() => _handleChange(_content)} style="font-family: monospace;"></textarea>
5555
</div>
5656
</div>

desktop/src/lib/ConfigWizard.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -898,7 +898,7 @@ function _closeWizard() {
898898
<div class="field">
899899
<label class="label" for={`openrouter-chat-system-${idx}`}>System Prompt (optional)</label>
900900
<div class="control">
901-
<textarea class="textarea" id={`openrouter-chat-system-${idx}`} rows="3" placeholder="You are a helpful Rust engineer assistant..." bind:value={$draft.roles[idx].openrouter_chat_system_prompt} />
901+
<textarea class="textarea" id={`openrouter-chat-system-${idx}`} rows="3" placeholder="You are a helpful Rust engineer assistant..." bind:value={$draft.roles[idx].openrouter_chat_system_prompt}></textarea>
902902
</div>
903903
</div>
904904
{/if}

desktop/src/lib/Editor/NovelWrapper.svelte

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
<script lang="ts">
22
import { onDestroy, onMount } from 'svelte';
3-
// @ts-expect-error
4-
// import { JSONEditor } from 'svelte-jsoneditor'; // Removed - not needed
3+
import { Editor } from '@tiptap/core';
4+
import StarterKit from '@tiptap/starter-kit';
5+
import { Markdown } from 'tiptap-markdown';
56
import { is_tauri, role } from '$lib/stores';
67
import { novelAutocompleteService } from '../services/novelAutocompleteService';
78
import { TerraphimSuggestion, terraphimSuggestionStyles } from './TerraphimSuggestion';
9+
import { SlashCommand, slashCommandStyles } from './SlashCommand';
810
911
export let html: string = ''; // initial content in HTML/JSON
1012
export const readOnly: boolean = false;
@@ -21,6 +23,9 @@ let _autocompleteStatus = '⏳ Initializing...';
2123
let autocompleteReady = false;
2224
let connectionTested = false;
2325
let styleElement: HTMLStyleElement | null = null;
26+
let editorInstance: Editor | null = null;
27+
let editorElement: HTMLDivElement | null = null;
28+
let isInitializing = false;
2429
2530
// Markdown plugin removed - not available in svelte-jsoneditor
2631
@@ -32,25 +37,65 @@ onMount(async () => {
3237
// Inject CSS styles for suggestions
3338
if (typeof document !== 'undefined') {
3439
styleElement = document.createElement('style');
35-
styleElement.textContent = terraphimSuggestionStyles;
40+
styleElement.textContent = `${terraphimSuggestionStyles}\n${slashCommandStyles}`;
3641
document.head.appendChild(styleElement);
3742
}
43+
44+
// Initialize TipTap editor
45+
if (typeof document !== 'undefined' && editorElement) {
46+
editorInstance = new Editor({
47+
element: editorElement as HTMLElement,
48+
extensions: [
49+
StarterKit,
50+
Markdown.configure({ html: true }),
51+
SlashCommand.configure({
52+
trigger: '/',
53+
}),
54+
...(enableAutocomplete
55+
? [
56+
TerraphimSuggestion.configure({
57+
trigger: suggestionTrigger,
58+
allowSpaces: false,
59+
limit: maxSuggestions,
60+
minLength: minQueryLength,
61+
debounce: debounceDelay,
62+
}),
63+
]
64+
: []),
65+
],
66+
content: html,
67+
editable: !readOnly,
68+
onUpdate: ({ editor }) => {
69+
_handleUpdate(editor as any);
70+
},
71+
});
72+
// Keep reference for other handlers
73+
editor = editorInstance as unknown;
74+
}
3875
});
3976
4077
onDestroy(() => {
4178
// Cleanup styles
4279
if (styleElement?.parentNode) {
4380
styleElement.parentNode.removeChild(styleElement);
4481
}
82+
if (editorInstance) {
83+
editorInstance.destroy();
84+
editorInstance = null;
85+
}
4586
});
4687
4788
// Watch for role changes and reinitialize
48-
$: if ($role && enableAutocomplete && autocompleteReady) {
89+
$: if ($role && enableAutocomplete) {
90+
// Only update the role on change; avoid re-triggering full initialization here
4991
novelAutocompleteService.setRole($role);
50-
initializeAutocomplete();
5192
}
5293
5394
async function initializeAutocomplete() {
95+
if (isInitializing) {
96+
return;
97+
}
98+
isInitializing = true;
5499
_autocompleteStatus = '⏳ Initializing autocomplete...';
55100
autocompleteReady = false;
56101
connectionTested = false;
@@ -65,20 +110,13 @@ async function initializeAutocomplete() {
65110
connectionTested = true;
66111
67112
if (connectionOk) {
68-
// Build the autocomplete index
69-
_autocompleteStatus = '🔨 Building autocomplete index...';
70-
const success = await novelAutocompleteService.buildAutocompleteIndex();
71-
72-
if (success) {
73-
if ($is_tauri) {
74-
_autocompleteStatus = '✅ Ready - Using Tauri backend';
75-
} else {
76-
_autocompleteStatus = '✅ Ready - Using MCP server backend';
77-
}
78-
autocompleteReady = true;
113+
// Defer heavy index building until first suggestion request to avoid race loops.
114+
if ($is_tauri) {
115+
_autocompleteStatus = '✅ Ready - Using Tauri backend';
79116
} else {
80-
_autocompleteStatus = '❌ Failed to build autocomplete index';
117+
_autocompleteStatus = '✅ Ready - Using MCP server backend';
81118
}
119+
autocompleteReady = true;
82120
} else {
83121
if ($is_tauri) {
84122
_autocompleteStatus = '❌ Tauri backend not available';
@@ -89,6 +127,8 @@ async function initializeAutocomplete() {
89127
} catch (error) {
90128
console.error('Error initializing autocomplete:', error);
91129
_autocompleteStatus = '❌ Autocomplete initialization error';
130+
} finally {
131+
isInitializing = false;
92132
}
93133
}
94134
@@ -220,23 +260,7 @@ Start typing below:`;
220260
};
221261
</script>
222262

223-
<JSONEditor
224-
defaultValue={html}
225-
isEditable={!readOnly}
226-
disableLocalStorage={true}
227-
onUpdate={_handleUpdate}
228-
extensions={[
229-
...(enableAutocomplete ? [
230-
TerraphimSuggestion.configure({
231-
trigger: suggestionTrigger,
232-
allowSpaces: false,
233-
limit: maxSuggestions,
234-
minLength: minQueryLength,
235-
debounce: debounceDelay,
236-
})
237-
] : [])
238-
]}
239-
/>
263+
<div class="novel-editor" bind:this={editorElement}></div>
240264

241265
<!-- Autocomplete Status and Controls -->
242266
{#if enableAutocomplete}

0 commit comments

Comments
 (0)