Some fixes around environments

This commit is contained in:
Gregory Schier
2024-12-21 11:04:49 -08:00
parent c1d5881167
commit 61d094d9fd
20 changed files with 95 additions and 46 deletions

View File

@@ -41,9 +41,9 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
leftSlot: e.id === activeEnvironment?.id ? <Icon icon="check" /> : <Icon icon="empty" />,
onSelect: async () => {
if (e.id !== activeEnvironment?.id) {
setActiveEnvironmentId(e.id);
await setActiveEnvironmentId(e.id);
} else {
setActiveEnvironmentId(null);
await setActiveEnvironmentId(null);
}
},
}),

View File

@@ -40,7 +40,8 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
const selectedEnvironment = allEnvironments.find((e) => e.id === selectedEnvironmentId);
const handleCreateEnvironment = async () => {
const e = await createEnvironment.mutateAsync();
if (baseEnvironment == null) return;
const e = await createEnvironment.mutateAsync(baseEnvironment);
if (e == null) return;
setSelectedEnvironmentId(e.id);
};

View File

@@ -1,7 +1,9 @@
import { save } from '@tauri-apps/plugin-dialog';
import type { Workspace } from '@yaakapp-internal/models';
import { useCallback, useMemo, useState } from 'react';
import slugify from 'slugify';
import type { Workspace } from '@yaakapp-internal/models';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useWorkspaces } from '../hooks/useWorkspaces';
import { count } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri';
import { Button } from './core/Button';
@@ -11,16 +13,32 @@ import { HStack, VStack } from './core/Stacks';
interface Props {
onHide: () => void;
onSuccess: (path: string) => void;
activeWorkspace: Workspace;
workspaces: Workspace[];
}
export function ExportDataDialog({
export function ExportDataDialog({ onHide, onSuccess }: Props) {
const allWorkspaces = useWorkspaces();
const activeWorkspace = useActiveWorkspace();
if (activeWorkspace == null || allWorkspaces.length === 0) return null;
return (
<ExportDataDialogContent
onHide={onHide}
onSuccess={onSuccess}
allWorkspaces={allWorkspaces}
activeWorkspace={activeWorkspace}
/>
);
}
function ExportDataDialogContent({
onHide,
onSuccess,
activeWorkspace,
workspaces: allWorkspaces,
}: Props) {
allWorkspaces,
}: Props & {
allWorkspaces: Workspace[];
activeWorkspace: Workspace;
}) {
const [selectedWorkspaces, setSelectedWorkspaces] = useState<Record<string, boolean>>({
[activeWorkspace.id]: true,
});

View File

@@ -17,6 +17,7 @@ export function WorkspaceSettingsDialog({ workspaceId }: Props) {
return (
<VStack space={3} className="pb-3 max-h-[50vh]">
{workspace.id}
<PlainInput
label="Workspace Name"
defaultValue={workspace.name}

View File

@@ -35,11 +35,16 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
const openWorkspaceNewWindow = settings?.openWorkspaceNewWindow ?? null;
const deleteSendHistory = useDeleteSendHistory();
const orderedWorkspaces = useMemo(
() => [...workspaces].sort((a, b) => (a.name.localeCompare(b.name) > 0 ? 1 : -1)),
[workspaces],
);
const { workspaceItems, extraItems } = useMemo<{
workspaceItems: RadioDropdownItem[];
extraItems: DropdownItem[];
}>(() => {
const workspaceItems: RadioDropdownItem[] = workspaces.map((w) => ({
const workspaceItems: RadioDropdownItem[] = orderedWorkspaces.map((w) => ({
key: w.id,
label: w.name,
value: w.id,
@@ -84,13 +89,13 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
return { workspaceItems, extraItems };
}, [
activeWorkspace,
activeWorkspace?.id,
activeWorkspaceId,
createWorkspace.mutate,
deleteSendHistory.mutate,
deleteWorkspace.mutate,
dialog,
workspaces,
orderedWorkspaces,
]);
const handleChange = useCallback(

View File

@@ -1,4 +1,4 @@
import { getRouteApi, useSearch } from '@tanstack/react-router';
import { useNavigate, useSearch } from '@tanstack/react-router';
import { useCallback, useEffect, useMemo } from 'react';
import { useCookieJars } from './useCookieJars';
@@ -38,17 +38,15 @@ export function useEnsureActiveCookieJar() {
}, [activeCookieJarId, cookieJars, setActiveCookieJarId]);
}
const routeApi = getRouteApi('/workspaces/$workspaceId/');
function useActiveCookieJarId() {
// NOTE: This query param is accessed from Rust side, so do not change
const { cookieJarId: id } = useSearch({ strict: false });
const navigate = routeApi.useNavigate();
const { cookie_jar_id: id } = useSearch({ strict: false });
const navigate = useNavigate({ from: '/workspaces/$workspaceId' });
const setId = useCallback(
(id: string) =>
navigate({
search: (prev) => ({ ...prev, cookieJarId: id }),
search: (prev) => ({ ...prev, cookie_jar_id: id }),
}),
[navigate],
);

View File

@@ -1,4 +1,4 @@
import { getRouteApi, useSearch } from '@tanstack/react-router';
import { useNavigate, useSearch } from '@tanstack/react-router';
import { useCallback } from 'react';
import { useEnvironments } from './useEnvironments';
@@ -11,17 +11,15 @@ export function useActiveEnvironment() {
export const QUERY_ENVIRONMENT_ID = 'environment_id';
const routeApi = getRouteApi('/workspaces/$workspaceId/');
function useActiveEnvironmentId() {
// NOTE: This query param is accessed from Rust side, so do not change
const { environmentId: id } = useSearch({ strict: false });
const navigate = routeApi.useNavigate();
const { environment_id: id} = useSearch({ strict: false });
const navigate = useNavigate({ from: '/workspaces/$workspaceId' });
const setId = useCallback(
(environment_id: string | null) =>
(environmentId: string | null) =>
navigate({
search: (prev) => ({ ...prev, environment_id: environment_id ?? undefined }),
search: (prev) => ({ ...prev, environment_id: environmentId ?? undefined }),
}),
[navigate],
);

View File

@@ -1,13 +1,13 @@
import { useFastMutation } from './useFastMutation';
import type { Environment } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai";
import { useSetAtom } from 'jotai';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveWorkspace } from './useActiveWorkspace';
import {environmentsAtom} from "./useEnvironments";
import { environmentsAtom } from './useEnvironments';
import { useFastMutation } from './useFastMutation';
import { usePrompt } from './usePrompt';
import {updateModelList} from "./useSyncModelStores";
import { updateModelList } from './useSyncModelStores';
export function useCreateEnvironment() {
const [, setActiveEnvironmentId] = useActiveEnvironment();
@@ -15,9 +15,9 @@ export function useCreateEnvironment() {
const workspace = useActiveWorkspace();
const setEnvironments = useSetAtom(environmentsAtom);
return useFastMutation<Environment | null, unknown, void>({
return useFastMutation<Environment | null, unknown, Environment>({
mutationKey: ['create_environment'],
mutationFn: async () => {
mutationFn: async (baseEnvironment) => {
const name = await prompt({
id: 'new-environment',
title: 'New Environment',
@@ -33,6 +33,7 @@ export function useCreateEnvironment() {
name,
variables: [],
workspaceId: workspace?.id,
environmentId: baseEnvironment.id,
});
},
onSettled: () => trackEvent('environment', 'create'),

View File

@@ -7,9 +7,8 @@ export const environmentsAtom = atom<Environment[]>([]);
export function useEnvironments() {
const allEnvironments = useAtomValue(environmentsAtom);
const baseEnvironment = allEnvironments.find((e) => e.environmentId == null);
const subEnvironments = allEnvironments.filter(
(e) => e.environmentId === (baseEnvironment?.id ?? 'n/a'),
);
const subEnvironments =
allEnvironments.filter((e) => e.environmentId === (baseEnvironment?.id ?? 'n/a')) ?? [];
return { baseEnvironment, subEnvironments, allEnvironments } as const;
}

View File

@@ -29,8 +29,6 @@ export function useExportData() {
render: ({ hide }) => (
<ExportDataDialog
onHide={hide}
workspaces={workspaces}
activeWorkspace={activeWorkspace}
onSuccess={() => {
toast.show({
color: 'success',

View File

@@ -1,5 +1,6 @@
import type { MutationKey } from '@tanstack/react-query';
import { useCallback } from 'react';
import { useToast } from './useToast';
export function useFastMutation<TData = unknown, TError = unknown, TVariables = void>({
mutationKey,
@@ -7,13 +8,17 @@ export function useFastMutation<TData = unknown, TError = unknown, TVariables =
onSuccess,
onError,
onSettled,
toastyError,
}: {
mutationKey: MutationKey;
mutationFn: (vars: TVariables) => Promise<TData>;
onSettled?: () => void;
onError?: (err: TError) => void;
onSuccess?: (data: TData) => void;
toastyError?: boolean;
}) {
const toast = useToast();
const mutateAsync = useCallback(
async (variables: TVariables) => {
try {
@@ -22,8 +27,14 @@ export function useFastMutation<TData = unknown, TError = unknown, TVariables =
return data;
} catch (err: unknown) {
const e = err as TError;
console.log('MUTATION FAILED', mutationKey, e);
console.log('Fast mutation error', mutationKey, e);
onError?.(e);
if (toastyError) {
toast.show({
id: 'error-' + mutationKey.join('.'),
message: String(e),
});
}
} finally {
onSettled?.();
}

View File

@@ -20,8 +20,11 @@ export function useSyncWorkspaceChildModels() {
const setEnvironments = useSetAtom(environmentsAtom);
const workspace = useActiveWorkspace();
const workspaceId = workspace?.id ?? 'n/a';
const workspaceId = workspace?.id;
useEffect(() => {
if (workspaceId == null) {
return;
}
(async function () {
console.log('Syncing model stores', { workspaceId });
// Set the things we need first, first

View File

@@ -13,6 +13,7 @@ export function useUpdateEnvironment(id: string | null) {
unknown,
Partial<Environment> | ((r: Environment) => Environment)
>({
toastyError: true,
mutationKey: ['update_environment', id],
mutationFn: async (v) => {
const environment = await getEnvironment(id);

View File

@@ -80,5 +80,10 @@ type TauriCmd =
export async function invokeCmd<T>(cmd: TauriCmd, args?: InvokeArgs): Promise<T> {
// console.log('RUN COMMAND', cmd, args);
return invoke(cmd, args);
try {
return await invoke(cmd, args);
} catch (err) {
console.warn('Tauri command error', cmd, err);
throw err;
}
}

View File

@@ -2,15 +2,15 @@ import { createFileRoute } from '@tanstack/react-router';
import { Workspace } from '../../../components/Workspace';
interface WorkspaceSearchSchema {
cookieJarId?: string | null;
environmentId?: string | null;
cookie_jar_id?: string | null;
environment_id?: string | null;
}
export const Route = createFileRoute('/workspaces/$workspaceId/')({
component: RouteComponent,
validateSearch: (search: Record<string, unknown>): WorkspaceSearchSchema => ({
environmentId: search.environment_id as string,
cookieJarId: search.cookie_jar_id as string,
environment_id: search.environment_id as string,
cookie_jar_id: search.cookie_jar_id as string,
}),
});