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
10 changes: 10 additions & 0 deletions ui/src/components/ai-chat/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,21 @@ import { t } from '@/locales'
import bus from '@/bus'
import { throttle } from 'lodash-es'
import { copyClick } from '@/utils/clipboard'
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'

provide('upload', (file: any, loading?: Ref<boolean>) => {
return props.type === 'debug-ai-chat'
? applicationApi.postUploadFile(file, 'TEMPORARY_120_MINUTE', 'TEMPORARY_120_MINUTE', loading)
: chatAPI.postUploadFile(file, chartOpenId.value, 'CHAT', loading)
})
provide('getSelectModelList', (params: any) => {
if (route.path.includes('resource-management')) {
return loadSharedApi({ type: 'model', systemType: 'systemManage' }).getSelectModelList(params)
} else {
return loadSharedApi({ type: 'model', systemType: 'workspace' }).getSelectModelList(params)
}
})

const transcribing = ref<boolean>(false)
defineOptions({ name: 'AiChat' })
const route = useRoute()
Expand Down
5 changes: 5 additions & 0 deletions ui/src/components/dynamics-form/constructor/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,10 @@ const input_type_list = [
label: t('dynamicsForm.input_type_list.UploadInput'),
value: 'UploadInput',
},

{
label: t('dynamicsForm.input_type_list.Model'),
value: 'Model',
},
]
export { input_type_list }
311 changes: 311 additions & 0 deletions ui/src/components/dynamics-form/constructor/items/ModelConstructor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
<template>
<el-form-item
:label="$t('views.model.modelForm.model_type.label')"
required
prop="model_type"
:rules="[{ required: true, message: $t('views.model.modelForm.model_type.requiredMessage') }]"
>
<el-select
v-model="formValue.model_type"
:placeholder="$t('views.model.modelForm.model_type.placeholder')"
@change="handleModelTypeChange"
>
<el-option
v-for="item in modelTypeList"
:key="item.value"
:label="item.text"
:value="item.value"
/>
</el-select>
</el-form-item>

<el-form-item
:label="$t('views.model.modelForm.model_type.kexuan', '可选模型')"
required
prop="provider_list"
:rules="[
{
required: true,
message: $t('views.model.modelForm.model_type.requiredMessage'),
type: 'array',
},
]"
>
<div class="flex-between w-full">
<ModelSelect
multiple
v-model="selectedIds"
:placeholder="$t('views.application.form.voicePlay.placeholder')"
:options="groupedModelOptions"
@change="handleProviderListChange"
:model-type="formValue.model_type"
>
<template #tag>
<el-tag
v-for="provider in formValue.provider_list"
:key="provider.model_id"
closable
type="info"
@close="removeSelectedModel(provider.model_id)"
style="margin-right: 4px"
>
<div class="flex align-center">
<span
v-html="
relatedObject(
providerOptions,
getModelInfo(provider.model_id)?.provider,
'provider',
)?.icon
"
class="model-icon mr-4"
></span>
<span class="mr-4">{{
relatedObject(
providerOptions,
getModelInfo(provider.model_id)?.provider,
'provider',
)?.name
}}</span>
<span class="mr-4"> > </span>
<span>{{ getModelInfo(provider.model_id)?.name }}</span>
</div>
</el-tag>
</template>
</ModelSelect>
</div>
</el-form-item>
<el-form-item
:label="$t('views.model.modelForm.model_type.moren', '默认模型')"
required
:rules="[
{
required: true,
message: $t('views.model.modelForm.model_type.requiredMessage'),
},
]"
v-if="formValue.provider_list && formValue.provider_list.length > 0"
>
<div class="flex-between w-full">
<el-select
v-model="formValue.default_value"
value-key="model_id"
placeholder="请选择默认模型"
>
<el-option-group
v-for="(modelList, providerName) in selectedModelsOptions"
:key="providerName"
:label="relatedObject(providerOptions, providerName, 'provider')?.name"
>
<el-option
v-for="item in modelList"
:key="item.id"
:label="item.name"
:value="getProviderItem(item.id)"
>
<div class="flex">
<span
v-html="relatedObject(providerOptions, providerName, 'provider')?.icon"
class="model-icon mr-8"
></span>
<span>{{ item.name }}</span>
</div>
</el-option>
</el-option-group>
</el-select>
<div class="ml-8">
<el-button @click="openParamSetting" @refreshForm="handleParamRefresh">
<el-icon>
<Operation />
</el-icon>
</el-button>
</div>
</div>
</el-form-item>
<AIModeParamSettingDialog ref="AIModeParamSettingDialogRef" @refresh="handleParamRefresh" />
</template>
<script setup lang="ts">
import { computed, onMounted, inject, ref } from 'vue'
import { modelTypeList } from '@/views/model/component/data'
import AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'
import { groupBy } from 'lodash'
import { providerList as providerOptions } from '../../items/model/provider-data'
import { relatedObject } from '@/utils/array'

const getSelectModelList = inject('getSelectModelList') as Function
const getModelParamsForm = inject('getModelParamsForm') as Function

const props = defineProps<{
modelValue: any
}>()

const emit = defineEmits(['update:modelValue'])

const formValue = computed({
set: (item: any) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
},
})

const selectedIds = computed({
get: () => (formValue.value.provider_list || []).map((p: any) => p.model_id),
set: (newIds: string[]) => {
const oldList = formValue.value.provider_list || []
const newList = newIds.map((id: string) => {
const existing = oldList.find((p: any) => p.model_id === id)
return existing || { model_id: id, model_params_setting: {} }
})
formValue.value.provider_list = newList
// find new model then get it default value
const oldIds = oldList.map((p: any) => p.model_id)
const addedIds = newIds.filter((id: string) => !oldIds.includes(id))
addedIds.forEach((id: string) => {
fetchDefaultParams(id)
})
},
})

const selectedModelsOptions = computed(() => {
const ids = (formValue.value.provider_list || []).map((p: any) => p.model_id)
const filtered = rawModelOptions.value.filter((m: any) => ids.includes(m.id))
return groupBy(filtered, 'provider')
})

function fetchDefaultParams(modelId: string) {
if (!getModelParamsForm) return
getModelParamsForm(modelId).then((res: any) => {
const formFields = res?.data || []
const defaults = (res?.data || [])
.map((item: any) => {
if (item.show_default_value === false) {
return { [item.field]: undefined }
} else {
return { [item.field]: item.default_value }
}
})
.reduce((x: any, y: any) => ({ ...x, ...y }), {})
// update to model_params_setting
const target = formValue.value.provider_list.find((p: any) => p.model_id === modelId)
if (target) {
target.model_params_setting = defaults
target.model_form_field = formFields
}
})
}
const AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()

const openParamSetting = () => {
const dv = formValue.value.default_value
if (!dv?.model_id) return
AIModeParamSettingDialogRef.value?.open(dv.model_id, undefined, dv?.model_params_setting)
}

const handleParamRefresh = (paramData: any) => {
const dv = formValue.value.default_value
if (dv?.model_id) {
formValue.value.default_value = { ...dv, model_params_setting: paramData }
const target = formValue.value.provider_list.find((p: any) => p.model_id === dv.model_id)
if (target) {
target.model_params_setting = paramData
}
}
}

const rawModelOptions = ref<any[]>([])
const groupedModelOptions = ref<Record<string, any[]>>({})

const fetchModelByType = (type: string) => {
if (!type || !getSelectModelList) return

getSelectModelList({ model_type: type }).then((res: any) => {
rawModelOptions.value = res?.data || []

groupedModelOptions.value = groupBy(res?.data, 'provider')
})
}

const handleModelTypeChange = (val: string) => {
formValue.value.provider_list = []
formValue.value.default_value = ''

if (val) {
fetchModelByType(val)
} else {
rawModelOptions.value = []
groupedModelOptions.value = {}
}
}

const getModelInfo = (modelId: string) => {
return rawModelOptions.value.find((item: any) => item.id === modelId)
}

// default_value 赋值
const getProviderItem = (modelId: string) => {
const found = formValue.value.provider_list.find((p: any) => p.model_id === modelId)
if (found) {
const { model_form_field, ...rest } = found
return rest
}
return { model_id: modelId, model_params_setting: {} }
}

function handleProviderListChange() {
const ids = (formValue.value.provider_list || []).map((p: any) => p.model_id)
const currentId = formValue.value.default_value?.model_id

if (currentId && !ids.includes(currentId)) {
formValue.value.default_value = {}
}
}

function removeSelectedModel(modelId: string) {
formValue.value.provider_list = formValue.value.provider_list.filter(
(p: any) => p.model_id !== modelId,
)
handleProviderListChange()
}

const getData = () => {
const providerList = (formValue.value.provider_list || []).map((p: any) => {
const modelInfo = getModelInfo(p.model_id)
return {
model_id: p.model_id,
model_name: modelInfo?.name || '',
provider: modelInfo?.provider || '',
model_params_setting: p.model_params_setting || {},
model_form_field: p.model_form_field || [],
}
})
return {
input_type: 'Model',
model_type: formValue.value.model_type,
default_value: formValue.value.default_value,
attrs: {
provider_list: providerList,
},
}
}

const rander = (form_data: any) => {
formValue.value.model_type = form_data.model_type
formValue.value.provider_list = form_data.attrs?.provider_list || []
formValue.value.default_value = form_data.default_value || ''

if (form_data.model_type) {
fetchModelByType(form_data.model_type)
}
}

defineExpose({ getData, rander })
</script>
<style lang="scss" scoped>
// AI模型选择:添加模型hover样式

.model-icon {
width: 18px;
}
</style>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review and Optimization Suggestions

Regularity and Issues

  1. Variable Naming:

    • rawModelOptions should be defined at the component level instead of inside a method.
    • Rename groupedModelOption to groupedModelOptions for consistency.
  2. Template Tags:

    • Ensure that all Vue template tags are properly closed (>).
  3. Event Handling:

    • In onMounted, call fetchModelByType with the initial formValue.model_type.
  4. Data Fetching:

    • The use of inject is not necessary here since these functions do not need access to parent properties.
  5. Dynamic Prop Binding:

    • Use dynamic binding for :model-type="modelValue.model_type" directly without needing an intermediate variable.
  6. Computed Properties:

    • Remove unnecessary variables like oldValuesList and oldIds.
  7. Style Declaration:

    • Move the hover styles to the .app-container class, which seems logical given their usage across multiple components.

Potential Improvements

  1. Code Reusability:

    • Extract common logic into functions such as filtering model options by provider.
  2. Error Handling:

    • Add error handling for network requests when fetching models and default params.
  3. Optimization:

    • Cache results from fetches if applicable and avoid redundant computations.

Here's a revised version:

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import {
  groupBy,
} from 'lodash';

const getSelectModelList = inject('getSelectModelList') as Function;
const getModelParamsForm = inject('getModelParamsForm') as Function;

const props = defineProps<{
  modelValue: any;
}>();

const emit = defineEmits(['update:modelValue']);

const formValue = computed({
  set: (item: any) => emit('update:modelValue', item),
  get: () => props.modelValue,
});

let rawModelOptions = reactive([]);
let groupedModelOptions = ref({});
const selectedIds = ref([]);

watchEffect(() => {
  handleModelTypeChange(formValue.value.model_type);
});

onMounted(async () => {
  await handleModelTypeChange(props.modelValue.model_type);
})

async function fetchData() {
  if (!props.modelValue.model_type) return [];

  try {
    const res = await getSelectModelList({ model_type: props.modelValue.model_type });
    return res.data || [];
  } catch (error) {
    console.error("Failed to fetch models:", error);
    return [];
  }
}

function fetchDefaultParams(modelId: string) {
  return getModelParamsForm(modelId)
    .then((res: any) => {
      return res.data;
    })
    .catch(error => {
      console.error(`Failed to fetch default parameters for ${modelId}:`, error);
      return {};
    });
}

async function initializeState() {
  const models = await fetchData();
  rawModelOptions.value = models;

  Object.assign(groupedModelOptions.value, groupBy(models, 'provider'))
}

computed({
  set(value) {
    emit('update:modelValue', value);
  },
})();

effect(() => {
  selectedIds.value = (
    formValue.value.provider_list || []
  ).flatMap(item => item.model_id);
})
  
watchEffect(async () => {
  formValue.value.provider_list.splice(0);

  if (selectedIds.value.length > 0) {
    const defaultParamsPromises = selectedIds.value.map(fetchDefaultParams);
    
    Promise.all(defaultParamsPromises).then(paramsSetMap => {
      for(const idAndParams of paramsSetMap.entries()) {
        const [id, params] = idAndParams as Array<any>;
        const originalEntry = formValue.value.provider_list.find(x => x.model_id === id); 
        if(originalEntry) {
          originalEntry.model_params_setting = params;  
        }
      }

      // Find missing defaults after setting them
      const idsToFetchNewDefaultsFor = formValue.value.provider_list.filter(
        entry => !entry.provider_params_settings ||
        Object.values(entry.provider_params_settings ?? {}).some(v => v === undefined)

      );

      idsToFetchNewDefaultsFor.forEach(id => fetchDefaultParams(id));
      
    }).catch(err => console.error("Failed to refresh default settings:", err));
  }

  if(selectedIds.value.length == 0){
    formValue.value.default_value ||= "";
  }
})

// ... Rest of your methods remain unchanged ...

Style Improvement

Ensure that your <style> block uses classes effectively and keeps the CSS clean:

<style lang="scss" scoped>
.app-container {
  // Global hover styles can go here instead
}
</style>

.template-container {
  // Hover styles specific to this component can remain under this section
}

This improved code follows best practices, provides better organization, and enhances performance through caching and reusability where appropriate.

3 changes: 3 additions & 0 deletions ui/src/components/dynamics-form/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const render = (
| (() => Promise<Result<Array<FormField>>>),
data?: Dict<any>,
) => {
console.log(data, '-----')
formFieldList.value = []
nextTick(() => {
if (typeof render_data == 'string') {
Expand Down Expand Up @@ -246,6 +247,7 @@ const render = (
}
const getFormDefaultValue = (fieldList: Array<any>, form_data?: any) => {
form_data = form_data ? form_data : {}
console.log(form_data)
const value = fieldList
.map((item) => {
if (form_data[item.field] !== undefined) {
Expand Down Expand Up @@ -274,6 +276,7 @@ const getFormDefaultValue = (fieldList: Array<any>, form_data?: any) => {
return {}
})
.reduce((x, y) => ({ ...x, ...y }), {})
console.log(value)
return value
}
/**
Expand Down
Loading
Loading