refactor: Services/Stores syntax + logic improvements
Refactors components to access stores directly instead of using exported getter functions. This change centralizes store access and logic, simplifying component code and improving maintainability by reducing the number of exported functions and promoting direct store interaction. Removes exported getter functions from `chat.svelte.ts`, `conversations.svelte.ts`, `models.svelte.ts` and `settings.svelte.ts`.
This commit is contained in:
parent
69065ddc56
commit
6a3d6e79d2
|
|
@ -9,16 +9,9 @@
|
||||||
} from '$lib/components/app';
|
} from '$lib/components/app';
|
||||||
import { INPUT_CLASSES } from '$lib/constants/input-classes';
|
import { INPUT_CLASSES } from '$lib/constants/input-classes';
|
||||||
import { config } from '$lib/stores/settings.svelte';
|
import { config } from '$lib/stores/settings.svelte';
|
||||||
import {
|
import { modelsStore, modelOptions, selectedModelId } from '$lib/stores/models.svelte';
|
||||||
modelOptions,
|
import { isRouterMode } from '$lib/stores/server.svelte';
|
||||||
selectedModelId,
|
import { chatStore } from '$lib/stores/chat.svelte';
|
||||||
isRouterMode,
|
|
||||||
fetchModelProps,
|
|
||||||
getModelProps,
|
|
||||||
modelSupportsVision,
|
|
||||||
modelSupportsAudio
|
|
||||||
} from '$lib/stores/models.svelte';
|
|
||||||
import { getConversationModel } from '$lib/stores/chat.svelte';
|
|
||||||
import { activeMessages } from '$lib/stores/conversations.svelte';
|
import { activeMessages } from '$lib/stores/conversations.svelte';
|
||||||
import {
|
import {
|
||||||
FileTypeCategory,
|
FileTypeCategory,
|
||||||
|
|
@ -77,7 +70,9 @@
|
||||||
let textareaRef: ChatFormTextarea | undefined = $state(undefined);
|
let textareaRef: ChatFormTextarea | undefined = $state(undefined);
|
||||||
|
|
||||||
// Check if model is selected (in ROUTER mode)
|
// Check if model is selected (in ROUTER mode)
|
||||||
let conversationModel = $derived(getConversationModel(activeMessages() as DatabaseMessage[]));
|
let conversationModel = $derived(
|
||||||
|
chatStore.getConversationModel(activeMessages() as DatabaseMessage[])
|
||||||
|
);
|
||||||
let isRouter = $derived(isRouterMode());
|
let isRouter = $derived(isRouterMode());
|
||||||
let hasModelSelected = $derived(!isRouter || !!conversationModel || !!selectedModelId());
|
let hasModelSelected = $derived(!isRouter || !!conversationModel || !!selectedModelId());
|
||||||
|
|
||||||
|
|
@ -109,9 +104,9 @@
|
||||||
// Fetch model props when active model changes
|
// Fetch model props when active model changes
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (isRouter && activeModelId) {
|
if (isRouter && activeModelId) {
|
||||||
const cached = getModelProps(activeModelId);
|
const cached = modelsStore.getModelProps(activeModelId);
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
fetchModelProps(activeModelId).then(() => {
|
modelsStore.fetchModelProps(activeModelId).then(() => {
|
||||||
modelPropsVersion++;
|
modelPropsVersion++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +117,7 @@
|
||||||
let hasAudioModality = $derived.by(() => {
|
let hasAudioModality = $derived.by(() => {
|
||||||
if (activeModelId) {
|
if (activeModelId) {
|
||||||
void modelPropsVersion; // Trigger reactivity on props fetch
|
void modelPropsVersion; // Trigger reactivity on props fetch
|
||||||
return modelSupportsAudio(activeModelId);
|
return modelsStore.modelSupportsAudio(activeModelId);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
@ -130,7 +125,7 @@
|
||||||
let hasVisionModality = $derived.by(() => {
|
let hasVisionModality = $derived.by(() => {
|
||||||
if (activeModelId) {
|
if (activeModelId) {
|
||||||
void modelPropsVersion; // Trigger reactivity on props fetch
|
void modelPropsVersion; // Trigger reactivity on props fetch
|
||||||
return modelSupportsVision(activeModelId);
|
return modelsStore.modelSupportsVision(activeModelId);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,9 @@
|
||||||
import { FileTypeCategory } from '$lib/enums';
|
import { FileTypeCategory } from '$lib/enums';
|
||||||
import { getFileTypeCategory } from '$lib/utils/file-type';
|
import { getFileTypeCategory } from '$lib/utils/file-type';
|
||||||
import { config } from '$lib/stores/settings.svelte';
|
import { config } from '$lib/stores/settings.svelte';
|
||||||
import {
|
import { modelsStore, modelOptions, selectedModelId } from '$lib/stores/models.svelte';
|
||||||
modelOptions,
|
import { isRouterMode } from '$lib/stores/server.svelte';
|
||||||
selectedModelId,
|
import { chatStore } from '$lib/stores/chat.svelte';
|
||||||
selectModelByName,
|
|
||||||
isRouterMode,
|
|
||||||
fetchModelProps,
|
|
||||||
getModelProps,
|
|
||||||
modelSupportsVision,
|
|
||||||
modelSupportsAudio
|
|
||||||
} from '$lib/stores/models.svelte';
|
|
||||||
import { getConversationModel } from '$lib/stores/chat.svelte';
|
|
||||||
import { activeMessages } from '$lib/stores/conversations.svelte';
|
import { activeMessages } from '$lib/stores/conversations.svelte';
|
||||||
import type { ChatUploadedFile } from '$lib/types/chat';
|
import type { ChatUploadedFile } from '$lib/types/chat';
|
||||||
|
|
||||||
|
|
@ -53,14 +45,16 @@
|
||||||
let currentConfig = $derived(config());
|
let currentConfig = $derived(config());
|
||||||
let isRouter = $derived(isRouterMode());
|
let isRouter = $derived(isRouterMode());
|
||||||
|
|
||||||
let conversationModel = $derived(getConversationModel(activeMessages() as DatabaseMessage[]));
|
let conversationModel = $derived(
|
||||||
|
chatStore.getConversationModel(activeMessages() as DatabaseMessage[])
|
||||||
|
);
|
||||||
|
|
||||||
let previousConversationModel: string | null = null;
|
let previousConversationModel: string | null = null;
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (conversationModel && conversationModel !== previousConversationModel) {
|
if (conversationModel && conversationModel !== previousConversationModel) {
|
||||||
previousConversationModel = conversationModel;
|
previousConversationModel = conversationModel;
|
||||||
selectModelByName(conversationModel);
|
modelsStore.selectModelByName(conversationModel);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -92,10 +86,10 @@
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (isRouter && activeModelId) {
|
if (isRouter && activeModelId) {
|
||||||
// Check if we already have cached props
|
// Check if we already have cached props
|
||||||
const cached = getModelProps(activeModelId);
|
const cached = modelsStore.getModelProps(activeModelId);
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
// Fetch props for this model
|
// Fetch props for this model
|
||||||
fetchModelProps(activeModelId).then(() => {
|
modelsStore.fetchModelProps(activeModelId).then(() => {
|
||||||
// Trigger reactivity update
|
// Trigger reactivity update
|
||||||
modelPropsVersion++;
|
modelPropsVersion++;
|
||||||
});
|
});
|
||||||
|
|
@ -107,7 +101,7 @@
|
||||||
let hasAudioModality = $derived.by(() => {
|
let hasAudioModality = $derived.by(() => {
|
||||||
if (activeModelId) {
|
if (activeModelId) {
|
||||||
void modelPropsVersion; // Trigger reactivity on props fetch
|
void modelPropsVersion; // Trigger reactivity on props fetch
|
||||||
return modelSupportsAudio(activeModelId);
|
return modelsStore.modelSupportsAudio(activeModelId);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
@ -115,7 +109,7 @@
|
||||||
let hasVisionModality = $derived.by(() => {
|
let hasVisionModality = $derived.by(() => {
|
||||||
if (activeModelId) {
|
if (activeModelId) {
|
||||||
void modelPropsVersion; // Trigger reactivity on props fetch
|
void modelPropsVersion; // Trigger reactivity on props fetch
|
||||||
return modelSupportsVision(activeModelId);
|
return modelsStore.modelSupportsVision(activeModelId);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getDeletionInfo } from '$lib/stores/chat.svelte';
|
import { chatStore } from '$lib/stores/chat.svelte';
|
||||||
import { copyToClipboard } from '$lib/utils/copy';
|
import { copyToClipboard } from '$lib/utils/copy';
|
||||||
import { isIMEComposing } from '$lib/utils/is-ime-composing';
|
import { isIMEComposing } from '$lib/utils/is-ime-composing';
|
||||||
import type { ApiChatCompletionToolCall } from '$lib/types/api';
|
import type { ApiChatCompletionToolCall } from '$lib/types/api';
|
||||||
|
|
@ -98,7 +98,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDelete() {
|
async function handleDelete() {
|
||||||
deletionInfo = await getDeletionInfo(message.id);
|
deletionInfo = await chatStore.getDeletionInfo(message.id);
|
||||||
showDeleteDialog = true;
|
showDeleteDialog = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
import Label from '$lib/components/ui/label/label.svelte';
|
import Label from '$lib/components/ui/label/label.svelte';
|
||||||
import { config } from '$lib/stores/settings.svelte';
|
import { config } from '$lib/stores/settings.svelte';
|
||||||
import { isRouterMode } from '$lib/stores/server.svelte';
|
import { isRouterMode } from '$lib/stores/server.svelte';
|
||||||
import { selectModel } from '$lib/stores/models.svelte';
|
import { modelsStore } from '$lib/stores/models.svelte';
|
||||||
import { copyToClipboard } from '$lib/utils/copy';
|
import { copyToClipboard } from '$lib/utils/copy';
|
||||||
import type { ApiChatCompletionToolCall } from '$lib/types/api';
|
import type { ApiChatCompletionToolCall } from '$lib/types/api';
|
||||||
|
|
||||||
|
|
@ -103,7 +103,7 @@
|
||||||
|
|
||||||
async function handleModelChange(modelId: string, modelName: string) {
|
async function handleModelChange(modelId: string, modelName: string) {
|
||||||
try {
|
try {
|
||||||
await selectModel(modelId);
|
await modelsStore.selectModelById(modelId);
|
||||||
|
|
||||||
// Pass the selected model name for regeneration
|
// Pass the selected model name for regeneration
|
||||||
onRegenerate(modelName);
|
onRegenerate(modelName);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ChatMessage } from '$lib/components/app';
|
import { ChatMessage } from '$lib/components/app';
|
||||||
import { DatabaseService } from '$lib/services/database';
|
import { DatabaseService } from '$lib/services/database';
|
||||||
import {
|
import { chatStore } from '$lib/stores/chat.svelte';
|
||||||
continueAssistantMessage,
|
import { conversationsStore, activeConversation } from '$lib/stores/conversations.svelte';
|
||||||
deleteMessage,
|
|
||||||
editAssistantMessage,
|
|
||||||
editMessageWithBranching,
|
|
||||||
editUserMessagePreserveResponses,
|
|
||||||
regenerateMessageWithBranching
|
|
||||||
} from '$lib/stores/chat.svelte';
|
|
||||||
import { activeConversation, navigateToSibling } from '$lib/stores/conversations.svelte';
|
|
||||||
import { getMessageSiblings } from '$lib/utils/branching';
|
import { getMessageSiblings } from '$lib/utils/branching';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -64,13 +57,13 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleNavigateToSibling(siblingId: string) {
|
async function handleNavigateToSibling(siblingId: string) {
|
||||||
await navigateToSibling(siblingId);
|
await conversationsStore.navigateToSibling(siblingId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleEditWithBranching(message: DatabaseMessage, newContent: string) {
|
async function handleEditWithBranching(message: DatabaseMessage, newContent: string) {
|
||||||
onUserAction?.();
|
onUserAction?.();
|
||||||
|
|
||||||
await editMessageWithBranching(message.id, newContent);
|
await chatStore.editMessageWithBranching(message.id, newContent);
|
||||||
|
|
||||||
refreshAllMessages();
|
refreshAllMessages();
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +75,7 @@
|
||||||
) {
|
) {
|
||||||
onUserAction?.();
|
onUserAction?.();
|
||||||
|
|
||||||
await editAssistantMessage(message.id, newContent, shouldBranch);
|
await chatStore.editAssistantMessage(message.id, newContent, shouldBranch);
|
||||||
|
|
||||||
refreshAllMessages();
|
refreshAllMessages();
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +83,7 @@
|
||||||
async function handleRegenerateWithBranching(message: DatabaseMessage, modelOverride?: string) {
|
async function handleRegenerateWithBranching(message: DatabaseMessage, modelOverride?: string) {
|
||||||
onUserAction?.();
|
onUserAction?.();
|
||||||
|
|
||||||
await regenerateMessageWithBranching(message.id, modelOverride);
|
await chatStore.regenerateMessageWithBranching(message.id, modelOverride);
|
||||||
|
|
||||||
refreshAllMessages();
|
refreshAllMessages();
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +91,7 @@
|
||||||
async function handleContinueAssistantMessage(message: DatabaseMessage) {
|
async function handleContinueAssistantMessage(message: DatabaseMessage) {
|
||||||
onUserAction?.();
|
onUserAction?.();
|
||||||
|
|
||||||
await continueAssistantMessage(message.id);
|
await chatStore.continueAssistantMessage(message.id);
|
||||||
|
|
||||||
refreshAllMessages();
|
refreshAllMessages();
|
||||||
}
|
}
|
||||||
|
|
@ -109,13 +102,13 @@
|
||||||
) {
|
) {
|
||||||
onUserAction?.();
|
onUserAction?.();
|
||||||
|
|
||||||
await editUserMessagePreserveResponses(message.id, newContent);
|
await chatStore.editUserMessagePreserveResponses(message.id, newContent);
|
||||||
|
|
||||||
refreshAllMessages();
|
refreshAllMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDeleteMessage(message: DatabaseMessage) {
|
async function handleDeleteMessage(message: DatabaseMessage) {
|
||||||
await deleteMessage(message.id);
|
await chatStore.deleteMessage(message.id);
|
||||||
|
|
||||||
refreshAllMessages();
|
refreshAllMessages();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,30 +16,15 @@
|
||||||
AUTO_SCROLL_INTERVAL,
|
AUTO_SCROLL_INTERVAL,
|
||||||
INITIAL_SCROLL_DELAY
|
INITIAL_SCROLL_DELAY
|
||||||
} from '$lib/constants/auto-scroll';
|
} from '$lib/constants/auto-scroll';
|
||||||
|
import { chatStore, errorDialog, isLoading } from '$lib/stores/chat.svelte';
|
||||||
import {
|
import {
|
||||||
dismissErrorDialog,
|
conversationsStore,
|
||||||
errorDialog,
|
|
||||||
isLoading,
|
|
||||||
sendMessage,
|
|
||||||
stopGeneration
|
|
||||||
} from '$lib/stores/chat.svelte';
|
|
||||||
import {
|
|
||||||
activeMessages,
|
activeMessages,
|
||||||
activeConversation,
|
activeConversation
|
||||||
deleteConversation
|
|
||||||
} from '$lib/stores/conversations.svelte';
|
} from '$lib/stores/conversations.svelte';
|
||||||
import { config } from '$lib/stores/settings.svelte';
|
import { config } from '$lib/stores/settings.svelte';
|
||||||
import { serverLoading, serverError, serverStore } from '$lib/stores/server.svelte';
|
import { serverLoading, serverError, serverStore, isRouterMode } from '$lib/stores/server.svelte';
|
||||||
import {
|
import { modelsStore, modelOptions, selectedModelId } from '$lib/stores/models.svelte';
|
||||||
modelOptions,
|
|
||||||
selectedModelId,
|
|
||||||
isRouterMode,
|
|
||||||
fetchModelProps,
|
|
||||||
getModelProps,
|
|
||||||
modelSupportsVision,
|
|
||||||
modelSupportsAudio
|
|
||||||
} from '$lib/stores/models.svelte';
|
|
||||||
import { getConversationModel } from '$lib/stores/chat.svelte';
|
|
||||||
import { parseFilesToMessageExtras } from '$lib/utils/convert-files-to-extra';
|
import { parseFilesToMessageExtras } from '$lib/utils/convert-files-to-extra';
|
||||||
import { isFileTypeSupported } from '$lib/utils/file-type';
|
import { isFileTypeSupported } from '$lib/utils/file-type';
|
||||||
import { filterFilesByModalities } from '$lib/utils/modality-file-validation';
|
import { filterFilesByModalities } from '$lib/utils/modality-file-validation';
|
||||||
|
|
@ -93,7 +78,9 @@
|
||||||
|
|
||||||
// Model-specific capability detection (same logic as ChatFormActions)
|
// Model-specific capability detection (same logic as ChatFormActions)
|
||||||
let isRouter = $derived(isRouterMode());
|
let isRouter = $derived(isRouterMode());
|
||||||
let conversationModel = $derived(getConversationModel(activeMessages() as DatabaseMessage[]));
|
let conversationModel = $derived(
|
||||||
|
chatStore.getConversationModel(activeMessages() as DatabaseMessage[])
|
||||||
|
);
|
||||||
|
|
||||||
// Get active model ID for fetching props
|
// Get active model ID for fetching props
|
||||||
let activeModelId = $derived.by(() => {
|
let activeModelId = $derived.by(() => {
|
||||||
|
|
@ -123,9 +110,9 @@
|
||||||
// Fetch model props when active model changes
|
// Fetch model props when active model changes
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (isRouter && activeModelId) {
|
if (isRouter && activeModelId) {
|
||||||
const cached = getModelProps(activeModelId);
|
const cached = modelsStore.getModelProps(activeModelId);
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
fetchModelProps(activeModelId).then(() => {
|
modelsStore.fetchModelProps(activeModelId).then(() => {
|
||||||
modelPropsVersion++;
|
modelPropsVersion++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -136,7 +123,7 @@
|
||||||
let hasAudioModality = $derived.by(() => {
|
let hasAudioModality = $derived.by(() => {
|
||||||
if (activeModelId) {
|
if (activeModelId) {
|
||||||
void modelPropsVersion; // Trigger reactivity on props fetch
|
void modelPropsVersion; // Trigger reactivity on props fetch
|
||||||
return modelSupportsAudio(activeModelId);
|
return modelsStore.modelSupportsAudio(activeModelId);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
@ -144,7 +131,7 @@
|
||||||
let hasVisionModality = $derived.by(() => {
|
let hasVisionModality = $derived.by(() => {
|
||||||
if (activeModelId) {
|
if (activeModelId) {
|
||||||
void modelPropsVersion; // Trigger reactivity on props fetch
|
void modelPropsVersion; // Trigger reactivity on props fetch
|
||||||
return modelSupportsVision(activeModelId);
|
return modelsStore.modelSupportsVision(activeModelId);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
@ -152,7 +139,7 @@
|
||||||
async function handleDeleteConfirm() {
|
async function handleDeleteConfirm() {
|
||||||
const conversation = activeConversation();
|
const conversation = activeConversation();
|
||||||
if (conversation) {
|
if (conversation) {
|
||||||
await deleteConversation(conversation.id);
|
await conversationsStore.deleteConversation(conversation.id);
|
||||||
}
|
}
|
||||||
showDeleteDialog = false;
|
showDeleteDialog = false;
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +162,7 @@
|
||||||
|
|
||||||
function handleErrorDialogOpenChange(open: boolean) {
|
function handleErrorDialogOpenChange(open: boolean) {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
dismissErrorDialog();
|
chatStore.dismissErrorDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,7 +249,7 @@
|
||||||
userScrolledUp = false;
|
userScrolledUp = false;
|
||||||
autoScrollEnabled = true;
|
autoScrollEnabled = true;
|
||||||
}
|
}
|
||||||
await sendMessage(message, extras);
|
await chatStore.sendMessage(message, extras);
|
||||||
scrollChatToBottom();
|
scrollChatToBottom();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -420,7 +407,7 @@
|
||||||
onFileRemove={handleFileRemove}
|
onFileRemove={handleFileRemove}
|
||||||
onFileUpload={handleFileUpload}
|
onFileUpload={handleFileUpload}
|
||||||
onSend={handleSendMessage}
|
onSend={handleSendMessage}
|
||||||
onStop={() => stopGeneration()}
|
onStop={() => chatStore.stopGeneration()}
|
||||||
showHelperText={false}
|
showHelperText={false}
|
||||||
bind:uploadedFiles
|
bind:uploadedFiles
|
||||||
/>
|
/>
|
||||||
|
|
@ -480,7 +467,7 @@
|
||||||
onFileRemove={handleFileRemove}
|
onFileRemove={handleFileRemove}
|
||||||
onFileUpload={handleFileUpload}
|
onFileUpload={handleFileUpload}
|
||||||
onSend={handleSendMessage}
|
onSend={handleSendMessage}
|
||||||
onStop={() => stopGeneration()}
|
onStop={() => chatStore.stopGeneration()}
|
||||||
showHelperText={true}
|
showHelperText={true}
|
||||||
bind:uploadedFiles
|
bind:uploadedFiles
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,7 @@
|
||||||
import { untrack } from 'svelte';
|
import { untrack } from 'svelte';
|
||||||
import { PROCESSING_INFO_TIMEOUT } from '$lib/constants/processing-info';
|
import { PROCESSING_INFO_TIMEOUT } from '$lib/constants/processing-info';
|
||||||
import { useProcessingState } from '$lib/hooks/use-processing-state.svelte';
|
import { useProcessingState } from '$lib/hooks/use-processing-state.svelte';
|
||||||
import {
|
import { chatStore, isLoading, isChatStreaming } from '$lib/stores/chat.svelte';
|
||||||
isLoading,
|
|
||||||
isChatStreaming,
|
|
||||||
clearProcessingState,
|
|
||||||
setActiveProcessingConversation,
|
|
||||||
restoreProcessingStateFromMessages
|
|
||||||
} from '$lib/stores/chat.svelte';
|
|
||||||
import { activeMessages, activeConversation } from '$lib/stores/conversations.svelte';
|
import { activeMessages, activeConversation } from '$lib/stores/conversations.svelte';
|
||||||
import { config } from '$lib/stores/settings.svelte';
|
import { config } from '$lib/stores/settings.svelte';
|
||||||
|
|
||||||
|
|
@ -26,7 +20,7 @@
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const conversation = activeConversation();
|
const conversation = activeConversation();
|
||||||
|
|
||||||
untrack(() => setActiveProcessingConversation(conversation?.id ?? null));
|
untrack(() => chatStore.setActiveProcessingConversation(conversation?.id ?? null));
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
|
@ -55,12 +49,12 @@
|
||||||
|
|
||||||
if (keepStatsVisible && conversation) {
|
if (keepStatsVisible && conversation) {
|
||||||
if (messages.length === 0) {
|
if (messages.length === 0) {
|
||||||
untrack(() => clearProcessingState(conversation.id));
|
untrack(() => chatStore.clearProcessingState(conversation.id));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isCurrentConversationLoading && !isStreaming) {
|
if (!isCurrentConversationLoading && !isStreaming) {
|
||||||
untrack(() => restoreProcessingStateFromMessages(messages, conversation.id));
|
untrack(() => chatStore.restoreProcessingStateFromMessages(messages, conversation.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
ChatSettingsFields
|
ChatSettingsFields
|
||||||
} from '$lib/components/app';
|
} from '$lib/components/app';
|
||||||
import { ScrollArea } from '$lib/components/ui/scroll-area';
|
import { ScrollArea } from '$lib/components/ui/scroll-area';
|
||||||
import { config, updateMultipleConfig } from '$lib/stores/settings.svelte';
|
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
||||||
import { setMode } from 'mode-watcher';
|
import { setMode } from 'mode-watcher';
|
||||||
import type { Component } from 'svelte';
|
import type { Component } from 'svelte';
|
||||||
|
|
||||||
|
|
@ -333,7 +333,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMultipleConfig(processedConfig);
|
settingsStore.updateMultipleConfig(processedConfig);
|
||||||
onSave?.();
|
onSave?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
import { Textarea } from '$lib/components/ui/textarea';
|
import { Textarea } from '$lib/components/ui/textarea';
|
||||||
import { SETTING_CONFIG_DEFAULT, SETTING_CONFIG_INFO } from '$lib/constants/settings-config';
|
import { SETTING_CONFIG_DEFAULT, SETTING_CONFIG_INFO } from '$lib/constants/settings-config';
|
||||||
import { supportsVision } from '$lib/stores/server.svelte';
|
import { supportsVision } from '$lib/stores/server.svelte';
|
||||||
import { getParameterInfo, resetParameterToServerDefault } from '$lib/stores/settings.svelte';
|
import { settingsStore } from '$lib/stores/settings.svelte';
|
||||||
import { ParameterSyncService } from '$lib/services/parameter-sync';
|
import { ParameterSyncService } from '$lib/services/parameter-sync';
|
||||||
import { ChatSettingsParameterSourceIndicator } from '$lib/components/app';
|
import { ChatSettingsParameterSourceIndicator } from '$lib/components/app';
|
||||||
import type { Component } from 'svelte';
|
import type { Component } from 'svelte';
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getParameterInfo(key);
|
return settingsStore.getParameterInfo(key);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
resetParameterToServerDefault(field.key);
|
settingsStore.resetParameterToServerDefault(field.key);
|
||||||
// Trigger UI update by calling onConfigChange with the default value
|
// Trigger UI update by calling onConfigChange with the default value
|
||||||
const defaultValue = propsDefault ?? SETTING_CONFIG_DEFAULT[field.key];
|
const defaultValue = propsDefault ?? SETTING_CONFIG_DEFAULT[field.key];
|
||||||
onConfigChange(field.key, String(defaultValue));
|
onConfigChange(field.key, String(defaultValue));
|
||||||
|
|
@ -175,7 +175,7 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
resetParameterToServerDefault(field.key);
|
settingsStore.resetParameterToServerDefault(field.key);
|
||||||
// Trigger UI update by calling onConfigChange with the default value
|
// Trigger UI update by calling onConfigChange with the default value
|
||||||
const defaultValue = propsDefault ?? SETTING_CONFIG_DEFAULT[field.key];
|
const defaultValue = propsDefault ?? SETTING_CONFIG_DEFAULT[field.key];
|
||||||
onConfigChange(field.key, String(defaultValue));
|
onConfigChange(field.key, String(defaultValue));
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||||
import { forceSyncWithServerDefaults } from '$lib/stores/settings.svelte';
|
import { settingsStore } from '$lib/stores/settings.svelte';
|
||||||
import { RotateCcw } from '@lucide/svelte';
|
import { RotateCcw } from '@lucide/svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleConfirmReset() {
|
function handleConfirmReset() {
|
||||||
forceSyncWithServerDefaults();
|
settingsStore.forceSyncWithServerDefaults();
|
||||||
onReset?.();
|
onReset?.();
|
||||||
|
|
||||||
showResetDialog = false;
|
showResetDialog = false;
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,7 @@
|
||||||
import * as Sidebar from '$lib/components/ui/sidebar';
|
import * as Sidebar from '$lib/components/ui/sidebar';
|
||||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||||
import Input from '$lib/components/ui/input/input.svelte';
|
import Input from '$lib/components/ui/input/input.svelte';
|
||||||
import {
|
import { conversationsStore, conversations } from '$lib/stores/conversations.svelte';
|
||||||
conversations,
|
|
||||||
deleteConversation,
|
|
||||||
updateConversationName
|
|
||||||
} from '$lib/stores/conversations.svelte';
|
|
||||||
import ChatSidebarActions from './ChatSidebarActions.svelte';
|
import ChatSidebarActions from './ChatSidebarActions.svelte';
|
||||||
|
|
||||||
const sidebar = Sidebar.useSidebar();
|
const sidebar = Sidebar.useSidebar();
|
||||||
|
|
@ -56,7 +52,7 @@
|
||||||
showDeleteDialog = false;
|
showDeleteDialog = false;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
deleteConversation(selectedConversation.id);
|
conversationsStore.deleteConversation(selectedConversation.id);
|
||||||
selectedConversation = null;
|
selectedConversation = null;
|
||||||
}, 100); // Wait for animation to finish
|
}, 100); // Wait for animation to finish
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +63,7 @@
|
||||||
|
|
||||||
showEditDialog = false;
|
showEditDialog = false;
|
||||||
|
|
||||||
updateConversationName(selectedConversation.id, editedName);
|
conversationsStore.updateConversationName(selectedConversation.id, editedName);
|
||||||
selectedConversation = null;
|
selectedConversation = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { Trash2, Pencil, MoreHorizontal, Download, Loader2 } from '@lucide/svelte';
|
import { Trash2, Pencil, MoreHorizontal, Download, Loader2 } from '@lucide/svelte';
|
||||||
import { ActionDropdown } from '$lib/components/app';
|
import { ActionDropdown } from '$lib/components/app';
|
||||||
import { getAllLoadingChats } from '$lib/stores/chat.svelte';
|
import { getAllLoadingChats } from '$lib/stores/chat.svelte';
|
||||||
import { downloadConversation } from '$lib/stores/conversations.svelte';
|
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -115,7 +115,7 @@
|
||||||
label: 'Export',
|
label: 'Export',
|
||||||
onclick: (e) => {
|
onclick: (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
downloadConversation(conversation.id);
|
conversationsStore.downloadConversation(conversation.id);
|
||||||
},
|
},
|
||||||
shortcut: ['shift', 'cmd', 's']
|
shortcut: ['shift', 'cmd', 's']
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,12 @@
|
||||||
import { cn } from '$lib/components/ui/utils';
|
import { cn } from '$lib/components/ui/utils';
|
||||||
import { portalToBody } from '$lib/utils/portal-to-body';
|
import { portalToBody } from '$lib/utils/portal-to-body';
|
||||||
import {
|
import {
|
||||||
fetchModels,
|
modelsStore,
|
||||||
modelOptions,
|
modelOptions,
|
||||||
modelsLoading,
|
modelsLoading,
|
||||||
modelsUpdating,
|
modelsUpdating,
|
||||||
selectModel,
|
|
||||||
selectedModelId,
|
selectedModelId,
|
||||||
unloadModel,
|
routerModels
|
||||||
routerModels,
|
|
||||||
loadModel
|
|
||||||
} from '$lib/stores/models.svelte';
|
} from '$lib/stores/models.svelte';
|
||||||
import { ServerModelStatus } from '$lib/enums';
|
import { ServerModelStatus } from '$lib/enums';
|
||||||
import { isRouterMode, serverStore } from '$lib/stores/server.svelte';
|
import { isRouterMode, serverStore } from '$lib/stores/server.svelte';
|
||||||
|
|
@ -89,7 +86,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
await fetchModels();
|
await modelsStore.fetch();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Unable to load models:', error);
|
console.error('Unable to load models:', error);
|
||||||
}
|
}
|
||||||
|
|
@ -261,13 +258,13 @@
|
||||||
onModelChange(option.id, option.model);
|
onModelChange(option.id, option.model);
|
||||||
} else {
|
} else {
|
||||||
// Update global selection
|
// Update global selection
|
||||||
await selectModel(option.id);
|
await modelsStore.selectModelById(option.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the model if not already loaded (router mode)
|
// Load the model if not already loaded (router mode)
|
||||||
if (isRouter && getModelStatus(option.model) !== ServerModelStatus.LOADED) {
|
if (isRouter && getModelStatus(option.model) !== ServerModelStatus.LOADED) {
|
||||||
try {
|
try {
|
||||||
await loadModel(option.model);
|
await modelsStore.loadModel(option.model);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load model:', error);
|
console.error('Failed to load model:', error);
|
||||||
}
|
}
|
||||||
|
|
@ -438,7 +435,7 @@
|
||||||
class="relative ml-2 flex h-4 w-4 shrink-0 items-center justify-center"
|
class="relative ml-2 flex h-4 w-4 shrink-0 items-center justify-center"
|
||||||
onclick={(e) => {
|
onclick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
unloadModel(option.model);
|
modelsStore.unloadModel(option.model);
|
||||||
}}
|
}}
|
||||||
title="Unload model"
|
title="Unload model"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
import { Input } from '$lib/components/ui/input';
|
import { Input } from '$lib/components/ui/input';
|
||||||
import Label from '$lib/components/ui/label/label.svelte';
|
import Label from '$lib/components/ui/label/label.svelte';
|
||||||
import { serverStore, serverLoading } from '$lib/stores/server.svelte';
|
import { serverStore, serverLoading } from '$lib/stores/server.svelte';
|
||||||
import { config, updateConfig } from '$lib/stores/settings.svelte';
|
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
||||||
import { fade, fly, scale } from 'svelte/transition';
|
import { fade, fly, scale } from 'svelte/transition';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -61,7 +61,7 @@
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update the API key in settings first
|
// Update the API key in settings first
|
||||||
updateConfig('apiKey', apiKeyInput.trim());
|
settingsStore.updateConfig('apiKey', apiKeyInput.trim());
|
||||||
|
|
||||||
// Test the API key by making a real request to the server
|
// Test the API key by making a real request to the server
|
||||||
const response = await fetch('./props', {
|
const response = await fetch('./props', {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export interface UseProcessingStateReturn {
|
||||||
* useProcessingState - Reactive processing state hook
|
* useProcessingState - Reactive processing state hook
|
||||||
*
|
*
|
||||||
* This hook provides reactive access to the processing state of the server.
|
* This hook provides reactive access to the processing state of the server.
|
||||||
* It directly reads from ChatStore's reactive state and provides
|
* It directly reads from chatStore's reactive state and provides
|
||||||
* formatted processing details for UI display.
|
* formatted processing details for UI display.
|
||||||
*
|
*
|
||||||
* **Features:**
|
* **Features:**
|
||||||
|
|
@ -30,7 +30,7 @@ export function useProcessingState(): UseProcessingStateReturn {
|
||||||
let isMonitoring = $state(false);
|
let isMonitoring = $state(false);
|
||||||
let lastKnownState = $state<ApiProcessingState | null>(null);
|
let lastKnownState = $state<ApiProcessingState | null>(null);
|
||||||
|
|
||||||
// Derive processing state reactively from ChatStore's direct state
|
// Derive processing state reactively from chatStore's direct state
|
||||||
const processingState = $derived.by(() => {
|
const processingState = $derived.by(() => {
|
||||||
if (!isMonitoring) {
|
if (!isMonitoring) {
|
||||||
return lastKnownState;
|
return lastKnownState;
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ import type { SettingsChatServiceOptions } from '$lib/types/settings';
|
||||||
* - Converts database messages to API format
|
* - Converts database messages to API format
|
||||||
* - Handles error translation for server responses
|
* - Handles error translation for server responses
|
||||||
*
|
*
|
||||||
* - **ChatStore**: Uses ChatService for all AI model communication
|
* - **chatStore**: Uses ChatService for all AI model communication
|
||||||
* - **ConversationsStore**: Provides message context for API requests
|
* - **conversationsStore**: Provides message context for API requests
|
||||||
*
|
*
|
||||||
* **Key Responsibilities:**
|
* **Key Responsibilities:**
|
||||||
* - Message format conversion (DatabaseMessage → API format)
|
* - Message format conversion (DatabaseMessage → API format)
|
||||||
|
|
@ -67,7 +67,7 @@ export class ChatService {
|
||||||
* @returns {Promise<string | void>} that resolves to the complete response string (non-streaming) or void (streaming)
|
* @returns {Promise<string | void>} that resolves to the complete response string (non-streaming) or void (streaming)
|
||||||
* @throws {Error} if the request fails or is aborted
|
* @throws {Error} if the request fails or is aborted
|
||||||
*/
|
*/
|
||||||
async sendMessage(
|
static async sendMessage(
|
||||||
messages: ApiChatMessageData[] | (DatabaseMessage & { extra?: DatabaseMessageExtra[] })[],
|
messages: ApiChatMessageData[] | (DatabaseMessage & { extra?: DatabaseMessageExtra[] })[],
|
||||||
options: SettingsChatServiceOptions = {},
|
options: SettingsChatServiceOptions = {},
|
||||||
conversationId?: string,
|
conversationId?: string,
|
||||||
|
|
@ -130,7 +130,7 @@ export class ChatService {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const processedMessages = this.injectSystemMessage(normalizedMessages);
|
const processedMessages = ChatService.injectSystemMessage(normalizedMessages);
|
||||||
|
|
||||||
const requestBody: ApiChatCompletionRequest = {
|
const requestBody: ApiChatCompletionRequest = {
|
||||||
messages: processedMessages.map((msg: ApiChatMessageData) => ({
|
messages: processedMessages.map((msg: ApiChatMessageData) => ({
|
||||||
|
|
@ -200,7 +200,7 @@ export class ChatService {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = await this.parseErrorResponse(response);
|
const error = await ChatService.parseErrorResponse(response);
|
||||||
if (onError) {
|
if (onError) {
|
||||||
onError(error);
|
onError(error);
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +208,7 @@ export class ChatService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream) {
|
if (stream) {
|
||||||
await this.handleStreamResponse(
|
await ChatService.handleStreamResponse(
|
||||||
response,
|
response,
|
||||||
onChunk,
|
onChunk,
|
||||||
onComplete,
|
onComplete,
|
||||||
|
|
@ -222,7 +222,7 @@ export class ChatService {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
return this.handleNonStreamResponse(
|
return ChatService.handleNonStreamResponse(
|
||||||
response,
|
response,
|
||||||
onComplete,
|
onComplete,
|
||||||
onError,
|
onError,
|
||||||
|
|
@ -276,7 +276,7 @@ export class ChatService {
|
||||||
* @returns {Promise<void>} Promise that resolves when streaming is complete
|
* @returns {Promise<void>} Promise that resolves when streaming is complete
|
||||||
* @throws {Error} if the stream cannot be read or parsed
|
* @throws {Error} if the stream cannot be read or parsed
|
||||||
*/
|
*/
|
||||||
private async handleStreamResponse(
|
private static async handleStreamResponse(
|
||||||
response: Response,
|
response: Response,
|
||||||
onChunk?: (chunk: string) => void,
|
onChunk?: (chunk: string) => void,
|
||||||
onComplete?: (
|
onComplete?: (
|
||||||
|
|
@ -323,7 +323,7 @@ export class ChatService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
aggregatedToolCalls = this.mergeToolCallDeltas(
|
aggregatedToolCalls = ChatService.mergeToolCallDeltas(
|
||||||
aggregatedToolCalls,
|
aggregatedToolCalls,
|
||||||
toolCalls,
|
toolCalls,
|
||||||
toolCallIndexOffset
|
toolCallIndexOffset
|
||||||
|
|
@ -378,14 +378,14 @@ export class ChatService {
|
||||||
const timings = parsed.timings;
|
const timings = parsed.timings;
|
||||||
const promptProgress = parsed.prompt_progress;
|
const promptProgress = parsed.prompt_progress;
|
||||||
|
|
||||||
const chunkModel = this.extractModelName(parsed);
|
const chunkModel = ChatService.extractModelName(parsed);
|
||||||
if (chunkModel && !modelEmitted) {
|
if (chunkModel && !modelEmitted) {
|
||||||
modelEmitted = true;
|
modelEmitted = true;
|
||||||
onModel?.(chunkModel);
|
onModel?.(chunkModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timings || promptProgress) {
|
if (timings || promptProgress) {
|
||||||
this.notifyTimings(timings, promptProgress, onTimings);
|
ChatService.notifyTimings(timings, promptProgress, onTimings);
|
||||||
if (timings) {
|
if (timings) {
|
||||||
lastTimings = timings;
|
lastTimings = timings;
|
||||||
}
|
}
|
||||||
|
|
@ -453,7 +453,7 @@ export class ChatService {
|
||||||
* @returns {Promise<string>} Promise that resolves to the generated content string
|
* @returns {Promise<string>} Promise that resolves to the generated content string
|
||||||
* @throws {Error} if the response cannot be parsed or is malformed
|
* @throws {Error} if the response cannot be parsed or is malformed
|
||||||
*/
|
*/
|
||||||
private async handleNonStreamResponse(
|
private static async handleNonStreamResponse(
|
||||||
response: Response,
|
response: Response,
|
||||||
onComplete?: (
|
onComplete?: (
|
||||||
response: string,
|
response: string,
|
||||||
|
|
@ -475,7 +475,7 @@ export class ChatService {
|
||||||
|
|
||||||
const data: ApiChatCompletionResponse = JSON.parse(responseText);
|
const data: ApiChatCompletionResponse = JSON.parse(responseText);
|
||||||
|
|
||||||
const responseModel = this.extractModelName(data);
|
const responseModel = ChatService.extractModelName(data);
|
||||||
if (responseModel) {
|
if (responseModel) {
|
||||||
onModel?.(responseModel);
|
onModel?.(responseModel);
|
||||||
}
|
}
|
||||||
|
|
@ -491,7 +491,7 @@ export class ChatService {
|
||||||
let serializedToolCalls: string | undefined;
|
let serializedToolCalls: string | undefined;
|
||||||
|
|
||||||
if (toolCalls && toolCalls.length > 0) {
|
if (toolCalls && toolCalls.length > 0) {
|
||||||
const mergedToolCalls = this.mergeToolCallDeltas([], toolCalls);
|
const mergedToolCalls = ChatService.mergeToolCallDeltas([], toolCalls);
|
||||||
|
|
||||||
if (mergedToolCalls.length > 0) {
|
if (mergedToolCalls.length > 0) {
|
||||||
serializedToolCalls = JSON.stringify(mergedToolCalls);
|
serializedToolCalls = JSON.stringify(mergedToolCalls);
|
||||||
|
|
@ -527,7 +527,7 @@ export class ChatService {
|
||||||
* @param indexOffset - Optional offset to apply to the index of new tool calls
|
* @param indexOffset - Optional offset to apply to the index of new tool calls
|
||||||
* @returns {ApiChatCompletionToolCall[]} The merged array of tool calls
|
* @returns {ApiChatCompletionToolCall[]} The merged array of tool calls
|
||||||
*/
|
*/
|
||||||
private mergeToolCallDeltas(
|
private static mergeToolCallDeltas(
|
||||||
existing: ApiChatCompletionToolCall[],
|
existing: ApiChatCompletionToolCall[],
|
||||||
deltas: ApiChatCompletionToolCallDelta[],
|
deltas: ApiChatCompletionToolCallDelta[],
|
||||||
indexOffset = 0
|
indexOffset = 0
|
||||||
|
|
@ -736,7 +736,7 @@ export class ChatService {
|
||||||
* @returns Array of messages with system message injected at the beginning if configured
|
* @returns Array of messages with system message injected at the beginning if configured
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private injectSystemMessage(messages: ApiChatMessageData[]): ApiChatMessageData[] {
|
private static injectSystemMessage(messages: ApiChatMessageData[]): ApiChatMessageData[] {
|
||||||
const currentConfig = config();
|
const currentConfig = config();
|
||||||
const systemMessage = currentConfig.systemMessage?.toString().trim();
|
const systemMessage = currentConfig.systemMessage?.toString().trim();
|
||||||
|
|
||||||
|
|
@ -770,7 +770,7 @@ export class ChatService {
|
||||||
* @param response - HTTP response object
|
* @param response - HTTP response object
|
||||||
* @returns Promise<Error> - Parsed error with context info if available
|
* @returns Promise<Error> - Parsed error with context info if available
|
||||||
*/
|
*/
|
||||||
private async parseErrorResponse(response: Response): Promise<Error> {
|
private static async parseErrorResponse(response: Response): Promise<Error> {
|
||||||
try {
|
try {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
const errorData: ApiErrorResponse = JSON.parse(errorText);
|
const errorData: ApiErrorResponse = JSON.parse(errorText);
|
||||||
|
|
@ -798,7 +798,7 @@ export class ChatService {
|
||||||
* @returns Model name string if found, undefined otherwise
|
* @returns Model name string if found, undefined otherwise
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private extractModelName(data: unknown): string | undefined {
|
private static extractModelName(data: unknown): string | undefined {
|
||||||
// WORKAROUND: In single model mode, use model name from props instead of API response
|
// WORKAROUND: In single model mode, use model name from props instead of API response
|
||||||
// because llama-server returns `gpt-3.5-turbo` value in the `model` field
|
// because llama-server returns `gpt-3.5-turbo` value in the `model` field
|
||||||
const isRouter = isRouterMode();
|
const isRouter = isRouterMode();
|
||||||
|
|
@ -849,7 +849,7 @@ export class ChatService {
|
||||||
* @param onTimingsCallback - Callback function to invoke with timing data
|
* @param onTimingsCallback - Callback function to invoke with timing data
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private notifyTimings(
|
private static notifyTimings(
|
||||||
timings: ChatMessageTimings | undefined,
|
timings: ChatMessageTimings | undefined,
|
||||||
promptProgress: ChatMessagePromptProgress | undefined,
|
promptProgress: ChatMessagePromptProgress | undefined,
|
||||||
onTimingsCallback:
|
onTimingsCallback:
|
||||||
|
|
@ -860,5 +860,3 @@ export class ChatService {
|
||||||
onTimingsCallback(timings, promptProgress);
|
onTimingsCallback(timings, promptProgress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const chatService = new ChatService();
|
|
||||||
|
|
|
||||||
|
|
@ -42,12 +42,12 @@ import { v4 as uuid } from 'uuid';
|
||||||
* - Uses DatabaseService for all persistence operations
|
* - Uses DatabaseService for all persistence operations
|
||||||
* - Adds import/export, navigation, and higher-level operations
|
* - Adds import/export, navigation, and higher-level operations
|
||||||
*
|
*
|
||||||
* - **ConversationsStore**: Reactive state management for conversations
|
* - **conversationsStore**: Reactive state management for conversations
|
||||||
* - Uses ConversationsService for database operations
|
* - Uses ConversationsService for database operations
|
||||||
* - Manages conversation list, active conversation, and messages in memory
|
* - Manages conversation list, active conversation, and messages in memory
|
||||||
*
|
*
|
||||||
* - **ChatStore**: Active AI interaction management
|
* - **chatStore**: Active AI interaction management
|
||||||
* - Uses ConversationsStore for conversation context
|
* - Uses conversationsStore for conversation context
|
||||||
* - Directly uses DatabaseService for message CRUD during streaming
|
* - Directly uses DatabaseService for message CRUD during streaming
|
||||||
*
|
*
|
||||||
* **Key Features:**
|
* **Key Features:**
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,5 @@
|
||||||
export { chatService } from './chat';
|
export { ChatService } from './chat';
|
||||||
|
export { DatabaseService } from './database';
|
||||||
|
export { ModelsService } from './models';
|
||||||
export { PropsService } from './props';
|
export { PropsService } from './props';
|
||||||
|
export { ParameterSyncService } from './parameter-sync';
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import type {
|
||||||
* - Check model status (ROUTER mode)
|
* - Check model status (ROUTER mode)
|
||||||
*
|
*
|
||||||
* **Used by:**
|
* **Used by:**
|
||||||
* - ModelsStore: Primary consumer for model state management
|
* - modelsStore: Primary consumer for model state management
|
||||||
*/
|
*/
|
||||||
export class ModelsService {
|
export class ModelsService {
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { getAuthHeaders } from '$lib/utils/api-headers';
|
||||||
* - Parse and validate server response
|
* - Parse and validate server response
|
||||||
*
|
*
|
||||||
* **Used by:**
|
* **Used by:**
|
||||||
* - ServerStore: Primary consumer for server state management
|
* - serverStore: Primary consumer for server state management
|
||||||
*/
|
*/
|
||||||
export class PropsService {
|
export class PropsService {
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { DatabaseService } from '$lib/services/database';
|
import { DatabaseService, ChatService } from '$lib/services';
|
||||||
import { chatService } from '$lib/services';
|
|
||||||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||||
import { config } from '$lib/stores/settings.svelte';
|
import { config } from '$lib/stores/settings.svelte';
|
||||||
import { contextSize } from '$lib/stores/server.svelte';
|
import { contextSize } from '$lib/stores/server.svelte';
|
||||||
|
|
@ -16,27 +15,27 @@ import type {
|
||||||
import type { DatabaseMessage, DatabaseMessageExtra } from '$lib/types/database';
|
import type { DatabaseMessage, DatabaseMessageExtra } from '$lib/types/database';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ChatStore - Active AI interaction and streaming state management
|
* chatStore - Active AI interaction and streaming state management
|
||||||
*
|
*
|
||||||
* **Terminology - Chat vs Conversation:**
|
* **Terminology - Chat vs Conversation:**
|
||||||
* - **Chat**: The active interaction space with the Chat Completions API. Represents the
|
* - **Chat**: The active interaction space with the Chat Completions API. Represents the
|
||||||
* real-time streaming session, loading states, and UI visualization of AI communication.
|
* real-time streaming session, loading states, and UI visualization of AI communication.
|
||||||
* A "chat" is ephemeral - it exists only while the user is actively interacting with the AI.
|
* A "chat" is ephemeral - it exists only while the user is actively interacting with the AI.
|
||||||
* - **Conversation**: The persistent database entity storing all messages and metadata.
|
* - **Conversation**: The persistent database entity storing all messages and metadata.
|
||||||
* Managed by ConversationsStore, conversations persist across sessions and page reloads.
|
* Managed by conversationsStore, conversations persist across sessions and page reloads.
|
||||||
*
|
*
|
||||||
* This store manages all active AI interactions including real-time streaming, response
|
* This store manages all active AI interactions including real-time streaming, response
|
||||||
* generation, and per-chat loading states. It handles the runtime layer between UI and
|
* generation, and per-chat loading states. It handles the runtime layer between UI and
|
||||||
* AI backend, supporting concurrent streaming across multiple conversations.
|
* AI backend, supporting concurrent streaming across multiple conversations.
|
||||||
*
|
*
|
||||||
* **Architecture & Relationships:**
|
* **Architecture & Relationships:**
|
||||||
* - **ChatStore** (this class): Active AI session and streaming management
|
* - **chatStore** (this class): Active AI session and streaming management
|
||||||
* - Manages real-time AI response streaming via ChatService
|
* - Manages real-time AI response streaming via ChatService
|
||||||
* - Tracks per-chat loading and streaming states for concurrent sessions
|
* - Tracks per-chat loading and streaming states for concurrent sessions
|
||||||
* - Handles message operations (send, edit, regenerate, branch)
|
* - Handles message operations (send, edit, regenerate, branch)
|
||||||
* - Coordinates with ConversationsStore for persistence
|
* - Coordinates with conversationsStore for persistence
|
||||||
*
|
*
|
||||||
* - **ConversationsStore**: Provides conversation data and message arrays for chat context
|
* - **conversationsStore**: Provides conversation data and message arrays for chat context
|
||||||
* - **ChatService**: Low-level API communication with llama.cpp server
|
* - **ChatService**: Low-level API communication with llama.cpp server
|
||||||
* - **DatabaseService**: Message persistence and retrieval
|
* - **DatabaseService**: Message persistence and retrieval
|
||||||
*
|
*
|
||||||
|
|
@ -501,7 +500,7 @@ class ChatStore {
|
||||||
|
|
||||||
const abortController = this.getOrCreateAbortController(assistantMessage.convId);
|
const abortController = this.getOrCreateAbortController(assistantMessage.convId);
|
||||||
|
|
||||||
await chatService.sendMessage(
|
await ChatService.sendMessage(
|
||||||
allMessages,
|
allMessages,
|
||||||
{
|
{
|
||||||
...this.getApiOptions(),
|
...this.getApiOptions(),
|
||||||
|
|
@ -1140,7 +1139,7 @@ class ChatStore {
|
||||||
|
|
||||||
const abortController = this.getOrCreateAbortController(msg.convId);
|
const abortController = this.getOrCreateAbortController(msg.convId);
|
||||||
|
|
||||||
await chatService.sendMessage(
|
await ChatService.sendMessage(
|
||||||
contextWithContinue,
|
contextWithContinue,
|
||||||
{
|
{
|
||||||
...this.getApiOptions(),
|
...this.getApiOptions(),
|
||||||
|
|
@ -1244,8 +1243,6 @@ class ChatStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ Public Methods for Per-Conversation State ============
|
|
||||||
|
|
||||||
public isChatLoadingPublic(convId: string): boolean {
|
public isChatLoadingPublic(convId: string): boolean {
|
||||||
return this.isChatLoading(convId);
|
return this.isChatLoading(convId);
|
||||||
}
|
}
|
||||||
|
|
@ -1264,57 +1261,13 @@ class ChatStore {
|
||||||
|
|
||||||
export const chatStore = new ChatStore();
|
export const chatStore = new ChatStore();
|
||||||
|
|
||||||
// ChatStore state exports
|
|
||||||
export const isLoading = () => chatStore.isLoading;
|
export const isLoading = () => chatStore.isLoading;
|
||||||
export const currentResponse = () => chatStore.currentResponse;
|
export const currentResponse = () => chatStore.currentResponse;
|
||||||
export const errorDialog = () => chatStore.errorDialogState;
|
export const errorDialog = () => chatStore.errorDialogState;
|
||||||
|
export const activeProcessingState = () => chatStore.activeProcessingState;
|
||||||
|
export const isChatStreaming = () => chatStore.isStreaming();
|
||||||
|
|
||||||
// ChatStore method exports
|
|
||||||
export const sendMessage = chatStore.sendMessage.bind(chatStore);
|
|
||||||
export const dismissErrorDialog = chatStore.dismissErrorDialog.bind(chatStore);
|
|
||||||
export function stopGeneration() {
|
|
||||||
chatStore.stopGeneration();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message operations
|
|
||||||
export const updateMessage = chatStore.updateMessage.bind(chatStore);
|
|
||||||
export const regenerateMessage = chatStore.regenerateMessage.bind(chatStore);
|
|
||||||
export const deleteMessage = chatStore.deleteMessage.bind(chatStore);
|
|
||||||
export const getDeletionInfo = chatStore.getDeletionInfo.bind(chatStore);
|
|
||||||
|
|
||||||
// Branching operations
|
|
||||||
export const editAssistantMessage = chatStore.editAssistantMessage.bind(chatStore);
|
|
||||||
export const editUserMessagePreserveResponses =
|
|
||||||
chatStore.editUserMessagePreserveResponses.bind(chatStore);
|
|
||||||
export const editMessageWithBranching = chatStore.editMessageWithBranching.bind(chatStore);
|
|
||||||
export const regenerateMessageWithBranching =
|
|
||||||
chatStore.regenerateMessageWithBranching.bind(chatStore);
|
|
||||||
export const continueAssistantMessage = chatStore.continueAssistantMessage.bind(chatStore);
|
|
||||||
|
|
||||||
// Per-conversation state access
|
|
||||||
export const isChatLoading = (convId: string) => chatStore.isChatLoadingPublic(convId);
|
export const isChatLoading = (convId: string) => chatStore.isChatLoadingPublic(convId);
|
||||||
export const getChatStreaming = (convId: string) => chatStore.getChatStreamingPublic(convId);
|
export const getChatStreaming = (convId: string) => chatStore.getChatStreamingPublic(convId);
|
||||||
export const getAllLoadingChats = () => chatStore.getAllLoadingChats();
|
export const getAllLoadingChats = () => chatStore.getAllLoadingChats();
|
||||||
export const getAllStreamingChats = () => chatStore.getAllStreamingChats();
|
export const getAllStreamingChats = () => chatStore.getAllStreamingChats();
|
||||||
|
|
||||||
// Sync/clear UI state when switching conversations
|
|
||||||
export const syncLoadingStateForChat = chatStore.syncLoadingStateForChat.bind(chatStore);
|
|
||||||
export const clearUIState = chatStore.clearUIState.bind(chatStore);
|
|
||||||
|
|
||||||
// Processing state (timing/context info)
|
|
||||||
export const getProcessingState = chatStore.getProcessingState.bind(chatStore);
|
|
||||||
export const getActiveProcessingState = chatStore.getActiveProcessingState.bind(chatStore);
|
|
||||||
export const activeProcessingState = () => chatStore.activeProcessingState;
|
|
||||||
export const getCurrentProcessingStateSync =
|
|
||||||
chatStore.getCurrentProcessingStateSync.bind(chatStore);
|
|
||||||
export const restoreProcessingStateFromMessages =
|
|
||||||
chatStore.restoreProcessingStateFromMessages.bind(chatStore);
|
|
||||||
export const clearProcessingState = chatStore.clearProcessingState.bind(chatStore);
|
|
||||||
export const updateProcessingStateFromTimings =
|
|
||||||
chatStore.updateProcessingStateFromTimings.bind(chatStore);
|
|
||||||
export const setActiveProcessingConversation =
|
|
||||||
chatStore.setActiveProcessingConversation.bind(chatStore);
|
|
||||||
export const isChatStreaming = () => chatStore.isStreaming();
|
|
||||||
|
|
||||||
// Model detection
|
|
||||||
export const getConversationModel = chatStore.getConversationModel.bind(chatStore);
|
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,12 @@ import type {
|
||||||
} from '$lib/types/database';
|
} from '$lib/types/database';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ConversationsStore - Persistent conversation data and lifecycle management
|
* conversationsStore - Persistent conversation data and lifecycle management
|
||||||
*
|
*
|
||||||
* **Terminology - Chat vs Conversation:**
|
* **Terminology - Chat vs Conversation:**
|
||||||
* - **Chat**: The active interaction space with the Chat Completions API. Represents the
|
* - **Chat**: The active interaction space with the Chat Completions API. Represents the
|
||||||
* real-time streaming session, loading states, and UI visualization of AI communication.
|
* real-time streaming session, loading states, and UI visualization of AI communication.
|
||||||
* Managed by ChatStore, a "chat" is ephemeral and exists during active AI interactions.
|
* Managed by chatStore, a "chat" is ephemeral and exists during active AI interactions.
|
||||||
* - **Conversation**: The persistent database entity storing all messages and metadata.
|
* - **Conversation**: The persistent database entity storing all messages and metadata.
|
||||||
* A "conversation" survives across sessions, page reloads, and browser restarts.
|
* A "conversation" survives across sessions, page reloads, and browser restarts.
|
||||||
* It contains the complete message history, branching structure, and conversation metadata.
|
* It contains the complete message history, branching structure, and conversation metadata.
|
||||||
|
|
@ -26,13 +26,13 @@ import type {
|
||||||
* conversation with its message history, providing reactive state for UI components.
|
* conversation with its message history, providing reactive state for UI components.
|
||||||
*
|
*
|
||||||
* **Architecture & Relationships:**
|
* **Architecture & Relationships:**
|
||||||
* - **ConversationsStore** (this class): Persistent conversation data management
|
* - **conversationsStore** (this class): Persistent conversation data management
|
||||||
* - Manages conversation list and active conversation state
|
* - Manages conversation list and active conversation state
|
||||||
* - Handles conversation CRUD operations via DatabaseService
|
* - Handles conversation CRUD operations via DatabaseService
|
||||||
* - Maintains active message array for current conversation
|
* - Maintains active message array for current conversation
|
||||||
* - Coordinates branching navigation (currNode tracking)
|
* - Coordinates branching navigation (currNode tracking)
|
||||||
*
|
*
|
||||||
* - **ChatStore**: Uses conversation data as context for active AI streaming
|
* - **chatStore**: Uses conversation data as context for active AI streaming
|
||||||
* - **DatabaseService**: Low-level IndexedDB storage for conversations and messages
|
* - **DatabaseService**: Low-level IndexedDB storage for conversations and messages
|
||||||
*
|
*
|
||||||
* **Key Features:**
|
* **Key Features:**
|
||||||
|
|
@ -148,7 +148,7 @@ class ConversationsStore {
|
||||||
clearActiveConversation(): void {
|
clearActiveConversation(): void {
|
||||||
this.activeConversation = null;
|
this.activeConversation = null;
|
||||||
this.activeMessages = [];
|
this.activeMessages = [];
|
||||||
// Active processing conversation is now managed by ChatStore
|
// Active processing conversation is now managed by chatStore
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -448,7 +448,7 @@ class ConversationsStore {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a message to the active messages array
|
* Adds a message to the active messages array
|
||||||
* Used by ChatStore when creating new messages
|
* Used by chatStore when creating new messages
|
||||||
* @param message - The message to add
|
* @param message - The message to add
|
||||||
*/
|
*/
|
||||||
addMessageToActive(message: DatabaseMessage): void {
|
addMessageToActive(message: DatabaseMessage): void {
|
||||||
|
|
@ -499,6 +499,8 @@ class ConversationsStore {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers file download in browser
|
* Triggers file download in browser
|
||||||
|
* @param data - The data to download
|
||||||
|
* @param filename - Optional filename for the download
|
||||||
*/
|
*/
|
||||||
private triggerDownload(data: ExportedConversations, filename?: string): void {
|
private triggerDownload(data: ExportedConversations, filename?: string): void {
|
||||||
const conversation =
|
const conversation =
|
||||||
|
|
@ -531,27 +533,7 @@ class ConversationsStore {
|
||||||
|
|
||||||
export const conversationsStore = new ConversationsStore();
|
export const conversationsStore = new ConversationsStore();
|
||||||
|
|
||||||
// Export getter functions for reactive access
|
|
||||||
export const conversations = () => conversationsStore.conversations;
|
export const conversations = () => conversationsStore.conversations;
|
||||||
export const activeConversation = () => conversationsStore.activeConversation;
|
export const activeConversation = () => conversationsStore.activeConversation;
|
||||||
export const activeMessages = () => conversationsStore.activeMessages;
|
export const activeMessages = () => conversationsStore.activeMessages;
|
||||||
export const isConversationsInitialized = () => conversationsStore.isInitialized;
|
export const isConversationsInitialized = () => conversationsStore.isInitialized;
|
||||||
|
|
||||||
// Export conversation operations
|
|
||||||
export const createConversation = conversationsStore.createConversation.bind(conversationsStore);
|
|
||||||
export const loadConversation = conversationsStore.loadConversation.bind(conversationsStore);
|
|
||||||
export const deleteConversation = conversationsStore.deleteConversation.bind(conversationsStore);
|
|
||||||
export const clearActiveConversation =
|
|
||||||
conversationsStore.clearActiveConversation.bind(conversationsStore);
|
|
||||||
export const updateConversationName =
|
|
||||||
conversationsStore.updateConversationName.bind(conversationsStore);
|
|
||||||
export const downloadConversation =
|
|
||||||
conversationsStore.downloadConversation.bind(conversationsStore);
|
|
||||||
export const exportAllConversations =
|
|
||||||
conversationsStore.exportAllConversations.bind(conversationsStore);
|
|
||||||
export const importConversations = conversationsStore.importConversations.bind(conversationsStore);
|
|
||||||
export const navigateToSibling = conversationsStore.navigateToSibling.bind(conversationsStore);
|
|
||||||
export const refreshActiveMessages =
|
|
||||||
conversationsStore.refreshActiveMessages.bind(conversationsStore);
|
|
||||||
export const setTitleUpdateConfirmationCallback =
|
|
||||||
conversationsStore.setTitleUpdateConfirmationCallback.bind(conversationsStore);
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { SvelteSet } from 'svelte/reactivity';
|
import { SvelteSet } from 'svelte/reactivity';
|
||||||
import { ModelsService } from '$lib/services/models';
|
import { ModelsService } from '$lib/services/models';
|
||||||
import { PropsService } from '$lib/services/props';
|
import { PropsService } from '$lib/services/props';
|
||||||
import { ServerModelStatus, ServerRole } from '$lib/enums';
|
import { ServerModelStatus } from '$lib/enums';
|
||||||
|
import { serverStore } from '$lib/stores/server.svelte';
|
||||||
import type { ModelOption, ModelModalities } from '$lib/types/models';
|
import type { ModelOption, ModelModalities } from '$lib/types/models';
|
||||||
import type { ApiModelDataEntry } from '$lib/types/api';
|
import type { ApiModelDataEntry } from '$lib/types/api';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ModelsStore - Reactive store for model management in both MODEL and ROUTER modes
|
* modelsStore - Reactive store for model management in both MODEL and ROUTER modes
|
||||||
*
|
*
|
||||||
* This store manages:
|
* This store manages:
|
||||||
* - Available models list
|
* - Available models list
|
||||||
|
|
@ -18,8 +19,8 @@ import type { ApiModelDataEntry } from '$lib/types/api';
|
||||||
* **Architecture & Relationships:**
|
* **Architecture & Relationships:**
|
||||||
* - **ModelsService**: Stateless service for model API communication
|
* - **ModelsService**: Stateless service for model API communication
|
||||||
* - **PropsService**: Stateless service for props/modalities fetching
|
* - **PropsService**: Stateless service for props/modalities fetching
|
||||||
* - **ModelsStore** (this class): Reactive store for model state
|
* - **modelsStore** (this class): Reactive store for model state
|
||||||
* - **ConversationsStore**: Tracks which conversations use which models
|
* - **conversationsStore**: Tracks which conversations use which models
|
||||||
*
|
*
|
||||||
* **API Inconsistency Workaround:**
|
* **API Inconsistency Workaround:**
|
||||||
* In MODEL mode, `/props` returns modalities for the single model.
|
* In MODEL mode, `/props` returns modalities for the single model.
|
||||||
|
|
@ -48,13 +49,6 @@ class ModelsStore {
|
||||||
private modelUsage = $state<Map<string, SvelteSet<string>>>(new Map());
|
private modelUsage = $state<Map<string, SvelteSet<string>>>(new Map());
|
||||||
private modelLoadingStates = $state<Map<string, boolean>>(new Map());
|
private modelLoadingStates = $state<Map<string, boolean>>(new Map());
|
||||||
|
|
||||||
/**
|
|
||||||
* Server role detection - determines API behavior
|
|
||||||
* In ROUTER mode, modalities come from /props?model=<id>
|
|
||||||
* In MODEL mode, modalities come from /props (single model)
|
|
||||||
*/
|
|
||||||
serverRole = $state<ServerRole | null>(null);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model-specific props cache
|
* Model-specific props cache
|
||||||
* Key: modelId, Value: props data including modalities
|
* Key: modelId, Value: props data including modalities
|
||||||
|
|
@ -83,14 +77,6 @@ class ModelsStore {
|
||||||
.map(([id]) => id);
|
.map(([id]) => id);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isRouterMode(): boolean {
|
|
||||||
return this.serverRole === ServerRole.ROUTER;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isModelMode(): boolean {
|
|
||||||
return this.serverRole === ServerRole.MODEL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// Methods - Model Modalities
|
// Methods - Model Modalities
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -189,10 +175,10 @@ class ModelsStore {
|
||||||
this.error = null;
|
this.error = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch server props to detect role and get modalities for MODEL mode
|
// Ensure server props are loaded (for role detection and MODEL mode modalities)
|
||||||
const serverProps = await PropsService.fetch();
|
if (!serverStore.props) {
|
||||||
this.serverRole =
|
await serverStore.fetch();
|
||||||
serverProps.role === ServerRole.ROUTER ? ServerRole.ROUTER : ServerRole.MODEL;
|
}
|
||||||
|
|
||||||
const response = await ModelsService.list();
|
const response = await ModelsService.list();
|
||||||
|
|
||||||
|
|
@ -216,10 +202,11 @@ class ModelsStore {
|
||||||
|
|
||||||
this.models = models;
|
this.models = models;
|
||||||
|
|
||||||
// In MODEL mode, populate modalities from /props (single model)
|
// In MODEL mode, populate modalities from serverStore.props (single model)
|
||||||
// WORKAROUND: In MODEL mode, /props returns modalities for the single model,
|
// WORKAROUND: In MODEL mode, /props returns modalities for the single model,
|
||||||
// but /v1/models doesn't include modalities. We bridge this gap here.
|
// but /v1/models doesn't include modalities. We bridge this gap here.
|
||||||
if (this.isModelMode && this.models.length > 0 && serverProps.modalities) {
|
const serverProps = serverStore.props;
|
||||||
|
if (serverStore.isModelMode && this.models.length > 0 && serverProps?.modalities) {
|
||||||
const modalities: ModelModalities = {
|
const modalities: ModelModalities = {
|
||||||
vision: serverProps.modalities.vision ?? false,
|
vision: serverProps.modalities.vision ?? false,
|
||||||
audio: serverProps.modalities.audio ?? false
|
audio: serverProps.modalities.audio ?? false
|
||||||
|
|
@ -347,7 +334,7 @@ class ModelsStore {
|
||||||
/**
|
/**
|
||||||
* Select a model for new conversations
|
* Select a model for new conversations
|
||||||
*/
|
*/
|
||||||
async select(modelId: string): Promise<void> {
|
async selectModelById(modelId: string): Promise<void> {
|
||||||
if (!modelId || this.updating) return;
|
if (!modelId || this.updating) return;
|
||||||
if (this.selectedModelId === modelId) return;
|
if (this.selectedModelId === modelId) return;
|
||||||
|
|
||||||
|
|
@ -581,7 +568,6 @@ class ModelsStore {
|
||||||
this.error = null;
|
this.error = null;
|
||||||
this.selectedModelId = null;
|
this.selectedModelId = null;
|
||||||
this.selectedModelName = null;
|
this.selectedModelName = null;
|
||||||
this.serverRole = null;
|
|
||||||
this.modelUsage.clear();
|
this.modelUsage.clear();
|
||||||
this.modelLoadingStates.clear();
|
this.modelLoadingStates.clear();
|
||||||
this.modelPropsCache.clear();
|
this.modelPropsCache.clear();
|
||||||
|
|
@ -591,10 +577,6 @@ class ModelsStore {
|
||||||
|
|
||||||
export const modelsStore = new ModelsStore();
|
export const modelsStore = new ModelsStore();
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Reactive Getters
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export const modelOptions = () => modelsStore.models;
|
export const modelOptions = () => modelsStore.models;
|
||||||
export const routerModels = () => modelsStore.routerModels;
|
export const routerModels = () => modelsStore.routerModels;
|
||||||
export const modelsLoading = () => modelsStore.loading;
|
export const modelsLoading = () => modelsStore.loading;
|
||||||
|
|
@ -605,34 +587,3 @@ export const selectedModelName = () => modelsStore.selectedModelName;
|
||||||
export const selectedModelOption = () => modelsStore.selectedModel;
|
export const selectedModelOption = () => modelsStore.selectedModel;
|
||||||
export const loadedModelIds = () => modelsStore.loadedModelIds;
|
export const loadedModelIds = () => modelsStore.loadedModelIds;
|
||||||
export const loadingModelIds = () => modelsStore.loadingModelIds;
|
export const loadingModelIds = () => modelsStore.loadingModelIds;
|
||||||
export const isRouterMode = () => modelsStore.isRouterMode;
|
|
||||||
export const isModelMode = () => modelsStore.isModelMode;
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Actions
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export const fetchModels = modelsStore.fetch.bind(modelsStore);
|
|
||||||
export const fetchRouterModels = modelsStore.fetchRouterModels.bind(modelsStore);
|
|
||||||
export const fetchModalitiesForLoadedModels =
|
|
||||||
modelsStore.fetchModalitiesForLoadedModels.bind(modelsStore);
|
|
||||||
export const updateModelModalities = modelsStore.updateModelModalities.bind(modelsStore);
|
|
||||||
export const selectModel = modelsStore.select.bind(modelsStore);
|
|
||||||
export const loadModel = modelsStore.loadModel.bind(modelsStore);
|
|
||||||
export const unloadModel = modelsStore.unloadModel.bind(modelsStore);
|
|
||||||
export const ensureModelLoaded = modelsStore.ensureModelLoaded.bind(modelsStore);
|
|
||||||
export const registerModelUsage = modelsStore.registerModelUsage.bind(modelsStore);
|
|
||||||
export const unregisterModelUsage = modelsStore.unregisterModelUsage.bind(modelsStore);
|
|
||||||
export const clearConversationUsage = modelsStore.clearConversationUsage.bind(modelsStore);
|
|
||||||
export const selectModelByName = modelsStore.selectModelByName.bind(modelsStore);
|
|
||||||
export const clearModelSelection = modelsStore.clearSelection.bind(modelsStore);
|
|
||||||
export const findModelByName = modelsStore.findModelByName.bind(modelsStore);
|
|
||||||
export const findModelById = modelsStore.findModelById.bind(modelsStore);
|
|
||||||
export const hasModel = modelsStore.hasModel.bind(modelsStore);
|
|
||||||
|
|
||||||
// Model modalities
|
|
||||||
export const getModelModalities = modelsStore.getModelModalities.bind(modelsStore);
|
|
||||||
export const modelSupportsVision = modelsStore.modelSupportsVision.bind(modelsStore);
|
|
||||||
export const modelSupportsAudio = modelsStore.modelSupportsAudio.bind(modelsStore);
|
|
||||||
export const fetchModelProps = modelsStore.fetchModelProps.bind(modelsStore);
|
|
||||||
export const getModelProps = modelsStore.getModelProps.bind(modelsStore);
|
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,15 @@ import { PropsService } from '$lib/services/props';
|
||||||
import { ServerRole, ModelModality } from '$lib/enums';
|
import { ServerRole, ModelModality } from '$lib/enums';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ServerStore - Server connection state, configuration, and role detection
|
* serverStore - Server connection state, configuration, and role detection
|
||||||
*
|
*
|
||||||
* This store manages the server connection state and properties fetched from `/props`.
|
* This store manages the server connection state and properties fetched from `/props`.
|
||||||
* It provides reactive state for server configuration and role detection.
|
* It provides reactive state for server configuration and role detection.
|
||||||
*
|
*
|
||||||
* **Architecture & Relationships:**
|
* **Architecture & Relationships:**
|
||||||
* - **PropsService**: Stateless service for fetching `/props` data
|
* - **PropsService**: Stateless service for fetching `/props` data
|
||||||
* - **ServerStore** (this class): Reactive store for server state
|
* - **serverStore** (this class): Reactive store for server state
|
||||||
* - **ModelsStore**: Independent store for model management (uses PropsService directly)
|
* - **modelsStore**: Independent store for model management (uses PropsService directly)
|
||||||
*
|
*
|
||||||
* **Key Features:**
|
* **Key Features:**
|
||||||
* - **Server State**: Connection status, loading, error handling
|
* - **Server State**: Connection status, loading, error handling
|
||||||
|
|
@ -18,7 +18,7 @@ import { ServerRole, ModelModality } from '$lib/enums';
|
||||||
* - **Default Params**: Server-wide generation defaults
|
* - **Default Params**: Server-wide generation defaults
|
||||||
*
|
*
|
||||||
* **Note on Modalities:**
|
* **Note on Modalities:**
|
||||||
* Model-specific modalities (vision, audio) are now managed by ModelsStore.
|
* Model-specific modalities (vision, audio) are now managed by modelsStore.
|
||||||
* Use `modelsStore.getModelModalities(modelId)` for per-model modality info.
|
* Use `modelsStore.getModelModalities(modelId)` for per-model modality info.
|
||||||
* The `supportsVision`/`supportsAudio` getters here are deprecated and only
|
* The `supportsVision`/`supportsAudio` getters here are deprecated and only
|
||||||
* apply to MODEL mode (single model).
|
* apply to MODEL mode (single model).
|
||||||
|
|
@ -30,10 +30,6 @@ class ServerStore {
|
||||||
role = $state<ServerRole | null>(null);
|
role = $state<ServerRole | null>(null);
|
||||||
private fetchPromise: Promise<void> | null = null;
|
private fetchPromise: Promise<void> | null = null;
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Computed Getters
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get model name from server props.
|
* Get model name from server props.
|
||||||
* In MODEL mode: extracts from model_path or model_alias
|
* In MODEL mode: extracts from model_path or model_alias
|
||||||
|
|
@ -93,10 +89,6 @@ class ServerStore {
|
||||||
return this.role === ServerRole.MODEL;
|
return this.role === ServerRole.MODEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Server Role Detection
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
private detectRole(props: ApiLlamaCppServerProps): void {
|
private detectRole(props: ApiLlamaCppServerProps): void {
|
||||||
const newRole = props?.role === ServerRole.ROUTER ? ServerRole.ROUTER : ServerRole.MODEL;
|
const newRole = props?.role === ServerRole.ROUTER ? ServerRole.ROUTER : ServerRole.MODEL;
|
||||||
if (this.role !== newRole) {
|
if (this.role !== newRole) {
|
||||||
|
|
@ -105,10 +97,6 @@ class ServerStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Fetch Server Properties
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
async fetch(): Promise<void> {
|
async fetch(): Promise<void> {
|
||||||
if (this.fetchPromise) return this.fetchPromise;
|
if (this.fetchPromise) return this.fetchPromise;
|
||||||
|
|
||||||
|
|
@ -134,10 +122,6 @@ class ServerStore {
|
||||||
await fetchPromise;
|
await fetchPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Error Handling
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
private getErrorMessage(error: unknown): string {
|
private getErrorMessage(error: unknown): string {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
const message = error.message || '';
|
const message = error.message || '';
|
||||||
|
|
@ -163,11 +147,6 @@ class ServerStore {
|
||||||
|
|
||||||
return 'Failed to connect to server';
|
return 'Failed to connect to server';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Clear State
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
this.props = null;
|
this.props = null;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
|
|
@ -179,10 +158,6 @@ class ServerStore {
|
||||||
|
|
||||||
export const serverStore = new ServerStore();
|
export const serverStore = new ServerStore();
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Reactive Getters (for use in components)
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export const serverProps = () => serverStore.props;
|
export const serverProps = () => serverStore.props;
|
||||||
export const serverLoading = () => serverStore.loading;
|
export const serverLoading = () => serverStore.loading;
|
||||||
export const serverError = () => serverStore.error;
|
export const serverError = () => serverStore.error;
|
||||||
|
|
@ -196,6 +171,3 @@ export const defaultParams = () => serverStore.defaultParams;
|
||||||
export const contextSize = () => serverStore.contextSize;
|
export const contextSize = () => serverStore.contextSize;
|
||||||
export const isRouterMode = () => serverStore.isRouterMode;
|
export const isRouterMode = () => serverStore.isRouterMode;
|
||||||
export const isModelMode = () => serverStore.isModelMode;
|
export const isModelMode = () => serverStore.isModelMode;
|
||||||
|
|
||||||
// Actions
|
|
||||||
export const fetchServerProps = serverStore.fetch.bind(serverStore);
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
/**
|
/**
|
||||||
* SettingsStore - Application configuration and theme management
|
* settingsStore - Application configuration and theme management
|
||||||
*
|
*
|
||||||
* This store manages all application settings including AI model parameters, UI preferences,
|
* This store manages all application settings including AI model parameters, UI preferences,
|
||||||
* and theme configuration. It provides persistent storage through localStorage with reactive
|
* and theme configuration. It provides persistent storage through localStorage with reactive
|
||||||
* state management using Svelte 5 runes.
|
* state management using Svelte 5 runes.
|
||||||
*
|
*
|
||||||
* **Architecture & Relationships:**
|
* **Architecture & Relationships:**
|
||||||
* - **SettingsStore** (this class): Configuration state management
|
* - **settingsStore** (this class): Configuration state management
|
||||||
* - Manages AI model parameters (temperature, max tokens, etc.)
|
* - Manages AI model parameters (temperature, max tokens, etc.)
|
||||||
* - Handles theme switching and persistence
|
* - Handles theme switching and persistence
|
||||||
* - Provides localStorage synchronization
|
* - Provides localStorage synchronization
|
||||||
|
|
@ -378,28 +378,8 @@ class SettingsStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and export the settings store instance
|
|
||||||
export const settingsStore = new SettingsStore();
|
export const settingsStore = new SettingsStore();
|
||||||
|
|
||||||
// Export reactive getters for easy access in components
|
|
||||||
export const config = () => settingsStore.config;
|
export const config = () => settingsStore.config;
|
||||||
export const theme = () => settingsStore.theme;
|
export const theme = () => settingsStore.theme;
|
||||||
export const isInitialized = () => settingsStore.isInitialized;
|
export const isInitialized = () => settingsStore.isInitialized;
|
||||||
|
|
||||||
// Export bound methods for easy access
|
|
||||||
export const updateConfig = settingsStore.updateConfig.bind(settingsStore);
|
|
||||||
export const updateMultipleConfig = settingsStore.updateMultipleConfig.bind(settingsStore);
|
|
||||||
export const updateTheme = settingsStore.updateTheme.bind(settingsStore);
|
|
||||||
export const resetConfig = settingsStore.resetConfig.bind(settingsStore);
|
|
||||||
export const resetTheme = settingsStore.resetTheme.bind(settingsStore);
|
|
||||||
export const resetAll = settingsStore.resetAll.bind(settingsStore);
|
|
||||||
export const getConfig = settingsStore.getConfig.bind(settingsStore);
|
|
||||||
export const getAllConfig = settingsStore.getAllConfig.bind(settingsStore);
|
|
||||||
export const syncWithServerDefaults = settingsStore.syncWithServerDefaults.bind(settingsStore);
|
|
||||||
export const forceSyncWithServerDefaults =
|
|
||||||
settingsStore.forceSyncWithServerDefaults.bind(settingsStore);
|
|
||||||
export const getParameterInfo = settingsStore.getParameterInfo.bind(settingsStore);
|
|
||||||
export const resetParameterToServerDefault =
|
|
||||||
settingsStore.resetParameterToServerDefault.bind(settingsStore);
|
|
||||||
export const getParameterDiff = settingsStore.getParameterDiff.bind(settingsStore);
|
|
||||||
export const clearAllUserOverrides = settingsStore.clearAllUserOverrides.bind(settingsStore);
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,7 @@
|
||||||
import { untrack } from 'svelte';
|
import { untrack } from 'svelte';
|
||||||
import { ChatSidebar, DialogConversationTitleUpdate } from '$lib/components/app';
|
import { ChatSidebar, DialogConversationTitleUpdate } from '$lib/components/app';
|
||||||
import { isLoading } from '$lib/stores/chat.svelte';
|
import { isLoading } from '$lib/stores/chat.svelte';
|
||||||
import {
|
import { conversationsStore, activeMessages } from '$lib/stores/conversations.svelte';
|
||||||
activeMessages,
|
|
||||||
setTitleUpdateConfirmationCallback
|
|
||||||
} from '$lib/stores/conversations.svelte';
|
|
||||||
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
|
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
|
||||||
import { isRouterMode, serverStore } from '$lib/stores/server.svelte';
|
import { isRouterMode, serverStore } from '$lib/stores/server.svelte';
|
||||||
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
||||||
|
|
@ -158,14 +155,16 @@
|
||||||
|
|
||||||
// Set up title update confirmation callback
|
// Set up title update confirmation callback
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
setTitleUpdateConfirmationCallback(async (currentTitle: string, newTitle: string) => {
|
conversationsStore.setTitleUpdateConfirmationCallback(
|
||||||
return new Promise<boolean>((resolve) => {
|
async (currentTitle: string, newTitle: string) => {
|
||||||
titleUpdateCurrentTitle = currentTitle;
|
return new Promise<boolean>((resolve) => {
|
||||||
titleUpdateNewTitle = newTitle;
|
titleUpdateCurrentTitle = currentTitle;
|
||||||
titleUpdateResolve = resolve;
|
titleUpdateNewTitle = newTitle;
|
||||||
titleUpdateDialogOpen = true;
|
titleUpdateResolve = resolve;
|
||||||
});
|
titleUpdateDialogOpen = true;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ChatScreen, DialogModelNotAvailable } from '$lib/components/app';
|
import { ChatScreen, DialogModelNotAvailable } from '$lib/components/app';
|
||||||
import { sendMessage, clearUIState } from '$lib/stores/chat.svelte';
|
import { chatStore } from '$lib/stores/chat.svelte';
|
||||||
import {
|
import { conversationsStore, isConversationsInitialized } from '$lib/stores/conversations.svelte';
|
||||||
conversationsStore,
|
import { modelsStore, modelOptions } from '$lib/stores/models.svelte';
|
||||||
isConversationsInitialized,
|
|
||||||
clearActiveConversation,
|
|
||||||
createConversation
|
|
||||||
} from '$lib/stores/conversations.svelte';
|
|
||||||
import {
|
|
||||||
fetchModels,
|
|
||||||
modelOptions,
|
|
||||||
selectModel,
|
|
||||||
findModelByName
|
|
||||||
} from '$lib/stores/models.svelte';
|
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { replaceState } from '$app/navigation';
|
import { replaceState } from '$app/navigation';
|
||||||
|
|
@ -39,14 +29,14 @@
|
||||||
|
|
||||||
async function handleUrlParams() {
|
async function handleUrlParams() {
|
||||||
// Ensure models are loaded first
|
// Ensure models are loaded first
|
||||||
await fetchModels();
|
await modelsStore.fetch();
|
||||||
|
|
||||||
// Handle model parameter - select model if provided
|
// Handle model parameter - select model if provided
|
||||||
if (modelParam) {
|
if (modelParam) {
|
||||||
const model = findModelByName(modelParam);
|
const model = modelsStore.findModelByName(modelParam);
|
||||||
if (model) {
|
if (model) {
|
||||||
try {
|
try {
|
||||||
await selectModel(model.id);
|
await modelsStore.selectModelById(model.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to select model:', error);
|
console.error('Failed to select model:', error);
|
||||||
requestedModelName = modelParam;
|
requestedModelName = modelParam;
|
||||||
|
|
@ -63,8 +53,8 @@
|
||||||
|
|
||||||
// Handle ?q= parameter - create new conversation and send message
|
// Handle ?q= parameter - create new conversation and send message
|
||||||
if (qParam !== null) {
|
if (qParam !== null) {
|
||||||
await createConversation();
|
await conversationsStore.createConversation();
|
||||||
await sendMessage(qParam);
|
await chatStore.sendMessage(qParam);
|
||||||
// Clear URL params after message is sent
|
// Clear URL params after message is sent
|
||||||
clearUrlParams();
|
clearUrlParams();
|
||||||
} else if (modelParam || newChatParam === 'true') {
|
} else if (modelParam || newChatParam === 'true') {
|
||||||
|
|
@ -78,8 +68,8 @@
|
||||||
await conversationsStore.initialize();
|
await conversationsStore.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearActiveConversation();
|
conversationsStore.clearActiveConversation();
|
||||||
clearUIState();
|
chatStore.clearUIState();
|
||||||
|
|
||||||
// Handle URL params only if we have ?q= or ?model= or ?new_chat=true
|
// Handle URL params only if we have ?q= or ?model= or ?new_chat=true
|
||||||
if (qParam !== null || modelParam !== null || newChatParam === 'true') {
|
if (qParam !== null || modelParam !== null || newChatParam === 'true') {
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,13 @@
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { afterNavigate } from '$app/navigation';
|
import { afterNavigate } from '$app/navigation';
|
||||||
import { ChatScreen, DialogModelNotAvailable } from '$lib/components/app';
|
import { ChatScreen, DialogModelNotAvailable } from '$lib/components/app';
|
||||||
|
import { chatStore, isLoading } from '$lib/stores/chat.svelte';
|
||||||
import {
|
import {
|
||||||
isLoading,
|
conversationsStore,
|
||||||
stopGeneration,
|
|
||||||
syncLoadingStateForChat,
|
|
||||||
sendMessage
|
|
||||||
} from '$lib/stores/chat.svelte';
|
|
||||||
import {
|
|
||||||
activeConversation,
|
activeConversation,
|
||||||
activeMessages,
|
activeMessages
|
||||||
loadConversation
|
|
||||||
} from '$lib/stores/conversations.svelte';
|
} from '$lib/stores/conversations.svelte';
|
||||||
import {
|
import { modelsStore, modelOptions, selectedModelId } from '$lib/stores/models.svelte';
|
||||||
selectModel,
|
|
||||||
modelOptions,
|
|
||||||
selectedModelId,
|
|
||||||
fetchModels,
|
|
||||||
findModelByName
|
|
||||||
} from '$lib/stores/models.svelte';
|
|
||||||
|
|
||||||
let chatId = $derived(page.params.id);
|
let chatId = $derived(page.params.id);
|
||||||
let currentChatId: string | undefined = undefined;
|
let currentChatId: string | undefined = undefined;
|
||||||
|
|
@ -49,14 +38,14 @@
|
||||||
|
|
||||||
async function handleUrlParams() {
|
async function handleUrlParams() {
|
||||||
// Ensure models are loaded first
|
// Ensure models are loaded first
|
||||||
await fetchModels();
|
await modelsStore.fetch();
|
||||||
|
|
||||||
// Handle model parameter - select model if provided
|
// Handle model parameter - select model if provided
|
||||||
if (modelParam) {
|
if (modelParam) {
|
||||||
const model = findModelByName(modelParam);
|
const model = modelsStore.findModelByName(modelParam);
|
||||||
if (model) {
|
if (model) {
|
||||||
try {
|
try {
|
||||||
await selectModel(model.id);
|
await modelsStore.selectModelById(model.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to select model:', error);
|
console.error('Failed to select model:', error);
|
||||||
requestedModelName = modelParam;
|
requestedModelName = modelParam;
|
||||||
|
|
@ -73,7 +62,7 @@
|
||||||
|
|
||||||
// Handle ?q= parameter - send message in current conversation
|
// Handle ?q= parameter - send message in current conversation
|
||||||
if (qParam !== null) {
|
if (qParam !== null) {
|
||||||
await sendMessage(qParam);
|
await chatStore.sendMessage(qParam);
|
||||||
// Clear URL params after message is sent
|
// Clear URL params after message is sent
|
||||||
clearUrlParams();
|
clearUrlParams();
|
||||||
} else if (modelParam) {
|
} else if (modelParam) {
|
||||||
|
|
@ -112,7 +101,7 @@
|
||||||
|
|
||||||
if (matchingModel) {
|
if (matchingModel) {
|
||||||
try {
|
try {
|
||||||
await selectModel(matchingModel.id);
|
await modelsStore.selectModelById(matchingModel.id);
|
||||||
console.log(`Automatically loaded model: ${lastMessageWithModel.model} from last message`);
|
console.log(`Automatically loaded model: ${lastMessageWithModel.model} from last message`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to automatically select model from last message:', error);
|
console.warn('Failed to automatically select model from last message:', error);
|
||||||
|
|
@ -141,9 +130,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const success = await loadConversation(chatId);
|
const success = await conversationsStore.loadConversation(chatId);
|
||||||
if (success) {
|
if (success) {
|
||||||
syncLoadingStateForChat(chatId);
|
chatStore.syncLoadingStateForChat(chatId);
|
||||||
|
|
||||||
// Handle URL params after conversation is loaded
|
// Handle URL params after conversation is loaded
|
||||||
if ((qParam !== null || modelParam !== null) && !urlParamsProcessed) {
|
if ((qParam !== null || modelParam !== null) && !urlParamsProcessed) {
|
||||||
|
|
@ -161,7 +150,7 @@
|
||||||
const handleBeforeUnload = () => {
|
const handleBeforeUnload = () => {
|
||||||
if (isLoading()) {
|
if (isLoading()) {
|
||||||
console.log('Page unload detected while streaming - aborting stream');
|
console.log('Page unload detected while streaming - aborting stream');
|
||||||
stopGeneration();
|
chatStore.stopGeneration();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,8 +92,8 @@
|
||||||
message: userMessage
|
message: userMessage
|
||||||
}}
|
}}
|
||||||
play={async () => {
|
play={async () => {
|
||||||
const { updateConfig } = await import('$lib/stores/settings.svelte');
|
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||||
updateConfig('disableReasoningFormat', false);
|
settingsStore.updateConfig('disableReasoningFormat', false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -104,8 +104,8 @@
|
||||||
message: assistantMessage
|
message: assistantMessage
|
||||||
}}
|
}}
|
||||||
play={async () => {
|
play={async () => {
|
||||||
const { updateConfig } = await import('$lib/stores/settings.svelte');
|
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||||
updateConfig('disableReasoningFormat', false);
|
settingsStore.updateConfig('disableReasoningFormat', false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -116,8 +116,8 @@
|
||||||
message: assistantWithReasoning
|
message: assistantWithReasoning
|
||||||
}}
|
}}
|
||||||
play={async () => {
|
play={async () => {
|
||||||
const { updateConfig } = await import('$lib/stores/settings.svelte');
|
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||||
updateConfig('disableReasoningFormat', false);
|
settingsStore.updateConfig('disableReasoningFormat', false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -128,8 +128,8 @@
|
||||||
message: rawOutputMessage
|
message: rawOutputMessage
|
||||||
}}
|
}}
|
||||||
play={async () => {
|
play={async () => {
|
||||||
const { updateConfig } = await import('$lib/stores/settings.svelte');
|
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||||
updateConfig('disableReasoningFormat', true);
|
settingsStore.updateConfig('disableReasoningFormat', true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -140,8 +140,8 @@
|
||||||
}}
|
}}
|
||||||
asChild
|
asChild
|
||||||
play={async () => {
|
play={async () => {
|
||||||
const { updateConfig } = await import('$lib/stores/settings.svelte');
|
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||||
updateConfig('disableReasoningFormat', false);
|
settingsStore.updateConfig('disableReasoningFormat', false);
|
||||||
// Phase 1: Stream reasoning content in chunks
|
// Phase 1: Stream reasoning content in chunks
|
||||||
let reasoningText =
|
let reasoningText =
|
||||||
'I need to think about this carefully. Let me break down the problem:\n\n1. The user is asking for help with something complex\n2. I should provide a thorough and helpful response\n3. I need to consider multiple approaches\n4. The best solution would be to explain step by step\n\nThis approach will ensure clarity and understanding.';
|
'I need to think about this carefully. Let me break down the problem:\n\n1. The user is asking for help with something complex\n2. I should provide a thorough and helpful response\n3. I need to consider multiple approaches\n4. The best solution would be to explain step by step\n\nThis approach will ensure clarity and understanding.';
|
||||||
|
|
@ -192,8 +192,8 @@
|
||||||
message: processingMessage
|
message: processingMessage
|
||||||
}}
|
}}
|
||||||
play={async () => {
|
play={async () => {
|
||||||
const { updateConfig } = await import('$lib/stores/settings.svelte');
|
const { settingsStore } = await import('$lib/stores/settings.svelte');
|
||||||
updateConfig('disableReasoningFormat', false);
|
settingsStore.updateConfig('disableReasoningFormat', false);
|
||||||
// Import the chat store to simulate loading state
|
// Import the chat store to simulate loading state
|
||||||
const { chatStore } = await import('$lib/stores/chat.svelte');
|
const { chatStore } = await import('$lib/stores/chat.svelte');
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue