Fix: simplify fetchOpenAICompatibleModelsInfo function

- fix clickoutside on inputwithselect component
- Add type-search on inputwithselect
This commit is contained in:
Joseph Hau committed 2025-03-19 21:05:32 +08:00
1 parent 074336fa53
commit 4edf9fe85b
2 files changed
+52 -50

No files matched your search

+4 -41
View File
@@ -531,62 +531,25 @@ export class LLMService implements ILLMService {
const response = await openai.models.list();
console.log('API返回的原始模型列表:', response);
// 检查响应格式
// 只处理标准 OpenAI 格式
if (response && response.data && Array.isArray(response.data)) {
// 标准 OpenAI 格式
return response.data
.map(model => ({
id: model.id,
name: model.id
}))
.sort((a, b) => a.id.localeCompare(b.id));
} else if (response && Array.isArray(response)) {
// 可能是一个简单的模型 ID 数组
return response
.map(modelId => ({
id: typeof modelId === 'string' ? modelId :
typeof modelId === 'object' && modelId !== null ?
(modelId.id || modelId.name || String(modelId)) : String(modelId),
name: typeof modelId === 'string' ? modelId :
typeof modelId === 'object' && modelId !== null ?
(modelId.name || modelId.id || String(modelId)) : String(modelId)
}))
.sort((a, b) => a.id.localeCompare(b.id));
} else if (response && typeof response === 'object') {
// 尝试从对象中提取有用信息
const possibleArrayProps = ['models', 'data', 'items', 'results', 'list'];
for (const prop of possibleArrayProps) {
if (response[prop as keyof typeof response] &&
Array.isArray(response[prop as keyof typeof response])) {
const modelArray = response[prop as keyof typeof response] as any[];
return modelArray
.map(model => {
// 处理不同类型的模型对象
if (typeof model === 'string') {
return { id: model, name: model };
} else if (typeof model === 'object' && model !== null) {
const id = model.id || model.name || model.model || String(model);
const name = model.name || model.id || model.model || String(model);
return { id: String(id), name: String(name) };
} else {
return { id: String(model), name: String(model) };
}
})
.sort((a, b) => a.id.localeCompare(b.id));
}
}
}
// 如果无法解析响应格式,记录并返回空数组
console.warn('无法解析API响应格式:', response);
// 如果格式不匹配标准格式,记录并返回空数组
console.warn('API返回格式与预期不符:', response);
return [];
} catch (error: any) {
console.error('获取模型列表失败:', error);
console.log('错误详情:', error.response?.data || error.message);
// 对于自定义接口,返回空数组
// 发生错误时返回空数组
return [];
}
}
+48 -9
View File
@@ -1,9 +1,10 @@
<template>
<div class="relative">
<div ref="componentRef" class="relative">
<div class="flex">
<input
ref="inputRef"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
@input="handleInput"
:type="type"
:required="required"
:placeholder="placeholder"
@@ -53,12 +54,12 @@
<div v-if="isLoading" class="p-2 text-center theme-manager-text-secondary">
{{ loadingText }}
</div>
<div v-else-if="options.length === 0" class="p-2 text-center theme-manager-text-secondary">
<div v-else-if="filteredOptions.length === 0" class="p-2 text-center theme-manager-text-secondary">
{{ noOptionsText }}
</div>
<ul v-else class="py-1">
<li
v-for="option in options"
v-for="option in filteredOptions"
:key="option.value"
@click="selectOption(option)"
:class="[
@@ -74,7 +75,7 @@
</template>
<script setup>
import { ref, defineProps, defineEmits, onMounted, onUnmounted } from 'vue';
import { ref, defineProps, defineEmits, onMounted, onUnmounted, computed } from 'vue';
const props = defineProps({
modelValue: {
@@ -126,14 +127,38 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue', 'select', 'fetchOptions']);
const isOpen = ref(false);
const inputRef = ref(null);
const searchText = ref('');
// 根据输入内容筛选选项
const filteredOptions = computed(() => {
if (!searchText.value) return props.options;
return props.options.filter(option =>
option.label.toLowerCase().includes(searchText.value.toLowerCase()) ||
option.value.toLowerCase().includes(searchText.value.toLowerCase())
);
});
// 处理输入事件
const handleInput = (event) => {
const value = event.target.value;
emit('update:modelValue', value);
searchText.value = value;
};
// Toggle dropdown visibility
const toggleDropdown = async () => {
isOpen.value = !isOpen.value;
// If dropdown is being opened and we have a fetch function, call it
// 如果打开下拉菜单,聚焦到输入框
if (isOpen.value) {
emit('fetchOptions');
// 等待DOM更新后聚焦
setTimeout(() => {
if (inputRef.value) {
inputRef.value.focus();
}
}, 10);
}
};
@@ -142,25 +167,39 @@ const selectOption = (option) => {
emit('update:modelValue', option.value);
emit('select', option);
isOpen.value = false;
searchText.value = '';
};
// Close dropdown when clicking outside
const handleClickOutside = (event) => {
if (isOpen.value && !event.target.closest('.relative')) {
console.log('Click detected', {
isOpen: isOpen.value,
isComponent: componentRef.value && componentRef.value.contains(event.target),
target: event.target
});
// 只有在下拉菜单打开且点击的是组件外部时才关闭下拉菜单
if (isOpen.value && componentRef.value && !componentRef.value.contains(event.target)) {
console.log('Closing dropdown');
isOpen.value = false;
searchText.value = '';
}
};
// 组件引用
const componentRef = ref(null);
// Add and remove event listener
onMounted(() => {
if (typeof document !== 'undefined') {
document.addEventListener('click', handleClickOutside);
// 使用捕获阶段以确保事件能够被正确捕获
document.addEventListener('mousedown', handleClickOutside, true);
}
});
onUnmounted(() => {
if (typeof document !== 'undefined') {
document.removeEventListener('click', handleClickOutside);
document.removeEventListener('mousedown', handleClickOutside, true);
}
});
</script>