update models settings, add custom openai compatible

This commit is contained in:
duanfuxiang 2025-03-18 09:39:25 +08:00
parent 9977b4ee77
commit 76288377c3
2 changed files with 67 additions and 44 deletions

View File

@ -44,6 +44,7 @@ class LLMManager implements LLMManagerInterface {
private siliconflowProvider: OpenAICompatibleProvider private siliconflowProvider: OpenAICompatibleProvider
private alibabaQwenProvider: OpenAICompatibleProvider private alibabaQwenProvider: OpenAICompatibleProvider
private ollamaProvider: OllamaProvider private ollamaProvider: OllamaProvider
private openaiCompatibleProvider: OpenAICompatibleProvider
private isInfioEnabled: boolean private isInfioEnabled: boolean
constructor(settings: InfioSettings) { constructor(settings: InfioSettings) {
@ -57,6 +58,7 @@ class LLMManager implements LLMManagerInterface {
this.googleProvider = new GeminiProvider(settings.googleProvider.apiKey) this.googleProvider = new GeminiProvider(settings.googleProvider.apiKey)
this.groqProvider = new GroqProvider(settings.groqProvider.apiKey) this.groqProvider = new GroqProvider(settings.groqProvider.apiKey)
this.ollamaProvider = new OllamaProvider(settings.groqProvider.baseUrl) this.ollamaProvider = new OllamaProvider(settings.groqProvider.baseUrl)
this.openaiCompatibleProvider = new OpenAICompatibleProvider(settings.openaicompatibleProvider.apiKey, settings.openaicompatibleProvider.baseUrl)
this.isInfioEnabled = !!settings.infioProvider.apiKey this.isInfioEnabled = !!settings.infioProvider.apiKey
} }
@ -160,6 +162,8 @@ class LLMManager implements LLMManagerInterface {
return await this.groqProvider.streamResponse(model, request, options) return await this.groqProvider.streamResponse(model, request, options)
case ApiProvider.Ollama: case ApiProvider.Ollama:
return await this.ollamaProvider.streamResponse(model, request, options) return await this.ollamaProvider.streamResponse(model, request, options)
case ApiProvider.OpenAICompatible:
return await this.openaiCompatibleProvider.streamResponse(model, request, options)
} }
} }
} }

View File

@ -160,10 +160,10 @@ export const ComboBoxComponent: React.FC<ComboBoxComponentProps> = ({
isEmbedding = false, isEmbedding = false,
updateModel, updateModel,
}) => { }) => {
// 提供商选择状态 // provider state
const [modelProvider, setModelProvider] = useState(provider); const [modelProvider, setModelProvider] = useState(provider);
// 搜索输入状态 // search state
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0); const [selectedIndex, setSelectedIndex] = useState(0);
@ -175,12 +175,12 @@ export const ComboBoxComponent: React.FC<ComboBoxComponentProps> = ({
// Replace useMemo with useEffect for async fetching // Replace useMemo with useEffect for async fetching
useEffect(() => { useEffect(() => {
const fetchModelIds = async () => { const fetchModelIds = async () => {
const ids = isEmbedding const ids = isEmbedding
? GetEmbeddingProviderModelIds(modelProvider) ? GetEmbeddingProviderModelIds(modelProvider)
: await GetProviderModelIds(modelProvider); : await GetProviderModelIds(modelProvider);
setModelIds(ids); setModelIds(ids);
}; };
fetchModelIds(); fetchModelIds();
}, [modelProvider, isEmbedding]); }, [modelProvider, isEmbedding]);
@ -191,7 +191,7 @@ export const ComboBoxComponent: React.FC<ComboBoxComponentProps> = ({
})) }))
}, [modelIds]) }, [modelIds])
// 初始化 fuse用于模糊搜索简单配置 threshold 可按需调整 // fuse, used for fuzzy search, simple configuration threshold can be adjusted as needed
const fuse: Fuse<SearchableItem> = useMemo(() => { const fuse: Fuse<SearchableItem> = useMemo(() => {
return new Fuse<SearchableItem>(searchableItems, { return new Fuse<SearchableItem>(searchableItems, {
keys: ["html"], keys: ["html"],
@ -218,7 +218,7 @@ export const ComboBoxComponent: React.FC<ComboBoxComponentProps> = ({
const listRef = useRef<HTMLDivElement>(null); const listRef = useRef<HTMLDivElement>(null);
const itemRefs = useRef<Array<HTMLDivElement | null>>([]); const itemRefs = useRef<Array<HTMLDivElement | null>>([]);
// 当选中项发生变化时,滚动到可视区域内 // when selected index changes, scroll to visible area
useEffect(() => { useEffect(() => {
if (itemRefs.current[selectedIndex]) { if (itemRefs.current[selectedIndex]) {
itemRefs.current[selectedIndex]?.scrollIntoView({ itemRefs.current[selectedIndex]?.scrollIntoView({
@ -234,7 +234,7 @@ export const ComboBoxComponent: React.FC<ComboBoxComponentProps> = ({
<Popover.Root modal={false} open={isOpen} onOpenChange={setIsOpen}> <Popover.Root modal={false} open={isOpen} onOpenChange={setIsOpen}>
<Popover.Trigger asChild> <Popover.Trigger asChild>
<div className="infio-llm-setting-item-control"> <div className="infio-llm-setting-item-control">
<span className="infio-llm-setting-model-id">{modelId}</span> <span className="infio-llm-setting-model-id">[{modelProvider}]{modelId}</span>
</div> </div>
</Popover.Trigger> </Popover.Trigger>
<Popover.Content <Popover.Content
@ -260,45 +260,64 @@ export const ComboBoxComponent: React.FC<ComboBoxComponentProps> = ({
</option> </option>
))} ))}
</select> </select>
<input {modelIds.length > 0 ? (
type="text" <input
className="infio-llm-setting-item-search" type="text"
placeholder="search model..." className="infio-llm-setting-item-search"
value={searchTerm} placeholder="search model..."
onChange={(e) => { value={searchTerm}
setSearchTerm(e.target.value); onChange={(e) => {
setSelectedIndex(0); setSearchTerm(e.target.value);
}} setSelectedIndex(0);
onKeyDown={(e) => { }}
switch (e.key) { onKeyDown={(e) => {
case "ArrowDown": switch (e.key) {
e.preventDefault(); case "ArrowDown":
setSelectedIndex((prev) => e.preventDefault();
Math.min(prev + 1, filteredOptions.length - 1) setSelectedIndex((prev) =>
); Math.min(prev + 1, filteredOptions.length - 1)
break; );
case "ArrowUp": break;
e.preventDefault(); case "ArrowUp":
setSelectedIndex((prev) => Math.max(prev - 1, 0)); e.preventDefault();
break; setSelectedIndex((prev) => Math.max(prev - 1, 0));
case "Enter": { break;
e.preventDefault(); case "Enter": {
const selectedOption = filteredOptions[selectedIndex]; e.preventDefault();
if (selectedOption) { const selectedOption = filteredOptions[selectedIndex];
updateModel(modelProvider, selectedOption.id); if (selectedOption) {
updateModel(modelProvider, selectedOption.id);
setSearchTerm("");
setIsOpen(false);
}
break;
}
case "Escape":
e.preventDefault();
setIsOpen(false);
setSearchTerm(""); setSearchTerm("");
break;
}
}}
/>
) : (
<input
type="text"
className="infio-llm-setting-item-search"
placeholder="input custom model name"
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
updateModel(modelProvider, searchTerm);
setIsOpen(false); setIsOpen(false);
} }
break; }}
} />
case "Escape": )}
e.preventDefault();
setIsOpen(false);
setSearchTerm("");
break;
}
}}
/>
</div> </div>
{filteredOptions.map((option, index) => ( {filteredOptions.map((option, index) => (
<Popover.Close key={option.id} asChild> <Popover.Close key={option.id} asChild>