Skip to content

Commit 8df2bc7

Browse files
yomybabyragingwind
authored andcommitted
refactor: chat card state managment and naming convention
1 parent 47f0324 commit 8df2bc7

7 files changed

Lines changed: 208 additions & 247 deletions

File tree

react/src/components/Chat/AIAgentSelect.tsx

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
import { AIAgent } from '../../hooks/useAIAgent';
1+
import { AIAgent, useAIAgent } from '../../hooks/useAIAgent';
22
import Flex from '../Flex';
33
import { FluentEmojiIcon } from '../FluentEmojiIcon';
44
import { useControllableValue } from 'ahooks';
55
import { Select, SelectProps, theme } from 'antd';
66
import React, { useState, useTransition } from 'react';
77

8-
interface ChatAgentSelectProps extends Omit<SelectProps, 'options'> {
9-
agents: AIAgent[];
10-
selectedAgent?: AIAgent;
11-
}
8+
interface ChatAgentSelectProps extends Omit<SelectProps, 'options'> {}
129

1310
function makeAgentOptions(agents: AIAgent[], filter?: string) {
1411
return agents
@@ -24,21 +21,17 @@ function makeAgentOptions(agents: AIAgent[], filter?: string) {
2421

2522
const AIAgentSelect: React.FC<ChatAgentSelectProps> = ({
2623
loading,
27-
agents,
28-
selectedAgent,
2924
...props
3025
}) => {
3126
const { token } = theme.useToken();
32-
const [controllableValue, setControllableValue] =
33-
useControllableValue<AIAgent>(props, {
34-
valuePropName: 'value',
35-
trigger: 'onChange',
36-
defaultValue: props.value,
37-
});
27+
const [controllableValue, setControllableValue] = useControllableValue(props);
3828

3929
const [searchAgent, setSearchAgent] = useState<string>();
4030
const [isSearchPending, startSearchTransition] = useTransition();
4131

32+
const { agents } = useAIAgent();
33+
const selectedAgent = agents.find((agent) => agent.id === controllableValue);
34+
4235
return (
4336
<>
4437
{selectedAgent && (

react/src/components/Chat/ChatCard.tsx

Lines changed: 97 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useUpdatableState } from '../../hooks';
22
import { useSuspenseTanQuery } from '../../hooks/reactQueryAlias';
3-
import { AIAgent, useAIAgent } from '../../hooks/useAIAgent';
3+
import { useAIAgent } from '../../hooks/useAIAgent';
44
import PureChatHeader from './ChatHeader';
55
import PureChatInput from './ChatInput';
66
import ChatMessages from './ChatMessages';
@@ -12,7 +12,7 @@ import {
1212
Model,
1313
} from './ChatModel';
1414
import { CustomModelForm } from './CustomModelForm';
15-
import { ChatCard_endpoint$key } from './__generated__/ChatCard_endpoint.graphql';
15+
import { ChatCardQuery } from './__generated__/ChatCardQuery.graphql';
1616
import { createOpenAI } from '@ai-sdk/openai';
1717
import { useChat } from '@ai-sdk/react';
1818
import { extractReasoningMiddleware, streamText, wrapLanguageModel } from 'ai';
@@ -21,20 +21,21 @@ import { createStyles } from 'antd-style';
2121
import graphql from 'babel-plugin-relay/macro';
2222
import _ from 'lodash';
2323
import React, {
24-
startTransition,
2524
useEffect,
2625
useMemo,
2726
useRef,
2827
useState,
2928
useTransition,
3029
} from 'react';
31-
import { useFragment } from 'react-relay';
30+
import { useTranslation } from 'react-i18next';
31+
import { useLazyLoadQuery } from 'react-relay';
3232

3333
interface ChatCardProps extends CardProps, ChatLifecycleEventType {
3434
chat: ChatType;
35-
selectedEndpoint: ChatCard_endpoint$key | null;
35+
onUpdateChat?: (partialChat: DeepPartial<ChatType>) => void;
3636
closable?: boolean;
3737
fetchOnClient?: boolean;
38+
defaultEndpointId?: string;
3839
}
3940

4041
const useStyles = createStyles(({ token, css }) => ({
@@ -66,23 +67,6 @@ const useStyles = createStyles(({ token, css }) => ({
6667
`,
6768
}));
6869

69-
function useEndpoint(selectedEndpoint?: ChatCard_endpoint$key | null) {
70-
const [endpointKey, setEndpoint] = useState<ChatCard_endpoint$key | null>(
71-
selectedEndpoint || null,
72-
);
73-
const endpoint = useFragment(
74-
graphql`
75-
fragment ChatCard_endpoint on Endpoint {
76-
endpoint_id
77-
url
78-
}
79-
`,
80-
endpointKey,
81-
);
82-
83-
return { endpoint, setEndpoint } as const;
84-
}
85-
8670
function createModelsURL(baseURL: string) {
8771
const { origin, port, pathname: path } = new URL(baseURL.trim());
8872
const host = port.length > 0 ? `${origin}:${port}` : origin;
@@ -95,7 +79,6 @@ function useModels(
9579
provider: ChatProviderType,
9680
fetchKey: string,
9781
baseURL?: string,
98-
token?: string,
9982
) {
10083
const { t } = useTranslation();
10184
const getModelsErrorMessage = (status?: number) => {
@@ -116,17 +99,11 @@ function useModels(
11699
const { data: modelsResult } = useSuspenseTanQuery<{
117100
data: Array<Model>;
118101
}>({
119-
queryKey: ['models', fetchKey, baseURL, token],
102+
queryKey: ['models', fetchKey, baseURL, provider.apiKey],
120103
queryFn: async () => {
121-
try {
122-
if (baseURL) {
123-
const url = createModelsURL(baseURL);
124-
const authToken = token || provider.apiKey;
125-
const res = await fetch(url, {
126-
headers: {
127-
Authorization: authToken ? `Bearer ${authToken}` : '',
128-
},
129-
});
104+
if (!baseURL) {
105+
return { data: [] };
106+
}
130107

131108
const url = createModelsURL(baseURL);
132109
const authToken = provider.apiKey;
@@ -149,7 +126,7 @@ function useModels(
149126
name: m.id,
150127
})) as BAIModel[];
151128

152-
const selectedModelId = useMemo(
129+
const modelId = useMemo(
153130
() =>
154131
provider.modelId &&
155132
_.includes(_.map(modelsResult?.data || [], 'id'), provider.modelId)
@@ -158,39 +135,17 @@ function useModels(
158135
[modelsResult?.data, provider.modelId],
159136
);
160137

161-
const [modelId, setModelId] = useState<string>(selectedModelId);
138+
const modelsError =
139+
modelsResult.error && getModelsErrorMessage(modelsResult.error);
162140

163-
useEffect(() => {
164-
setModelId(selectedModelId);
165-
}, [selectedModelId]);
166-
167-
return { models, modelId, setModelId } as const;
141+
return {
142+
models,
143+
modelId,
144+
modelsError,
145+
} as const;
168146
}
169147

170-
function useAgents(provider: ChatProviderType) {
171-
const { agents } = useAIAgent();
172-
const [agent, setAgent] = useState<AIAgent | undefined>(undefined);
173-
174-
const selectedAgent = useMemo(() => {
175-
return agents.find((a) => a.id === provider.agentId);
176-
}, [agents, provider.agentId]);
177-
178-
useEffect(() => {
179-
setAgent(selectedAgent);
180-
}, [selectedAgent]);
181-
182-
return { agents, agent, setAgent } as const;
183-
}
184-
185-
const ChatHeader = React.memo(PureChatHeader, (prev, next) => {
186-
if (prev.modelId !== next.modelId) return false;
187-
if (prev.endpoint !== next.endpoint) return false;
188-
if (prev.agent !== next.agent) return false;
189-
if (prev.fetchKey !== next.fetchKey) return false;
190-
if (prev.sync !== next.sync) return false;
191-
if (prev.closable !== next.closable) return false;
192-
return true;
193-
});
148+
const ChatHeader = PureChatHeader;
194149

195150
const ChatInput = React.memo(PureChatInput);
196151

@@ -200,12 +155,32 @@ function createBaseURL(basePath: string, endpointUrl?: string | null) {
200155

201156
const ChatCard: React.FC<ChatCardProps> = ({
202157
chat,
203-
selectedEndpoint,
158+
onUpdateChat,
204159
closable,
205160
fetchOnClient,
206161
onRequestClose,
207162
onCreateNewChat,
208163
}) => {
164+
const endpointResult = useLazyLoadQuery<ChatCardQuery>(
165+
graphql`
166+
query ChatCardQuery($endpointId: UUID!) {
167+
endpoint(endpoint_id: $endpointId) @catch {
168+
endpoint_id
169+
url
170+
...ChatHeader_Endpoint
171+
}
172+
}
173+
`,
174+
{
175+
endpointId: chat.provider.endpointId || '',
176+
},
177+
{
178+
fetchPolicy: chat.provider.endpointId ? 'store-or-network' : 'store-only',
179+
},
180+
);
181+
const endpoint = endpointResult.endpoint.ok
182+
? endpointResult.endpoint.value
183+
: null;
209184
const {
210185
styles: { chatCard: chatCardStyle, alert: alertStyle, ...chatCardStyles },
211186
} = useStyles();
@@ -215,19 +190,14 @@ const ChatCard: React.FC<ChatCardProps> = ({
215190
const [fetchKey, updateFetchKey] = useUpdatableState('first');
216191
const [startTime, setStartTime] = useState<number | null>(null);
217192

218-
const { endpoint, setEndpoint } = useEndpoint(selectedEndpoint);
219-
const [baseURL, setBaseURL] = useState<string | undefined>(
220-
createBaseURL(chat.provider.basePath, endpoint?.url),
221-
);
222-
const [token, setToken] = useState<string | undefined>();
223-
const { models, modelId, setModelId } = useModels(
193+
const baseURL = createBaseURL(chat.provider.basePath, endpoint?.url);
194+
const { models, modelId, modelsError } = useModels(
224195
chat.provider,
225196
fetchKey,
226197
baseURL,
227-
token,
228198
);
229-
const { agents, agent, setAgent } = useAgents(chat.provider);
230-
const [sync, setSync] = useState(chat.sync);
199+
const { agents } = useAIAgent();
200+
const agent = agents.find((a) => a.id === chat.provider.agentId);
231201

232202
const {
233203
error,
@@ -251,7 +221,7 @@ const ChatCard: React.FC<ChatCardProps> = ({
251221
const body = JSON.parse(init?.body as string);
252222
const provider = createOpenAI({
253223
baseURL: baseURL,
254-
apiKey: token || chat.provider.apiKey || 'dummy',
224+
apiKey: chat.provider.apiKey || 'dummy',
255225
});
256226
const result = streamText({
257227
abortSignal: init?.signal || undefined,
@@ -274,14 +244,6 @@ const ChatCard: React.FC<ChatCardProps> = ({
274244
},
275245
});
276246

277-
useEffect(() => {
278-
startTransition(() => {
279-
setBaseURL(createBaseURL(chat.provider.basePath, endpoint?.url));
280-
setToken(undefined);
281-
updateFetchKey('first');
282-
});
283-
}, [endpoint?.url, chat.provider.basePath, updateFetchKey]);
284-
285247
const isStreaming = status === 'streaming' || status === 'submitted';
286248

287249
return (
@@ -291,37 +253,74 @@ const ChatCard: React.FC<ChatCardProps> = ({
291253
classNames={chatCardStyles}
292254
title={
293255
<ChatHeader
294-
chat={chat}
256+
// model
295257
models={models}
296258
modelId={modelId}
297-
setModelId={setModelId}
298-
endpoint={endpoint}
299-
setEndpoint={setEndpoint}
259+
onChangeModel={(modelId) => {
260+
onUpdateChat?.({
261+
provider: {
262+
modelId,
263+
},
264+
});
265+
}}
266+
// agent
300267
agents={agents}
301268
agent={agent}
302-
setAgent={setAgent}
303-
sync={sync}
304-
setSync={setSync}
269+
onChangeAgent={(agent) => {
270+
onUpdateChat?.({
271+
provider: {
272+
agentId: agent.id,
273+
endpointId: agent.endpoint_id,
274+
},
275+
});
276+
}}
277+
// endpoint
278+
endpointFrgmt={endpoint}
279+
onChangeEndpoint={(endpointId) => {
280+
onUpdateChat?.({
281+
provider: {
282+
endpointId,
283+
},
284+
});
285+
}}
286+
// sync
287+
sync={chat.sync}
288+
onChangeSync={(sync) => {
289+
onUpdateChat?.({
290+
sync,
291+
});
292+
}}
293+
// others
305294
fetchKey={fetchKey}
306295
closable={closable}
307-
onCreateNewChat={onCreateNewChat}
308-
onRequestClose={onRequestClose}
309-
setMessages={setMessages}
296+
onClickCreate={onCreateNewChat}
297+
onClickClose={() => {
298+
onRequestClose?.(chat);
299+
}}
300+
onClickDeleteChatHistory={() => {
301+
setMessages([]);
302+
}}
310303
/>
311304
}
312305
ref={dropContainerRef}
313306
>
314-
{_.isEmpty(models) && (
307+
{endpoint && _.isEmpty(models) && (
315308
<CustomModelForm
316309
baseURL={baseURL}
317-
token={token}
310+
token={chat.provider.apiKey}
318311
endpointId={endpoint?.endpoint_id}
319312
loading={isPendingUpdate}
320313
onSubmit={(data) => {
321314
startUpdateTransition(() => {
322315
updateFetchKey();
323-
setBaseURL(data.baseURL);
324-
setToken(data.token);
316+
onUpdateChat?.({
317+
...chat,
318+
provider: {
319+
...chat.provider,
320+
baseURL: data.baseURL,
321+
apiKey: data.token,
322+
},
323+
});
325324
});
326325
}}
327326
/>
@@ -342,7 +341,7 @@ const ChatCard: React.FC<ChatCardProps> = ({
342341
startTime={startTime}
343342
/>
344343
<ChatInput
345-
sync={sync}
344+
sync={chat.sync}
346345
input={input}
347346
setInput={setInput}
348347
stop={stop}

0 commit comments

Comments
 (0)