diff --git a/docs/scripting.md b/docs/scripting.md index 765d42546..0b0c78715 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -85,11 +85,14 @@ cyb::search_by_embedding(text:string, count: int) -> string[]; #### Experemental -OpenAI promts(beta, in developement) +OpenAI promts(beta) + +- api key should be added using cyb-keys +- this is wrapper around [openai api](https://platform.openai.com/docs/api-reference/chat/create) ``` // Apply prompt OpenAI and get result -cyb::open_ai_prompt(prompt: string; params: json) -> string; +cyb::open_ai_completions(messages: object[]; apiKey: string; params: json) -> string | AsyncIterable; ``` #### Debug diff --git a/package.json b/package.json index 114a7a606..4b1b50e55 100644 --- a/package.json +++ b/package.json @@ -192,7 +192,7 @@ "core-js": "^3.30.0", "crypto": "^1.0.1", "cyb-cozo-lib-wasm": "^0.7.145", - "cyb-rune-wasm": "^0.0.82", + "cyb-rune-wasm": "^0.0.841", "datastore-core": "^9.2.3", "datastore-idb": "^2.1.4", "dateformat": "^3.0.3", @@ -254,7 +254,6 @@ "tone": "^14.7.77", "ts-jest": "^29.1.1", "ts-jest-resolver": "^2.0.1", - "typeit-react": "^2.7.1", "videostream": "^3.2.2", "web3": "1.2.4", "web3-utils": "^4.2.1", diff --git a/src/components/ledger/stageActionBar.jsx b/src/components/ledger/stageActionBar.jsx index da4995def..4c3648abc 100644 --- a/src/components/ledger/stageActionBar.jsx +++ b/src/components/ledger/stageActionBar.jsx @@ -5,6 +5,8 @@ import { ActionBar, Pane, Text } from '@cybercongress/gravity'; import { BondStatus } from 'cosmjs-types/cosmos/staking/v1beta1/staking'; import { useBackend } from 'src/contexts/backend/backend'; import { CHAIN_ID, BASE_DENOM } from 'src/constants/config'; +import { KEY_TYPE } from 'src/pages/Keys/types'; + import { ContainetLedger } from './container'; import { Dots } from '../ui/Dots'; import Account from '../account/account'; @@ -22,6 +24,7 @@ import AddFileButton from '../buttons/AddFile/AddFile'; const imgKeplr = require('../../image/keplr-icon.svg'); const imgRead = require('../../image/duplicate-outline.svg'); +const imgSecrets = require('../../image/secrets.svg'); const T = new LocalizedStrings(i18n); @@ -405,7 +408,7 @@ export function ConnectAddress({ selectMethodFunc, selectMethod, selectNetwork, - connctAddress, + connectAddress, keplr, onClickBack, }) { @@ -413,16 +416,16 @@ export function ConnectAddress({ {keplr ? ( selectMethodFunc('keplr')} - active={selectMethod === 'keplr'} + onClick={() => selectMethodFunc(KEY_TYPE.keplr)} + active={selectMethod === KEY_TYPE.keplr} img={imgKeplr} text="keplr" /> @@ -439,11 +442,17 @@ export function ConnectAddress({ )} selectMethodFunc('read-only')} - active={selectMethod === 'read-only'} + onClick={() => selectMethodFunc(KEY_TYPE.readOnly)} + active={selectMethod === KEY_TYPE.readOnly} img={imgRead} text="read-only" /> + selectMethodFunc(KEY_TYPE.secrets)} + active={selectMethod === KEY_TYPE.secrets} + img={imgSecrets} + text="secrets" + /> in diff --git a/src/containers/ipfs/components/SoulCompanion/SoulCompanion.tsx b/src/containers/ipfs/components/SoulCompanion/SoulCompanion.tsx index 9a8f4afbb..04d4d79d6 100644 --- a/src/containers/ipfs/components/SoulCompanion/SoulCompanion.tsx +++ b/src/containers/ipfs/components/SoulCompanion/SoulCompanion.tsx @@ -34,7 +34,7 @@ function SoulCompanion({ if (details.type && details.type !== 'text' && details.text) { setStatus('done'); setMetaItems([ - { type: 'text', text: `Skip companion for '${details.content}'.` }, + [{ type: 'text', text: `Skip companion for '${details.content}'.` }], ]); return; } @@ -65,21 +65,23 @@ function SoulCompanion({ ); } return ( -
-
    - {metaItems.map((item, index) => ( -
  • - {item.type === 'text' && ( -

    {item.text}

    - )} - {item.type === 'link' && ( - - {shortenString(item.title, 64)} - - )} -
  • - ))} -
+
+ {metaItems.map((row, index) => ( +
    + {row.map((item, index) => ( +
  • + {item.type === 'text' && ( +

    {item.text}

    + )} + {item.type === 'link' && ( + + {shortenString(item.title, 64)} + + )} +
  • + ))} +
+ ))}
); } diff --git a/src/containers/ipfs/components/SoulCompanion/soulCompanion.module.scss b/src/containers/ipfs/components/SoulCompanion/soulCompanion.module.scss index d65acd3fa..ba4d32dab 100644 --- a/src/containers/ipfs/components/SoulCompanion/soulCompanion.module.scss +++ b/src/containers/ipfs/components/SoulCompanion/soulCompanion.module.scss @@ -1,11 +1,14 @@ .itemLinks { display: flex; - margin: -10px 0; list-style-type: none; font-size: 14px; } +.soulCompanion { + margin: -10px 0; +} + .itemText { font-size: 14px; display: block; diff --git a/src/contexts/scripting/scripting.tsx b/src/contexts/scripting/scripting.tsx index 418093e7f..fae0a08d4 100644 --- a/src/contexts/scripting/scripting.tsx +++ b/src/contexts/scripting/scripting.tsx @@ -45,15 +45,17 @@ function ScriptingProvider({ children }: { children: React.ReactNode }) { const dispatch = useAppDispatch(); useEffect(() => { + runeBackend.pushContext('secrets', secrets); + const setupObservervable = async () => { const { isSoulInitialized$ } = runeBackend; const soulSubscription = (await isSoulInitialized$).subscribe((v) => { - setIsSoulInitialized(!!v); if (v) { runeRef.current = runeBackend; console.log('👻 soul initalized'); } + setIsSoulInitialized(!!v); }); const embeddingApiSubscription = (await embeddingApi$).subscribe( @@ -72,28 +74,38 @@ function ScriptingProvider({ children }: { children: React.ReactNode }) { }; setupObservervable(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const runeEntryPoints = useAppSelector(selectRuneEntypoints); const citizenship = useAppSelector(selectCurrentPassport); + const secrets = useAppSelector((state) => state.scripting.context.secrets); useEffect(() => { - (async () => { - if (citizenship) { - const particleCid = citizenship.extension.particle; + if (!isSoulInitialized || !runeRef.current) { + return; + } + + if (citizenship) { + const particleCid = citizenship.extension.particle; + + runeRef.current.pushContext('user', { + address: citizenship.owner, + nickname: citizenship.extension.nickname, + citizenship, + particle: particleCid, + } as UserContext); + } else { + runeRef.current.popContext(['user']); + } + }, [citizenship, isSoulInitialized]); - await runeBackend.pushContext('user', { - address: citizenship.owner, - nickname: citizenship.extension.nickname, - citizenship, - particle: particleCid, - } as UserContext); - } else { - await runeBackend.popContext(['user', 'secrets']); - } - })(); - }, [citizenship, runeBackend]); + useEffect(() => { + if (isSoulInitialized && runeRef.current) { + runeRef.current.pushContext('secrets', secrets); + } + }, [secrets, isSoulInitialized]); useEffect(() => { (async () => { diff --git a/src/features/ipfs/Drive/BackendStatus.tsx b/src/features/ipfs/Drive/BackendStatus.tsx index d2f155340..d810f8cce 100644 --- a/src/features/ipfs/Drive/BackendStatus.tsx +++ b/src/features/ipfs/Drive/BackendStatus.tsx @@ -19,7 +19,7 @@ import { downloadJson } from 'src/utils/json'; import { useBackend } from 'src/contexts/backend/backend'; import { EmbeddinsDbEntity } from 'src/services/CozoDb/types/entities'; import { isObject } from 'lodash'; -import { promptToOpenAI } from 'src/services/scripting/services/llmRequests/openai'; +import { openAICompletion } from 'src/services/scripting/services/llmRequests/openai'; const getProgressTrackingInfo = (progress?: ProgressTracking) => { if (!progress) { diff --git a/src/image/secrets.svg b/src/image/secrets.svg new file mode 100644 index 000000000..8fa3e12b7 --- /dev/null +++ b/src/image/secrets.svg @@ -0,0 +1 @@ +Eye diff --git a/src/index.tsx b/src/index.tsx index b4d764ae4..7e92bd538 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -46,7 +46,6 @@ import HubProvider from './contexts/hub'; import { INDEX_HTTPS, INDEX_WEBSOCKET } from './constants/config'; import ScriptingProvider from './contexts/scripting/scripting'; -import ErrorBoundary from './components/ErrorBoundary/ErrorBoundary'; const httpLink = new HttpLink({ uri: INDEX_HTTPS, @@ -100,7 +99,8 @@ if (container === null) { const root = createRoot(container); -function Providers({ children }: { children: React.ReactNode }) { +// for Storybook, WIP +export function Providers({ children }: { children: React.ReactNode }) { return ( @@ -116,7 +116,8 @@ function Providers({ children }: { children: React.ReactNode }) { - {children} + {/* {children} */} + {children} diff --git a/src/pages/Keys/ActionBar/actionBar.tsx b/src/pages/Keys/ActionBar/actionBar.tsx index be5b09d7b..2296fd444 100644 --- a/src/pages/Keys/ActionBar/actionBar.tsx +++ b/src/pages/Keys/ActionBar/actionBar.tsx @@ -10,6 +10,8 @@ import { deleteAddress } from 'src/redux/features/pocket'; import BroadcastChannelSender from 'src/services/backend/channels/BroadcastChannelSender'; import ActionBarConnect from './actionBarConnect'; import ActionBarKeplr from './actionBarKeplr'; +import { KEY_LIST_TYPE, KEY_TYPE } from '../types'; +import { removeSecret } from 'src/redux/reducers/scripting'; const STAGE_INIT = 1; const STAGE_CONNECT = 2; @@ -40,9 +42,8 @@ type Props = { selectAccount: any; hoverCard?: string; - refreshTweet?: any; - updateTweetFunc?: any; updateAddress: () => void; + keyType: string; selectedAddress?: string; @@ -58,9 +59,7 @@ function ActionBar({ selectCard, selectAccount, hoverCard, - // actionBar tweet - refreshTweet, - updateTweetFunc, + keyType, selectedAddress, // global props updateAddress, @@ -184,39 +183,28 @@ function ActionBar({ ); + const onDeleteClick = () => { + if (!selectedAddress) { + return; + } + + if (keyType === KEY_LIST_TYPE.key) { + dispatch(deleteAddress(selectedAddress)); + updateAddress(); + } + + dispatch(removeSecret({ key: selectedAddress })); + }; + if (selectedAddress) { return ( {defaultAccount.account?.cyber?.bech32 !== selectedAddress && + keyType === KEY_LIST_TYPE.key && buttonActivate} - - - - ); - } - - if (typeActionBar === '' && stage === STAGE_INIT) { - return ( - - - {buttonConnect} - {/* {defaultAccounts !== null && defaultAccounts.cyber && ( - - )} */} + ); @@ -233,47 +221,66 @@ function ActionBar({ ); } - if (typeActionBar === 'noCyber' && stage === STAGE_INIT) { - return ( - - - {connect && buttonConnect} - {makeActive && buttonActivate} - - - ); - } + if (stage === STAGE_INIT) { + if (typeActionBar === '') { + return ( + + + {buttonConnect} + {/* {defaultAccounts !== null && defaultAccounts.cyber && ( + + )} */} + + + ); + } - if (typeActionBar === 'keplr' && stage === STAGE_INIT) { - return ( - - - {connect && buttonConnect} - {keplr && ( + if (typeActionBar === 'noCyber') { + return ( + + + {connect && buttonConnect} + {makeActive && buttonActivate} + + + ); + } + if (typeActionBar === KEY_TYPE.keplr) { + return ( + + + {connect && buttonConnect} + {keplr && ( + setStage(STAGE_SEND_KEPLR)} + /> + )} + {makeActive && buttonActivate} + + + ); + } + + if (typeActionBar === KEY_TYPE.readOnly) { + return ( + + + {connect && buttonConnect} setStage(STAGE_SEND_KEPLR)} + img={imgRead} + onClick={() => setStage(STAGE_SEND_READ_ONLY)} /> - )} - {makeActive && buttonActivate} - - - ); - } - - if (typeActionBar === 'read-only' && stage === STAGE_INIT) { - return ( - - - {connect && buttonConnect} - setStage(STAGE_SEND_READ_ONLY)} - /> - {makeActive && buttonActivate} - - - ); + {makeActive && buttonActivate} + + + ); + } } if (stage === STAGE_SEND_KEPLR) { diff --git a/src/pages/Keys/ActionBar/actionBarConnect.tsx b/src/pages/Keys/ActionBar/actionBarConnect.tsx index 163a59b0b..77695d823 100644 --- a/src/pages/Keys/ActionBar/actionBarConnect.tsx +++ b/src/pages/Keys/ActionBar/actionBarConnect.tsx @@ -15,11 +15,14 @@ import { useDispatch } from 'react-redux'; import { addAddressPocket } from 'src/redux/features/pocket'; import { AccountValue } from 'src/types/defaultAccount'; import { CHAIN_ID } from 'src/constants/config'; +import { KEY_TYPE } from '../types'; +import ActionBarSecrets from './actionBarSecrets'; const { STAGE_INIT, HDPATH, STAGE_ERROR } = LEDGER; const STAGE_ADD_ADDRESS_USER = 2.1; const STAGE_ADD_ADDRESS_OK = 2.2; +const STAGE_ADD_SECRETS = 100; const checkAddress = (obj, network, address) => Object.keys(obj).some((k) => { @@ -58,12 +61,14 @@ function ActionBarConnect({ } }, [valueInputAddres]); - const connctAddress = () => { + const connectAddress = () => { switch (selectMethod) { - case 'keplr': + case KEY_TYPE.keplr: connectKeplr(); break; - + case KEY_TYPE.secrets: + onClickToggleSecrets(); + break; default: onClickAddAddressUser(); break; @@ -82,6 +87,14 @@ function ActionBarConnect({ setStage(STAGE_ADD_ADDRESS_USER); }; + const onClickToggleSecrets = () => { + setStage(STAGE_ADD_SECRETS); + }; + + const onClickAddSecrets = () => { + console.log('onClickAddSecrets'); + }; + const onClickAddAddressUserToLocalStr = async () => { const accounts = { bech32: valueInputAddres, keys: 'read-only' }; @@ -144,7 +157,7 @@ function ActionBarConnect({ selectMethodFunc={selectMethodFunc} selectMethod={selectMethod} selectNetwork={selectNetwork} - connctAddress={connctAddress} + connectAddress={connectAddress} keplr={signer} onClickBack={onClickBack} /> @@ -181,6 +194,10 @@ function ActionBarConnect({ ); } + if (stage === STAGE_ADD_SECRETS) { + return setStage(STAGE_INIT)} />; + } + if (stage === STAGE_ADD_ADDRESS_OK) { return ( diff --git a/src/pages/Keys/ActionBar/actionBarSecrets.tsx b/src/pages/Keys/ActionBar/actionBarSecrets.tsx new file mode 100644 index 000000000..57884f265 --- /dev/null +++ b/src/pages/Keys/ActionBar/actionBarSecrets.tsx @@ -0,0 +1,57 @@ +import { useEffect, useState } from 'react'; +import { Pane } from '@cybercongress/gravity'; +import { Input, ActionBar } from 'src/components'; +import { + loadJsonFromLocalStorage, + saveJsonToLocalStorage, +} from 'src/utils/localStorage'; +import { useDispatch } from 'react-redux'; +import { setSecret } from 'src/redux/reducers/scripting'; + +function ActionBarSecrets({ onClickBack }: { onClickBack: () => void }) { + const [key, setKey] = useState(''); + const [value, setValue] = useState(''); + const dispatch = useDispatch(); + + const onSave = async () => { + dispatch(setSecret({ key, value })); + onClickBack(); + }; + + return ( + + + setKey(e.target.value)} + placeholder="key/name" + autoFocus + /> + + setValue(e.target.value)} + placeholder="secret value" + autoFocus + /> + + + ); +} + +export default ActionBarSecrets; diff --git a/src/pages/Keys/KeyItem/KeyItem.module.scss b/src/pages/Keys/KeyItem/KeyItem.module.scss index 2ccb17192..0cf0525bd 100644 --- a/src/pages/Keys/KeyItem/KeyItem.module.scss +++ b/src/pages/Keys/KeyItem/KeyItem.module.scss @@ -8,13 +8,17 @@ position: relative; min-height: 230px; - >img { + > img { height: auto; } .imageWrapper { position: relative; - + width: 226px; + height: 227px; + justify-content: center; + align-items: center; + display: flex; .active { position: absolute; left: 0; @@ -25,7 +29,7 @@ .content { line-height: 30px; - >* { + > * { line-height: initial; } @@ -70,4 +74,8 @@ .passportMain { color: var(--green-light); } -} \ No newline at end of file +} + +.pointer { + cursor: pointer; +} diff --git a/src/pages/Keys/KeyItem/KeyItemSecrets.tsx b/src/pages/Keys/KeyItem/KeyItemSecrets.tsx new file mode 100644 index 000000000..b8d3ecb02 --- /dev/null +++ b/src/pages/Keys/KeyItem/KeyItemSecrets.tsx @@ -0,0 +1,46 @@ +import Pill from 'src/components/Pill/Pill'; +import styles from './KeyItem.module.scss'; + +import cx from 'classnames'; + +import { useState } from 'react'; +import { JsonRecord } from 'src/utils/localStorage'; + +type Props = { + name: string; + value: JsonRecord[keyof JsonRecord]; + selected: boolean; + selectKey: (name: string) => void; +}; + +function KeyItemSecrets({ name, value, selected, selectKey }: Props) { + const [showValue, setShowValue] = useState(false); + return ( +
selectKey(name)} + > +
+ + + +
+ +
+ secret
+ contains value{' '} + setShowValue(!showValue)} + > + + +
+
+
+ ); +} + +export default KeyItemSecrets; diff --git a/src/pages/Keys/KeyItem/images/secrets.png b/src/pages/Keys/KeyItem/images/secrets.png new file mode 100644 index 000000000..e2be3b9a4 Binary files /dev/null and b/src/pages/Keys/KeyItem/images/secrets.png differ diff --git a/src/pages/Keys/Keys.tsx b/src/pages/Keys/Keys.tsx index 21a31cb39..d8c366484 100644 --- a/src/pages/Keys/Keys.tsx +++ b/src/pages/Keys/Keys.tsx @@ -6,15 +6,22 @@ import ActionBar from 'src/pages/Keys/ActionBar/actionBar'; import { initPocket } from 'src/redux/features/pocket'; import styles from './Keys.module.scss'; import { useState } from 'react'; +import KeyItemSecrets from './KeyItem/KeyItemSecrets'; +import { KEY_LIST_TYPE } from './types'; function Keys() { const { accounts } = useSelector((state: RootState) => state.pocket); + const { secrets } = useSelector( + (state: RootState) => state.scripting.context + ); const [selectedKey, setSelectedKey] = useState(); + const [keyType, setKeyType] = useState(KEY_LIST_TYPE.key); const dispatch = useDispatch(); - function selectKey(address: string) { + function selectKey(keyType: string, address: string) { + setKeyType(keyType); setSelectedKey(selectedKey === address ? null : address); } @@ -32,7 +39,7 @@ function Keys() { key={account.bech32} account={account} selected={selectedKey === account.bech32} - selectKey={selectKey} + selectKey={(addr) => selectKey(KEY_LIST_TYPE.key, addr)} /> ); }) @@ -42,12 +49,26 @@ function Keys() { add your first key by connecting your wallet

)} + {Object.keys(secrets).map((name) => { + return ( + + selectKey(KEY_LIST_TYPE.secret, keyName) + } + /> + ); + })}
{ diff --git a/src/pages/Keys/types.ts b/src/pages/Keys/types.ts new file mode 100644 index 000000000..786d56d46 --- /dev/null +++ b/src/pages/Keys/types.ts @@ -0,0 +1,10 @@ +export const KEY_TYPE = { + keplr: 'keplr', + readOnly: 'read-only', + secrets: 'secrets', +}; + +export const KEY_LIST_TYPE = { + key: 'key', + secret: 'secret', +}; diff --git a/src/pages/robot/Soul/Soul.tsx b/src/pages/robot/Soul/Soul.tsx index 35e788cdb..3e138ec7e 100644 --- a/src/pages/robot/Soul/Soul.tsx +++ b/src/pages/robot/Soul/Soul.tsx @@ -31,7 +31,7 @@ import ScriptingActionBar from './ScriptingActionBar/ScriptingActionBar'; // import 'codemirror/theme/tomorrow-night-bright.css'; // import 'codemirror/theme/the-matrix.css'; -import { extractRuneContent } from 'src/services/scripting/helpers'; +// import { extractRuneContent } from 'src/services/scripting/helpers'; import { useBackend } from 'src/contexts/backend/backend'; import defaultParticleScript from 'src/services/scripting/rune/default/particle.rn'; @@ -44,9 +44,6 @@ import 'codemirror/theme/tomorrow-night-eighties.css'; import './codeMirror.css'; import styles from './Soul.module.scss'; -const defaultBio = - 'Hello, _##name##_!\r\nYou can fill any info about yourself using **markdown**.'; - const entrypointName = 'particle'; const highlightErrors = ( @@ -91,10 +88,8 @@ function Soul() { const [log, setLog] = useState([]); const [isChanged, setIsChanged] = useState(false); const [code, setCode] = useState(''); - // const [bio, setBio] = useState(''); const [isLoaded, setIsLoaded] = useState(true); - const addToLog = useCallback( (lines: string[]) => setLog((log) => [...log, ...lines]), [] diff --git a/src/redux/reducers/scripting.ts b/src/redux/reducers/scripting.ts index 0d1938c41..2cb1dcb67 100644 --- a/src/redux/reducers/scripting.ts +++ b/src/redux/reducers/scripting.ts @@ -41,7 +41,10 @@ console.log('----isParticleScriptEnabled', isParticleScriptEnabled); const initialScriptEntrypoints: ScriptEntrypoints = { particle: { title: 'Personal processor', - script: loadStringFromLocalStorage('particle', defaultParticleScript), + script: loadStringFromLocalStorage( + 'particle', + defaultParticleScript + ) as string, enabled: !!isParticleScriptEnabled, }, // myParticle: { @@ -53,7 +56,7 @@ const initialScriptEntrypoints: ScriptEntrypoints = { const initialState: SliceState = { context: { - secrets: loadJsonFromLocalStorage('secrets', {}) as TabularKeyValues, + secrets: loadJsonFromLocalStorage('secrets', {}), params: {}, user: {}, }, @@ -62,12 +65,12 @@ const initialState: SliceState = { }, }; -export function setSecrets(secrets: TabularKeyValues): AppThunk { - return (dispatch) => { - saveJsonToLocalStorage('secrets', secrets); - dispatch(setContext({ name: 'secrets', item: secrets })); - }; -} +// export function setSecrets(secrets: TabularKeyValues): AppThunk { +// return (dispatch) => { +// saveJsonToLocalStorage('secrets', secrets); +// dispatch(setContext({ name: 'secrets', item: secrets })); +// }; +// } const slice = createSlice({ name: 'scripting', @@ -93,6 +96,17 @@ const slice = createSlice({ state.scripts.entrypoints[name].script = code; } }, + setSecret: ( + state, + { payload }: PayloadAction<{ key: string; value: string }> + ) => { + state.context.secrets[payload.key] = payload.value; + saveJsonToLocalStorage('secrets', state.context.secrets); + }, + removeSecret: (state, { payload }: PayloadAction<{ key: string }>) => { + delete state.context.secrets[payload.key]; + saveJsonToLocalStorage('secrets', state.context.secrets); + }, setEntrypointEnabled: ( state, { @@ -115,7 +129,12 @@ export const selectRuneEntypoints = (store: RootState) => export type ScriptingActionTypes = SliceActions; -export const { setEntrypoint, setEntrypointEnabled, setContext } = - slice.actions; +export const { + setEntrypoint, + setEntrypointEnabled, + setContext, + setSecret, + removeSecret, +} = slice.actions; export default slice.reducer; diff --git a/src/services/scripting/engine.ts b/src/services/scripting/engine.ts index a000a6d9f..225744b4a 100644 --- a/src/services/scripting/engine.ts +++ b/src/services/scripting/engine.ts @@ -4,11 +4,21 @@ import { v4 as uuidv4 } from 'uuid'; import { TabularKeyValues } from 'src/types/data'; import { keyValuesToObject } from 'src/utils/localStorage'; +import { entityToDto } from 'src/utils/dto'; import { mapObjIndexed } from 'ramda'; import { removeBrokenUnicode } from 'src/utils/string'; -import { extractRuneScript } from './helpers'; +import { + BehaviorSubject, + ReplaySubject, + combineLatest, + distinctUntilChanged, + map, +} from 'rxjs'; + +import defaultParticleScript from 'src/services/scripting/rune/default/particle.rn'; +import runtimeScript from 'src/services/scripting/rune/runtime.rn'; import { ScriptCallback, @@ -22,29 +32,33 @@ import { ScriptMyCampanion, } from './types'; -import runtimeScript from './rune/runtime.rn'; -import { - BehaviorSubject, - ReplaySubject, - combineLatest, - distinctUntilChanged, - map, - share, -} from 'rxjs'; +import { extractRuneScript } from './helpers'; + +type RuneEntrypoint = { + readOnly: boolean; + execute: boolean; + funcName: string; + funcParams: EntrypointParams; + params: Object; // context data + input: string; // main code + script: string; // runtime code +}; const compileConfig = { budget: 1_000_000, experimental: false, - instructions: true, + instructions: false, options: [], }; -type CompilerParams = { - readOnly: boolean; - execute: boolean; - funcName: string; - funcParams: EntrypointParams; - config: typeof compileConfig; +const defaultRuneEntrypoint: RuneEntrypoint = { + readOnly: false, + execute: true, + funcName: 'main', + funcParams: {}, + params: {}, + input: defaultParticleScript, + script: runtimeScript, }; const toRecord = (item: TabularKeyValues) => @@ -55,39 +69,6 @@ export type LoadParams = { secrets: TabularKeyValues; }; -// export interface RuneEngine { -// pushContext( -// key: K, -// value: ScriptContext[K] -// ): void; -// popContext(names: (keyof ScriptContext)[]): void; -// setEntrypoints(entrypoints: ScriptEntrypoints): void; -// // getSingleDep(name: T): EngineDeps[T]; - -// load(params: LoadParams): Promise; -// init(): Promise; -// run( -// script: string, -// compileParams: Partial, -// callback?: ScriptCallback -// ): Promise; -// askCompanion( -// cid: string, -// contentType: string, -// content: string, -// callback?: ScriptCallback -// ): Promise; -// personalProcessor( -// params: ScriptParticleParams -// ): Promise; -// executeFunction( -// script: string, -// funcName: string, -// params: EntrypointParams -// ): Promise; -// executeCallback(refId: string, data: any): Promise; -// } - // eslint-disable-next-line import/prefer-default-export function enigine() { let entrypoints: Partial = {}; @@ -124,13 +105,9 @@ function enigine() { const pushContext = ( name: K, - value: ScriptContext[K] | TabularKeyValues + value: ScriptContext[K] //| TabularKeyValues ) => { - if (name === 'secrets') { - context[name] = toRecord(value as TabularKeyValues); - return; - } - + // context[name] = toRecord(value as TabularKeyValues); context[name] = value; }; @@ -150,17 +127,9 @@ function enigine() { entrypoints$.next(entrypoints); }; - const defaultCompilerParams: CompilerParams = { - readOnly: false, - execute: true, - funcName: 'main', - funcParams: {}, - config: compileConfig, - }; - const run = async ( script: string, - compileParams: Partial, + compileParams: Partial, callback?: ScriptCallback ) => { const refId = uuidv4().toString(); @@ -171,22 +140,24 @@ function enigine() { refId, }; const compilerParams = { - ...defaultCompilerParams, + ...defaultRuneEntrypoint, ...compileParams, + input: script, + script: runtimeScript, + params: scriptParams, }; - const outputData = await compile( - script, - runtimeScript, - scriptParams, - compilerParams - ); + + // console.log('-----run', scriptParams); + const outputData = await compile(compilerParams, compileConfig); + + // Parse the JSON string const { result, error } = outputData; try { scriptCallbacks.delete(refId); return { - ...outputData, + ...entityToDto(outputData), error, result: result ? JSON.parse(removeBrokenUnicode(result)) @@ -296,7 +267,7 @@ function enigine() { if (resultType === 'error') { return { action: 'error', - metaItems: [{ type: 'text', text: 'No particle entrypoint' }], + metaItems: [[{ type: 'text', text: 'No particle entrypoint' }]], }; } @@ -317,7 +288,7 @@ function enigine() { console.error('---askCompanion error', output); return { action: 'error', - metaItems: [{ type: 'text', text: output.error }], + metaItems: [[{ type: 'text', text: output.error }]], }; } diff --git a/src/services/scripting/helpers.ts b/src/services/scripting/helpers.ts index cceed14c1..6e4eeecee 100644 --- a/src/services/scripting/helpers.ts +++ b/src/services/scripting/helpers.ts @@ -1,4 +1,5 @@ import { Nullable } from 'src/types'; +import { v4 as uuidv4 } from 'uuid'; export async function getScriptFromParticle(cid?: Nullable) { throw new Error('Not implemented'); @@ -50,3 +51,5 @@ export function extractRuneScript(markdown: string) { // if no rune tag, consider this like pure script return hasRune ? script : md; } + +export const generateRefId = () => uuidv4().toString(); diff --git a/src/services/scripting/rune/default/particle.rn b/src/services/scripting/rune/default/particle.rn index 1742d805f..c4f10173c 100644 --- a/src/services/scripting/rune/default/particle.rn +++ b/src/services/scripting/rune/default/particle.rn @@ -23,6 +23,7 @@ pub async fn moon_domain_resolver() { pub async fn ask_companion(cid, content_type, content) { // plain text item let links = [meta_text("similar: ")]; + let rows = [links]; // search closest 5 particles using local data from the brain let similar_results = cyb::search_by_embedding(content, 5).await; @@ -37,7 +38,24 @@ pub async fn ask_companion(cid, content_type, content) { links = [meta_text("no similar particles found")]; } - return content_result(links) + let secrets = cyb::context.secrets; + if let Some(api_key) = secrets.get("openAI_key") { + let messages = [ + #{ + "role": "system", + "content": "You should give description or summary of any content. aswer should not exceed 32 words" + }, + #{ + "role": "user", + "content": content + } + ]; + + let inference = cyb::open_ai_completions(messages, api_key, #{"model": "gpt-3.5-turbo"}).await; + rows.push([meta_text(`inference: ${inference}`)]); + } + + return content_result(rows) } // Transform content of the particle diff --git a/src/services/scripting/runeDeps.ts b/src/services/scripting/runeDeps.ts index 073cdc50d..2b667946c 100644 --- a/src/services/scripting/runeDeps.ts +++ b/src/services/scripting/runeDeps.ts @@ -15,7 +15,6 @@ import { RuneEngine } from './engine'; import DbApiWrapper from '../backend/services/DbApi/DbApi'; import { IpfsApi } from '../backend/workers/background/api/ipfsApi'; import { EmbeddingApi } from '../backend/workers/background/api/mlApi'; -import { Observable } from '@apollo/client'; export type RuneInnerDeps = { ipfsApi: Option; diff --git a/src/services/scripting/services/llmRequests/openai.ts b/src/services/scripting/services/llmRequests/openai.ts index 252275721..1e86491db 100644 --- a/src/services/scripting/services/llmRequests/openai.ts +++ b/src/services/scripting/services/llmRequests/openai.ts @@ -1,35 +1,76 @@ /* eslint-disable import/prefer-default-export */ /* eslint-disable import/no-unused-modules */ -import axios from 'axios'; +import axios, { ResponseType } from 'axios'; // https://platform.openai.com/docs/models/overview // gpt-3.5-turbo -// https://platform.openai.com/docs/api-reference/chat/create -export const promptToOpenAI = async ( - prompt: string, +type OpenAiMessage = { + role: 'system' | 'user' | 'assistant'; + content: string; +}; + +interface OpenAIParams { + model: string; + messages: OpenAiMessage[]; + [key: string]: any; +} + +const defaultOpenAIParams: Partial = { + model: 'gpt-3.5-turbo', +}; + +export const openAICompletion = async ( + messages: OpenAiMessage[], apiKey: string, - params: any = { - model: 'text-davinci-003', // 'gpt-3.5-turbo', - maxTokens: 500, - stop: '.', - n: 1, - } -) => { - //prompt: `Complete this sentence: "${input}"`, - const response = await axios.post( - 'https://api.openai.com/v1/completions', - { - prompt, + params: Partial = {} +): Promise> => { + const requestOptions = { + method: 'post', + url: 'https://api.openai.com/v1/chat/completions', + data: { + messages, + ...defaultOpenAIParams, ...params, }, - { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${apiKey}`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${apiKey}`, + }, + responseType: (params.stream ? 'stream' : 'json') as ResponseType, + }; + + const response = await axios(requestOptions); + + if (!params.stream) { + // Non-streaming request + console.log('response', response); + return response.data.choices[0].message.content; + } else { + // Streaming request + const asyncIterable: AsyncIterable = { + [Symbol.asyncIterator]: async function* () { + let result = ''; + for await (const chunk of response.data) { + const str = chunk.toString(); + const lines = str.split('\n').filter((line) => line.trim() !== ''); + for (const line of lines) { + const message = line.replace(/^data: /, ''); + if (message === '[DONE]') { + return; + } + try { + const parsed = JSON.parse(message); + result += parsed.choices[0].delta.content; + yield parsed.choices[0].delta.content; + } catch (error) { + console.error('Error parsing stream message:', message, error); + } + } + } }, - } - ); - console.log('response', response); - return response.data.choices[0].text; + }; + + return asyncIterable; + } }; diff --git a/src/services/scripting/types.ts b/src/services/scripting/types.ts index fe5f04411..8d44431d6 100644 --- a/src/services/scripting/types.ts +++ b/src/services/scripting/types.ts @@ -1,5 +1,6 @@ import { Citizenship } from 'src/types/citizenship'; import { KeyValueString, TabularKeyValues } from 'src/types/data'; +import { JsonRecord } from 'src/utils/localStorage'; type ParamsContext = { path?: string[]; @@ -17,7 +18,7 @@ export type UserContext = { type ScriptContext = { params: ParamsContext; user: UserContext; - secrets: TabularKeyValues; + secrets: JsonRecord; }; type EngineContext = Omit & { @@ -75,7 +76,7 @@ type MetaTextComponent = { type ScriptMyCampanion = { action: 'pass' | 'answer' | 'error'; - metaItems: (MetaLinkComponent | MetaTextComponent)[]; + metaItems: (MetaLinkComponent | MetaTextComponent)[][]; }; // type ScriptScopeParams = { diff --git a/src/services/scripting/wasmBindings.js b/src/services/scripting/wasmBindings.js index 0ece63fc5..94d034c83 100644 --- a/src/services/scripting/wasmBindings.js +++ b/src/services/scripting/wasmBindings.js @@ -1,7 +1,7 @@ /* eslint-disable import/no-unused-modules */ import { getFromLink, getToLink } from 'src/utils/search/utils'; import runeDeps from './runeDeps'; -import { promptToOpenAI } from './services/llmRequests/openai'; +import { openAICompletion } from './services/llmRequests/openai'; // let runeDeps; @@ -34,8 +34,8 @@ export async function jsAddContenToIpfs(content) { return runeDeps.addContenToIpfs(content); } -export async function jsPromptToOpenAI(prompt, apiKey, params) { - const result = await promptToOpenAI(prompt, apiKey, params); +export async function jsOpenAICompletions(messages, apiKey, params, refId) { + const result = await openAICompletion(messages, apiKey, params); return result; } diff --git a/src/utils/localStorage.ts b/src/utils/localStorage.ts index 50d63faa4..30d94da88 100644 --- a/src/utils/localStorage.ts +++ b/src/utils/localStorage.ts @@ -8,6 +8,8 @@ type StringTypeKeys = | `${ScriptEntrypointNames[0]}_enabled` | `${ScriptEntrypointNames[1]}_enabled`; +export type JsonRecord = Record; + const jsonKeyMap: Record = { secrets: 'secrets', }; @@ -25,13 +27,13 @@ const keyValuesToObject = (data: KeyValueString[]) => { ); }; -const saveJsonToLocalStorage = (storageKey: JsonTypeKeys, data: Object) => { +const saveJsonToLocalStorage = (storageKey: JsonTypeKeys, data: JsonRecord) => { localStorage.setItem(jsonKeyMap[storageKey], JSON.stringify(data)); }; const loadJsonFromLocalStorage = ( storageKey: JsonTypeKeys, - defaultData: Object + defaultData: JsonRecord = {} ) => { const raw = localStorage.getItem(jsonKeyMap[storageKey]); return raw ? JSON.parse(raw) : defaultData; diff --git a/yarn.lock b/yarn.lock index 767f28f45..84d6f9f6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10295,11 +10295,6 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.7.tgz#b14cebc75455eeeb160d5fe23c2fcc0c64f724d8" integrity sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g== -"@types/web-animations-js@^2.2.16": - version "2.2.16" - resolved "https://registry.yarnpkg.com/@types/web-animations-js/-/web-animations-js-2.2.16.tgz#fd84c49a7a2f148b588ba8b372496162b427742f" - integrity sha512-ATELeWMFwj8eQiH0KmvsCl1V2lu/qx/CjOBmv4ADSZS5u8r4reMyjCXtxG7khqyiwH3IOMNdrON/Ugn94OUcRA== - "@types/webpack@^5.28.1": version "5.28.1" resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-5.28.1.tgz#c369baeff31abe54b45f7f29997e1623604198d6" @@ -14597,10 +14592,10 @@ cyb-cozo-lib-wasm@^0.7.145: resolved "https://registry.yarnpkg.com/cyb-cozo-lib-wasm/-/cyb-cozo-lib-wasm-0.7.145.tgz#df82255d478415134d0a2cf82de1467c2009db92" integrity sha512-vCbiFuBPFOaMyS9kPutatBDekAqEafmsQ9jX34J3eMzQiBmoyujsm+z6jZiLwVWz2yDNtF/H17ofgexalHKjnw== -cyb-rune-wasm@^0.0.82: - version "0.0.82" - resolved "https://registry.yarnpkg.com/cyb-rune-wasm/-/cyb-rune-wasm-0.0.82.tgz#f5ecb2c3f0d9e55b6a44b542191d0e0de57a8b65" - integrity sha512-u+SIcFd09gEWwVlStGAimI465xkt7tBF5YtZ58nsQIs8OdWX3IXeHsnkmcJPGeP7ZVQDFY3SCwSwHM77F1p7Gw== +cyb-rune-wasm@^0.0.841: + version "0.0.841" + resolved "https://registry.yarnpkg.com/cyb-rune-wasm/-/cyb-rune-wasm-0.0.841.tgz#048402b9ff0fe0c82f9d03e1b90a60847279b6ac" + integrity sha512-+gLp8Hif/STVItVCeE3vNIKh9JJsXLh4dyZiU4V9QYBKmCdOsfc5ZkUFsOQmhvT9jORQx8oLQRVPmBMhptPORg== d3-array@1, d3-array@^1.1.1, d3-array@^1.2.0, d3-array@^1.2.1: version "1.2.4" @@ -27762,14 +27757,6 @@ react-dom@16.8.6: prop-types "^15.6.2" scheduler "^0.13.6" -react-dom@>=18.2.0: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" - integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== - dependencies: - loose-envify "^1.1.0" - scheduler "^0.23.2" - react-dom@^18.0.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" @@ -28151,13 +28138,6 @@ react@16.8.6: prop-types "^15.6.2" scheduler "^0.13.6" -react@>=18.2.0: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" - integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== - dependencies: - loose-envify "^1.1.0" - react@^18.0.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" @@ -29257,13 +29237,6 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" -scheduler@^0.23.2: - version "0.23.2" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" - integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== - dependencies: - loose-envify "^1.1.0" - schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" @@ -31873,22 +31846,6 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typeit-react@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/typeit-react/-/typeit-react-2.7.1.tgz#30a4b947ddd70320285a414426d711b7db77202f" - integrity sha512-40MB1PeED6FOh3yY4ZWhSIppmzqdnwBh7iTOtjgrYntVqs0Bit94w5D+eLqMIWKvRY9NX2+2xSYk6j2QkcTjTQ== - dependencies: - react ">=18.2.0" - react-dom ">=18.2.0" - typeit "^8.8.3" - -typeit@^8.8.3: - version "8.8.3" - resolved "https://registry.yarnpkg.com/typeit/-/typeit-8.8.3.tgz#1933700fb92f0c9d1f53fc9c2d927bb83004052f" - integrity sha512-K7nChkj6iyylUi713VBDULUXXLF0pfB6nFPVhNnXTKO2An7NzVz5fjoAHk2FAC3TFLiSnU+QsqhDmap17oBELw== - dependencies: - "@types/web-animations-js" "^2.2.16" - typescript@^5.0.2: version "5.0.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"