feat: add sqlite storage and terminal ui
This commit is contained in:
@@ -1,22 +1,19 @@
|
||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { useCallback, useMemo, useRef } from "react";
|
||||
import { Keyboard, Platform, ScrollView, Text, View } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
import { AgentInputArea } from "@/components/agent-input-area";
|
||||
import { Composer } from "@/components/composer";
|
||||
import { FileDropZone } from "@/components/file-drop-zone";
|
||||
import { AgentStreamView } from "@/components/agent-stream-view";
|
||||
import type { ImageAttachment } from "@/components/message-input";
|
||||
import { useAgentFormState } from "@/hooks/use-agent-form-state";
|
||||
import { useAgentInputDraft } from "@/hooks/use-agent-input-draft";
|
||||
import { useDraftAgentCreateFlow } from "@/hooks/use-draft-agent-create-flow";
|
||||
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
||||
import { buildWorkspaceDraftAgentConfig } from "@/screens/workspace/workspace-draft-agent-config";
|
||||
import { buildDraftStoreKey } from "@/stores/draft-keys";
|
||||
import type { Agent } from "@/stores/session-store";
|
||||
import { encodeImages } from "@/utils/encode-images";
|
||||
import { shouldAutoFocusWorkspaceDraftComposer } from "@/screens/workspace/workspace-draft-pane-focus";
|
||||
import type {
|
||||
AgentCapabilityFlags,
|
||||
AgentSessionConfig,
|
||||
} from "@server/server/agent/agent-sdk-types";
|
||||
import type { AgentCapabilityFlags } from "@server/server/agent/agent-sdk-types";
|
||||
import type { AgentSnapshotPayload } from "@server/shared/messages";
|
||||
|
||||
const EMPTY_PENDING_PERMISSIONS = new Map();
|
||||
@@ -27,6 +24,7 @@ const DRAFT_CAPABILITIES: AgentCapabilityFlags = {
|
||||
supportsMcpServers: false,
|
||||
supportsReasoningStream: false,
|
||||
supportsToolInvocations: false,
|
||||
supportsTerminalMode: false,
|
||||
};
|
||||
|
||||
type WorkspaceDraftAgentTabProps = {
|
||||
@@ -51,65 +49,31 @@ export function WorkspaceDraftAgentTab({
|
||||
const client = useHostRuntimeClient(serverId);
|
||||
const isConnected = useHostRuntimeIsConnected(serverId);
|
||||
const addImagesRef = useRef<((images: ImageAttachment[]) => void) | null>(null);
|
||||
const draftInput = useAgentInputDraft(
|
||||
buildDraftStoreKey({
|
||||
serverId,
|
||||
agentId: tabId,
|
||||
draftId,
|
||||
}),
|
||||
const draftStoreKey = useMemo(
|
||||
() =>
|
||||
buildDraftStoreKey({
|
||||
serverId,
|
||||
agentId: tabId,
|
||||
draftId,
|
||||
}),
|
||||
[draftId, serverId, tabId],
|
||||
);
|
||||
|
||||
const {
|
||||
selectedProvider,
|
||||
setProviderFromUser,
|
||||
selectedMode,
|
||||
setModeFromUser,
|
||||
selectedModel,
|
||||
setModelFromUser,
|
||||
selectedThinkingOptionId,
|
||||
setThinkingOptionFromUser,
|
||||
workingDir,
|
||||
setWorkingDir,
|
||||
providerDefinitions,
|
||||
modeOptions,
|
||||
availableModels,
|
||||
allProviderModels,
|
||||
isAllModelsLoading,
|
||||
availableThinkingOptions,
|
||||
isModelLoading,
|
||||
setProviderAndModelFromUser,
|
||||
persistFormPreferences,
|
||||
} = useAgentFormState({
|
||||
initialServerId: serverId,
|
||||
initialValues: { workingDir: workspaceId },
|
||||
isVisible: true,
|
||||
isCreateFlow: true,
|
||||
onlineServerIds: isConnected ? [serverId] : [],
|
||||
});
|
||||
|
||||
// Lock working directory to workspace.
|
||||
useEffect(() => {
|
||||
if (workingDir.trim() === workspaceId.trim()) {
|
||||
return;
|
||||
}
|
||||
setWorkingDir(workspaceId);
|
||||
}, [setWorkingDir, workingDir, workspaceId]);
|
||||
|
||||
const effectiveDraftModelId = useMemo(() => {
|
||||
if (selectedModel.trim()) {
|
||||
return selectedModel.trim();
|
||||
}
|
||||
return availableModels.find((model) => model.isDefault)?.id ?? availableModels[0]?.id ?? "";
|
||||
}, [availableModels, selectedModel]);
|
||||
|
||||
const effectiveDraftThinkingOptionId = useMemo(() => {
|
||||
if (selectedThinkingOptionId.trim()) {
|
||||
return selectedThinkingOptionId.trim();
|
||||
}
|
||||
const selectedModelDefinition =
|
||||
availableModels.find((model) => model.id === effectiveDraftModelId) ?? null;
|
||||
return selectedModelDefinition?.defaultThinkingOptionId ?? "";
|
||||
}, [availableModels, effectiveDraftModelId, selectedThinkingOptionId]);
|
||||
const draftInput = useAgentInputDraft(
|
||||
{
|
||||
draftKey: draftStoreKey,
|
||||
composer: {
|
||||
initialServerId: serverId,
|
||||
initialValues: { workingDir: workspaceId },
|
||||
isVisible: true,
|
||||
onlineServerIds: isConnected ? [serverId] : [],
|
||||
lockedWorkingDir: workspaceId,
|
||||
},
|
||||
},
|
||||
);
|
||||
const composerState = draftInput.composerState;
|
||||
if (!composerState) {
|
||||
throw new Error("Workspace draft composer state is required");
|
||||
}
|
||||
|
||||
const {
|
||||
formErrorMessage,
|
||||
@@ -124,13 +88,13 @@ export function WorkspaceDraftAgentTab({
|
||||
if (!text.trim()) {
|
||||
return "Initial prompt is required";
|
||||
}
|
||||
if (providerDefinitions.length === 0) {
|
||||
if (composerState.providerDefinitions.length === 0) {
|
||||
return "No available providers on the selected host";
|
||||
}
|
||||
if (isModelLoading) {
|
||||
if (composerState.isModelLoading) {
|
||||
return "Model defaults are still loading";
|
||||
}
|
||||
if (!effectiveDraftModelId) {
|
||||
if (!composerState.effectiveModelId) {
|
||||
return "No model is available for the selected provider";
|
||||
}
|
||||
if (!client) {
|
||||
@@ -139,7 +103,7 @@ export function WorkspaceDraftAgentTab({
|
||||
return null;
|
||||
},
|
||||
onBeforeSubmit: () => {
|
||||
void persistFormPreferences();
|
||||
void composerState.persistFormPreferences();
|
||||
if (Platform.OS === "web") {
|
||||
(document.activeElement as HTMLElement | null)?.blur?.();
|
||||
}
|
||||
@@ -147,13 +111,17 @@ export function WorkspaceDraftAgentTab({
|
||||
},
|
||||
buildDraftAgent: (attempt) => {
|
||||
const now = attempt.timestamp;
|
||||
const model = effectiveDraftModelId || null;
|
||||
const thinkingOptionId = effectiveDraftThinkingOptionId || null;
|
||||
const modeId = modeOptions.length > 0 && selectedMode !== "" ? selectedMode : null;
|
||||
const model = composerState.effectiveModelId || null;
|
||||
const thinkingOptionId = composerState.effectiveThinkingOptionId || null;
|
||||
const modeId =
|
||||
composerState.modeOptions.length > 0 && composerState.selectedMode !== ""
|
||||
? composerState.selectedMode
|
||||
: null;
|
||||
return {
|
||||
serverId,
|
||||
id: tabId,
|
||||
provider: selectedProvider,
|
||||
provider: composerState.selectedProvider,
|
||||
terminal: false,
|
||||
status: "running",
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
@@ -164,7 +132,7 @@ export function WorkspaceDraftAgentTab({
|
||||
availableModes: [],
|
||||
pendingPermissions: [],
|
||||
persistence: null,
|
||||
runtimeInfo: { provider: selectedProvider, sessionId: null, model, modeId },
|
||||
runtimeInfo: { provider: composerState.selectedProvider, sessionId: null, model, modeId },
|
||||
title: "Agent",
|
||||
cwd: workspaceId,
|
||||
model,
|
||||
@@ -177,21 +145,20 @@ export function WorkspaceDraftAgentTab({
|
||||
throw new Error("Host is not connected");
|
||||
}
|
||||
|
||||
const modeId = modeOptions.length > 0 && selectedMode !== "" ? selectedMode : undefined;
|
||||
const config: AgentSessionConfig = {
|
||||
provider: selectedProvider,
|
||||
const config = buildWorkspaceDraftAgentConfig({
|
||||
provider: composerState.selectedProvider,
|
||||
cwd: workspaceId,
|
||||
...(modeId ? { modeId } : {}),
|
||||
...(effectiveDraftModelId ? { model: effectiveDraftModelId } : {}),
|
||||
...(effectiveDraftThinkingOptionId
|
||||
? { thinkingOptionId: effectiveDraftThinkingOptionId }
|
||||
...(composerState.modeOptions.length > 0 && composerState.selectedMode !== ""
|
||||
? { modeId: composerState.selectedMode }
|
||||
: {}),
|
||||
};
|
||||
model: composerState.effectiveModelId || undefined,
|
||||
thinkingOptionId: composerState.effectiveThinkingOptionId || undefined,
|
||||
});
|
||||
|
||||
const imagesData = await encodeImages(images);
|
||||
const result = await client.createAgent({
|
||||
config,
|
||||
initialPrompt: text,
|
||||
...(text ? { initialPrompt: text } : {}),
|
||||
clientMessageId: attempt.clientMessageId,
|
||||
...(imagesData && imagesData.length > 0 ? { images: imagesData } : {}),
|
||||
});
|
||||
@@ -206,25 +173,6 @@ export function WorkspaceDraftAgentTab({
|
||||
},
|
||||
});
|
||||
|
||||
const draftCommandConfig = useMemo(() => {
|
||||
return {
|
||||
provider: selectedProvider,
|
||||
cwd: workspaceId,
|
||||
...(modeOptions.length > 0 && selectedMode !== "" ? { modeId: selectedMode } : {}),
|
||||
...(effectiveDraftModelId ? { model: effectiveDraftModelId } : {}),
|
||||
...(effectiveDraftThinkingOptionId
|
||||
? { thinkingOptionId: effectiveDraftThinkingOptionId }
|
||||
: {}),
|
||||
};
|
||||
}, [
|
||||
effectiveDraftModelId,
|
||||
effectiveDraftThinkingOptionId,
|
||||
modeOptions.length,
|
||||
selectedMode,
|
||||
selectedProvider,
|
||||
workspaceId,
|
||||
]);
|
||||
|
||||
const handleFilesDropped = useCallback((files: ImageAttachment[]) => {
|
||||
addImagesRef.current?.(files);
|
||||
}, []);
|
||||
@@ -265,11 +213,12 @@ export function WorkspaceDraftAgentTab({
|
||||
</View>
|
||||
|
||||
<View style={styles.inputAreaWrapper}>
|
||||
<AgentInputArea
|
||||
<Composer
|
||||
agentId={tabId}
|
||||
serverId={serverId}
|
||||
isInputActive={isPaneFocused}
|
||||
onSubmitMessage={handleCreateFromInput}
|
||||
allowEmptySubmit={false}
|
||||
isSubmitLoading={isSubmitting}
|
||||
blurOnSubmit={true}
|
||||
value={draftInput.text}
|
||||
@@ -279,24 +228,9 @@ export function WorkspaceDraftAgentTab({
|
||||
clearDraft={draftInput.clear}
|
||||
autoFocus={shouldAutoFocusWorkspaceDraftComposer({ isPaneFocused, isSubmitting })}
|
||||
onAddImages={handleAddImagesCallback}
|
||||
commandDraftConfig={draftCommandConfig}
|
||||
commandDraftConfig={composerState.commandDraftConfig}
|
||||
statusControls={{
|
||||
providerDefinitions,
|
||||
selectedProvider,
|
||||
onSelectProvider: setProviderFromUser,
|
||||
modeOptions,
|
||||
selectedMode,
|
||||
onSelectMode: setModeFromUser,
|
||||
models: availableModels,
|
||||
selectedModel,
|
||||
onSelectModel: setModelFromUser,
|
||||
isModelLoading,
|
||||
allProviderModels,
|
||||
isAllModelsLoading,
|
||||
onSelectProviderAndModel: setProviderAndModelFromUser,
|
||||
thinkingOptions: availableThinkingOptions,
|
||||
selectedThinkingOptionId,
|
||||
onSelectThinkingOption: setThinkingOptionFromUser,
|
||||
...composerState.statusControls,
|
||||
disabled: isSubmitting,
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user