feat: add sqlite storage and terminal ui

This commit is contained in:
Mohamed Boudra
2026-03-29 23:20:18 +07:00
parent e5fd61f131
commit 7d02e24d84
189 changed files with 17868 additions and 4663 deletions
+225 -2
View File
@@ -1,9 +1,44 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { AttachmentMetadata } from "@/attachments/types";
import type { DraftAgentStatusBarProps } from "@/components/agent-status-bar";
import type { DraftCommandConfig } from "@/hooks/use-agent-commands-query";
import {
useAgentFormState,
type CreateAgentInitialValues,
type UseAgentFormStateResult,
} from "@/hooks/use-agent-form-state";
import { useDraftStore } from "@/stores/draft-store";
import type { AgentModelDefinition } from "@server/server/agent/agent-sdk-types";
type ImageUpdater = AttachmentMetadata[] | ((prev: AttachmentMetadata[]) => AttachmentMetadata[]);
type AgentInputDraftComposerOptions = {
initialServerId: string | null;
initialValues?: CreateAgentInitialValues;
isVisible?: boolean;
onlineServerIds?: string[];
lockedWorkingDir?: string;
};
type DraftKeyContext = {
selectedServerId: string | null;
};
type DraftKeyInput = string | ((context: DraftKeyContext) => string);
type UseAgentInputDraftInput = {
draftKey: DraftKeyInput;
composer?: AgentInputDraftComposerOptions;
};
type DraftComposerState = UseAgentFormStateResult & {
workingDir: string;
effectiveModelId: string;
effectiveThinkingOptionId: string;
statusControls: DraftAgentStatusBarProps;
commandDraftConfig: DraftCommandConfig | undefined;
};
interface AgentInputDraft {
text: string;
setText: (text: string) => void;
@@ -11,6 +46,7 @@ interface AgentInputDraft {
setImages: (updater: ImageUpdater) => void;
clear: (lifecycle: "sent" | "abandoned") => void;
isHydrated: boolean;
composerState: DraftComposerState | null;
}
function hasDraftContent(input: { text: string; images: AttachmentMetadata[] }): boolean {
@@ -36,7 +72,108 @@ function areImagesEqual(input: {
});
}
export function useAgentInputDraft(draftKey: string): AgentInputDraft {
function resolveDraftKey(input: {
draftKey: DraftKeyInput;
selectedServerId: string | null;
}): string {
if (typeof input.draftKey === "function") {
return input.draftKey({ selectedServerId: input.selectedServerId });
}
return input.draftKey;
}
function resolveEffectiveComposerModelId(input: {
selectedModel: string;
availableModels: AgentModelDefinition[];
}): string {
const selectedModel = input.selectedModel.trim();
if (selectedModel) {
return selectedModel;
}
return input.availableModels.find((model) => model.isDefault)?.id ?? input.availableModels[0]?.id ?? "";
}
function resolveEffectiveComposerThinkingOptionId(input: {
selectedThinkingOptionId: string;
availableModels: AgentModelDefinition[];
effectiveModelId: string;
}): string {
const selectedThinkingOptionId = input.selectedThinkingOptionId.trim();
if (selectedThinkingOptionId) {
return selectedThinkingOptionId;
}
const selectedModelDefinition =
input.availableModels.find((model) => model.id === input.effectiveModelId) ?? null;
return selectedModelDefinition?.defaultThinkingOptionId ?? "";
}
function buildDraftComposerCommandConfig(input: {
provider: DraftAgentStatusBarProps["selectedProvider"];
cwd: string;
modeOptions: DraftAgentStatusBarProps["modeOptions"];
selectedMode: string;
effectiveModelId: string;
effectiveThinkingOptionId: string;
}): DraftCommandConfig | undefined {
const cwd = input.cwd.trim();
if (!cwd) {
return undefined;
}
return {
provider: input.provider,
cwd,
...(input.modeOptions.length > 0 && input.selectedMode !== "" ? { modeId: input.selectedMode } : {}),
...(input.effectiveModelId ? { model: input.effectiveModelId } : {}),
...(input.effectiveThinkingOptionId
? { thinkingOptionId: input.effectiveThinkingOptionId }
: {}),
};
}
function buildDraftStatusControls(input: {
formState: UseAgentFormStateResult;
}): DraftAgentStatusBarProps {
const { formState } = input;
return {
providerDefinitions: formState.providerDefinitions,
selectedProvider: formState.selectedProvider,
onSelectProvider: formState.setProviderFromUser,
modeOptions: formState.modeOptions,
selectedMode: formState.selectedMode,
onSelectMode: formState.setModeFromUser,
models: formState.availableModels,
selectedModel: formState.selectedModel,
onSelectModel: formState.setModelFromUser,
isModelLoading: formState.isModelLoading,
allProviderModels: formState.allProviderModels,
isAllModelsLoading: formState.isAllModelsLoading,
onSelectProviderAndModel: formState.setProviderAndModelFromUser,
thinkingOptions: formState.availableThinkingOptions,
selectedThinkingOptionId: formState.selectedThinkingOptionId,
onSelectThinkingOption: formState.setThinkingOptionFromUser,
};
}
export function useAgentInputDraft(input: UseAgentInputDraftInput): AgentInputDraft {
const composerOptions = input.composer ?? null;
const formState = useAgentFormState({
initialServerId: composerOptions?.initialServerId ?? null,
initialValues: composerOptions?.initialValues,
isVisible: composerOptions?.isVisible ?? false,
isCreateFlow: true,
onlineServerIds: composerOptions?.onlineServerIds ?? [],
});
const draftKey = useMemo(
() =>
resolveDraftKey({
draftKey: input.draftKey,
selectedServerId: formState.selectedServerId,
}),
[formState.selectedServerId, input.draftKey],
);
const [text, setText] = useState("");
const [images, setImagesState] = useState<AttachmentMetadata[]>([]);
const [isHydrated, setIsHydrated] = useState(false);
@@ -148,6 +285,83 @@ export function useAgentInputDraft(draftKey: string): AgentInputDraft {
});
}, [draftKey, images, text]);
const lockedWorkingDir = composerOptions?.lockedWorkingDir?.trim() ?? "";
useEffect(() => {
if (!composerOptions || !lockedWorkingDir) {
return;
}
if (formState.workingDir.trim() === lockedWorkingDir) {
return;
}
formState.setWorkingDir(lockedWorkingDir);
}, [composerOptions, formState, lockedWorkingDir]);
const effectiveModelId = useMemo(
() =>
resolveEffectiveComposerModelId({
selectedModel: formState.selectedModel,
availableModels: formState.availableModels,
}),
[formState.availableModels, formState.selectedModel],
);
const effectiveThinkingOptionId = useMemo(
() =>
resolveEffectiveComposerThinkingOptionId({
selectedThinkingOptionId: formState.selectedThinkingOptionId,
availableModels: formState.availableModels,
effectiveModelId,
}),
[effectiveModelId, formState.availableModels, formState.selectedThinkingOptionId],
);
const workingDir = lockedWorkingDir || formState.workingDir;
const commandDraftConfig = useMemo(
() =>
composerOptions
? buildDraftComposerCommandConfig({
provider: formState.selectedProvider,
cwd: workingDir,
modeOptions: formState.modeOptions,
selectedMode: formState.selectedMode,
effectiveModelId,
effectiveThinkingOptionId,
})
: undefined,
[
composerOptions,
effectiveModelId,
effectiveThinkingOptionId,
workingDir,
formState.modeOptions,
formState.selectedMode,
formState.selectedProvider,
],
);
const composerState = useMemo<DraftComposerState | null>(() => {
if (!composerOptions) {
return null;
}
return {
...formState,
workingDir,
effectiveModelId,
effectiveThinkingOptionId,
statusControls: buildDraftStatusControls({ formState }),
commandDraftConfig,
};
}, [
commandDraftConfig,
composerOptions,
effectiveModelId,
effectiveThinkingOptionId,
formState,
workingDir,
]);
return {
text,
setText,
@@ -155,5 +369,14 @@ export function useAgentInputDraft(draftKey: string): AgentInputDraft {
setImages,
clear,
isHydrated,
composerState,
};
}
export const __private__ = {
resolveDraftKey,
resolveEffectiveComposerModelId,
resolveEffectiveComposerThinkingOptionId,
buildDraftComposerCommandConfig,
buildDraftStatusControls,
};