From 10b034e7bf92118b7a0773aa3f72349e4e228d6b Mon Sep 17 00:00:00 2001 From: Tristan Chin <23557893+maxijonson@users.noreply.github.com> Date: Thu, 6 Jul 2023 23:15:06 -0400 Subject: [PATCH] [lib] Fix cumulative metrics [web] Implement migrations (#33) --- packages/lib/src/classes/Conversation.ts | 4 +-- .../web/src/components/SettingsFormModal.tsx | 9 ++++-- packages/web/src/config/constants.ts | 3 ++ .../web/src/contexts/PersistenceContext.ts | 2 ++ packages/web/src/contexts/SettingsContext.ts | 2 ++ .../providers/PersistenceProvider.tsx | 5 +++- .../contexts/providers/SettingsProvider.tsx | 22 ++++++++++++-- .../persistence/1688489405401_initial.ts | 6 ++++ .../entities/migrations/persistence/index.ts | 18 ++++++++++++ .../settings/1688489405401_initial.ts | 6 ++++ .../src/entities/migrations/settings/index.ts | 18 ++++++++++++ packages/web/src/entities/persistence.ts | 1 + packages/web/src/entities/settings.ts | 2 ++ packages/web/src/hooks/useStorage.tsx | 29 +++++++++++++------ 14 files changed, 110 insertions(+), 17 deletions(-) create mode 100644 packages/web/src/entities/migrations/persistence/1688489405401_initial.ts create mode 100644 packages/web/src/entities/migrations/persistence/index.ts create mode 100644 packages/web/src/entities/migrations/settings/1688489405401_initial.ts create mode 100644 packages/web/src/entities/migrations/settings/index.ts diff --git a/packages/lib/src/classes/Conversation.ts b/packages/lib/src/classes/Conversation.ts index 58a75f4..3375cfa 100644 --- a/packages/lib/src/classes/Conversation.ts +++ b/packages/lib/src/classes/Conversation.ts @@ -655,7 +655,7 @@ export class Conversation { const unsubscribeStreaming = message.onMessageStreamingStop((m) => { // FIXME: Find out how the size is calculated for messages with function calls, fix in Message class and remove this condition - if (message.isFunctionCall()) { + if (!message.isFunctionCall()) { this.cumulativeSize += this.getSize() + m.size; this.cumulativeCost += this.getCost() + m.cost; } @@ -736,7 +736,7 @@ export class Conversation { } // FIXME: Find out how the size is calculated for messages with function calls, fix in Message class and remove this condition - if (message.isFunctionCall()) { + if (!message.isFunctionCall()) { this.cumulativeSize += this.getSize() + message.size; this.cumulativeCost += this.getCost() + message.cost; } diff --git a/packages/web/src/components/SettingsFormModal.tsx b/packages/web/src/components/SettingsFormModal.tsx index 642928e..ebc06af 100644 --- a/packages/web/src/components/SettingsFormModal.tsx +++ b/packages/web/src/components/SettingsFormModal.tsx @@ -14,14 +14,17 @@ const SettingsFormModal = ({ const theme = useMantineTheme(); const isSm = useMediaQuery(`(max-width: ${theme.breakpoints.md})`); - const { setSettings } = useSettings(); + const { settings, setSettings } = useSettings(); const onSubmit = React.useCallback( (values: ConversationFormValues) => { - setSettings(values); + setSettings({ + ...settings, + ...values, + }); onClose(); }, - [onClose, setSettings] + [onClose, setSettings, settings] ); return ( diff --git a/packages/web/src/config/constants.ts b/packages/web/src/config/constants.ts index 8f11f3e..e222aac 100644 --- a/packages/web/src/config/constants.ts +++ b/packages/web/src/config/constants.ts @@ -34,3 +34,6 @@ export const CODE_LANGUAGES = [ ] as const; export const DISCORD_SERVER_INVITE = "https://discord.gg/Aa77KCmwRx"; + +export const STORAGEKEY_PERSISTENCE = "gpt-turbo-persistence"; +export const STORAGEKEY_SETTINGS = "gpt-turbo-settings"; diff --git a/packages/web/src/contexts/PersistenceContext.ts b/packages/web/src/contexts/PersistenceContext.ts index cd51d5b..7b1934e 100644 --- a/packages/web/src/contexts/PersistenceContext.ts +++ b/packages/web/src/contexts/PersistenceContext.ts @@ -3,6 +3,7 @@ import makeNotImplemented from "../utils/makeNotImplemented"; import { Persistence } from "../entities/persistence"; import { PersistenceContext as PersistenceContextEntity } from "../entities/persistenceContext"; import { PersistencePrompt } from "../entities/persistencePrompt"; +import { persistenceVersion } from "../entities/migrations/persistence"; export interface PersistenceContextValue { persistence: Persistence; @@ -18,6 +19,7 @@ export interface PersistenceContextValue { const notImplemented = makeNotImplemented("PersistenceContext"); export const PersistenceContext = React.createContext({ persistence: { + version: persistenceVersion, conversations: [], contexts: [], prompts: [], diff --git a/packages/web/src/contexts/SettingsContext.ts b/packages/web/src/contexts/SettingsContext.ts index e697359..13fb46b 100644 --- a/packages/web/src/contexts/SettingsContext.ts +++ b/packages/web/src/contexts/SettingsContext.ts @@ -8,6 +8,7 @@ import { import React from "react"; import makeNotImplemented from "../utils/makeNotImplemented"; import { Settings } from "../entities/settings"; +import { settingsVersion } from "../entities/migrations/settings"; export interface SettingsContextValue { settings: Settings; @@ -17,6 +18,7 @@ export interface SettingsContextValue { const notImplemented = makeNotImplemented("SettingsContext"); export const SettingsContext = React.createContext({ settings: { + version: settingsVersion, save: false, apiKey: "", diff --git a/packages/web/src/contexts/providers/PersistenceProvider.tsx b/packages/web/src/contexts/providers/PersistenceProvider.tsx index 126ba20..d50cac8 100644 --- a/packages/web/src/contexts/providers/PersistenceProvider.tsx +++ b/packages/web/src/contexts/providers/PersistenceProvider.tsx @@ -10,6 +10,8 @@ import { PersistenceConversation } from "../../entities/persistenceConversation" import { Conversation, Message } from "gpt-turbo"; import useCallableFunctions from "../../hooks/useCallableFunctions"; import { PersistenceCallableFunction } from "../../entities/persistenceCallableFunction"; +import { STORAGEKEY_PERSISTENCE } from "../../config/constants"; +import { persistenceVersion } from "../../entities/migrations/persistence"; interface PersistenceProviderProps { children?: React.ReactNode; @@ -39,8 +41,9 @@ const PersistenceProvider = ({ children }: PersistenceProviderProps) => { const { value: persistence, setValue: setPersistence } = useStorage( - "gpt-turbo-persistence", + STORAGEKEY_PERSISTENCE, { + version: persistenceVersion, conversations: [], contexts: [], prompts: [], diff --git a/packages/web/src/contexts/providers/SettingsProvider.tsx b/packages/web/src/contexts/providers/SettingsProvider.tsx index ba8ce1e..b51a761 100644 --- a/packages/web/src/contexts/providers/SettingsProvider.tsx +++ b/packages/web/src/contexts/providers/SettingsProvider.tsx @@ -2,6 +2,14 @@ import React from "react"; import { SettingsContext, SettingsContextValue } from "../SettingsContext"; import useStorage from "../../hooks/useStorage"; import { Settings, settingsSchema } from "../../entities/settings"; +import { STORAGEKEY_SETTINGS } from "../../config/constants"; +import { + DEFAULT_CONTEXT, + DEFAULT_DISABLEMODERATION, + DEFAULT_DRY, + DEFAULT_MODEL, +} from "gpt-turbo"; +import { settingsVersion } from "../../entities/migrations/settings"; interface SettingsProviderProps { children?: React.ReactNode; @@ -9,8 +17,18 @@ interface SettingsProviderProps { const SettingsProvider = ({ children }: SettingsProviderProps) => { const { value: settings, setValue: setSettings } = useStorage( - "gpt-turbo-settings", - settingsSchema.parse({}), + STORAGEKEY_SETTINGS, + { + apiKey: "", + context: DEFAULT_CONTEXT, + disableModeration: DEFAULT_DISABLEMODERATION, + dry: DEFAULT_DRY, + functionIds: [], + model: DEFAULT_MODEL, + save: false, + stream: true, + version: settingsVersion, + }, settingsSchema ); diff --git a/packages/web/src/entities/migrations/persistence/1688489405401_initial.ts b/packages/web/src/entities/migrations/persistence/1688489405401_initial.ts new file mode 100644 index 0000000..f19e241 --- /dev/null +++ b/packages/web/src/entities/migrations/persistence/1688489405401_initial.ts @@ -0,0 +1,6 @@ +export const migratePersistenceInitial = ( + value: Record +): Record => { + // Applies the initial migration to add the version key + return value; +}; diff --git a/packages/web/src/entities/migrations/persistence/index.ts b/packages/web/src/entities/migrations/persistence/index.ts new file mode 100644 index 0000000..f6bd905 --- /dev/null +++ b/packages/web/src/entities/migrations/persistence/index.ts @@ -0,0 +1,18 @@ +import { migratePersistenceInitial } from "./1688489405401_initial"; + +const migrations: ((value: Record) => Record)[] = [ + migratePersistenceInitial, +]; + +export const persistenceVersion = migrations.length; + +export const migratePersistence = (value: Record) => + migrations.reduce((acc, migration, i) => { + const version = i + 1; + + if ((acc.version ?? 0) >= version) return acc; + return { + ...migration(acc), + version, + }; + }, value); diff --git a/packages/web/src/entities/migrations/settings/1688489405401_initial.ts b/packages/web/src/entities/migrations/settings/1688489405401_initial.ts new file mode 100644 index 0000000..2d23b95 --- /dev/null +++ b/packages/web/src/entities/migrations/settings/1688489405401_initial.ts @@ -0,0 +1,6 @@ +export const migrateSettingsInitial = ( + value: Record +): Record => { + // Applies the initial migration to add the version key + return value; +}; diff --git a/packages/web/src/entities/migrations/settings/index.ts b/packages/web/src/entities/migrations/settings/index.ts new file mode 100644 index 0000000..861a070 --- /dev/null +++ b/packages/web/src/entities/migrations/settings/index.ts @@ -0,0 +1,18 @@ +import { migrateSettingsInitial } from "./1688489405401_initial"; + +const migrations: ((value: Record) => Record)[] = [ + migrateSettingsInitial, +]; + +export const settingsVersion = migrations.length; + +export const migrateSettings = (value: Record) => + migrations.reduce((acc, migration, i) => { + const version = i + 1; + + if ((acc.version ?? 0) >= version) return acc; + return { + ...migration(acc), + version, + }; + }, value); diff --git a/packages/web/src/entities/persistence.ts b/packages/web/src/entities/persistence.ts index 02a0525..8c51ffc 100644 --- a/packages/web/src/entities/persistence.ts +++ b/packages/web/src/entities/persistence.ts @@ -5,6 +5,7 @@ import { persistencePromptSchema } from "./persistencePrompt"; import { persistenceCallableFunctionSchema } from "./persistenceCallableFunction"; export const persistenceSchema = z.object({ + version: z.number(), conversations: z.array(persistenceConversationSchema), contexts: z.array(persistenceContextSchema).refine((contexts) => { const names = contexts.map((c) => c.name.toLowerCase()); diff --git a/packages/web/src/entities/settings.ts b/packages/web/src/entities/settings.ts index f08834a..4f6b424 100644 --- a/packages/web/src/entities/settings.ts +++ b/packages/web/src/entities/settings.ts @@ -10,6 +10,8 @@ import { import { z } from "zod"; export const settingsSchema = z.object({ + version: z.number(), + save: z.boolean().default(false), apiKey: conversationConfigSchema.shape.apiKey.default(""), diff --git a/packages/web/src/hooks/useStorage.tsx b/packages/web/src/hooks/useStorage.tsx index e72c918..84a4eda 100644 --- a/packages/web/src/hooks/useStorage.tsx +++ b/packages/web/src/hooks/useStorage.tsx @@ -2,6 +2,12 @@ import { Button, Stack, Text } from "@mantine/core"; import { useLocalStorage } from "@mantine/hooks"; import { notifications } from "@mantine/notifications"; import { ZodError, ZodType } from "zod"; +import { + STORAGEKEY_PERSISTENCE, + STORAGEKEY_SETTINGS, +} from "../config/constants"; +import { migratePersistence } from "../entities/migrations/persistence"; +import { migrateSettings } from "../entities/migrations/settings"; const warns = new Set(); @@ -61,6 +67,16 @@ const repairValueFromZodError = ( ); }; +const getMigratedValue = (key: string, value: any) => { + switch (key) { + case STORAGEKEY_PERSISTENCE: + return migratePersistence(value); + case STORAGEKEY_SETTINGS: + return migrateSettings(value); + } + return value; +}; + const useStorage = ( key: string, defaultValue: T, @@ -70,16 +86,11 @@ const useStorage = ( key: key, defaultValue, getInitialValueInEffect: false, - serialize: (value) => - JSON.stringify( - (() => { - if (value === undefined) return defaultValue; - if (schema) return schema.parse(value); - return value; - })() - ), + serialize: (value) => { + return JSON.stringify(schema?.parse(value) ?? value); + }, deserialize: (v) => { - const value = JSON.parse(v); + const value = getMigratedValue(key, JSON.parse(v)); if (!schema) return value; const result = schema.safeParse(value);