ui: Stores architecture improvements (#26910)
* refactor: Stores barrel imports + SSR gates * refactor: Drop agenticStore wrapper exports * refactor: Drop chatStore wrapper exports * refactor: Drop modelsStore wrapper exports * refactor: Drop serverStore wrapper exports * refactor: Drop unused mcpStore wrapper exports * refactor: Drop mcpResourceStore wrapper exports * refactor: Drop conversationsStore wrapper exports + move buildConversationTree to utils * refactor: Drop settingsStore wrapper exports * refactor: Drop unused toolsStore wrapper exports * refactor: Fix lint errors from store wrapper removal * fix: Missing change * refactor: Cleanup * refactor: Context Stats store
This commit is contained in:
@@ -8,24 +8,21 @@
|
||||
* demand if they aren't cached yet.
|
||||
*/
|
||||
|
||||
import { chatStore } from '$lib/stores/chat.svelte';
|
||||
import { activeMessages } from '$lib/stores/conversations.svelte';
|
||||
import { modelOptions, modelsStore, selectedModelId } from '$lib/stores/models.svelte';
|
||||
import { isRouterMode } from '$lib/stores/server.svelte';
|
||||
import { chatStore, conversationsStore, modelsStore, serverStore } from '$lib/stores';
|
||||
|
||||
export function useChatScreenActiveModel() {
|
||||
const isRouter = $derived(isRouterMode());
|
||||
const isRouter = $derived(serverStore.isRouterMode);
|
||||
const conversationModel = $derived(
|
||||
chatStore.getConversationModel(activeMessages() as DatabaseMessage[])
|
||||
chatStore.getConversationModel(conversationsStore.activeMessages as DatabaseMessage[])
|
||||
);
|
||||
const activeModelId = $derived.by(() => {
|
||||
const options = modelOptions();
|
||||
const options = modelsStore.models;
|
||||
|
||||
if (!isRouter) {
|
||||
return options.length > 0 ? options[0].model : null;
|
||||
}
|
||||
|
||||
const selectedId = selectedModelId();
|
||||
const selectedId = modelsStore.selectedModelId;
|
||||
|
||||
if (selectedId) {
|
||||
const model = options.find((m) => m.id === selectedId);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* caller's onDrop callback.
|
||||
*/
|
||||
|
||||
import { getAddFilesHandler, isEditing } from '$lib/stores/chat.svelte';
|
||||
import { chatStore } from '$lib/stores';
|
||||
|
||||
interface UseChatScreenDragAndDropOptions {
|
||||
/** Called when the user drops files and no message is being edited. */
|
||||
@@ -49,8 +49,8 @@ export function useChatScreenDragAndDrop(options: UseChatScreenDragAndDropOption
|
||||
|
||||
const files = Array.from(event.dataTransfer.files);
|
||||
|
||||
if (isEditing()) {
|
||||
const handler = getAddFilesHandler();
|
||||
if (chatStore.isEditing()) {
|
||||
const handler = chatStore.getAddFilesHandler();
|
||||
|
||||
if (handler) {
|
||||
handler(files);
|
||||
|
||||
@@ -88,7 +88,9 @@ export function useChatScreenFileUpload(options: UseChatScreenFileUploadOptions)
|
||||
}
|
||||
|
||||
return {
|
||||
fileErrorData,
|
||||
get fileErrorData() {
|
||||
return fileErrorData;
|
||||
},
|
||||
handleFileRemove,
|
||||
handleFileUpload,
|
||||
get showFileErrorDialog() {
|
||||
|
||||
@@ -1,30 +1,14 @@
|
||||
/**
|
||||
* Reactive state for the context usage gauge: resolves the active model,
|
||||
* fetches its cached props, parses live server stats, and exposes per-turn
|
||||
* read / fresh / cache / output and cumulative token counts.
|
||||
* View layer over contextStatsStore for the context usage gauge: adds
|
||||
* color levels, transient detail formatting, on-demand /props fetching
|
||||
* and model loading on top of the store's token stats.
|
||||
*/
|
||||
|
||||
import { useProcessingState } from './use-processing-state.svelte';
|
||||
import { colorLevelFromPercent } from '$lib/components/app/chat/ChatForm/ChatFormContextGauge/context-gauge';
|
||||
import { STATS_UNITS } from '$lib/constants';
|
||||
import { ColorLevel, MessageRole } from '$lib/enums';
|
||||
import { chatStore } from '$lib/stores/chat.svelte';
|
||||
import { activeMessages } from '$lib/stores/conversations.svelte';
|
||||
import {
|
||||
modelOptions,
|
||||
modelsStore,
|
||||
selectedModelId,
|
||||
singleModelName
|
||||
} from '$lib/stores/models.svelte';
|
||||
import { isRouterMode } from '$lib/stores/server.svelte';
|
||||
import type { ChatMessageTimings, DatabaseMessage } from '$lib/types';
|
||||
|
||||
interface LiveStats {
|
||||
freshTokens: number;
|
||||
promptTokens: number;
|
||||
cacheTokens: number;
|
||||
outputTokens: number;
|
||||
}
|
||||
import { ColorLevel } from '$lib/enums';
|
||||
import { contextStatsStore, modelsStore } from '$lib/stores';
|
||||
|
||||
export interface UseContextGaugeReturn {
|
||||
readonly activeModelId: string | null;
|
||||
@@ -32,6 +16,7 @@ export interface UseContextGaugeReturn {
|
||||
readonly isActiveModelLoading: boolean;
|
||||
readonly contextTotal: number | null;
|
||||
readonly contextUsed: number;
|
||||
readonly contextAvailable: number | null;
|
||||
readonly currentRead: number;
|
||||
readonly currentFresh: number;
|
||||
readonly currentCache: number;
|
||||
@@ -49,34 +34,6 @@ export interface UseContextGaugeReturn {
|
||||
startMonitoring(): void;
|
||||
}
|
||||
|
||||
function lastAssistantTimings(messages: DatabaseMessage[]): ChatMessageTimings | undefined {
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const m = messages[i];
|
||||
|
||||
if (m.role === MessageRole.ASSISTANT && m.timings) return m.timings;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function deriveLiveStats(
|
||||
state: ReturnType<typeof useProcessingState>['processingState']
|
||||
): LiveStats | null {
|
||||
if (!state || (state.status !== 'preparing' && state.status !== 'generating')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const promptTokens = state.promptTokens ?? 0;
|
||||
const cacheTokens = state.cacheTokens ?? 0;
|
||||
|
||||
return {
|
||||
cacheTokens,
|
||||
freshTokens: promptTokens,
|
||||
outputTokens: state.outputTokensUsed ?? 0,
|
||||
promptTokens: promptTokens + cacheTokens
|
||||
};
|
||||
}
|
||||
|
||||
const TRANSIENT_DETAILS_EXCLUDED_PREFIXES = ['Context:', 'Output:'];
|
||||
|
||||
function filterTransientDetails(raw: string[]): string[] {
|
||||
@@ -91,150 +48,39 @@ function filterTransientDetails(raw: string[]): string[] {
|
||||
|
||||
export function useContextGauge(): UseContextGaugeReturn {
|
||||
const processingState = useProcessingState();
|
||||
// Resolve the model the gauge reports context for: explicit selection >
|
||||
// last assistant model > single-model mode (mirrors useChatScreenActiveModel).
|
||||
const activeModelId = $derived.by(() => {
|
||||
if (!isRouterMode()) {
|
||||
return singleModelName();
|
||||
}
|
||||
|
||||
const selectedId = selectedModelId();
|
||||
|
||||
if (selectedId) {
|
||||
const model = modelOptions().find((m) => m.id === selectedId);
|
||||
|
||||
if (model) return model.model;
|
||||
}
|
||||
|
||||
return chatStore.getConversationModel(activeMessages() as DatabaseMessage[]);
|
||||
});
|
||||
const isActiveModelLoaded = $derived(
|
||||
activeModelId !== null && (!isRouterMode() || modelsStore.isModelLoaded(activeModelId))
|
||||
);
|
||||
const isActiveModelLoading = $derived(
|
||||
activeModelId !== null && modelsStore.isModelOperationInProgress(activeModelId)
|
||||
);
|
||||
|
||||
// Pull /props on demand so n_ctx surfaces before the first chat request.
|
||||
$effect(() => {
|
||||
if (activeModelId && isActiveModelLoaded) {
|
||||
const cached = modelsStore.getModelProps(activeModelId);
|
||||
const modelId = contextStatsStore.activeModelId;
|
||||
|
||||
if (modelId && contextStatsStore.isActiveModelLoaded) {
|
||||
const cached = modelsStore.getModelProps(modelId);
|
||||
|
||||
if (!cached) {
|
||||
void modelsStore.fetchModelProps(activeModelId);
|
||||
void modelsStore.fetchModelProps(modelId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const contextTotal = $derived.by(() => {
|
||||
void modelsStore.propsCacheVersion;
|
||||
|
||||
return activeModelId ? modelsStore.getModelContextSize(activeModelId) : null;
|
||||
});
|
||||
const liveStats = $derived(deriveLiveStats(processingState.processingState));
|
||||
const currentRead = $derived.by(() => {
|
||||
const timings = lastAssistantTimings(activeMessages() as DatabaseMessage[]);
|
||||
|
||||
let read = 0;
|
||||
|
||||
if (timings) {
|
||||
read = (timings.prompt_n ?? 0) + (timings.cache_n ?? 0);
|
||||
}
|
||||
|
||||
// live.promptTokens is already the combined reading (prompt + cache),
|
||||
// so do not also add live.cacheTokens.
|
||||
if (liveStats && liveStats.promptTokens > 0) {
|
||||
read = Math.max(read, liveStats.promptTokens);
|
||||
}
|
||||
|
||||
return read;
|
||||
});
|
||||
const currentFresh = $derived.by(() => {
|
||||
const timings = lastAssistantTimings(activeMessages() as DatabaseMessage[]);
|
||||
const fresh = timings?.prompt_n ?? 0;
|
||||
|
||||
return Math.max(fresh, liveStats?.freshTokens ?? 0);
|
||||
});
|
||||
const currentCache = $derived.by(() => {
|
||||
const timings = lastAssistantTimings(activeMessages() as DatabaseMessage[]);
|
||||
const cached = timings?.cache_n ?? 0;
|
||||
|
||||
if (liveStats && liveStats.promptTokens > 0) {
|
||||
return Math.max(cached, liveStats.cacheTokens);
|
||||
}
|
||||
|
||||
return cached;
|
||||
});
|
||||
const currentOutput = $derived.by(() => {
|
||||
if (liveStats && liveStats.outputTokens > 0) return liveStats.outputTokens;
|
||||
|
||||
const timings = lastAssistantTimings(activeMessages() as DatabaseMessage[]);
|
||||
|
||||
return timings?.predicted_n ?? 0;
|
||||
});
|
||||
const kvTotal = $derived(currentRead + currentOutput);
|
||||
const contextUsed = $derived(currentRead + currentOutput);
|
||||
const cumulative = $derived.by(() => {
|
||||
const messages = activeMessages() as DatabaseMessage[];
|
||||
// Agentic sessions stamp the same agentic.llm totals onto every
|
||||
// assistant message; cache_n is never per-turn so cache_total stays 0.
|
||||
const agenticMessages = messages.filter(
|
||||
(m) => m.role === MessageRole.ASSISTANT && m.timings?.agentic?.llm?.predicted_n != null
|
||||
);
|
||||
|
||||
if (agenticMessages.length > 0) {
|
||||
const llm = agenticMessages[agenticMessages.length - 1].timings!.agentic!.llm;
|
||||
const output = llm.predicted_n ?? 0;
|
||||
const outputMs = llm.predicted_ms ?? 0;
|
||||
const averageTokensPerSecond = outputMs > 0 && output > 0 ? (output / outputMs) * 1000 : null;
|
||||
|
||||
return {
|
||||
averageTokensPerSecond,
|
||||
cacheTotal: 0,
|
||||
output,
|
||||
read: llm.prompt_n ?? 0
|
||||
};
|
||||
}
|
||||
|
||||
let read = 0;
|
||||
let output = 0;
|
||||
let outputMs = 0;
|
||||
let cacheTotal = 0;
|
||||
|
||||
for (const m of messages) {
|
||||
if (m.role !== MessageRole.ASSISTANT || !m.timings) continue;
|
||||
|
||||
read += m.timings.prompt_n ?? 0;
|
||||
cacheTotal += m.timings.cache_n ?? 0;
|
||||
output += m.timings.predicted_n ?? 0;
|
||||
outputMs += m.timings.predicted_ms ?? 0;
|
||||
}
|
||||
const averageTokensPerSecond = outputMs > 0 && output > 0 ? (output / outputMs) * 1000 : null;
|
||||
|
||||
return { averageTokensPerSecond, cacheTotal, output, read };
|
||||
});
|
||||
const contextPercent = $derived.by(() => {
|
||||
if (contextTotal === null || contextTotal <= 0) return null;
|
||||
|
||||
return Math.round((contextUsed / contextTotal) * 100);
|
||||
});
|
||||
const colorLevel = $derived(colorLevelFromPercent(contextPercent));
|
||||
const colorLevel = $derived(colorLevelFromPercent(contextStatsStore.contextPercent));
|
||||
// Drop lines the surrounding Context / Output / speed rows already render.
|
||||
const transientDetails = $derived(filterTransientDetails(processingState.getTechnicalDetails()));
|
||||
const hasAnyUsage = $derived(
|
||||
cumulative.read > 0 ||
|
||||
cumulative.output > 0 ||
|
||||
currentRead > 0 ||
|
||||
currentOutput > 0 ||
|
||||
cumulative.averageTokensPerSecond !== null ||
|
||||
contextStatsStore.cumulativeRead > 0 ||
|
||||
contextStatsStore.cumulativeOutput > 0 ||
|
||||
contextStatsStore.currentRead > 0 ||
|
||||
contextStatsStore.currentOutput > 0 ||
|
||||
contextStatsStore.averageTokensPerSecond !== null ||
|
||||
transientDetails.length > 0
|
||||
);
|
||||
|
||||
async function loadModel() {
|
||||
if (!activeModelId || isActiveModelLoading) return;
|
||||
const modelId = contextStatsStore.activeModelId;
|
||||
|
||||
if (!modelId || contextStatsStore.isActiveModelLoading) return;
|
||||
|
||||
try {
|
||||
await modelsStore.loadModel(activeModelId);
|
||||
await modelsStore.loadModel(modelId);
|
||||
} catch {
|
||||
// toast already surfaced by modelsStore.loadModel
|
||||
}
|
||||
@@ -242,55 +88,58 @@ export function useContextGauge(): UseContextGaugeReturn {
|
||||
|
||||
return {
|
||||
get activeModelId() {
|
||||
return activeModelId;
|
||||
return contextStatsStore.activeModelId;
|
||||
},
|
||||
get averageTokensPerSecond() {
|
||||
return cumulative.averageTokensPerSecond;
|
||||
return contextStatsStore.averageTokensPerSecond;
|
||||
},
|
||||
get colorLevel() {
|
||||
return colorLevel;
|
||||
},
|
||||
get contextAvailable() {
|
||||
return contextStatsStore.contextAvailable;
|
||||
},
|
||||
get contextPercent() {
|
||||
return contextPercent;
|
||||
return contextStatsStore.contextPercent;
|
||||
},
|
||||
get contextTotal() {
|
||||
return contextTotal;
|
||||
return contextStatsStore.contextTotal;
|
||||
},
|
||||
get contextUsed() {
|
||||
return contextUsed;
|
||||
return contextStatsStore.contextUsed;
|
||||
},
|
||||
get cumulativeCacheTotal() {
|
||||
return cumulative.cacheTotal;
|
||||
return contextStatsStore.cumulativeCacheTotal;
|
||||
},
|
||||
get cumulativeOutput() {
|
||||
return cumulative.output;
|
||||
return contextStatsStore.cumulativeOutput;
|
||||
},
|
||||
get cumulativeRead() {
|
||||
return cumulative.read;
|
||||
return contextStatsStore.cumulativeRead;
|
||||
},
|
||||
get currentCache() {
|
||||
return currentCache;
|
||||
return contextStatsStore.currentCache;
|
||||
},
|
||||
get currentFresh() {
|
||||
return currentFresh;
|
||||
return contextStatsStore.currentFresh;
|
||||
},
|
||||
get currentOutput() {
|
||||
return currentOutput;
|
||||
return contextStatsStore.currentOutput;
|
||||
},
|
||||
get currentRead() {
|
||||
return currentRead;
|
||||
return contextStatsStore.currentRead;
|
||||
},
|
||||
get hasAnyUsage() {
|
||||
return hasAnyUsage;
|
||||
},
|
||||
get isActiveModelLoaded() {
|
||||
return isActiveModelLoaded;
|
||||
return contextStatsStore.isActiveModelLoaded;
|
||||
},
|
||||
get isActiveModelLoading() {
|
||||
return isActiveModelLoading;
|
||||
return contextStatsStore.isActiveModelLoading;
|
||||
},
|
||||
get kvTotal() {
|
||||
return kvTotal;
|
||||
return contextStatsStore.kvTotal;
|
||||
},
|
||||
loadModel,
|
||||
startMonitoring: () => processingState.startMonitoring(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { afterNavigate, beforeNavigate } from '$app/navigation';
|
||||
import { draftMessagesStore } from '$lib/stores/draft-messages.svelte';
|
||||
import { draftMessagesStore } from '$lib/stores';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
interface UseDraftMessagesOptions {
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import { filterModelOptions, groupModelOptions } from '$lib/components/app/models/utils';
|
||||
import { CHAT_INPUT_FOCUS_SELECTOR } from '$lib/constants';
|
||||
import {
|
||||
modelOptions,
|
||||
modelsLoading,
|
||||
modelsStore,
|
||||
modelsUpdating,
|
||||
selectedModelId,
|
||||
singleModelName
|
||||
} from '$lib/stores/models.svelte';
|
||||
import { isRouterMode } from '$lib/stores/server.svelte';
|
||||
import { modelsStore, serverStore } from '$lib/stores';
|
||||
import type { ModelOption } from '$lib/types/models';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
@@ -54,17 +46,17 @@ export interface UseModelsSelectorReturn {
|
||||
*/
|
||||
export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSelectorReturn {
|
||||
const options = $derived(
|
||||
modelOptions().filter((option) => {
|
||||
modelsStore.models.filter((option) => {
|
||||
const modelProps = modelsStore.getModelProps(option.model);
|
||||
|
||||
return modelProps?.ui !== false;
|
||||
})
|
||||
);
|
||||
const loading = $derived(modelsLoading());
|
||||
const updating = $derived(modelsUpdating());
|
||||
const activeId = $derived(selectedModelId());
|
||||
const isRouter = $derived(isRouterMode());
|
||||
const serverModel = $derived(singleModelName());
|
||||
const loading = $derived(modelsStore.loading);
|
||||
const updating = $derived(modelsStore.updating);
|
||||
const activeId = $derived(modelsStore.selectedModelId);
|
||||
const isRouter = $derived(serverStore.isRouterMode);
|
||||
const serverModel = $derived(modelsStore.singleModelName);
|
||||
const currentModel = $derived(opts.currentModel());
|
||||
const onModelChange = $derived(opts.onModelChange?.());
|
||||
const isHighlightedCurrentModelActive = $derived.by(() => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { STATS_UNITS } from '$lib/constants';
|
||||
import { activeProcessingState } from '$lib/stores/chat.svelte';
|
||||
import { chatStore } from '$lib/stores';
|
||||
import type { ApiProcessingState, LiveGenerationStats, LiveProcessingStats } from '$lib/types';
|
||||
|
||||
export interface UseProcessingStateReturn {
|
||||
@@ -42,8 +42,8 @@ export function useProcessingState(): UseProcessingStateReturn {
|
||||
return lastKnownState;
|
||||
}
|
||||
|
||||
// Read directly from the reactive state export
|
||||
return activeProcessingState();
|
||||
// Read directly from the reactive state
|
||||
return chatStore.activeProcessingState;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { BUILD_VERSION_LOCALSTORAGE_KEY, SW_CONFIG } from '$lib/constants';
|
||||
import { versionStore } from '$lib/stores/version.svelte';
|
||||
import { versionStore } from '$lib/stores';
|
||||
import { useRegisterSW } from 'virtual:pwa-register/svelte';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import { REASONING_EFFORT_LEVELS, REASONING_EFFORT_TOKENS } from '$lib/constants';
|
||||
import { ReasoningEffort } from '$lib/enums';
|
||||
import { chatStore } from '$lib/stores/chat.svelte';
|
||||
import { activeMessages, conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
import {
|
||||
checkModelSupportsThinking,
|
||||
loadedModelIds,
|
||||
modelsStore,
|
||||
propsCacheVersion,
|
||||
supportsThinking
|
||||
} from '$lib/stores/models.svelte';
|
||||
import { isRouterMode } from '$lib/stores/server.svelte';
|
||||
import { chatStore, conversationsStore, modelsStore, serverStore } from '$lib/stores';
|
||||
import type { ReasoningEffortLevel } from '$lib/types';
|
||||
import type { DatabaseMessage } from '$lib/types/database';
|
||||
|
||||
@@ -33,12 +24,14 @@ export interface UseReasoningMenuReturn {
|
||||
*/
|
||||
export function useReasoningMenu(): UseReasoningMenuReturn {
|
||||
const conversationModel = $derived(
|
||||
chatStore.getConversationModel(activeMessages() as DatabaseMessage[])
|
||||
chatStore.getConversationModel(conversationsStore.activeMessages as DatabaseMessage[])
|
||||
);
|
||||
// a router chat can carry reasoning from an earlier turn before the props
|
||||
// cache is primed, so a model that already produced thinking still qualifies
|
||||
const modelSupportsThinkingFromMessages = $derived.by(() => {
|
||||
const modelId = isRouterMode() ? modelsStore.selectedModelName || conversationModel : null;
|
||||
const modelId = serverStore.isRouterMode
|
||||
? modelsStore.selectedModelName || conversationModel
|
||||
: null;
|
||||
|
||||
if (!modelId) return false;
|
||||
|
||||
@@ -47,16 +40,18 @@ export function useReasoningMenu(): UseReasoningMenuReturn {
|
||||
);
|
||||
});
|
||||
const modelSupportsThinking = $derived.by(() => {
|
||||
loadedModelIds();
|
||||
propsCacheVersion();
|
||||
void modelsStore.loadedModelIds;
|
||||
void modelsStore.propsCacheVersion;
|
||||
|
||||
if (isRouterMode()) {
|
||||
if (serverStore.isRouterMode) {
|
||||
const modelId = modelsStore.selectedModelName || conversationModel;
|
||||
|
||||
return checkModelSupportsThinking(modelId ?? '') || modelSupportsThinkingFromMessages;
|
||||
return (
|
||||
modelsStore.checkModelSupportsThinking(modelId ?? '') || modelSupportsThinkingFromMessages
|
||||
);
|
||||
}
|
||||
|
||||
return supportsThinking() || modelSupportsThinkingFromMessages;
|
||||
return modelsStore.supportsThinking || modelSupportsThinkingFromMessages;
|
||||
});
|
||||
const currentEffort = $derived(conversationsStore.getReasoningEffort());
|
||||
const thinkingEnabled = $derived(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { beforeNavigate } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { ROUTES } from '$lib/constants';
|
||||
import { settingsReferrer } from '$lib/stores/settings-referrer.svelte';
|
||||
import { settingsReferrer } from '$lib/stores';
|
||||
|
||||
export interface ChatSettings {
|
||||
reset: () => void;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { CLI_FLAGS } from '$lib/constants';
|
||||
import { ToolSource } from '$lib/enums';
|
||||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
import { mcpStore } from '$lib/stores/mcp.svelte';
|
||||
import { toolsStore } from '$lib/stores/tools.svelte';
|
||||
import { conversationsStore, mcpStore, toolsStore } from '$lib/stores';
|
||||
import type { ToolGroup } from '$lib/types';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user