ソースを参照

Feat: Add model editing functionality with improved UI labels (#8855)

### What problem does this PR solve?

Add edit button for local LLM models
<img width="1531" height="1428" alt="image"
src="https://github.com/user-attachments/assets/19d62255-59a6-4a7e-9772-8b8743101f78"
/>

<img width="1531" height="1428" alt="image"
src="https://github.com/user-attachments/assets/c3a0f77e-cc6b-4190-95a6-13835463428b"
/>



### Type of change

- [ ] Bug Fix (non-breaking change which fixes an issue)
- [x] New Feature (non-breaking change which adds functionality)
- [ ] Documentation Update
- [ ] Refactoring
- [ ] Performance Improvement
- [ ] Other (please describe):

---------

Co-authored-by: Liu An <asiro@qq.com>
tags/v0.20.0
Adrian Altermatt 3ヶ月前
コミット
6691532079
コミッターのメールアドレスに関連付けられたアカウントが存在しません

+ 44
- 12
api/apps/llm_app.py ファイルの表示

@login_required @login_required
def my_llms(): def my_llms():
try: try:
res = {}
for o in TenantLLMService.get_my_llms(current_user.id):
if o["llm_factory"] not in res:
res[o["llm_factory"]] = {
"tags": o["tags"],
"llm": []
}
res[o["llm_factory"]]["llm"].append({
"type": o["model_type"],
"name": o["llm_name"],
"used_token": o["used_tokens"]
})
include_details = request.args.get('include_details', 'false').lower() == 'true'
if include_details:
res = {}
objs = TenantLLMService.query(tenant_id=current_user.id)
factories = LLMFactoriesService.query(status=StatusEnum.VALID.value)
for o in objs:
o_dict = o.to_dict()
factory_tags = None
for f in factories:
if f.name == o_dict["llm_factory"]:
factory_tags = f.tags
break
if o_dict["llm_factory"] not in res:
res[o_dict["llm_factory"]] = {
"tags": factory_tags,
"llm": []
}
res[o_dict["llm_factory"]]["llm"].append({
"type": o_dict["model_type"],
"name": o_dict["llm_name"],
"used_token": o_dict["used_tokens"],
"api_base": o_dict["api_base"] or "",
"max_tokens": o_dict["max_tokens"] or 8192
})
else:
res = {}
for o in TenantLLMService.get_my_llms(current_user.id):
if o["llm_factory"] not in res:
res[o["llm_factory"]] = {
"tags": o["tags"],
"llm": []
}
res[o["llm_factory"]]["llm"].append({
"type": o["model_type"],
"name": o["llm_name"],
"used_token": o["used_tokens"]
})
return get_json_result(data=res) return get_json_result(data=res)
except Exception as e: except Exception as e:
return server_error_response(e) return server_error_response(e)






@manager.route('/list', methods=['GET']) # noqa: F821 @manager.route('/list', methods=['GET']) # noqa: F821
@login_required @login_required
def list_app(): def list_app():

+ 22
- 0
web/src/hooks/llm-hooks.tsx ファイルの表示

return { data, loading }; return { data, loading };
}; };


export const useFetchMyLlmListDetailed = (): ResponseGetType<
Record<string, any>
> => {
const { data, isFetching: loading } = useQuery({
queryKey: ['myLlmListDetailed'],
initialData: {},
gcTime: 0,
queryFn: async () => {
const { data } = await userService.my_llm({ include_details: true });

return data?.data ?? {};
},
});

return { data, loading };
};


export const useSelectLlmList = () => { export const useSelectLlmList = () => {
const { data: myLlmList, loading: myLlmListLoading } = useFetchMyLlmList(); const { data: myLlmList, loading: myLlmListLoading } = useFetchMyLlmList();
const { data: factoryList, loading: factoryListLoading } = const { data: factoryList, loading: factoryListLoading } =
if (data.code === 0) { if (data.code === 0) {
message.success(t('message.modified')); message.success(t('message.modified'));
queryClient.invalidateQueries({ queryKey: ['myLlmList'] }); queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
queryClient.invalidateQueries({ queryKey: ['factoryList'] }); queryClient.invalidateQueries({ queryKey: ['factoryList'] });
} }
return data.code; return data.code;
const { data } = await userService.add_llm(params); const { data } = await userService.add_llm(params);
if (data.code === 0) { if (data.code === 0) {
queryClient.invalidateQueries({ queryKey: ['myLlmList'] }); queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
queryClient.invalidateQueries({ queryKey: ['factoryList'] }); queryClient.invalidateQueries({ queryKey: ['factoryList'] });
message.success(t('message.modified')); message.success(t('message.modified'));
} }
const { data } = await userService.delete_llm(params); const { data } = await userService.delete_llm(params);
if (data.code === 0) { if (data.code === 0) {
queryClient.invalidateQueries({ queryKey: ['myLlmList'] }); queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
queryClient.invalidateQueries({ queryKey: ['factoryList'] }); queryClient.invalidateQueries({ queryKey: ['factoryList'] });
message.success(t('message.deleted')); message.success(t('message.deleted'));
} }
const { data } = await userService.deleteFactory(params); const { data } = await userService.deleteFactory(params);
if (data.code === 0) { if (data.code === 0) {
queryClient.invalidateQueries({ queryKey: ['myLlmList'] }); queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
queryClient.invalidateQueries({ queryKey: ['factoryList'] }); queryClient.invalidateQueries({ queryKey: ['factoryList'] });
message.success(t('message.deleted')); message.success(t('message.deleted'));
} }

+ 1
- 0
web/src/locales/de.ts ファイルの表示

apiKeyTip: apiKeyTip:
'Der API-Schlüssel kann durch Registrierung beim entsprechenden LLM-Anbieter erhalten werden.', 'Der API-Schlüssel kann durch Registrierung beim entsprechenden LLM-Anbieter erhalten werden.',
showMoreModels: 'Mehr Modelle anzeigen', showMoreModels: 'Mehr Modelle anzeigen',
hideModels: 'Modelle ausblenden',
baseUrl: 'Basis-URL', baseUrl: 'Basis-URL',
baseUrlTip: baseUrlTip:
'Wenn Ihr API-Schlüssel von OpenAI stammt, ignorieren Sie dies. Andere Zwischenanbieter geben diese Basis-URL mit dem API-Schlüssel an.', 'Wenn Ihr API-Schlüssel von OpenAI stammt, ignorieren Sie dies. Andere Zwischenanbieter geben diese Basis-URL mit dem API-Schlüssel an.',

+ 5
- 2
web/src/locales/en.ts ファイルの表示

cancel: 'Cancel', cancel: 'Cancel',
addedModels: 'Added models', addedModels: 'Added models',
modelsToBeAdded: 'Models to be added', modelsToBeAdded: 'Models to be added',
addTheModel: 'Add the model',
addTheModel: 'Add Model',
apiKey: 'API-Key', apiKey: 'API-Key',
apiKeyMessage: apiKeyMessage:
'Please enter the API key (for locally deployed model,ignore this).', 'Please enter the API key (for locally deployed model,ignore this).',
apiKeyTip: apiKeyTip:
'The API key can be obtained by registering the corresponding LLM supplier.', 'The API key can be obtained by registering the corresponding LLM supplier.',
showMoreModels: 'Show more models',
showMoreModels: 'View Models',
hideModels: 'Hide Models',
baseUrl: 'Base-Url', baseUrl: 'Base-Url',
baseUrlTip: baseUrlTip:
'If your API key is from OpenAI, just ignore it. Any other intermediate providers will give this base url with the API key.', 'If your API key is from OpenAI, just ignore it. Any other intermediate providers will give this base url with the API key.',
workspace: 'Workspace', workspace: 'Workspace',
upgrade: 'Upgrade', upgrade: 'Upgrade',
addLlmTitle: 'Add LLM', addLlmTitle: 'Add LLM',
editLlmTitle: 'Edit {{name}} Model',
editModel: 'Edit Model',
modelName: 'Model name', modelName: 'Model name',
modelID: 'Model ID', modelID: 'Model ID',
modelUid: 'Model UID', modelUid: 'Model UID',

+ 1
- 0
web/src/locales/es.ts ファイルの表示

apiKeyTip: apiKeyTip:
'La clave API puede obtenerse registrándose con el proveedor correspondiente de LLM.', 'La clave API puede obtenerse registrándose con el proveedor correspondiente de LLM.',
showMoreModels: 'Mostrar más modelos', showMoreModels: 'Mostrar más modelos',
hideModels: 'Ocultar modelos',
baseUrl: 'URL base', baseUrl: 'URL base',
baseUrlTip: baseUrlTip:
'Si tu clave API es de OpenAI, ignora esto. Cualquier otro proveedor intermedio proporcionará esta URL base junto con la clave API.', 'Si tu clave API es de OpenAI, ignora esto. Cualquier otro proveedor intermedio proporcionará esta URL base junto con la clave API.',

+ 1
- 0
web/src/locales/id.ts ファイルの表示

apiKeyTip: apiKeyTip:
'Kunci API dapat diperoleh dengan mendaftar ke penyedia LLM yang sesuai.', 'Kunci API dapat diperoleh dengan mendaftar ke penyedia LLM yang sesuai.',
showMoreModels: 'Tampilkan lebih banyak model', showMoreModels: 'Tampilkan lebih banyak model',
hideModels: 'Sembunyikan model',
baseUrl: 'Base-Url', baseUrl: 'Base-Url',
baseUrlTip: baseUrlTip:
'Jika kunci API Anda berasal dari OpenAI, abaikan saja. Penyedia perantara lainnya akan memberikan base url ini dengan kunci API.', 'Jika kunci API Anda berasal dari OpenAI, abaikan saja. Penyedia perantara lainnya akan memberikan base url ini dengan kunci API.',

+ 3
- 0
web/src/locales/ja.ts ファイルの表示

apiKeyTip: apiKeyTip:
'APIキーは、対応するLLMサプライヤーに登録することで取得できます。', 'APIキーは、対応するLLMサプライヤーに登録することで取得できます。',
showMoreModels: 'さらにモデルを表示', showMoreModels: 'さらにモデルを表示',
hideModels: 'モデルを隠す',
baseUrl: 'ベースURL', baseUrl: 'ベースURL',
baseUrlTip: baseUrlTip:
'APIキーがOpenAIからのものであれば無視してください。他の中間プロバイダーはAPIキーと共にこのベースURLを提供します。', 'APIキーがOpenAIからのものであれば無視してください。他の中間プロバイダーはAPIキーと共にこのベースURLを提供します。',
workspace: 'ワークスペース', workspace: 'ワークスペース',
upgrade: 'アップグレード', upgrade: 'アップグレード',
addLlmTitle: 'LLMを追加', addLlmTitle: 'LLMを追加',
editLlmTitle: '{{name}}モデルを編集',
editModel: 'モデルを編集',
modelName: 'モデル名', modelName: 'モデル名',
modelID: 'モデルID', modelID: 'モデルID',
modelUid: 'モデルUID', modelUid: 'モデルUID',

+ 1
- 0
web/src/locales/pt-br.ts ファイルの表示

apiKeyTip: apiKeyTip:
'A chave da API pode ser obtida registrando-se no fornecedor correspondente do LLM.', 'A chave da API pode ser obtida registrando-se no fornecedor correspondente do LLM.',
showMoreModels: 'Mostrar mais modelos', showMoreModels: 'Mostrar mais modelos',
hideModels: 'Ocultar modelos',
baseUrl: 'URL Base', baseUrl: 'URL Base',
baseUrlTip: baseUrlTip:
'Se sua chave da API for do OpenAI, ignore isso. Outros provedores intermediários fornecerão essa URL base com a chave da API.', 'Se sua chave da API for do OpenAI, ignore isso. Outros provedores intermediários fornecerão essa URL base com a chave da API.',

+ 1
- 0
web/src/locales/vi.ts ファイルの表示

apiKeyTip: apiKeyTip:
'Khóa API có thể được lấy bằng cách đăng ký nhà cung cấp LLM tương ứng.', 'Khóa API có thể được lấy bằng cách đăng ký nhà cung cấp LLM tương ứng.',
showMoreModels: 'Hiển thị thêm mô hình', showMoreModels: 'Hiển thị thêm mô hình',
hideModels: 'Ẩn mô hình',
baseUrl: 'Base-Url', baseUrl: 'Base-Url',
baseUrlTip: baseUrlTip:
'Nếu khóa API của bạn từ OpenAI, chỉ cần bỏ qua nó. Bất kỳ nhà cung cấp trung gian nào khác sẽ cung cấp URL cơ sở này với khóa API.', 'Nếu khóa API của bạn từ OpenAI, chỉ cần bỏ qua nó. Bất kỳ nhà cung cấp trung gian nào khác sẽ cung cấp URL cơ sở này với khóa API.',

+ 1
- 0
web/src/locales/zh-traditional.ts ファイルの表示

apiKeyMessage: '請輸入api key(如果是本地部署的模型,請忽略它)', apiKeyMessage: '請輸入api key(如果是本地部署的模型,請忽略它)',
apiKeyTip: 'API key可以通過註冊相應的LLM供應商來獲取。', apiKeyTip: 'API key可以通過註冊相應的LLM供應商來獲取。',
showMoreModels: '展示更多模型', showMoreModels: '展示更多模型',
hideModels: '隱藏模型',
baseUrl: 'base-url', baseUrl: 'base-url',
baseUrlTip: baseUrlTip:
'如果您的 API 密鑰來自 OpenAI,請忽略它。任何其他中間提供商都會提供帶有 API 密鑰的基本 URL。', '如果您的 API 密鑰來自 OpenAI,請忽略它。任何其他中間提供商都會提供帶有 API 密鑰的基本 URL。',

+ 3
- 0
web/src/locales/zh.ts ファイルの表示

apiKeyMessage: '请输入api key(如果是本地部署的模型,请忽略它)', apiKeyMessage: '请输入api key(如果是本地部署的模型,请忽略它)',
apiKeyTip: 'API key可以通过注册相应的LLM供应商来获取。', apiKeyTip: 'API key可以通过注册相应的LLM供应商来获取。',
showMoreModels: '展示更多模型', showMoreModels: '展示更多模型',
hideModels: '隐藏模型',
baseUrl: 'Base-Url', baseUrl: 'Base-Url',
baseUrlTip: baseUrlTip:
'如果您的 API 密钥来自 OpenAI,请忽略它。 任何其他中间提供商都会提供带有 API 密钥的基本 URL。', '如果您的 API 密钥来自 OpenAI,请忽略它。 任何其他中间提供商都会提供带有 API 密钥的基本 URL。',
workspace: '工作空间', workspace: '工作空间',
upgrade: '升级', upgrade: '升级',
addLlmTitle: '添加 LLM', addLlmTitle: '添加 LLM',
editLlmTitle: '编辑 {{name}} 模型',
editModel: '编辑模型',
modelName: '模型名称', modelName: '模型名称',
modelID: '模型ID', modelID: '模型ID',
modelUid: '模型UID', modelUid: '模型UID',

+ 3
- 1
web/src/pages/user-setting/setting-model/api-key-modal/index.tsx ファイルの表示

loading: boolean; loading: boolean;
initialValue: string; initialValue: string;
llmFactory: string; llmFactory: string;
editMode?: boolean;
onOk: (postBody: ApiKeyPostBody) => void; onOk: (postBody: ApiKeyPostBody) => void;
showModal?(): void; showModal?(): void;
} }
llmFactory, llmFactory,
loading, loading,
initialValue, initialValue,
editMode = false,
onOk, onOk,
}: IProps) => { }: IProps) => {
const [form] = Form.useForm(); const [form] = Form.useForm();


return ( return (
<Modal <Modal
title={t('modify')}
title={editMode ? t('editModel') : t('modify')}
open={visible} open={visible}
onOk={handleOk} onOk={handleOk}
onCancel={hideModal} onCancel={hideModal}

+ 34
- 3
web/src/pages/user-setting/setting-model/hooks.ts ファイルの表示

} from '@/hooks/llm-hooks'; } from '@/hooks/llm-hooks';
import { useFetchTenantInfo } from '@/hooks/user-setting-hooks'; import { useFetchTenantInfo } from '@/hooks/user-setting-hooks';
import { IAddLlmRequestBody } from '@/interfaces/request/llm'; import { IAddLlmRequestBody } from '@/interfaces/request/llm';
import { getRealModelName } from '@/utils/llm-util';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { ApiKeyPostBody } from '../interface'; import { ApiKeyPostBody } from '../interface';


const [savingParams, setSavingParams] = useState<SavingParamsState>( const [savingParams, setSavingParams] = useState<SavingParamsState>(
{} as SavingParamsState, {} as SavingParamsState,
); );
const [editMode, setEditMode] = useState(false);
const { saveApiKey, loading } = useSaveApiKey(); const { saveApiKey, loading } = useSaveApiKey();
const { const {
visible: apiKeyVisible, visible: apiKeyVisible,


if (ret === 0) { if (ret === 0) {
hideApiKeyModal(); hideApiKeyModal();
setEditMode(false);
} }
}, },
[hideApiKeyModal, saveApiKey, savingParams], [hideApiKeyModal, saveApiKey, savingParams],
); );


const onShowApiKeyModal = useCallback( const onShowApiKeyModal = useCallback(
(savingParams: SavingParamsState) => {
(savingParams: SavingParamsState, isEdit = false) => {
setSavingParams(savingParams); setSavingParams(savingParams);
setEditMode(isEdit);
showApiKeyModal(); showApiKeyModal();
}, },
[showApiKeyModal, setSavingParams], [showApiKeyModal, setSavingParams],
saveApiKeyLoading: loading, saveApiKeyLoading: loading,
initialApiKey: '', initialApiKey: '',
llmFactory: savingParams.llm_factory, llmFactory: savingParams.llm_factory,
editMode,
onApiKeySavingOk, onApiKeySavingOk,
apiKeyVisible, apiKeyVisible,
hideApiKeyModal, hideApiKeyModal,


export const useSubmitOllama = () => { export const useSubmitOllama = () => {
const [selectedLlmFactory, setSelectedLlmFactory] = useState<string>(''); const [selectedLlmFactory, setSelectedLlmFactory] = useState<string>('');
const [editMode, setEditMode] = useState(false);
const [initialValues, setInitialValues] = useState<Partial<IAddLlmRequestBody> | undefined>();
const [originalModelName, setOriginalModelName] = useState<string>('');
const { addLlm, loading } = useAddLlm(); const { addLlm, loading } = useAddLlm();
const { const {
visible: llmAddingVisible, visible: llmAddingVisible,


const onLlmAddingOk = useCallback( const onLlmAddingOk = useCallback(
async (payload: IAddLlmRequestBody) => { async (payload: IAddLlmRequestBody) => {
const ret = await addLlm(payload);
const cleanedPayload = { ...payload };
if (!cleanedPayload.api_key || cleanedPayload.api_key.trim() === '') {
delete cleanedPayload.api_key;
}
const ret = await addLlm(cleanedPayload);
if (ret === 0) { if (ret === 0) {
hideLlmAddingModal(); hideLlmAddingModal();
setEditMode(false);
setInitialValues(undefined);
} }
}, },
[hideLlmAddingModal, addLlm], [hideLlmAddingModal, addLlm],
); );


const handleShowLlmAddingModal = (llmFactory: string) => {
const handleShowLlmAddingModal = (llmFactory: string, isEdit = false, modelData?: any, detailedData?: any) => {
setSelectedLlmFactory(llmFactory); setSelectedLlmFactory(llmFactory);
setEditMode(isEdit);
if (isEdit && detailedData) {
const initialVals = {
llm_name: getRealModelName(detailedData.name),
model_type: detailedData.type,
api_base: detailedData.api_base || '',
max_tokens: detailedData.max_tokens || 8192,
api_key: '',
};
setInitialValues(initialVals);
} else {
setInitialValues(undefined);
}
showLlmAddingModal(); showLlmAddingModal();
}; };


return { return {
llmAddingLoading: loading, llmAddingLoading: loading,
editMode,
initialValues,
onLlmAddingOk, onLlmAddingOk,
llmAddingVisible, llmAddingVisible,
hideLlmAddingModal, hideLlmAddingModal,

+ 48
- 9
web/src/pages/user-setting/setting-model/index.tsx ファイルの表示

import { useTheme } from '@/components/theme-provider'; import { useTheme } from '@/components/theme-provider';
import { LLMFactory } from '@/constants/llm'; import { LLMFactory } from '@/constants/llm';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks'; import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
import { LlmItem, useSelectLlmList } from '@/hooks/llm-hooks';
import { LlmItem, useSelectLlmList, useFetchMyLlmListDetailed } from '@/hooks/llm-hooks';
import { getRealModelName } from '@/utils/llm-util'; import { getRealModelName } from '@/utils/llm-util';
import { CloseCircleOutlined, SettingOutlined } from '@ant-design/icons';
import { CloseCircleOutlined, EditOutlined, SettingOutlined } from '@ant-design/icons';
import { import {
Button, Button,
Card, Card,
interface IModelCardProps { interface IModelCardProps {
item: LlmItem; item: LlmItem;
clickApiKey: (llmFactory: string) => void; clickApiKey: (llmFactory: string) => void;
handleEditModel: (model: any, factory: LlmItem) => void;
} }


const ModelCard = ({ item, clickApiKey }: IModelCardProps) => {
const ModelCard = ({ item, clickApiKey, handleEditModel }: IModelCardProps) => {
const { visible, switchVisible } = useSetModalState(); const { visible, switchVisible } = useSetModalState();
const { t } = useTranslate('setting'); const { t } = useTranslate('setting');
const { theme } = useTheme(); const { theme } = useTheme();
</Button> </Button>
<Button onClick={handleShowMoreClick}> <Button onClick={handleShowMoreClick}>
<Flex align="center" gap={4}> <Flex align="center" gap={4}>
{t('showMoreModels')}
{visible ? t('hideModels') : t('showMoreModels')}
<MoreModelIcon /> <MoreModelIcon />
</Flex> </Flex>
</Button> </Button>
size="small" size="small"
dataSource={item.llm} dataSource={item.llm}
className={styles.llmList} className={styles.llmList}
renderItem={(item) => (
renderItem={(model) => (
<List.Item> <List.Item>
<Space> <Space>
{getRealModelName(item.name)}
<Tag color="#b8b8b8">{item.type}</Tag>
{getRealModelName(model.name)}
<Tag color="#b8b8b8">{model.type}</Tag>
{isLocalLlmFactory(item.name) && (
<Tooltip title={t('edit', { keyPrefix: 'common' })}>
<Button type={'text'} onClick={() => handleEditModel(model, item)}>
<EditOutlined style={{ color: '#1890ff' }} />
</Button>
</Tooltip>
)}
<Tooltip title={t('delete', { keyPrefix: 'common' })}> <Tooltip title={t('delete', { keyPrefix: 'common' })}>
<Button type={'text'} onClick={handleDeleteLlm(item.name)}>
<Button type={'text'} onClick={handleDeleteLlm(model.name)}>
<CloseCircleOutlined style={{ color: '#D92D20' }} /> <CloseCircleOutlined style={{ color: '#D92D20' }} />
</Button> </Button>
</Tooltip> </Tooltip>


const UserSettingModel = () => { const UserSettingModel = () => {
const { factoryList, myLlmList: llmList, loading } = useSelectLlmList(); const { factoryList, myLlmList: llmList, loading } = useSelectLlmList();
const { data: detailedLlmList } = useFetchMyLlmListDetailed();
const { theme } = useTheme(); const { theme } = useTheme();
const { const {
saveApiKeyLoading, saveApiKeyLoading,
initialApiKey, initialApiKey,
llmFactory, llmFactory,
editMode,
onApiKeySavingOk, onApiKeySavingOk,
apiKeyVisible, apiKeyVisible,
hideApiKeyModal, hideApiKeyModal,
showLlmAddingModal, showLlmAddingModal,
onLlmAddingOk, onLlmAddingOk,
llmAddingLoading, llmAddingLoading,
editMode: llmEditMode,
initialValues: llmInitialValues,
selectedLlmFactory, selectedLlmFactory,
} = useSubmitOllama(); } = useSubmitOllama();


[showApiKeyModal, showLlmAddingModal, ModalMap], [showApiKeyModal, showLlmAddingModal, ModalMap],
); );


const handleEditModel = useCallback(
(model: any, factory: LlmItem) => {
if (factory) {
const detailedFactory = detailedLlmList[factory.name];
const detailedModel = detailedFactory?.llm?.find((m: any) => m.name === model.name);
const editData = {
llm_factory: factory.name,
llm_name: model.name,
model_type: model.type
};
if (isLocalLlmFactory(factory.name)) {
showLlmAddingModal(factory.name, true, editData, detailedModel);
} else if (factory.name in ModalMap) {
ModalMap[factory.name as keyof typeof ModalMap]();
} else {
showApiKeyModal(editData, true);
}
}
},
[showApiKeyModal, showLlmAddingModal, ModalMap, detailedLlmList],
);

const items: CollapseProps['items'] = [ const items: CollapseProps['items'] = [
{ {
key: '1', key: '1',
grid={{ gutter: 16, column: 1 }} grid={{ gutter: 16, column: 1 }}
dataSource={llmList} dataSource={llmList}
renderItem={(item) => ( renderItem={(item) => (
<ModelCard item={item} clickApiKey={handleAddModel}></ModelCard>
<ModelCard item={item} clickApiKey={handleAddModel} handleEditModel={handleEditModel}></ModelCard>
)} )}
/> />
), ),
hideModal={hideApiKeyModal} hideModal={hideApiKeyModal}
loading={saveApiKeyLoading} loading={saveApiKeyLoading}
initialValue={initialApiKey} initialValue={initialApiKey}
editMode={editMode}
onOk={onApiKeySavingOk} onOk={onApiKeySavingOk}
llmFactory={llmFactory} llmFactory={llmFactory}
></ApiKeyModal> ></ApiKeyModal>
hideModal={hideLlmAddingModal} hideModal={hideLlmAddingModal}
onOk={onLlmAddingOk} onOk={onLlmAddingOk}
loading={llmAddingLoading} loading={llmAddingLoading}
editMode={llmEditMode}
initialValues={llmInitialValues}
llmFactory={selectedLlmFactory} llmFactory={selectedLlmFactory}
></OllamaModal> ></OllamaModal>
<VolcEngineModal <VolcEngineModal

+ 29
- 3
web/src/pages/user-setting/setting-model/ollama-modal/index.tsx ファイルの表示

Switch, Switch,
} from 'antd'; } from 'antd';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import { useEffect } from 'react';


type FieldType = IAddLlmRequestBody & { vision: boolean }; type FieldType = IAddLlmRequestBody & { vision: boolean };


onOk, onOk,
loading, loading,
llmFactory, llmFactory,
}: IModalProps<IAddLlmRequestBody> & { llmFactory: string }) => {
editMode = false,
initialValues,
}: IModalProps<IAddLlmRequestBody> & {
llmFactory: string;
editMode?: boolean;
initialValues?: Partial<IAddLlmRequestBody>;
}) => {
const [form] = Form.useForm<FieldType>(); const [form] = Form.useForm<FieldType>();


const { t } = useTranslate('setting'); const { t } = useTranslate('setting');
await handleOk(); await handleOk();
} }
}; };

useEffect(() => {
if (visible && editMode && initialValues) {
const formValues = {
llm_name: initialValues.llm_name,
model_type: initialValues.model_type,
api_base: initialValues.api_base,
max_tokens: initialValues.max_tokens || 8192,
api_key: '',
...initialValues,
};
form.setFieldsValue(formValues);
} else if (visible && !editMode) {
form.resetFields();
}
}, [visible, editMode, initialValues, form]);
const url = const url =
llmFactoryToUrlMap[llmFactory as LlmFactory] || llmFactoryToUrlMap[llmFactory as LlmFactory] ||
}; };
return ( return (
<Modal <Modal
title={t('addLlmTitle', { name: llmFactory })}
title={editMode ? t('editLlmTitle', { name: llmFactory }) : t('addLlmTitle', { name: llmFactory })}
open={visible} open={visible}
onOk={handleOk} onOk={handleOk}
onCancel={hideModal} onCancel={hideModal}
name="api_key" name="api_key"
rules={[{ required: false, message: t('apiKeyMessage') }]} rules={[{ required: false, message: t('apiKeyMessage') }]}
> >
<Input placeholder={t('apiKeyMessage')} onKeyDown={handleKeyDown} />
<Input
placeholder={t('apiKeyMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item> </Form.Item>
<Form.Item<FieldType> <Form.Item<FieldType>
label={t('maxTokens')} label={t('maxTokens')}

読み込み中…
キャンセル
保存