diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index ebe0224d..33dcb4d0 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -287,6 +287,7 @@ dependencies = [ "local-ip-address 0.6.11", "qrcode", "reqwest 0.12.28", + "rustls", "serde", "serde_json", "sqlx", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 6d2ca373..830b7a93 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -38,6 +38,7 @@ base64 = "0.22" local-ip-address = "0.6" uuid = { version = "1", features = ["v4"] } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } +rustls = { version = "0.23", default-features = false, features = ["ring", "std"] } tauri-plugin-devtools = { version = "2", optional = true } sqlx = { version = "0.8.6", features = ["sqlite", "runtime-tokio"] } hex = "0.4.3" diff --git a/src-tauri/gen/android/app/tauri.properties b/src-tauri/gen/android/app/tauri.properties index 299b4875..1463df20 100644 --- a/src-tauri/gen/android/app/tauri.properties +++ b/src-tauri/gen/android/app/tauri.properties @@ -1,3 +1,3 @@ // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -tauri.android.versionName=0.7.0-pre.3 -tauri.android.versionCode=7000 \ No newline at end of file +tauri.android.versionName=0.7.3-pre.1 +tauri.android.versionCode=7003 \ No newline at end of file diff --git a/src-tauri/gen/android/gradlew b/src-tauri/gen/android/gradlew old mode 100644 new mode 100755 diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 4d5258fb..eae2bad2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -11,6 +11,8 @@ use sync::commands::{ #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { + let _ = rustls::crypto::ring::default_provider().install_default(); + let migrations = vec![ Migration { version: 1, diff --git a/src/lib/components/discovery/DiscoveryModal.svelte b/src/lib/components/discovery/DiscoveryModal.svelte index 29b57f54..7e37ca4a 100644 --- a/src/lib/components/discovery/DiscoveryModal.svelte +++ b/src/lib/components/discovery/DiscoveryModal.svelte @@ -254,7 +254,7 @@ !v && onClose()}> {#if selectedCard} +
- +
diff --git a/src/lib/components/ui/button/button.svelte b/src/lib/components/ui/button/button.svelte index f39e0236..48e08892 100644 --- a/src/lib/components/ui/button/button.svelte +++ b/src/lib/components/ui/button/button.svelte @@ -100,9 +100,9 @@ {/if} {#if isMobile} - {#if mobileLabel}{mobileLabel}{/if} + {#if mobileLabel}{mobileLabel}{/if} {:else} - {#if label}{label}{/if} + {#if label}{label}{/if} {#if children && !label}{@render children()}{/if} {#if EndIcon}{/if} {/if} @@ -139,16 +139,18 @@ {#if href} {#if Icon}{/if} - {#if mobileLabel}{mobileLabel}{/if} - {#if label}{/if} + {#if mobileLabel}{mobileLabel}{/if} + {#if label}{/if} {#if children && !label}{/if} {#if EndIcon} {:else} diff --git a/src/lib/components/vault/InteractiveVaultAssistant.svelte b/src/lib/components/vault/InteractiveVaultAssistant.svelte index 305d2bf9..1bf10a00 100644 --- a/src/lib/components/vault/InteractiveVaultAssistant.svelte +++ b/src/lib/components/vault/InteractiveVaultAssistant.svelte @@ -44,14 +44,13 @@ import VaultEntityEditPanel from './VaultEntityEditPanel.svelte' import { fade, slide } from 'svelte/transition' import { onMount, onDestroy, tick } from 'svelte' - import * as Sheet from '$lib/components/ui/sheet' import * as Dialog from '$lib/components/ui/dialog' import * as ResponsiveModal from '$lib/components/ui/responsive-modal' import { parseMarkdown } from '$lib/utils/markdown' import { cn } from '$lib/utils/cn' import { isTouchDevice } from '$lib/utils/swipe' import { SvelteSet } from 'svelte/reactivity' - import { createIsMobile } from '$lib/hooks/is-mobile.svelte' + import { createIsCompact } from '$lib/hooks/is-compact.svelte' interface Props { onClose: () => void @@ -61,8 +60,8 @@ let { onClose, onEditEntity, focusedEntity = null }: Props = $props() - // Mobile detection - const isMobile = createIsMobile() + // Layout breakpoint: below 1024px we use the compact (tabs, full-screen) layout + const isCompact = createIsCompact() // AbortController for cancelling ongoing requests let abortController: AbortController | null = null @@ -75,7 +74,7 @@ let inputValue = $state('') let isGenerating = $state(false) let error = $state(null) - let messagesContainer: HTMLDivElement + let messagesContainer = $state(null) let expandedReasoning = $state>(new Set()) // Progress state @@ -98,6 +97,36 @@ // Tracks the most recently viewed character via show_entity (fallback when no focusedEntity) let viewedEntity = $state(null) + + // Compact-width tab state + let activeTab = $state<'chat' | 'entity'>('chat') + + // Auto-fall-back to chat tab when the Entity tab loses its content + // (e.g. user closed the editor, approved/rejected the last pending change, + // or conversation switched away from an active change). + $effect(() => { + const entityTabAvailable = vaultEditor.editorOpen && vaultEditor.activeChange !== null + if (!entityTabAvailable && activeTab === 'entity') { + activeTab = 'chat' + } + }) + + // Pulse the Entity tab when a new pending change arrives while user is on Chat + let entityTabPulsing = $state(false) + let prevPendingCount = vaultEditor.pendingCount + $effect(() => { + const current = vaultEditor.pendingCount + if (current > prevPendingCount && activeTab === 'chat') { + entityTabPulsing = true + const timer = setTimeout(() => { + entityTabPulsing = false + }, 800) + prevPendingCount = current + return () => clearTimeout(timer) + } + prevPendingCount = current + }) + const activeCharacterEntity = $derived( focusedEntity?.entityType === 'character' ? focusedEntity @@ -106,6 +135,15 @@ : null, ) + const entityTabLabel = $derived.by(() => { + const type = vaultEditor.activeChange?.entityType + if (type === 'character') return 'Character' + if (type === 'lorebook') return 'Lorebook' + if (type === 'lorebook-entry') return 'Lorebook' + if (type === 'scenario') return 'Scenario' + return 'Entity' + }) + const entityIcons = { character: User, 'lorebook-entry': BookOpen, @@ -142,7 +180,7 @@ loadConversationsList() // Auto-open focused entity if provided - if (focusedEntity && !isMobile.current) { + if (focusedEntity) { let entityData: any = null if (focusedEntity.entityType === 'character') { entityData = characterVault.getById(focusedEntity.entityId) @@ -227,6 +265,8 @@ } service.reset() vaultEditor.reset() + activeTab = 'chat' + prevPendingCount = 0 initializeService() await loadConversationsList() } @@ -240,6 +280,8 @@ const loaded = await service.loadConversation(id) if (loaded) { vaultEditor.reset() + activeTab = 'chat' + prevPendingCount = 0 // Restore full UI state from persisted data if (loaded.chatMessages.length > 0) { messages = loaded.chatMessages @@ -275,6 +317,8 @@ if (service?.getConversationId() === id) { service.reset() vaultEditor.reset() + activeTab = 'chat' + prevPendingCount = 0 initializeService() } await loadConversationsList() @@ -289,14 +333,17 @@ } let editPanelRef = $state | null>(null) - let editPanelMobileRef = $state | null>(null) - function handleSetPortrait(imageId: string) { + async function handleSetPortrait(imageId: string) { if (!activeCharacterEntity || !service) return const dataUrl = service.generatedImages.get(imageId) if (!dataUrl) return - const panel = editPanelRef ?? editPanelMobileRef - panel?.setPortrait(dataUrl) + // On compact, the panel only mounts inside the Entity tab — switch first so the ref exists. + if (isCompact.current && activeTab !== 'entity') { + activeTab = 'entity' + await tick() + } + editPanelRef?.setPortrait(dataUrl) } async function handleSend() { @@ -386,16 +433,13 @@ vaultEditor.addPendingChange(incoming) await handleApprove(incoming) // Open the newly created lorebook in the editor - if (!isMobile.current) { - await tick() - vaultEditor.openEditor(incoming) - } + await tick() + vaultEditor.openEditor(incoming) } else { vaultEditor.addPendingChange(incoming) - // Auto-open entity editor on desktop (store handles same-lorebook skip) - if (!isMobile.current) { - vaultEditor.openEditorSmart(incoming) - } + // Auto-open entity editor (store handles same-lorebook skip). + // On compact, the Entity tab becomes available — user still has to tap to switch. + vaultEditor.openEditorSmart(incoming) } // Track for immediate chat display streamingChanges = [...streamingChanges, incoming] @@ -418,9 +462,7 @@ case 'show_entity': // Open entity in view mode (no approval workflow) - if (!isMobile.current) { - vaultEditor.openViewer(event.change, event.entityId, event.entityType) - } + vaultEditor.openViewer(event.change, event.entityId, event.entityType) // Track which character is currently being viewed so the Set Portrait button appears if (event.entityType === 'character') { viewedEntity = { @@ -542,106 +584,202 @@ } - !open && onClose()}> - -
- -
-
- -
-
- -
-

Vault Assistant

+{#snippet assistantContent()} + Vault Assistant +
+ +
+
+ +
+
+
+

Vault Assistant

- {#if vaultEditor.pendingCount > 0} -
- -
- {/if}
- - -
- - {#if vaultEditor.editorOpen && vaultEditor.activeChange && !isMobile.current} -
0} +
+
- {/if} + + Approve All + + {vaultEditor.pendingBreakdown} + + +
+ {/if} +
- + +
+ + {#if vaultEditor.editorOpen && vaultEditor.activeChange && !isCompact.current}
- -
+ + handleApprove(specificChange ?? vaultEditor.activeChange!)} + onReject={(change) => handleReject(change)} + onClose={() => vaultEditor.closeEditor()} + /> +
+ {/if} + + +
+ +
+ + {#if conversationSelectorOpen} + + + +
(conversationSelectorOpen = false)} + transition:fade={{ duration: 100 }} + >
+ +
+
+ + + + {#if conversations.length > 0} +
+ {#each conversations as conv, i (conv.id)} +
+ + +
+ {/each} + {/if} +
+
+ {/if} +
+ + + {#if pendingOnly.length > 0} +
- {#if conversationSelectorOpen} + {#if pendingListOpen}
(conversationSelectorOpen = false)} + onclick={() => (pendingListOpen = false)} transition:fade={{ duration: 100 }} >
@@ -650,169 +788,113 @@ transition:slide={{ duration: 150 }} >
- - - - {#if conversations.length > 0} -
- {#each conversations as conv, i (conv.id)} +
+ +
+ +
+
+ {getChangeName(change)} +
+
+ + {aStyle.label} + + + {change.entityType === 'lorebook-entry' ? 'entry' : change.entityType} + +
+
+ + + +
e.stopPropagation()} >
- {/each} - {/if} +
+ {/each}
{/if}
+ {/if} - - {#if pendingOnly.length > 0} -
- - {#if pendingListOpen} - - - -
(pendingListOpen = false)} - transition:fade={{ duration: 100 }} - >
- -
+ {#if isCompact.current && vaultEditor.editorOpen && vaultEditor.activeChange} +
+ + - -
-
- {/each} -
-
+ {vaultEditor.pendingCount} + {/if} -
- {/if} + +
+ {/if} - + + {#if !isCompact.current || activeTab === 'chat'}
{#each messages as message (message.id)}
@@ -822,12 +904,7 @@ message.role === 'user' ? 'justify-end' : 'justify-start', )} > -
+
{#if isGenerating}
-
+
-
+