diff --git a/client/.env.example b/client/.env.example index ad0e15c94..8df4edb80 100644 --- a/client/.env.example +++ b/client/.env.example @@ -1,4 +1,3 @@ -REACT_APP_GAME_KEY=8144b347-8437-415d-b276-aac68c83b1dc REACT_APP_API_URL=https://latitude-game-api.herokuapp.com #REACT_APP_API_URL=http://localhost:8000 EXTEND_ESLINT = true \ No newline at end of file diff --git a/client/src/contexts/EditorProvider.tsx b/client/src/contexts/EditorProvider.tsx index d842862bb..9752703d2 100644 --- a/client/src/contexts/EditorProvider.tsx +++ b/client/src/contexts/EditorProvider.tsx @@ -12,7 +12,6 @@ import { useLazyGetSpellQuery, Spell } from '../state/spells' import LoadingScreen from '../features/common/LoadingScreen/LoadingScreen' import { MyNode } from '../features/common/Node/Node' import gridimg from '../grid.png' -import { useSpell } from './SpellProvider' import { usePubSub } from './PubSubProvider' import { useRete, ReteContext } from './ReteProvider' // import { ThothTab } from './TabManagerProvider' @@ -41,7 +40,7 @@ const Context = createContext({ setEditor: (editor: any) => {}, getNodeMap: () => {}, getNodes: () => {}, - loadGraph: (graph: any) => {}, + loadChain: (chain: any) => {}, setContainer: () => {}, undo: () => {}, redo: () => {}, @@ -52,7 +51,7 @@ export const useEditor = () => useContext(Context) const EditorProvider = ({ children }) => { const [editor, setEditorState] = useState({ components: [], - loadGraph: (graph: any) => {}, + loadGraph: (chain: any) => {}, }) const editorRef = useRef({ trigger: (event: string) => {}, @@ -70,8 +69,6 @@ const EditorProvider = ({ children }) => { } const buildEditor = async (container, _spell, tab, thoth) => { - // copy spell in case it is read only - const spell = JSON.parse(JSON.stringify(_spell)) // eslint-disable-next-line no-console const newEditor = await initEditor({ container, @@ -86,7 +83,11 @@ const EditorProvider = ({ children }) => { // set editor to the map setEditor(newEditor) - if (tab.type === 'spell') newEditor.loadGraph(spell.graph) + if (tab.type === 'spell') { + // copy spell in case it is read onl + const spell = JSON.parse(JSON.stringify(_spell)) + newEditor.loadGraph(spell.chain) + } if (tab.type === 'module') { const moduleDoc = await thoth.getModule(tab.module) @@ -118,7 +119,7 @@ const EditorProvider = ({ children }) => { return editor && Object.fromEntries(editor.components) } - const loadGraph = graph => { + const loadChain = graph => { editor.loadGraph(graph) } @@ -134,7 +135,7 @@ const EditorProvider = ({ children }) => { buildEditor, getNodeMap, getNodes, - loadGraph, + loadChain, setEditor, getEditor, undo, @@ -149,17 +150,17 @@ const RawEditor = ({ tab, children }) => { const [getSpell, { data: spell, isLoading }] = useLazyGetSpellQuery() const [loaded, setLoaded] = useState(false) const { buildEditor } = useEditor() - const { getCurrentGameState, updateCurrentGameState } = useSpell() // This will be the main interface between thoth and rete const reteInterface = useRete() useEffect(() => { if (!tab) return - getSpell(tab.spell) + if (tab?.spell) getSpell(tab.spell) }, [tab]) - if (isLoading || !tab || !spell) return + if (!tab || (tab.type === 'spell' && (isLoading || !spell))) + return return ( <> @@ -180,11 +181,7 @@ const RawEditor = ({ tab, children }) => {
{ if (el && !loaded) { - buildEditor(el, spell, tab, { - ...reteInterface, - getCurrentGameState, - updateCurrentGameState, - }) + buildEditor(el, spell, tab, reteInterface) setLoaded(true) } }} diff --git a/client/src/contexts/ModuleProvider.tsx b/client/src/contexts/ModuleProvider.tsx index 37bfcde4e..db533368e 100644 --- a/client/src/contexts/ModuleProvider.tsx +++ b/client/src/contexts/ModuleProvider.tsx @@ -77,7 +77,7 @@ const ModuleProvider = ({ children }) => { const getSpellModules = async spell => { // should actually look for spells that have a data.module key set to a string - const moduleNames = Object.values(spell.graph.nodes) + const moduleNames = Object.values(spell.chain.nodes) .filter((n: any) => n.name === 'Module') .map((n: any) => n.data.name) diff --git a/client/src/contexts/ReteProvider.tsx b/client/src/contexts/ReteProvider.tsx index f1b87afb7..620ec3a43 100644 --- a/client/src/contexts/ReteProvider.tsx +++ b/client/src/contexts/ReteProvider.tsx @@ -1,8 +1,11 @@ import { EngineContext } from '@latitudegames/thoth-core' import { useContext, createContext } from 'react' +import { useDispatch } from 'react-redux' import { postEnkiCompletion } from '../services/game-api/enki' import { completion as _completion } from '../services/game-api/text' +import { selectGameStateBySpellId, updateGameState } from '../state/gameState' +import { store } from '../state/store' import { invokeInference } from '../utils/huggingfaceHelper' import { useDB } from './DatabaseProvider' import { usePubSub } from './PubSubProvider' @@ -42,6 +45,8 @@ const Context = createContext({ getGameState: () => {}, setGameState: () => {}, getModules: async () => {}, + getCurrentGameState: () => ({} as Record), + updateCurrentGameState: () => new Promise(() => {}) as Promise, completion: _completion, enkiCompletion: async (): Promise<{ outputs: string[] }> => await new Promise(resolve => { @@ -57,6 +62,7 @@ export const useRete = () => useContext(Context) const ReteProvider = ({ children, tab }) => { const { events, publish, subscribe } = usePubSub() + const dispatch = useDispatch() const { models: { spells, modules }, } = useDB() @@ -135,6 +141,22 @@ const ReteProvider = ({ children, tab }) => { publish($TEXT_EDITOR_CLEAR(tab.id)) } + const getCurrentGameState = () => { + const currentGameState = selectGameStateBySpellId( + store.getState().gameState, + tab.spell + ) + return currentGameState?.state + } + + const updateCurrentGameState = update => { + const newState = { + spellId: tab.spell, + state: update, + } + dispatch(updateGameState(newState)) + } + const publicInterface = { onInspector, onAddModule, @@ -148,6 +170,8 @@ const ReteProvider = ({ children, tab }) => { completion, enkiCompletion, huggingface, + getCurrentGameState, + updateCurrentGameState, ...modules, // going to need to manuall create theses diff --git a/client/src/database/models/moduleModel.ts b/client/src/database/models/moduleModel.ts index 1db3ba8a9..f5f599d37 100644 --- a/client/src/database/models/moduleModel.ts +++ b/client/src/database/models/moduleModel.ts @@ -26,9 +26,6 @@ const loadModuleModel = db => { const updateModule = async (moduleName: string, update: object) => { const module = await getModule(moduleName) - // eslint-disable-next-line - console.log('module', module) - const updatedModule = await module.atomicUpdate(oldData => { return { ...oldData, @@ -68,6 +65,37 @@ const loadModuleModel = db => { return await db.modules.insert(newModule) } + const getNestedModules = async (moduleNames: string[]) => { + const moduleDocs = await Promise.all( + moduleNames.map(moduleName => getModule(moduleName)) + ) + if (moduleDocs.length === 0) return [] + const modules = moduleDocs.filter(Boolean).map(module => module.toJSON()) + const nestedModules = await Promise.all( + modules.map(async module => { + const nestedModuleNames = Object.values(module.data.nodes) + .filter((n: any) => n.data.module) + .map((n: any) => n.data.module) + if (nestedModuleNames.length === 0) { + return [] + } else { + const nextModuleLayer = await getNestedModules(nestedModuleNames) + return nextModuleLayer.flat() + } + }) + ) + return modules.concat(nestedModules.flat()) + } + + const getSpellModules = async spell => { + const moduleNames = Object.values(spell.chain.nodes) + .filter((n: any) => n.data.module) + .map((n: any) => n.data.module) + + const modules = await getNestedModules(moduleNames) + return modules + } + return { insert, getModules, @@ -76,6 +104,7 @@ const loadModuleModel = db => { updateModule, findOneModule, updateOrCreate, + getSpellModules, } } export default loadModuleModel diff --git a/client/src/features/StartScreen/components/CreateNew.jsx b/client/src/features/StartScreen/components/CreateNew.jsx index 87c7a60aa..d30cdbc21 100644 --- a/client/src/features/StartScreen/components/CreateNew.jsx +++ b/client/src/features/StartScreen/components/CreateNew.jsx @@ -43,12 +43,16 @@ const CreateNew = () => { const onCreate = async () => { const placeholderName = uniqueNamesGenerator(customConfig) const { data: spell } = await newSpell({ - graph: defaultGraph, + chain: defaultGraph, name: placeholderName, }) await clearTabs() - await openTab({ name: spell.name, spellId: spell.name, type: 'spell' }) + await openTab({ + name: placeholderName, + spellId: placeholderName, + type: 'spell', + }) setLocation('/thoth') } diff --git a/client/src/features/Thoth/Thoth.jsx b/client/src/features/Thoth/Thoth.jsx index 9502cdd43..5e5068142 100644 --- a/client/src/features/Thoth/Thoth.jsx +++ b/client/src/features/Thoth/Thoth.jsx @@ -37,7 +37,12 @@ const Thoth = ({ empty }) => { {!empty && tabs.map((tab, i) => ( - + ))} ) diff --git a/client/src/features/Thoth/components/EditorWindow/Deployment.tsx b/client/src/features/Thoth/components/EditorWindow/Deployment.tsx index 1ac68143a..cd0645583 100644 --- a/client/src/features/Thoth/components/EditorWindow/Deployment.tsx +++ b/client/src/features/Thoth/components/EditorWindow/Deployment.tsx @@ -22,9 +22,10 @@ const DeploymentView = ({ open, setOpen, spellId }) => { const [deploySpell] = useDeploySpellMutation() const spell = useSelector(state => selectSpellById(state, spellId)) - const { data: deployments, isLoading } = useGetDeploymentsQuery( - spell?.name || '' - ) + const name = spell?.name as string + const { data: deployments, isLoading } = useGetDeploymentsQuery(name, { + skip: !spell?.name, + }) const deploy = message => { if (!spell) return @@ -32,6 +33,12 @@ const DeploymentView = ({ open, setOpen, spellId }) => { enqueueSnackbar('Spell deployed', { variant: 'success' }) } + const buildUrl = version => { + return encodeURI( + `${process.env.REACT_APP_API_URL}/games/spells/${spellId}/${version}` + ) + } + const copy = url => { const el = document.createElement('textarea') el.value = url @@ -127,10 +134,12 @@ const DeploymentView = ({ open, setOpen, spellId }) => { > - +

Change notes

{ const saveSpell = async () => { const currentSpell = spellRef.current - const graph = serialize(currentSpell) + const chain = serialize(currentSpell) - await saveSpellMutation({ ...currentSpell, graph }) + await saveSpellMutation({ ...currentSpell, chain }) } const createStateManager = () => { diff --git a/client/src/features/Thoth/components/StateManagerWindow.jsx b/client/src/features/Thoth/components/StateManagerWindow.tsx similarity index 63% rename from client/src/features/Thoth/components/StateManagerWindow.jsx rename to client/src/features/Thoth/components/StateManagerWindow.tsx index 3e8caea4c..7d2411a0c 100644 --- a/client/src/features/Thoth/components/StateManagerWindow.jsx +++ b/client/src/features/Thoth/components/StateManagerWindow.tsx @@ -2,18 +2,31 @@ import Editor from '@monaco-editor/react' import jsonFormat from 'json-format' import { useSnackbar } from 'notistack' import { useState, useEffect } from 'react' +import { useSelector } from 'react-redux' -import { useSpell } from '../../../contexts/SpellProvider' +import { selectSpellById, useSaveSpellMutation } from '../../../state/spells' +import { + selectGameStateBySpellId, + // updateGameState, +} from '../../../state/gameState' import Window from '../../common/Window/Window' import '../thoth.module.css' +import { RootState } from '../../../state/store' +import WindowMessage from './WindowMessage' + +const StateManager = ({ tab, ...props }) => { + // const dispatch = useDispatch() + const [saveSpell] = useSaveSpellMutation() + const gameState = useSelector((state: RootState) => { + return selectGameStateBySpellId(state.gameState, tab.spell) + }) + const spell = useSelector(state => selectSpellById(state, tab.spell)) -const StateManager = props => { - const { currentSpell, rewriteCurrentGameState } = useSpell() const { enqueueSnackbar } = useSnackbar() - const [typing, setTyping] = useState(null) + const [typing, setTyping] = useState(false) const [code, setCode] = useState('{}') - const [height, setHeight] = useState() + const [height, setHeight] = useState() const bottomHeight = 50 @@ -41,7 +54,7 @@ const StateManager = props => { useEffect(() => { if (props?.node?.rect?.height) - setHeight(props.node.rect.height - bottomHeight) + setHeight((props.node.rect.height - bottomHeight) as number) // this is to dynamically set the appriopriate height so that Monaco editor doesnt break flexbox when resizing props.node.setEventListener('resize', data => { @@ -61,9 +74,11 @@ const StateManager = props => { return () => clearTimeout(delayDebounceFn) }, [code]) + // update code when game state changes useEffect(() => { - if (currentSpell?.gameState) setCode(jsonFormat(currentSpell.gameState)) - }, [currentSpell]) + if (!gameState) return + setCode(jsonFormat(gameState.state)) + }, [gameState]) const onClear = () => { const reset = `{}` @@ -76,7 +91,13 @@ const StateManager = props => { } const onSave = () => { - rewriteCurrentGameState(JSON.parse(code)) + if (!gameState) return + const parsedState = JSON.parse(code) + const spellUpdate = { + ...spell, + gameState: parsedState, + } + saveSpell(spellUpdate) enqueueSnackbar('State saved', { preventDuplicate: true, variant: 'success', @@ -92,6 +113,9 @@ const StateManager = props => { ) + if (tab.type === 'module') + return + return ( { +const Workspace = ({ tab, tabs, appPubSub }) => { const [loadSpell, { data: spellData }] = useLazyGetSpellQuery() const [saveSpell] = useSaveSpellMutation() const { saveModule } = useModule() @@ -29,17 +32,31 @@ const Workspace = ({ tab, appPubSub }) => { 'save nodecreated noderemoved connectioncreated connectionremoved nodetranslated', debounce(() => { if (tab.type === 'spell') { - saveSpell({ ...spellData, graph: editor.toJSON() }, false) + saveSpell({ ...spellData, chain: editor.toJSON() }, false) } if (tab.type === 'module') { saveModule(tab.module, { data: editor.toJSON() }, false) + // when a module is saved, we look for any open spell tabs, and check if they have the module. + /// if they do, we trigger a save to ensure the module change is captured to the server + tabs + .filter(tab => tab.type === 'spell') + .forEach(filteredTab => { + if (filteredTab.spell) { + const spell = selectSpellById( + store.getState(), + filteredTab.spell + ) + if (spell?.modules.some(module => module.name === tab.module)) + saveSpell({ ...spell }) + } + }) } }, 500) ) }, [editor]) useEffect(() => { - if (!tab) return + if (!tab || !tab.spell) return loadSpell(tab.spell) }, [tab]) diff --git a/client/src/features/common/MenuBar/MenuBar.js b/client/src/features/common/MenuBar/MenuBar.js index e557509ea..c3ca0527b 100644 --- a/client/src/features/common/MenuBar/MenuBar.js +++ b/client/src/features/common/MenuBar/MenuBar.js @@ -220,8 +220,6 @@ const MenuBar = () => { eval(func) } - console.log('MENU BAR') - return (
    Thoth logo diff --git a/client/src/features/common/Window/Window.js b/client/src/features/common/Window/Window.js deleted file mode 100644 index 57e70fcd3..000000000 --- a/client/src/features/common/Window/Window.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Scrollbars } from 'react-custom-scrollbars' - -import WindowToolbar from './WindowToolbar' - -import css from './window.module.css' - -const WindowLayout = props => { - return ( -
    - {props.children} -
    - ) -} - -const Window = ({ outline, dark, borderless, darker, grid, ...props }) => { - return ( -
    - {props.toolbar} - {props.children} -
    - ) -} - -export default Window diff --git a/client/src/features/common/Window/Window.tsx b/client/src/features/common/Window/Window.tsx new file mode 100644 index 000000000..3a14b24e0 --- /dev/null +++ b/client/src/features/common/Window/Window.tsx @@ -0,0 +1,52 @@ +import { Scrollbars } from 'react-custom-scrollbars' + +import WindowToolbar from './WindowToolbar' + +import css from './window.module.css' +import { ReactElement } from 'react' + +const WindowLayout = props => { + return ( +
    + {props.children} +
    + ) +} + +type Props = { + outline?: boolean + dark?: boolean + borderless?: boolean + darker?: boolean + grid?: boolean + toolbar: ReactElement + children: ReactElement | ReactElement[] +} + +const Window = (props: Props) => { + const { + outline = false, + dark = false, + borderless = false, + darker = false, + grid = false, + } = props + + return ( +
    + {props.toolbar} + {props.children} +
    + ) +} + +export default Window diff --git a/client/src/state/gameState.ts b/client/src/state/gameState.ts new file mode 100644 index 000000000..95105d563 --- /dev/null +++ b/client/src/state/gameState.ts @@ -0,0 +1,62 @@ +import { createDraftSafeSelector } from '@reduxjs/toolkit' +import { v4 as uuidv4 } from 'uuid' + +import { + createSlice, + // createSelector, + createEntityAdapter, +} from '@reduxjs/toolkit' + +export interface GameState { + id: string + state: Record + spellId: string +} + +const gameStateAdapater = createEntityAdapter() +const gameStateSelectors = gameStateAdapater.getSelectors() +const initialState = gameStateAdapater.getInitialState() + +const gameStateSlice = createSlice({ + name: 'gameState', + initialState, + reducers: { + updateGameState: (state, action) => { + const gameState = selectGameStateBySpellId(state, action.payload.spellId) + + if (!gameState) { + gameStateAdapater.addOne(state, { id: uuidv4(), ...action.payload }) + } else { + const changes = { + state: { + ...gameState.state, + ...action.payload.state, + }, + } + gameStateAdapater.updateOne(state, { + id: gameState.id, + changes: changes, + }) + } + }, + createGameState: (state, action) => { + const newGameState = { + ...action.payload, + history: [], + } + + gameStateAdapater.addOne(state, newGameState) + }, + }, +}) + +export const selectGameStateBySpellId = createDraftSafeSelector( + [gameStateSelectors.selectAll, (_, spellId) => spellId], + (gameStates: GameState[], spellId) => { + return gameStates.find(state => state.spellId === spellId) + } +) + +export const { selectById } = gameStateSelectors +export const { updateGameState, createGameState } = gameStateSlice.actions +export default gameStateSlice.reducer diff --git a/client/src/state/spells.ts b/client/src/state/spells.ts index e6981562c..37545e97d 100644 --- a/client/src/state/spells.ts +++ b/client/src/state/spells.ts @@ -1,27 +1,35 @@ import { createSelector } from '@reduxjs/toolkit' -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' +import { + createApi, + fetchBaseQuery, + FetchBaseQueryError, +} from '@reduxjs/toolkit/query/react' import { Spell as SpellType } from '@latitudegames/thoth-core/types' +import { getAuthHeader } from '../utils/authHelper' import { initDB } from '../database' - -function camelize(str) { - return str - .replace(/(?:^\w|[A-Z]|\b\w)/g, function (word, index) { - return index === 0 ? word.toLowerCase() : word.toUpperCase() - }) - .replace(/\s+/g, '') -} - -const _spellModel = async () => { +import { QueryReturnValue } from '@reduxjs/toolkit/dist/query/baseQueryTypes' +import { updateGameState } from './gameState' +import { Module } from '../database/schemas/module' +// function camelize(str) { +// return str +// .replace(/(?:^\w|[A-Z]|\b\w)/g, function (word, index) { +// return index === 0 ? word.toLowerCase() : word.toUpperCase() +// }) +// .replace(/\s+/g, '') +// } + +const _moduleModel = async () => { const db = await initDB() - const { spells } = db.models - return spells + const { modules } = db.models + return modules } export interface Spell { id?: string user?: Record | null | undefined name: string - graph: SpellType + chain: SpellType + modules: Module[] gameState: Record createdAt?: number updatedAt?: number @@ -39,82 +47,96 @@ export interface DeployArgs { message: string } -// stubbed temp data -const versions: Record = {} - export const spellApi = createApi({ reducerPath: 'spellApi', baseQuery: fetchBaseQuery({ - baseUrl: process.env.REACT_APP_API_URL || 'localhost:8000/', + baseUrl: `${process.env.REACT_APP_API_URL}/game` || 'localhost:8000/game', + prepareHeaders: headers => { + const authHeader = getAuthHeader() + if (authHeader?.Authorization) + headers.set('authorization', authHeader['Authorization']) + return headers + }, }), tagTypes: ['Spell', 'Version'], endpoints: builder => ({ getSpells: builder.query({ providesTags: ['Spell'], - async queryFn() { - const spellModel = await _spellModel() - const spells = await spellModel.getSpells() - - return { data: spells.map(spell => spell.toJSON()) } - }, + query: () => '/spells', }), getSpell: builder.query({ providesTags: ['Spell'], - async queryFn(spellId) { - const spellModel = await _spellModel() - const spell = await spellModel.getSpell(spellId) + query: spellId => { + return { + url: `spells/${spellId}`, + } + }, + async onQueryStarted(id, { dispatch, queryFulfilled }) { + const { data: spell } = await queryFulfilled - return { data: spell.toJSON() } + dispatch( + updateGameState({ state: spell.gameState, spellId: spell.name }) + ) }, }), saveSpell: builder.mutation, Partial>({ invalidatesTags: ['Spell'], - async queryFn(spell) { - const spellModel = await _spellModel() - const updatedSpell = await spellModel.saveSpell(spell.name, spell) - return { data: updatedSpell.toJSON() } + // needed to use queryFn as query option didnt seem to allow async functions. + async queryFn(spell, { dispatch }, extraOptions, baseQuery) { + const moduleModel = await _moduleModel() + const modules = await moduleModel.getSpellModules(spell) + + if (spell.gameState) + dispatch( + updateGameState({ state: spell.gameState, spellId: spell.name }) + ) + + spell.modules = modules + + const baseQueryOptions = { + url: 'spells/save', + body: spell, + method: 'POST', + } + + // cast into proper response shape expected by queryFn return + // probbably a way to directly pass in type args to baseQuery but couldnt find. + return baseQuery(baseQueryOptions) as QueryReturnValue< + Partial, + FetchBaseQueryError, + unknown + > }, }), newSpell: builder.mutation>({ invalidatesTags: ['Spell'], - async queryFn(spellData) { - const newSpell = { gameState: {}, ...spellData } - const spellModel = await _spellModel() - - const spell = await spellModel.newSpell(newSpell) - - return { data: spell.toJSON() } + query: spellData => { + const spell = { + ...spellData, + gameState: {}, + } + return { + url: '/spells/save', + method: 'POST', + body: spell, + } }, }), deploySpell: builder.mutation({ invalidatesTags: ['Version'], - async queryFn({ spellId, message }) { - if (!versions[spellId]) versions[spellId] = [] - - const _versions = versions[spellId] - const version = '0.0.' + (_versions.length + 1) - const url = `${process.env.REACT_APP_API_URL}/spells/${camelize( - spellId - )}/${version}` - const deployment = { version, message, spellId, url } - - versions[spellId].push(deployment) - + query({ spellId, message }) { return { - data: deployment as DeployedSpellVersion, + url: `/spells/${spellId}/deploy`, + body: { + message, + }, + method: 'POST', } }, }), getDeployments: builder.query({ providesTags: ['Version'], - async queryFn(spellId) { - console.log('egtting versions!') - const result = versions[spellId] || [] - console.log('results', result) - return { - data: result.reverse(), - } - }, + query: spellId => ({ url: `/spells/deployed/${spellId}` }), }), }), }) @@ -128,13 +150,28 @@ export const selectAllSpells = createSelector( ) export const selectSpellById = createSelector( - [selectAllSpells, (state, spellId) => spellId], + [ + selectAllSpells, + (state, spellId) => { + return spellId + }, + ], (spells, spellId) => spells.find(spell => { return spell.name === spellId }) ) +export const selectSpellsByModuleName = createSelector( + [selectAllSpells, (state, moduleName) => moduleName], + (spells, moduleName) => + spells.filter( + spell => + spell.modules && + spell?.modules.some(module => module.name === moduleName) + ) +) + export const { useGetSpellQuery, useGetSpellsQuery, diff --git a/client/src/state/store.ts b/client/src/state/store.ts index ba8fca672..bc1ad231b 100644 --- a/client/src/state/store.ts +++ b/client/src/state/store.ts @@ -1,11 +1,13 @@ import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit' import { setupListeners } from '@reduxjs/toolkit/query/react' import tabReducer from './tabs' +import gameStateReducer from './gameState' import { spellApi } from './spells' export const store = configureStore({ reducer: { tabs: tabReducer, + gameState: gameStateReducer, [spellApi.reducerPath]: spellApi.reducer, }, middleware: getDefaultMiddleware => diff --git a/client/src/state/tabs.ts b/client/src/state/tabs.ts index 70bb88b32..cf4753b21 100644 --- a/client/src/state/tabs.ts +++ b/client/src/state/tabs.ts @@ -59,7 +59,11 @@ export const tabSlice = createSlice({ reducers: { tabOpened: (state, action) => { const activeTab = _activeTabSelector(state) as Tab - if (activeTab) activeTab.active = false + if (activeTab) + tabAdapater.updateOne(state, { + id: activeTab.id, + changes: { active: false }, + }) const tab = buildTab(action.payload, { active: true }) tabAdapater.addOne(state, tab) diff --git a/client/src/utils/openaiHelper.ts b/client/src/utils/openaiHelper.ts deleted file mode 100644 index 959b6b32a..000000000 --- a/client/src/utils/openaiHelper.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - ModelCompletionOpts, - OpenAIResultChoice, -} from '@latitudegames/thoth-core/types' - -export const completion = async (body: ModelCompletionOpts) => { - const url = process.env.REACT_APP_API_URL - - try { - const response = await fetch(url + '/ml/text/completions', { - method: 'POST', - mode: 'cors', - headers: { - 'Content-Type': 'application/json', - 'x-api-key': process.env.REACT_APP_GAME_KEY || '', - }, - body: JSON.stringify({ ...body, prompt: body.prompt?.trimEnd() }), - }) - const parsedResponse = await response.json() - const { result }: { result: OpenAIResultChoice | string } = parsedResponse - return result - } catch (err) { - // eslint-disable-next-line no-console - console.warn('fetch error', err) - } -} diff --git a/core/src/components/ModuleInput.ts b/core/src/components/ModuleInput.ts index 0f37e931f..1a200e5c6 100644 --- a/core/src/components/ModuleInput.ts +++ b/core/src/components/ModuleInput.ts @@ -21,7 +21,6 @@ export class ModuleInput extends ThothComponent { module: object category: string info: string - workspaceType: 'module' | 'spell' contextMenuName: string constructor() { @@ -41,7 +40,6 @@ export class ModuleInput extends ThothComponent { this.category = 'Module' this.info = info - this.workspaceType = 'module' } // the builder is used to "assemble" the node component. diff --git a/core/src/components/ModuleOutput.ts b/core/src/components/ModuleOutput.ts index fd1ae1d72..0cec178bf 100644 --- a/core/src/components/ModuleOutput.ts +++ b/core/src/components/ModuleOutput.ts @@ -16,7 +16,6 @@ export class ModuleOutput extends ThothComponent { module: object category: string info: string - workspaceType: 'module' | 'spell' contextMenuName: string constructor() { @@ -37,7 +36,6 @@ export class ModuleOutput extends ThothComponent { this.category = 'Module' this.info = info - this.workspaceType = 'module' } // the builder is used to "assemble" the node component. diff --git a/core/src/components/ModuleTriggerIn.ts b/core/src/components/ModuleTriggerIn.ts index 113e79452..4950ff066 100644 --- a/core/src/components/ModuleTriggerIn.ts +++ b/core/src/components/ModuleTriggerIn.ts @@ -16,7 +16,6 @@ export class ModuleTriggerIn extends ThothComponent { module: object category: string info: string - workspaceType: 'module' | 'spell' contextMenuName: string nodeTaskMap: Record = {} @@ -43,7 +42,6 @@ export class ModuleTriggerIn extends ThothComponent { this.category = 'Module' this.info = info - this.workspaceType = 'module' } async run(node: ThothNode, data: NodeData) { diff --git a/core/src/components/ModuleTriggerOut.ts b/core/src/components/ModuleTriggerOut.ts index 51c46b7e9..37a5e9482 100644 --- a/core/src/components/ModuleTriggerOut.ts +++ b/core/src/components/ModuleTriggerOut.ts @@ -16,7 +16,6 @@ export class ModuleTriggerOut extends ThothComponent { module: object category: string info: string - workspaceType: 'module' | 'spell' contextMenuName: string constructor() { @@ -37,7 +36,6 @@ export class ModuleTriggerOut extends ThothComponent { this.category = 'Module' this.info = info - this.workspaceType = 'module' } // the builder is used to "assemble" the node component. diff --git a/core/src/components/StateRead.ts b/core/src/components/StateRead.ts index 6e9d85341..3e24852ed 100644 --- a/core/src/components/StateRead.ts +++ b/core/src/components/StateRead.ts @@ -17,6 +17,7 @@ export class StateRead extends ThothComponent { outputs: {}, } this.category = 'State' + this.workspaceType = 'spell' this.info = info } diff --git a/core/src/components/StateWrite.js b/core/src/components/StateWrite.js index f2eff5321..407d61427 100644 --- a/core/src/components/StateWrite.js +++ b/core/src/components/StateWrite.js @@ -17,6 +17,7 @@ export class StateWrite extends ThothComponent { outputs: {}, } + this.workspaceType = 'spell' this.category = 'State' this.info = info } diff --git a/core/src/editor.ts b/core/src/editor.ts index a7d152331..1942a542a 100644 --- a/core/src/editor.ts +++ b/core/src/editor.ts @@ -95,10 +95,10 @@ export const initEditor = async function ({ category: string }) => { //@seang: disabling component filtering in anticipation of needing to treat spells as "top level modules" in the publishing workflow - // const tabType = editor.tab.type - // const { workspaceType } = component + const tabType = editor.tab.type + const { workspaceType } = component - // if (workspaceType && workspaceType !== tabType) return null + if (workspaceType && workspaceType !== tabType) return null return [component.category] }, }) diff --git a/core/src/engine.ts b/core/src/engine.ts index 92677c2ec..895afaa00 100644 --- a/core/src/engine.ts +++ b/core/src/engine.ts @@ -38,7 +38,7 @@ export type EngineContext = { body: ModelCompletionOpts ) => Promise getCurrentGameState: () => Record - updateCurrentGameState: () => Record + updateCurrentGameState: () => void enkiCompletion: ( taskName: string, inputs: string[] diff --git a/core/src/thoth-component.ts b/core/src/thoth-component.ts index 5159b73f4..c74f76641 100644 --- a/core/src/thoth-component.ts +++ b/core/src/thoth-component.ts @@ -36,6 +36,7 @@ export abstract class ThothComponent extends ThothEngineComponent { category: string info: string display: boolean + workspaceType: 'module' | 'spell' | null | undefined constructor(name: string) { super(name)