b7f52be4c9
CI / Run CI (push) Has been cancelled
CI / check-backend (push) Has been cancelled
CI / check-frontend (push) Has been cancelled
CI / tests (push) Has been cancelled
CI / e2e-tests (push) Has been cancelled
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
279 lines
6.2 KiB
TypeScript
279 lines
6.2 KiB
TypeScript
import { isEqual } from 'lodash';
|
|
import { AtomEffect, DefaultValue, atom, selector } from 'recoil';
|
|
import { Socket } from 'socket.io-client';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
import { ICommand } from './types/command';
|
|
import { IMode } from './types/mode';
|
|
|
|
import {
|
|
IAction,
|
|
IAsk,
|
|
IAuthConfig,
|
|
ICallFn,
|
|
IChainlitConfig,
|
|
IMcp,
|
|
IMessageElement,
|
|
IStep,
|
|
ITasklistElement,
|
|
IUser,
|
|
ThreadHistory
|
|
} from './types';
|
|
import { groupByDate } from './utils/group';
|
|
import { WavRecorder, WavStreamPlayer } from './wavtools';
|
|
|
|
export interface ISession {
|
|
socket: Socket;
|
|
error?: boolean;
|
|
}
|
|
|
|
export const threadIdToResumeState = atom<string | undefined>({
|
|
key: 'ThreadIdToResume',
|
|
default: undefined
|
|
});
|
|
|
|
export const resumeThreadErrorState = atom<string | undefined>({
|
|
key: 'ResumeThreadErrorState',
|
|
default: undefined
|
|
});
|
|
|
|
export const chatProfileState = atom<string | undefined>({
|
|
key: 'ChatProfile',
|
|
default: undefined
|
|
});
|
|
|
|
const sessionIdAtom = atom<string>({
|
|
key: 'SessionId',
|
|
default: uuidv4()
|
|
});
|
|
|
|
export const sessionIdState = selector({
|
|
key: 'SessionIdSelector',
|
|
get: ({ get }) => get(sessionIdAtom),
|
|
set: ({ set }, newValue) =>
|
|
set(sessionIdAtom, newValue instanceof DefaultValue ? uuidv4() : newValue)
|
|
});
|
|
|
|
export const sessionState = atom<ISession | undefined>({
|
|
key: 'Session',
|
|
dangerouslyAllowMutability: true,
|
|
default: undefined
|
|
});
|
|
|
|
export const actionState = atom<IAction[]>({
|
|
key: 'Actions',
|
|
default: []
|
|
});
|
|
|
|
export const messagesState = atom<IStep[]>({
|
|
key: 'Messages',
|
|
dangerouslyAllowMutability: true,
|
|
default: []
|
|
});
|
|
|
|
export const commandsState = atom<ICommand[]>({
|
|
key: 'Commands',
|
|
default: []
|
|
});
|
|
|
|
export const modesState = atom<IMode[]>({
|
|
key: 'Modes',
|
|
default: []
|
|
});
|
|
|
|
export const tokenCountState = atom<number>({
|
|
key: 'TokenCount',
|
|
default: 0
|
|
});
|
|
|
|
export const loadingState = atom<boolean>({
|
|
key: 'Loading',
|
|
default: false
|
|
});
|
|
|
|
export const askUserState = atom<IAsk | undefined>({
|
|
key: 'AskUser',
|
|
default: undefined
|
|
});
|
|
|
|
export const wavRecorderState = atom({
|
|
key: 'WavRecorder',
|
|
dangerouslyAllowMutability: true,
|
|
default: new WavRecorder()
|
|
});
|
|
|
|
export const wavStreamPlayerState = atom({
|
|
key: 'WavStreamPlayer',
|
|
dangerouslyAllowMutability: true,
|
|
default: new WavStreamPlayer()
|
|
});
|
|
|
|
export const audioConnectionState = atom<'connecting' | 'on' | 'off'>({
|
|
key: 'AudioConnection',
|
|
default: 'off'
|
|
});
|
|
|
|
export const isAiSpeakingState = atom({
|
|
key: 'isAiSpeaking',
|
|
default: false
|
|
});
|
|
|
|
export const callFnState = atom<ICallFn | undefined>({
|
|
key: 'CallFn',
|
|
default: undefined
|
|
});
|
|
|
|
export const chatSettingsInputsState = atom<any>({
|
|
key: 'ChatSettings',
|
|
default: []
|
|
});
|
|
|
|
export const chatSettingsDefaultValueSelector = selector({
|
|
key: 'ChatSettingsValue/Default',
|
|
get: ({ get }) => {
|
|
const chatSettings = get(chatSettingsInputsState);
|
|
|
|
const collectInitialValues = (
|
|
inputs: any[],
|
|
acc: Record<string, any>
|
|
): Record<string, any> => {
|
|
if (!Array.isArray(inputs)) {
|
|
return acc;
|
|
}
|
|
|
|
inputs.forEach((input) => {
|
|
if (!input) {
|
|
return;
|
|
}
|
|
if (Array.isArray(input?.inputs) && input.inputs.length > 0) {
|
|
// Handle tabs
|
|
collectInitialValues(input.inputs, acc);
|
|
} else if (input?.id !== undefined) {
|
|
acc[input.id] = input.initial;
|
|
}
|
|
});
|
|
|
|
return acc;
|
|
};
|
|
|
|
return collectInitialValues(chatSettings, {});
|
|
}
|
|
});
|
|
|
|
export const chatSettingsValueState = atom<Record<string, any>>({
|
|
key: 'ChatSettingsValue',
|
|
default: chatSettingsDefaultValueSelector
|
|
});
|
|
|
|
export const elementState = atom<IMessageElement[]>({
|
|
key: 'DisplayElements',
|
|
default: []
|
|
});
|
|
|
|
export const tasklistState = atom<ITasklistElement[]>({
|
|
key: 'TasklistElements',
|
|
default: []
|
|
});
|
|
|
|
export const firstUserInteraction = atom<string | undefined>({
|
|
key: 'FirstUserInteraction',
|
|
default: undefined
|
|
});
|
|
|
|
export const userState = atom<IUser | undefined | null>({
|
|
key: 'User',
|
|
default: undefined
|
|
});
|
|
|
|
export const configState = atom<IChainlitConfig | undefined>({
|
|
key: 'ChainlitConfig',
|
|
default: undefined
|
|
});
|
|
|
|
export const authState = atom<IAuthConfig | undefined>({
|
|
key: 'AuthConfig',
|
|
default: undefined
|
|
});
|
|
|
|
export const threadHistoryState = atom<ThreadHistory | undefined>({
|
|
key: 'ThreadHistory',
|
|
default: {
|
|
threads: undefined,
|
|
currentThreadId: undefined,
|
|
timeGroupedThreads: undefined,
|
|
pageInfo: undefined
|
|
},
|
|
effects: [
|
|
({ setSelf, onSet }: { setSelf: any; onSet: any }) => {
|
|
onSet(
|
|
(
|
|
newValue: ThreadHistory | undefined,
|
|
oldValue: ThreadHistory | undefined
|
|
) => {
|
|
let timeGroupedThreads = newValue?.timeGroupedThreads;
|
|
if (
|
|
newValue?.threads &&
|
|
!isEqual(newValue.threads, oldValue?.timeGroupedThreads)
|
|
) {
|
|
timeGroupedThreads = groupByDate(newValue.threads);
|
|
}
|
|
|
|
setSelf({
|
|
...newValue,
|
|
timeGroupedThreads
|
|
});
|
|
}
|
|
);
|
|
}
|
|
]
|
|
});
|
|
|
|
export const sideViewState = atom<
|
|
{ title: string; elements: IMessageElement[]; key?: string } | undefined
|
|
>({
|
|
key: 'SideView',
|
|
default: undefined
|
|
});
|
|
|
|
export const currentThreadIdState = atom<string | undefined>({
|
|
key: 'CurrentThreadId',
|
|
default: undefined
|
|
});
|
|
|
|
const localStorageEffect =
|
|
<T>(key: string): AtomEffect<T> =>
|
|
({ setSelf, onSet }) => {
|
|
// When the atom is first initialized, try to get its value from localStorage
|
|
const savedValue = localStorage.getItem(key);
|
|
if (savedValue != null) {
|
|
try {
|
|
setSelf(JSON.parse(savedValue));
|
|
} catch (error) {
|
|
console.error(
|
|
`Error parsing localStorage value for key "${key}":`,
|
|
error
|
|
);
|
|
}
|
|
}
|
|
|
|
// Subscribe to state changes and update localStorage
|
|
onSet((newValue, _, isReset) => {
|
|
if (isReset) {
|
|
localStorage.removeItem(key);
|
|
} else {
|
|
localStorage.setItem(key, JSON.stringify(newValue));
|
|
}
|
|
});
|
|
};
|
|
|
|
export const mcpState = atom<IMcp[]>({
|
|
key: 'Mcp',
|
|
default: [],
|
|
effects: [localStorageEffect<IMcp[]>('mcp_storage_key')]
|
|
});
|
|
|
|
export const favoriteMessagesState = atom<IStep[]>({
|
|
key: 'favoriteMessagesState',
|
|
default: []
|
|
});
|