From d77a849176108445e572035ac0c66b11de3076ed Mon Sep 17 00:00:00 2001 From: Hu Gang <18768366022@163.com> Date: Sun, 18 May 2025 14:18:05 +0800 Subject: [PATCH] feat: app center --- src/apis/appCenter/appCenterService.ts | 50 ++++- src/apis/appCenter/type.ts | 191 ++++++++++++++++- src/apis/paths/conversation.ts | 6 +- src/apis/paths/model.ts | 79 +++---- src/apis/paths/type.ts | 11 +- src/assets/styles/codePreview.scss | 1 + src/assets/svgs/default_agent_icon.svg | 17 ++ src/components/bubble/index.vue | 139 ++++++++----- src/components/sessionCard/type.ts | 4 +- src/i18n/lang/en.ts | 2 +- src/i18n/lang/zh-cn.ts | 4 +- .../app/components/SelectAppTypeDialog.vue | 127 ++++++++++++ src/views/app/index.vue | 195 +++++++++++------- src/views/app/style.scss | 30 ++- src/views/createapp/components/appConfig.vue | 31 ++- src/views/createapp/index.vue | 158 +++++++++----- .../dialogue/components/InterPreview.vue | 62 ++++-- src/views/settings/Model.vue | 38 ++-- src/views/settings/components/AddModel.vue | 57 ++--- src/views/settings/components/ModelCard.vue | 6 +- src/views/settings/index.vue | 1 + 21 files changed, 894 insertions(+), 315 deletions(-) create mode 100644 src/assets/svgs/default_agent_icon.svg create mode 100644 src/views/app/components/SelectAppTypeDialog.vue diff --git a/src/apis/appCenter/appCenterService.ts b/src/apis/appCenter/appCenterService.ts index ac2b346c..809ab20f 100644 --- a/src/apis/appCenter/appCenterService.ts +++ b/src/apis/appCenter/appCenterService.ts @@ -1,7 +1,12 @@ // Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. import { post, get, del, put } from 'src/apis/server'; import type { FcResponse } from 'src/apis/server'; -import { QueryAppListParamsType, CreateOrUpdateAppParamsType } from './type'; +import { + QueryAppListParamsType, + CreateOrUpdateAppParamsType, + AppDetail, +} from './type'; + /** * 获取应用列表 * @param params @@ -9,7 +14,28 @@ import { QueryAppListParamsType, CreateOrUpdateAppParamsType } from './type'; */ export const queryAppList = ( params: QueryAppListParamsType, -): Promise<[any, FcResponse | undefined]> => { +): Promise< + [ + any, + ( + | FcResponse<{ + applications: { + appId: string; + author: string; + description: string; + favorited: boolean; + appType: 'flow' | 'agent'; + icon: string; + name: string; + published: boolean; + }[]; + currentPage: number; + totalApps: number; + }> + | undefined + ), + ] +> => { return get('/api/app', params); }; @@ -18,10 +44,8 @@ export const queryAppList = ( * @param params * @returns */ -export const createOrUpdateApp = ( - params: CreateOrUpdateAppParamsType, -): Promise<[any, FcResponse | undefined]> => { - return post('/api/app', params); +export const createOrUpdateApp = (params: CreateOrUpdateAppParamsType) => { + return post<{ appId: string }>('/api/app', params); }; /** @@ -29,10 +53,8 @@ export const createOrUpdateApp = ( * @param params * @returns */ -export const querySingleAppData = (params: { - id: string; -}): Promise<[any, FcResponse | undefined]> => { - return get(`/api/app/${params.id}`); +export const querySingleAppData = (params: { id: string }) => { + return get(`/api/app/${params.id}`); }; /** @@ -75,7 +97,13 @@ export const changeSingleAppCollect = (params: { * @returns */ export const getPartAppConfgUser = (): Promise< - [any, FcResponse | undefined] + [ + any, + ( + | FcResponse<{ userInfoList: { userName: string; userSub: string }[] }> + | undefined + ), + ] > => { return get('/api/user'); }; diff --git a/src/apis/appCenter/type.ts b/src/apis/appCenter/type.ts index ae30e0e9..95d12c7b 100644 --- a/src/apis/appCenter/type.ts +++ b/src/apis/appCenter/type.ts @@ -31,9 +31,8 @@ export interface QueryAppListParamsType { */ export enum SearchType { All = 'all', - Author = 'author', - Description = 'description', - Name = 'name', + Agent = 'agent', + Flow = 'flow', } /** @@ -43,7 +42,11 @@ export interface CreateOrUpdateAppParamsType { /** * 应用ID */ - appId?: string; + appId?: string | null; + /** + * 应用类型 + */ + appType: 'flow' | 'agent'; /** * 应用简介 */ @@ -72,6 +75,10 @@ export interface CreateOrUpdateAppParamsType { * 推荐问题(列表,最多3项) */ recommendedQuestions?: string[]; + /** + * Mcpservice,MCP服务 + */ + mcpService?: string[]; /** * 工作流(列表,每个元素为工作流ID) */ @@ -79,6 +86,140 @@ export interface CreateOrUpdateAppParamsType { [property: string]: any; } +/** + * MCPServiceMetadata,MCPService的元数据 + */ +export interface MCPServiceMetadataInput { + /** + * Author,创建者的用户名 + */ + author: string; + /** + * MCP服务配置 + */ + config: MCPServiceConfig; + /** + * Description,元数据描述 + */ + description: string; + /** + * Hashes,资源(App、Service等)下所有文件的hash值 + */ + hashes?: { [key: string]: string } | null; + /** + * Icon,图标 + */ + icon?: string; + /** + * Id,元数据ID + */ + id: string; + /** + * Name,元数据名称 + */ + name: string; + /** + * Tools,MCP服务Tools列表 + */ + tools: MCPServiceToolsdataInput[]; + type?: MetadataType; + [property: string]: any; +} + +/** + * MCPServiceToolsdata,MCP Service中tool信息 + */ +export interface MCPServiceToolsdataInput { + /** + * Description,Tool功能描述 + */ + description: string; + /** + * Input Args,Tool参数列表 + */ + input_args: MCPServiceToolsArgs[]; + /** + * Name,Tool名称 + */ + name: string; + /** + * Output Args,Tool参数列表 + */ + output_args: MCPServiceToolsArgs[]; + [property: string]: any; +} + +/** + * MetadataType,元数据类型 + */ +export enum MetadataType { + Agent = 'agent', + Flow = 'flow', + McpService = 'mcp_service', + Model = 'model', + Prompt = 'prompt', + Service = 'service', +} + +/** + * MCPServiceToolsArgs,MCP Service中tool参数信息 + */ +export interface MCPServiceToolsArgs { + /** + * Description,Tool参数描述 + */ + description: string; + /** + * Name,Tool参数名称 + */ + name: string; + /** + * Tool参数类型 + */ + type: MCPServiceToolsArgsType; + [property: string]: any; +} + +/** + * 传输协议(Stdio/SSE/Streamable) + * + * MCPTransmitProto,MCP传输方式 + */ +export enum MCPTransmitProto { + SSE = 'sse', + Stdio = 'stdio', + Streamable = 'stream', +} + +/** + * MCP服务配置 + * + * MCPServiceConfig,MCPService的API配置 + */ +export interface MCPServiceConfig { + /** + * Config,对应MCP的配置 + */ + config: { [key: string]: any }; + /** + * 传输协议(Stdio/SSE/Streamable) + */ + transmitProto?: MCPTransmitProto; + [property: string]: any; +} + +/** + * Tool参数类型 + * + * MCPServiceToolsArgsType,MCPService tool参数数据类型 + */ +export enum MCPServiceToolsArgsType { + Boolean = 'boolean', + Double = 'double', + Integer = 'integer', + String = 'string', +} + /** * AppLink, 应用链接数据结构 */ @@ -109,7 +250,43 @@ export interface AppPermissionData { } export enum Visibility { - Private = 'private', - Protected = 'protected', - Public = 'public', + private = 'private', + protected = 'protected', + public = 'public', +} + +export interface AppDetail { + type: 'flow' | 'agent'; + icon?: string; + name: string; + description: string; + dialogRounds?: number; + permission?: { + visibility: keyof typeof Visibility; + authorizedUsers: string[]; + }; + links?: AppLink[]; + recommendedQuestions?: string[]; + workflows?: { + id: string; + name: string; + description: string; + debug: boolean; + }; + mcpService?: string[]; + model?: { + provider: string; + icon?: string; + url: string; + model: string; + apiKey: string; + maxTokens: number; + id: string; + author: string; + hashes?: any; + }; + prompt?: string; + knowledge?: string; + appId: string; + published: boolean; } diff --git a/src/apis/paths/conversation.ts b/src/apis/paths/conversation.ts index f7998279..b8dd3c34 100644 --- a/src/apis/paths/conversation.ts +++ b/src/apis/paths/conversation.ts @@ -70,7 +70,9 @@ export const createSessionDebug = (params: any): Promise<[any, any]> => { */ export const updateSession = (params: { conversationId: string; - title: string; + modelId?: string; + kbIds?: string[]; + title?: string; }): Promise< [ any, @@ -88,6 +90,8 @@ export const updateSession = (params: { BASE_URL, { title: params.title, + modelId: params.modelId, + kbIds: params.kbIds, }, { conversationId: params.conversationId, diff --git a/src/apis/paths/model.ts b/src/apis/paths/model.ts index c4708c4a..5b26f71e 100644 --- a/src/apis/paths/model.ts +++ b/src/apis/paths/model.ts @@ -1,5 +1,5 @@ -import { get, post, del } from '../server'; -import { addedModalList } from './type'; +import { get, post, del, put } from '../server'; +import { AddedModalList } from './type'; enum Provider { OLLAMA = 'Ollama', VLLM = 'VLLM', @@ -15,20 +15,28 @@ enum Provider { * @returns */ const getUserModelList = () => { - return get<{ - models: { - modelId: string; + return get< + { + llmId: string; icon: string; - description: string; - name: string; - model: string; - url: string; - provider: string; + openaiBaseUrl: string; + openaiApiKey: string; + modelName: string; maxTokens: number; - apiKey: string; - }[]; - totalModels: number; - }>('/api/model'); + }[] + >('/api/llm'); +}; + +const getModelById = (modelId: string) => { + return get<{ + modelId: string; + icon: string; + model: string; + url: string; + provider: string; + maxTokens: number; + apiKey: string; + }>(`/api/model/${modelId}`); }; /** @@ -36,16 +44,14 @@ const getUserModelList = () => { * @returns */ const getModelProviderList = () => { - return get<{ - providers: { - providerId: string; + return get< + { + provider: string; icon: string; url: string; description: string; - name: string; - }[]; - totalProviders: number; - }>('/api/model/provider'); + }[] + >('/api/llm/provider'); }; const getKnowledgeList = (conversationId?: string) => { @@ -55,8 +61,8 @@ const getKnowledgeList = (conversationId?: string) => { const getAllModels = (searchKey: string) => { return get<{ models: { modelId: string; modelName: string }[]; - }>('/api/model/all', { - searchKey, + }>('/api/model/model', { + keyword: searchKey, }); }; @@ -64,9 +70,7 @@ const getAllModels = (searchKey: string) => { * 获取已添加模型列表 */ const getAddedModels = () => { - return get<{ - models: addedModalList[]; - }>('/api/model'); + return get('/api/llm'); }; /** @@ -74,17 +78,15 @@ const getAddedModels = () => { * @param params * @returns */ -const createModel = (params: { - modelId?: string; - name: string; - provider: string; - icon?: string; - apiKey: string; - maxTokens: string; - model: string; - url: string; +const createOrUpdateModel = (params: { + llmId?: string; + icon: string; + openaiBaseUrl?: string; + openaiApiKey: string; + modelName: string; + maxTokens: number; }) => { - return post('/api/model', params); + return put('/api/llm', params, { llmId: params.llmId }); }; /** @@ -93,7 +95,7 @@ const createModel = (params: { * @returns */ const deleteModel = (modelId: string) => { - return del(`/api/model/${modelId}`); + return del(`/api/llm`, undefined, { llmId: modelId }); }; const updateModelAndKnowLedgeList = (params: { @@ -109,8 +111,9 @@ export const modelApi = { getAddedModels, getUserModelList, getModelProviderList, - createModel, + createOrUpdateModel, getAllModels, deleteModel, getKnowledgeList, + getModelById, }; diff --git a/src/apis/paths/type.ts b/src/apis/paths/type.ts index 017796d3..f420fb21 100644 --- a/src/apis/paths/type.ts +++ b/src/apis/paths/type.ts @@ -206,10 +206,13 @@ export interface serviceApiData { /** * addedModalList, 获取可选modal列表 */ -export interface addedModalList { - modelId: string; - model: string; - icon: string; +export interface AddedModalList { + llmId: string; + icon?: string; + openaiBaseUrl?: string; + openaiApiKey: string; + modelName: string; + maxTokens: string; } /** * teamKnowledgeList, 获取teamKnowledgeList列表 diff --git a/src/assets/styles/codePreview.scss b/src/assets/styles/codePreview.scss index 2dc303b3..dfc876ed 100644 --- a/src/assets/styles/codePreview.scss +++ b/src/assets/styles/codePreview.scss @@ -1,4 +1,5 @@ #markdown-preview { + line-height: 24px; .hljs { color: var(--o-text-color-secondary); font-size: 14px; diff --git a/src/assets/svgs/default_agent_icon.svg b/src/assets/svgs/default_agent_icon.svg new file mode 100644 index 00000000..c078614e --- /dev/null +++ b/src/assets/svgs/default_agent_icon.svg @@ -0,0 +1,17 @@ + + + Created with Pixso. + + + + + + + + + + + + + + diff --git a/src/components/bubble/index.vue b/src/components/bubble/index.vue index a0679492..9057e652 100644 --- a/src/components/bubble/index.vue +++ b/src/components/bubble/index.vue @@ -6,9 +6,10 @@ type Style = Record<'avatar' | 'content', CSSProperties>; interface BubbleProps { avatar?: string; - content: string; + content?: string; loading?: boolean; placement?: 'start' | 'end'; + date?: string; shape?: 'round' | 'corner'; styles?: Partial diff --git a/src/views/app/index.vue b/src/views/app/index.vue index 394a87b7..f9af6202 100644 --- a/src/views/app/index.vue +++ b/src/views/app/index.vue @@ -17,43 +17,42 @@ :suffix-icon="IconCaretDown" > - - - + + - + {{ $t('app.app_create') }} -
-
- {{ $t('app.all_app') }} -
-
- {{ $t('app.my_created') }} -
-
- {{ $t('app.my_favorite') }} -
-
+ + + + + +
+
+ + {{ appItem.appType === 'flow' ? '工作流' : '智能体' }} + +
@@ -132,11 +143,14 @@ @change="handleChangePage" />
+ @@ -127,20 +132,23 @@ onMounted(() => { line-height: 22px; font-weight: 700; margin-bottom: 8px; + color: var(--o-text-color-primary); } .added-model { display: grid; grid-template-columns: repeat(4, 1fr); + align-content: flex-start; /* 关键:子项顶部对齐 */ gap: 16px; min-height: 210px; &__operate { display: flex; gap: 8px; - color: rgb(99, 149, 253); font-size: 12px; line-height: 16px; + color: rgb(99, 149, 253); + a { cursor: pointer; user-select: none; diff --git a/src/views/settings/components/AddModel.vue b/src/views/settings/components/AddModel.vue index 45eb882b..7e825d73 100644 --- a/src/views/settings/components/AddModel.vue +++ b/src/views/settings/components/AddModel.vue @@ -22,19 +22,25 @@ interface From { } export interface ModelProvider { - providerId: string; + provider: string; icon: string; url: string; description: string; - name: string; } const props = defineProps<{ visible: boolean; type: 'add' | 'edit'; + model?: { + llmId: string; + icon: string; + openaiBaseUrl: string; + openaiApiKey: string; + modelName: string; + maxTokens: number; + }; title?: string; provider?: ModelProvider; - default?: Partial; }>(); const emits = defineEmits<{ @@ -99,10 +105,13 @@ async function onConfirm(formEl: FormInstance | undefined) { if (!formEl) return; await formEl.validate(async (valid) => { if (!valid) return; - const [err, _] = await api.createModel({ - ...form.value, - provider: props.provider?.name || '', - name: '', + const [err, _] = await api.createOrUpdateModel({ + llmId: props.model?.llmId || undefined, + icon: props.provider?.icon || '', + openaiApiKey: form.value.apiKey, + openaiBaseUrl: props.provider?.url, + modelName: form.value.model, + maxTokens: Number(form.value.maxTokens), }); if (err) { ElMessage.error(err.message); @@ -114,14 +123,22 @@ async function onConfirm(formEl: FormInstance | undefined) { } watch( - () => [props.default, props.provider], + () => [props.model, props.provider], () => { nextTick(() => { formRef.value && formRef.value.resetFields(); - if (props.default) { - form.value = { ...form.value, ...props.default }; - console.log(form.value); + console.log(props.model); + + if (props.model) { + const { openaiApiKey, openaiBaseUrl, maxTokens, modelName } = + props.model; + form.value = { + url: openaiBaseUrl, + model: modelName, + apiKey: openaiApiKey, + maxTokens: String(maxTokens), + }; } if (props.provider) { form.value.url = props.provider.url; @@ -152,24 +169,10 @@ watch( /> - - - + > (() => { width: 100%; border-radius: 8px; padding: 16px; - background-color: rgb(244, 246, 250); + background-color: var(--el-collapse-header-bg); .card-header { display: flex; @@ -66,6 +66,7 @@ const cardStyles = computed(() => { &__title { display: flex; align-items: center; + color: var(--o-text-color-primary); .icon { width: 30px; height: 30px; @@ -89,6 +90,7 @@ const cardStyles = computed(() => { } .card-description { + height: 44px; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; @@ -97,7 +99,7 @@ const cardStyles = computed(() => { text-overflow: ellipsis; line-height: 22px; font-size: 14px; - color: rgb(78, 88, 101); + color: var(--o-text-color-secondary); margin-top: 5px; } diff --git a/src/views/settings/index.vue b/src/views/settings/index.vue index b4d51073..d7918c57 100644 --- a/src/views/settings/index.vue +++ b/src/views/settings/index.vue @@ -48,6 +48,7 @@ const handleClick = (tab: TabsPaneContext, event: Event) => { line-height: 24px; font-weight: 700; margin-bottom: 8px; + color: var(--o-text-color-primary); } &-container { -- Gitee