Skip to content

Commit

Permalink
✨ feat: 初步完成插件市场动态加载全链路
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx committed Aug 22, 2023
1 parent 8009691 commit bc5e40f
Show file tree
Hide file tree
Showing 20 changed files with 325 additions and 98 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ config.rules['unicorn/no-typeof-undefined'] = 0;
config.rules['unicorn/explicit-length-check'] = 0;
config.rules['unicorn/prefer-code-point'] = 0;
config.rules['no-extra-boolean-cast'] = 0;
config.rules['unicorn/no-useless-undefined'] = 0;

module.exports = config;
22 changes: 14 additions & 8 deletions src/features/AgentSetting/AgentPlugin/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Avatar, Form, ItemGroup } from '@lobehub/ui';
import { useWhyDidYouUpdate } from 'ahooks';
import { Skeleton, Switch } from 'antd';
import { createStyles } from 'antd-style';
import isEqual from 'fast-deep-equal';
import { ToyBrick } from 'lucide-react';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';

import { FORM_STYLE } from '@/const/layoutTokens';
import { useSessionStore } from '@/store/session';
import { AgentAction } from '@/store/session/slices/agentConfig';
import { PluginAction } from '@/store/session/slices/plugin';
import { LobeAgentConfig } from '@/types/session';

const useStyles = createStyles(({ css }) => ({
Expand All @@ -21,6 +21,7 @@ const useStyles = createStyles(({ css }) => ({
li {
height: 14px !important;
}
li + li {
margin-block-start: 12px !important;
}
Expand All @@ -29,17 +30,16 @@ const useStyles = createStyles(({ css }) => ({

export interface AgentPluginProps {
config: LobeAgentConfig;
updateConfig: AgentAction['toggleAgentPlugin'];
updateConfig: PluginAction['toggleAgentPlugin'];
}

const AgentPlugin = memo<AgentPluginProps>(({ config, updateConfig }) => {
const { t } = useTranslation('setting');
const { styles } = useStyles();
const useFetchPluginList = useSessionStore((s) => s.useFetchPluginList);
const pluginManifestLoading = useSessionStore((s) => s.pluginManifestLoading, isEqual);
const { data } = useFetchPluginList();

useWhyDidYouUpdate('AgentPlugin', { config, updateConfig });

const plugin: ItemGroup = useMemo(() => {
let children;

Expand Down Expand Up @@ -70,8 +70,14 @@ const AgentPlugin = memo<AgentPluginProps>(({ config, updateConfig }) => {
avatar: <Avatar avatar={item.meta.avatar} />,
children: (
<Switch
checked={!config.plugins ? false : config.plugins.includes(item.name)}
onChange={() => updateConfig(item.name)}
checked={
// 如果在加载中,说明激活了
pluginManifestLoading[item.name] || !config.plugins
? false
: config.plugins.includes(item.name)
}
loading={pluginManifestLoading[item.name]}
onChange={(checked) => updateConfig(item.name, checked)}
/>
),
desc: item.meta?.description,
Expand All @@ -85,7 +91,7 @@ const AgentPlugin = memo<AgentPluginProps>(({ config, updateConfig }) => {
icon: ToyBrick,
title: t('settingPlugin.title'),
};
}, [config, data?.plugins]);
}, [config, data?.plugins, pluginManifestLoading]);

return <Form items={[plugin]} {...FORM_STYLE} />;
});
Expand Down
6 changes: 5 additions & 1 deletion src/layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { PropsWithChildren, memo, useCallback, useEffect } from 'react';

import { createI18nNext } from '@/locales/create';
import { useGlobalStore, useOnFinishHydrationGlobal } from '@/store/global';
import { useSessionStore } from '@/store/session';
import { useOnFinishHydrationSession, useSessionStore } from '@/store/session';
import { GlobalStyle } from '@/styles';

import { useStyles } from './style';
Expand All @@ -24,6 +24,10 @@ const Layout = memo<PropsWithChildren>(({ children }) => {
});
});

useOnFinishHydrationSession((s) => {
s.checkLocalEnabledPlugins();
});

return (
<ConfigProvider locale={Zh_CN}>
<App className={styles.bg}>{children}</App>
Expand Down
6 changes: 4 additions & 2 deletions src/pages/chat/[id]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { useOnFinishHydrationSession, useSessionStore } from '@/store/session';
import ChatLayout from '../layout';

const Layout = memo<PropsWithChildren>(({ children }) => {
const [activeSession, toggleTopic] = useSessionStore((s) => {
return [s.activeSession, s.toggleTopic];
const [activeSession, toggleTopic, useFetchPluginList] = useSessionStore((s) => {
return [s.activeSession, s.toggleTopic, s.useFetchPluginList];
}, shallow);

const router = useRouter();
Expand All @@ -24,6 +24,8 @@ const Layout = memo<PropsWithChildren>(({ children }) => {
toggleTopic();
}, [id]);

useFetchPluginList();

return <ChatLayout>{children}</ChatLayout>;
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,50 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';

import { PluginsRender } from '@/plugins/Render';
import { useSessionStore } from '@/store/session';
import { ChatMessage } from '@/types/chatMessage';

import CustomRender from './CustomRender';

export interface FunctionMessageProps extends ChatMessage {
loading?: boolean;
}

const PluginMessage = memo<FunctionMessageProps>(({ content, name }) => {
const { t } = useTranslation('plugin');

const Render = PluginsRender[name || ''];

let isJSON = true;
try {
JSON.parse(content);
} catch {
isJSON = false;
}
const PluginMessage = memo<FunctionMessageProps>(
({
content,
// loading,
name,
}) => {
const { t } = useTranslation('plugin');

const manifest = useSessionStore((s) => s.pluginManifestMap[name || '']);
let isJSON = true;
try {
JSON.parse(content);
} catch {
isJSON = false;
}

// if (!loading)

if (!isJSON) {
return (
<Flexbox gap={8} horizontal>
<div>
<Loading3QuartersOutlined spin />
</div>
{t('loading.content')}
</Flexbox>
);
}

if (!manifest.ui?.url) return;

if (!isJSON) {
return (
<Flexbox gap={8} horizontal>
<div>
<Loading3QuartersOutlined spin />
</div>
{t('loading.content')}
</Flexbox>
<CustomRender content={JSON.parse(content)} name={name || 'unknown'} url={manifest.ui?.url} />
);
}

if (Render) {
return <Render content={JSON.parse(content)} name={name || 'unknown'} />;
}

return null;
});
},
);

export default PluginMessage;
13 changes: 4 additions & 9 deletions src/services/chatModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { merge } from 'lodash-es';
import { ChatCompletionFunctions } from 'openai-edge/types/api';

import { LOBE_CHAT_ACCESS_CODE, OPENAI_API_KEY_HEADER_KEY, OPENAI_END_POINT } from '@/const/fetch';
import { PluginsMap } from '@/plugins';
import { useGlobalStore } from '@/store/global';
import { pluginSelectors, useSessionStore } from '@/store/session';
import { initialLobeAgentConfig } from '@/store/session/initialState';
import type { OpenAIStreamPayload } from '@/types/openai';

Expand All @@ -30,15 +30,10 @@ export const fetchChatModel = (
);
// ============ 1. 前置处理 functions ============ //

const filterFunctions: ChatCompletionFunctions[] = Object.values(PluginsMap)
.filter((p) => {
// 如果不存在 enabledPlugins,那么全部不启用
if (!enabledPlugins) return false;
const filterFunctions: ChatCompletionFunctions[] = pluginSelectors.enabledSchema(enabledPlugins)(
useSessionStore.getState(),
);

// 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
return enabledPlugins.includes(p.name);
})
.map((f) => f.schema);
const functions = filterFunctions.length === 0 ? undefined : filterFunctions;

return fetch(URLS.openai, {
Expand Down
12 changes: 11 additions & 1 deletion src/services/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';

import { LOBE_CHAT_ACCESS_CODE } from '@/const/fetch';
import { PLUGINS_INDEX_URL } from '@/const/url';
import { useGlobalStore } from '@/store/global';

import { URLS } from './url';
Expand All @@ -11,7 +12,7 @@ interface FetchChatModelOptions {
}

/**
* 专门用于对话的 fetch
* 请求插件结果
*/
export const fetchPlugin = async (
params: PluginRequestPayload,
Expand All @@ -29,3 +30,12 @@ export const fetchPlugin = async (

return await res.text();
};

/**
* 请求插件列表
*/
export const getPluginList = async () => {
const res = await fetch(PLUGINS_INDEX_URL);

return res.json();
};
9 changes: 6 additions & 3 deletions src/store/session/hooks/useOnFinishHydrationSession.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { useEffect } from 'react';

import { useSessionStore } from '../store';
import { SessionStore, useSessionStore } from '../store';

/**
* 当 Session 水合完毕后才会执行的 useEffect
* @param fn
* @param deps
*/
export const useOnFinishHydrationSession = (fn: () => void, deps: any[] = []) => {
export const useOnFinishHydrationSession = (
fn: (state: SessionStore) => void,
deps: any[] = [],
) => {
useEffect(() => {
const hasRehydrated = useSessionStore.persist.hasHydrated();
// 只有当水合完毕后再开始做操作
if (!hasRehydrated) return;

fn();
fn(useSessionStore.getState());
}, deps);
};
5 changes: 3 additions & 2 deletions src/store/session/hooks/useSessionHydrated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { useSessionStore } from '../store';
export const useSessionHydrated = () => {
// 根据 sessions 是否有值来判断是否已经初始化
const hasInited = !!Object.values(useSessionStore.getState().sessions).length;

// 并且插件的 manifest 也准备完毕
const manifestPrepared = useSessionStore((s) => s.manifestPrepared);
const [isInit, setInit] = useState(hasInited);

useEffect(() => {
Expand All @@ -16,5 +17,5 @@ export const useSessionHydrated = () => {
}
}, []);

return isInit;
return isInit && manifestPrepared;
};
4 changes: 3 additions & 1 deletion src/store/session/initialState.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { AgentConfigState, initialAgentConfigState } from './slices/agentConfig/initialState';
import { ChatState, initialChatState } from './slices/chat/initialState';
import { PluginState, initialPluginState } from './slices/plugin/initialState';
import { SessionState, initialSessionState } from './slices/session/initialState';

export type SessionStoreState = SessionState & ChatState & AgentConfigState;
export type SessionStoreState = SessionState & ChatState & AgentConfigState & PluginState;

export const initialState: SessionStoreState = {
...initialSessionState,
...initialChatState,
...initialAgentConfigState,
...initialPluginState,
};

export { initialLobeAgentConfig } from './slices/agentConfig';
Expand Down
1 change: 1 addition & 0 deletions src/store/session/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { agentSelectors } from './slices/agentConfig/selectors';
export { chatSelectors, topicSelectors } from './slices/chat/selectors';
export { pluginSelectors } from './slices/plugin/selectors';
export { sessionSelectors } from './slices/session/selectors';
35 changes: 0 additions & 35 deletions src/store/session/slices/agentConfig/action.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { LobeChatPluginsMarketIndex } from '@lobehub/chat-plugin-sdk';
import { produce } from 'immer';
import useSWR, { SWRResponse } from 'swr';
import { StateCreator } from 'zustand/vanilla';

import { promptPickEmoji, promptSummaryAgentName, promptSummaryDescription } from '@/prompts/agent';
import { getPluginList } from '@/services/plugin';
import { MetaData } from '@/types/meta';
import { LobeAgentConfig } from '@/types/session';
import { fetchPresetTaskResult } from '@/utils/fetch';
Expand Down Expand Up @@ -51,8 +47,6 @@ export interface AgentAction {
* @returns 任意类型的返回值
*/
internalUpdateAgentMeta: (id: string) => any;
toggleAgentPlugin: (pluginId: string) => void;

/**
* 更新代理配置
* @param config - 部分 LobeAgentConfig 的配置
Expand All @@ -65,7 +59,6 @@ export interface AgentAction {
* @param value - 加载状态的值
*/
updateLoadingState: (key: keyof SessionLoadingState, value: boolean) => void;
useFetchPluginList: () => SWRResponse<LobeChatPluginsMarketIndex>;
}

export const createAgentSlice: StateCreator<
Expand Down Expand Up @@ -196,35 +189,13 @@ export const createAgentSlice: StateCreator<
};
},

toggleAgentPlugin: (id: string) => {
const { activeId } = get();
const session = sessionSelectors.currentSession(get());
if (!activeId || !session) return;

const config = produce(session.config, (draft) => {
if (draft.plugins === undefined) {
draft.plugins = [id];
} else {
const plugins = draft.plugins;
if (plugins.includes(id)) {
plugins.splice(plugins.indexOf(id), 1);
} else {
plugins.push(id);
}
}
});

get().dispatchSession({ config, id: activeId, type: 'updateSessionConfig' });
},

updateAgentConfig: (config) => {
const { activeId } = get();
const session = sessionSelectors.currentSession(get());
if (!activeId || !session) return;

get().dispatchSession({ config, id: activeId, type: 'updateSessionConfig' });
},

updateAgentMeta: (meta) => {
const { activeId } = get();
const session = sessionSelectors.currentSession(get());
Expand All @@ -248,10 +219,4 @@ export const createAgentSlice: StateCreator<
t('updateLoadingState', { key, value }),
);
},
useFetchPluginList: () =>
useSWR<LobeChatPluginsMarketIndex>('fetchPluginList', getPluginList, {
onSuccess: (pluginMarketIndex) => {
set({ pluginList: pluginMarketIndex.plugins });
},
}),
});
Loading

0 comments on commit bc5e40f

Please sign in to comment.