Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ui/src/utils/resource-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ export const isSystemResource = () => {
export const isSystemSharedResource = () => {
return router.currentRoute.value.meta.resourceScope === 'system-shared'
}

export const getResourceScope = () => {
return router.currentRoute.value.meta.resourceScope
}
5 changes: 2 additions & 3 deletions ui/src/views/workflow/application/ApplicationWorkflowView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ import { WorkflowMode } from '@/workflow-canvas/types'
import DefaultModelSettingButton from '../components/default-model-setting/DefaultModelSettingButton.vue'
import WorkflowViewLayout from '../components/WorkflowViewLayout.vue'
import Conversation from '@/components/conversation/index.vue'
import { getResourceScope } from '@/utils/resource-context.ts'

defineOptions({ name: 'ApplicationWorkflowView' })

// 为画布节点中的 ModelSelect 提供参数表单接口。
provide('getModelParamsForm', ModelApi.getModelParamsForm)
provide('resourceScope', getResourceScope())

const DEFAULT_WORKFLOW: LogicFlow.GraphConfigData = {
nodes: cloneDeep(defaultApplicationNodes),
Expand Down
6 changes: 3 additions & 3 deletions ui/src/workflow-canvas/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, ref, shallowRef, useTemplateRef } from 'vue'
import { inject, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, useTemplateRef } from 'vue'
import { cloneDeep } from 'lodash'
import LogicFlow, { type GraphModel } from '@logicflow/core'
import { SelectionSelect } from '@logicflow/extension'
Expand All @@ -15,7 +15,7 @@ import NodeSearch from './component/NodeSearch.vue'
defineOptions({ name: 'MkWorkflow' })

type CanvasWorkflowNodeModel = WorkflowNodeModel & { set_loop_body?: () => void }

const apiType = inject('resourceScope', 'workspace')
const props = withDefaults(
defineProps<{
data?: LogicFlow.GraphConfigData | null
Expand Down Expand Up @@ -73,7 +73,7 @@ function renderGraphData(data: LogicFlow.GraphConfigData = props.data ?? {}) {
lf.value.graphModel.get_provide = (model: LogicFlow.NodeData | null, graph: GraphModel | null) => ({
getModel: () => model,
getGraph: () => graph,
apiType: 'workspace', // TODO: apiType
apiType: apiType,
workflowMode: props.workflowMode,
loopWorkflowMode: props.loopWorkflowMode,
})
Expand Down
7 changes: 6 additions & 1 deletion ui/src/workflow-canvas/nodes/base-node/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, inject, onMounted, ref, useTemplateRef } from 'vue'
import { computed, inject, onMounted, ref, useTemplateRef, provide } from 'vue'
import { cloneDeep } from 'lodash'
import type { FormInstance } from 'element-plus'
import type { ModelItem, ModelProviderItem } from '@/api/types'
Expand All @@ -19,9 +19,14 @@ defineOptions({ name: 'WorkflowBaseNode' })

const getModel = inject('getModel') as () => WorkflowNodeModel
const apiType = (inject('apiType') as string) || 'workspace'

const model = getModel()
const store = useWorkflowStore(apiType)

// 为画布节点中的 ModelSelect 提供参数表单接口。
provide('getModelParamsForm', store.force.getModelParamsForm)
provide('getSelectModelList', store.force.getModelList)

const formRef = useTemplateRef<FormInstance>('formRef')

// 基本信息与节点数据初始化
Expand Down
11 changes: 7 additions & 4 deletions ui/src/workflow-canvas/nodes/form-node/index.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
<script setup lang="ts">
import { computed, inject, onMounted, useTemplateRef } from 'vue'
import { computed, inject, onMounted, useTemplateRef, provide } from 'vue'
import { cloneDeep } from 'lodash'
import type { FormInstance } from 'element-plus'
import type { FormField, VisibilityFieldOption } from '@/components/mk-dynamics-form'
import NodeContainer from '@/workflow-canvas/core/node-container/index.vue'
import type { WorkflowNodeModel } from '@/workflow-canvas/core/workflow-node'
import { handleNodeWheel } from '@/workflow-canvas/core/utils'
import FormSettingTable from './component/form-setting/FormSettingTable.vue'

import { useWorkflowStore } from '@/workflow-canvas/store'
defineOptions({ name: 'WorkflowFormNode' })

const apiType = (inject('apiType') as string) || 'workspace'
const store = useWorkflowStore(apiType)
const getModel = inject<() => WorkflowNodeModel>('getModel')!
const model = getModel()

// 为画布节点中的 ModelSelect 提供参数表单接口。
provide('getModelParamsForm', store.force.getModelParamsForm)
provide('getSelectModelList', store.force.getModelList)
interface FormNodeForm {
is_result: boolean
form_field_list: FormField[]
Expand Down
2 changes: 2 additions & 0 deletions ui/src/workflow-canvas/store/api/system-resource/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import modelAPI from '@/api/admin/workspace/model/model'
export default { getModelList: modelAPI.getModelList, getModelParamsForm: modelAPI.getModelParamsForm }
2 changes: 0 additions & 2 deletions ui/src/workflow-canvas/store/api/system/index.ts

This file was deleted.

2 changes: 1 addition & 1 deletion ui/src/workflow-canvas/store/api/workspace/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import modelAPI from '@/api/admin/workspace/model/model'
import providerAPI from '@/api/admin/model-provider'
export default { getModelList: modelAPI.getModelList, getProviderList: providerAPI.getProviderList }
export default { getModelList: modelAPI.getModelList, getProviderList: providerAPI.getProviderList, getModelParamsForm: modelAPI.getModelParamsForm }
76 changes: 43 additions & 33 deletions ui/src/workflow-canvas/store/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import type { Dict, ModelItem, ModelProviderItem } from '@/api/types'
import type { Dict, DynamicFormField, ModelItem, ModelProviderItem } from '@/api/types'

type ApiModule = { getModelList: (query?: Dict<unknown>) => Promise<ModelItem[]>; getProviderList: () => Promise<ModelProviderItem[]> }
// 底层 api/*/index.ts 提供的原始接口实现,不含缓存层能力。
type ApiModule = {
getModelList: (query?: Dict<unknown>) => Promise<ModelItem[]>
getProviderList: () => Promise<ModelProviderItem[]>
getModelParamsForm: (modelId: string) => Promise<DynamicFormField[]>
}

// useWorkflowStore 返回的包装接口:默认走缓存,通过 store.force.xxx() 强制刷新。
type WorkflowStore = ApiModule & { force: ApiModule }

const apiModules = import.meta.glob<{ default: ApiModule }>('./api/*/index.ts', { eager: true })

Expand All @@ -14,46 +22,48 @@ const apiMap = Object.fromEntries(
const cacheMap = new Map<string, Map<string, unknown>>()
const pendingMap = new Map<string, Map<string, Promise<unknown>>>()

export function useWorkflowStore(apiType: string): ApiModule {
const api = apiMap[apiType]
export function useWorkflowStore(apiType: string): WorkflowStore {
const api: ApiModule | undefined = apiMap[apiType]
if (!api) {
throw new Error(`[useWorkflowStore] unknown apiType: "${apiType}"`)
}
const resolvedApi: ApiModule = api

const cache = cacheMap.get(apiType) ?? new Map<string, unknown>()
cacheMap.set(apiType, cache)

const pending = pendingMap.get(apiType) ?? new Map<string, Promise<unknown>>()
pendingMap.set(apiType, pending)

return {
getModelList(query?: Dict<unknown>): Promise<ModelItem[]> {
const key = `model:${JSON.stringify(query ?? {})}`
if (cache.has(key)) return Promise.resolve(cache.get(key) as ModelItem[])
if (pending.has(key)) return pending.get(key) as Promise<ModelItem[]>

const promise = api.getModelList(query).then((list) => {
cache.set(key, list)
pending.delete(key)
return list
})

pending.set(key, promise)
return promise
},
getProviderList(): Promise<ModelProviderItem[]> {
const key = 'provider'
if (cache.has(key)) return Promise.resolve(cache.get(key) as ModelProviderItem[])
if (pending.has(key)) return pending.get(key) as Promise<ModelProviderItem[]>

const promise = api.getProviderList().then((list) => {
cache.set(key, list)
pending.delete(key)
return list
})

pending.set(key, promise)
return promise
},
// 统一的缓存 + 请求去重逻辑。force 时跳过缓存读取并覆盖缓存,但仍复用在途请求以避免重复触发。
function withCache<T>(key: string, fetcher: () => Promise<T>, force = false): Promise<T> {
if (!force && cache.has(key)) return Promise.resolve(cache.get(key) as T)
if (pending.has(key)) return pending.get(key) as Promise<T>

const promise = fetcher().then((data) => {
cache.set(key, data)
pending.delete(key)
return data
})

pending.set(key, promise)
return promise
}

// 生成一组接口方法;force 为 true 时对应的调用会跳过缓存强制刷新。
function build(force: boolean): ApiModule {
return {
getModelList(query?: Dict<unknown>): Promise<ModelItem[]> {
return withCache(`model:${JSON.stringify(query ?? {})}`, () => resolvedApi.getModelList(query), force)
},
getProviderList(): Promise<ModelProviderItem[]> {
return withCache('provider', () => resolvedApi.getProviderList(), force)
},
getModelParamsForm(modelId: string): Promise<DynamicFormField[]> {
return withCache(`modelParamsForm:${modelId}`, () => resolvedApi.getModelParamsForm(modelId), force)
},
}
}

return { ...build(false), force: build(true) }
}
Loading