Skip to content
This repository has been archived by the owner on Jun 2, 2024. It is now read-only.

Commit

Permalink
[web] Conversation edit/duplicate/import/export (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
maxijonson committed Jul 18, 2023
1 parent ce59e12 commit dab7811
Show file tree
Hide file tree
Showing 47 changed files with 1,401 additions and 832 deletions.
30 changes: 28 additions & 2 deletions packages/lib/src/classes/Conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,13 @@ export class Conversation {
this.functions = this.functions.filter((fn) => fn.id !== id);
}

/**
* Removes all functions from the conversation.
*/
public clearFunctions() {
this.functions = [];
}

/**
* Adds a function to the conversation. This function can be "called" by the assistant, generating a function call message.
*
Expand Down Expand Up @@ -567,14 +574,14 @@ export class Conversation {
return {
...this.config.chatCompletionConfig,
...this.config.config,
};
} satisfies ConversationConfigParameters;
}

/**
* Assigns a new config to the conversation.
*
* @param config The new config to use.
* @param merge Set to `true` to merge the new config with the existing config instead of replacing it.
* @param merge Set to `true` to shallow merge the new config with the existing config instead of replacing it.
*/
public setConfig(config: ConversationConfigParameters, merge = false) {
const newConfig = merge ? { ...this.getConfig(), ...config } : config;
Expand All @@ -588,6 +595,25 @@ export class Conversation {
}
}

/**
* Gets the current request options of the conversation.
*/
public getRequestOptions() {
return this.requestOptions;
}

/**
* Sets new request options to be used as defaults for all HTTP requests made by this conversation.
*
* @param requestOptions The new request options to use.
* @param merge Set to `true` to shallow merge the new request options with the existing request options instead of replacing them.
*/
public setRequestOptions(requestOptions: RequestOptions, merge = false) {
this.requestOptions = merge
? { ...this.requestOptions, ...requestOptions }
: requestOptions;
}

private notifyMessageAdded(message: Message) {
this.addMessageListeners.forEach((listener) => listener(message));
}
Expand Down
3 changes: 2 additions & 1 deletion packages/web/src/changelog/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ReactNode } from "react";
import v4_4_0 from "./v4-4-0";
import v4_4_1 from "./v4-4-1";
import v4_5_0 from "./v4-5-0";

export type ChangelogEntrySection = {
label: ReactNode;
Expand All @@ -16,4 +17,4 @@ export type ChangelogEntry = {
};

// First entry is the latest version
export const changelog = [v4_4_1, v4_4_0];
export const changelog = [v4_5_0, v4_4_1, v4_4_0];
33 changes: 33 additions & 0 deletions packages/web/src/changelog/v4-5-0.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ChangelogEntry } from ".";
import { CHANGELOG_SECTION } from "../config/constants";

const v4_5_0: ChangelogEntry = {
version: "4.5.0 - Conversation Edit, Duplicate and Export",
date: new Date("july 16 2023"),
sections: [
{
label: CHANGELOG_SECTION.FEATURES,
items: [
"Added the ability to change conversation settings",
"Added the ability to duplicate conversations",
"Added the ability to import/export conversations",
"Added the ability to generate a conversation name using the assistant.",
],
},
{
label: CHANGELOG_SECTION.IMPROVEMENTS,
items: [
"Moved the advanced conversation settings to their own tab",
"Default settings are now saved in the conversation form",
],
},
{
label: CHANGELOG_SECTION.REMOVALS,
items: [
"Removed the default conversation settings from the settings popup. This is now done directly in the conversation form.",
],
},
],
};

export default v4_5_0;
99 changes: 72 additions & 27 deletions packages/web/src/components/AddConversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ import {
Title,
Card,
createStyles,
Button,
Box,
} from "@mantine/core";
import React from "react";
import { ConversationFormValues } from "../contexts/ConversationFormContext";
import { addConversation } from "../store/actions/conversations/addConversation";
import { setActiveConversation } from "../store/actions/conversations/setActiveConversation";
import { addPersistedConversationId } from "../store/actions/persistence/addPersistedConversationId";
import ConversationForm from "./forms/ConversationForm/ConversationForm";
import { useGetFunction } from "../store/hooks/callableFunctions/useGetFunction";
import { BiImport } from "react-icons/bi";
import ConversationDropzone, {
ConversationNavbarDropzoneProps,
} from "./inputs/ConversationDropzone";
import { importConversations } from "../store/actions/conversations/importConversations";
import { useAppStore } from "../store";

const useStyles = createStyles((theme) => ({
card: {
Expand All @@ -28,8 +34,11 @@ const useStyles = createStyles((theme) => ({
}));

const AddConversation = () => {
const showConversationImport = useAppStore(
(state) => state.showConversationImport
);
const { classes } = useStyles();
const getFunction = useGetFunction();
const dropzoneOpenRef = React.useRef<() => void>(null);

const onSubmit = React.useCallback(
({
Expand All @@ -39,40 +48,76 @@ const AddConversation = () => {
functionIds,
...values
}: ConversationFormValues) => {
const newConversation = addConversation(values, { headers, proxy });
const newConversation = addConversation(
values,
{ headers, proxy },
functionIds,
save
);
setActiveConversation(newConversation.id, true);
},
[]
);

for (const functionId of functionIds) {
const callableFunction = getFunction(functionId);
if (callableFunction) {
newConversation.addFunction(callableFunction);
}
}
const onDrop = React.useCallback<ConversationNavbarDropzoneProps["onDrop"]>(
async (importedConversations) => {
const conversations = await importConversations(
importedConversations
);

if (save) {
addPersistedConversationId(newConversation.id);
if (conversations.length) {
setActiveConversation(conversations[0].id);
}
},
[getFunction]
[]
);

return (
<Container h="100%" p={0} m={0} fluid>
<Center h="100%">
<Card
className={classes.card}
shadow="sm"
padding="lg"
radius="md"
withBorder
>
<Stack w="100%" h="100%">
<Title order={4} align="center">
New Conversation
</Title>
<ConversationForm onSubmit={onSubmit} hideAppSettings />
</Stack>
</Card>
<Stack>
<Card
className={classes.card}
shadow="sm"
padding="lg"
radius="md"
withBorder
>
<Stack w="100%" h="100%">
<Title order={4} align="center">
New Conversation
</Title>
<ConversationForm onSubmit={onSubmit} />
</Stack>
</Card>
{showConversationImport && (
<Card
className={classes.card}
shadow="sm"
padding={0}
radius="md"
withBorder
>
<ConversationDropzone
onDrop={onDrop}
openRef={dropzoneOpenRef}
>
<Box p="lg">
<Button
variant="light"
fullWidth
leftIcon={<BiImport />}
onClick={() =>
dropzoneOpenRef.current?.()
}
>
Import Conversation
</Button>
</Box>
</ConversationDropzone>
</Card>
)}
</Stack>
</Center>
</Container>
);
Expand Down
83 changes: 38 additions & 45 deletions packages/web/src/components/AppSettings/AppSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,45 @@
import {
ColorScheme,
Divider,
Group,
SegmentedControl,
Stack,
Switch,
Text,
useMantineColorScheme,
} from "@mantine/core";
import AppStorageUsage from "./AppStorageUsage";
import { useAppStore } from "../../store";
import { toggleShowUsage } from "../../store/actions/appSettings/toggleShowUsage";
import AppSettingsDangerZone from "./AppSettingsDangerZone";
import { Tabs, useMantineTheme } from "@mantine/core";
import GeneralSettings from "./GeneralSettings";
import React from "react";
import { useMediaQuery } from "@mantine/hooks";
import { BiCog, BiData } from "react-icons/bi";
import DataSettings from "./DataSettings";

const AppSettings = () => {
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
const showUsage = useAppStore((state) => state.showUsage);

return (
<Stack>
<Group position="apart" noWrap>
<Text>Theme</Text>
<SegmentedControl
data={[
{ label: "Light", value: "light" },
{ label: "Dark", value: "dark" },
]}
value={colorScheme}
onChange={(value) =>
toggleColorScheme(value as ColorScheme)
}
/>
</Group>
type AppSettingsTab = "general" | "data";

<Group position="apart" noWrap>
<Text>Show Usage</Text>
<Switch
checked={showUsage}
onChange={() => toggleShowUsage()}
/>
</Group>

<AppStorageUsage />
const AppSettings = () => {
const theme = useMantineTheme();
const isSm = useMediaQuery(`(max-width: ${theme.breakpoints.md})`);

<Divider label="Danger Zone" labelPosition="center" color="red" />
const [currentTab, setCurrentTab] =
React.useState<AppSettingsTab>("general");

<AppSettingsDangerZone />
</Stack>
return (
<Tabs
keepMounted={false}
value={currentTab}
onTabChange={(tab) =>
setCurrentTab((tab as AppSettingsTab) ?? "general")
}
orientation={isSm ? "horizontal" : "vertical"}
mih="40vh"
variant="outline"
>
<Tabs.List mb={isSm ? "md" : undefined}>
<Tabs.Tab value="general" icon={<BiCog />}>
General
</Tabs.Tab>
<Tabs.Tab value="data" icon={<BiData />}>
Data
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="general" px="md">
<GeneralSettings />
</Tabs.Panel>
<Tabs.Panel value="data" px="md">
<DataSettings />
</Tabs.Panel>
</Tabs>
);
};

Expand Down
27 changes: 22 additions & 5 deletions packages/web/src/components/AppSettings/AppSettingsDangerZone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,39 @@ import { resetCallableFunctionWarnings } from "../../store/actions/callableFunct
import { resetDefaultSettings } from "../../store/actions/defaultConversationSettings/resetDefaultSettings";
import { removeAllSavedContexts } from "../../store/actions/savedContexts/removeAllSavedContexts";
import { removeAllSavedPrompts } from "../../store/actions/savedPrompts/removeAllSavedPrompts";
import { useAppStore } from "../../store";
import { shallow } from "zustand/shallow";

const AppSettingsDangerZone = () => {
const [conversations, functions, contexts, prompts] = useAppStore(
(state) => [
state.conversations,
state.callableFunctions,
state.savedContexts,
state.savedPrompts,
],
shallow
);

const actions = React.useMemo(
() => [
{
label: "Delete conversations",
label: `Delete all conversations (${conversations.length})`,
unconfirmedLabel: "Delete",
action: removeAllConversations,
},
{
label: "Delete Functions",
label: `Delete all functions (${functions.length})`,
unconfirmedLabel: "Delete",
action: removeAllCallableFunctions,
},
{
label: "Delete Saved Contexts",
label: `Delete all saved contexts (${contexts.length})`,
unconfirmedLabel: "Delete",
action: removeAllSavedContexts,
},
{
label: "Delete Saved Prompts",
label: `Delete all saved prompts (${prompts.length})`,
unconfirmedLabel: "Delete",
action: removeAllSavedPrompts,
},
Expand All @@ -42,7 +54,12 @@ const AppSettingsDangerZone = () => {
action: resetDefaultSettings,
},
],
[]
[
contexts.length,
conversations.length,
functions.length,
prompts.length,
]
);

return (
Expand Down
Loading

0 comments on commit dab7811

Please sign in to comment.