Files
chainlit--chainlit/libs/react-client/src/state.ts
T
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:48:47 +08:00

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: []
});