Fuzzy search openrouter models

This commit is contained in:
Saoud Rizwan
2024-10-04 00:57:58 -04:00
parent ecff59004d
commit 4573010e16
2 changed files with 56 additions and 15 deletions

View File

@@ -85,6 +85,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
((a.tokensIn || 0) + (a.tokensOut || 0) + (a.cacheWrites || 0) + (a.cacheReads || 0)) ((a.tokensIn || 0) + (a.tokensOut || 0) + (a.cacheWrites || 0) + (a.cacheReads || 0))
) )
case "mostRelevant": case "mostRelevant":
// NOTE: you must never sort directly on object since it will cause members to be reordered
return searchQuery ? 0 : b.ts - a.ts // Keep fuse order if searching, otherwise sort by newest return searchQuery ? 0 : b.ts - a.ts // Keep fuse order if searching, otherwise sort by newest
case "newest": case "newest":
default: default:
@@ -427,7 +428,10 @@ const ExportButton = ({ itemId }: { itemId: string }) => (
) )
// https://gist.github.com/evenfrost/1ba123656ded32fb7a0cd4651efd4db0 // https://gist.github.com/evenfrost/1ba123656ded32fb7a0cd4651efd4db0
const highlight = (fuseSearchResult: FuseResult<any>[], highlightClassName: string = "history-item-highlight") => { export const highlight = (
fuseSearchResult: FuseResult<any>[],
highlightClassName: string = "history-item-highlight"
) => {
const set = (obj: Record<string, any>, path: string, value: any) => { const set = (obj: Record<string, any>, path: string, value: any) => {
const pathValue = path.split(".") const pathValue = path.split(".")
let i: number let i: number

View File

@@ -1,11 +1,13 @@
import React, { useMemo, useState, useRef, useEffect, memo } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import Fuse from "fuse.js"
import React, { memo, useEffect, useMemo, useRef, useState } from "react"
import { useRemark } from "react-remark"
import { useMount } from "react-use"
import styled from "styled-components" import styled from "styled-components"
import { useExtensionState } from "../../context/ExtensionStateContext" import { useExtensionState } from "../../context/ExtensionStateContext"
import { useMount } from "react-use"
import { vscode } from "../../utils/vscode" import { vscode } from "../../utils/vscode"
import { ModelInfoView, normalizeApiConfiguration } from "./ApiOptions" import { ModelInfoView, normalizeApiConfiguration } from "./ApiOptions"
import { useRemark } from "react-remark" import { highlight } from "../history/HistoryView"
const OpenRouterModelPicker: React.FC = () => { const OpenRouterModelPicker: React.FC = () => {
const { apiConfiguration, setApiConfiguration, openRouterModels } = useExtensionState() const { apiConfiguration, setApiConfiguration, openRouterModels } = useExtensionState()
@@ -31,12 +33,6 @@ const OpenRouterModelPicker: React.FC = () => {
vscode.postMessage({ type: "refreshOpenRouterModels" }) vscode.postMessage({ type: "refreshOpenRouterModels" })
}) })
const filteredModelIds = useMemo(() => {
return Object.keys(openRouterModels)
.filter((modelId) => modelId.toLowerCase().includes(searchTerm.toLowerCase()))
.sort((a, b) => a.localeCompare(b))
}, [openRouterModels, searchTerm])
useEffect(() => { useEffect(() => {
const handleClickOutside = (event: MouseEvent) => { const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
@@ -50,8 +46,45 @@ const OpenRouterModelPicker: React.FC = () => {
} }
}, []) }, [])
const searchableItems = useMemo(() => {
return Object.keys(openRouterModels)
.sort((a, b) => a.localeCompare(b))
.map((id) => ({
id,
html: id,
}))
}, [openRouterModels])
const fuse = useMemo(() => {
return new Fuse(searchableItems, {
keys: ["html"], // highlight function will update this
threshold: 0.6,
shouldSort: true,
isCaseSensitive: false,
ignoreLocation: false,
includeMatches: true,
minMatchCharLength: 1,
})
}, [searchableItems])
const modelSearchResults = useMemo(() => {
let results: { id: string; html: string }[] = searchTerm
? highlight(fuse.search(searchTerm), "model-item-highlight")
: searchableItems
// results.sort((a, b) => a.id.localeCompare(b.id)) NOTE: sorting like this causes ids in objects to be reordered and mismatched
return results
}, [searchableItems, searchTerm, fuse])
return ( return (
<> <>
<style>
{`
.model-item-highlight {
background-color: var(--vscode-editor-findMatchHighlightBackground);
color: inherit;
}
`}
</style>
<DropdownWrapper ref={dropdownRef}> <DropdownWrapper ref={dropdownRef}>
<label htmlFor="model-search"> <label htmlFor="model-search">
<span style={{ fontWeight: 500 }}>Model</span> <span style={{ fontWeight: 500 }}>Model</span>
@@ -65,7 +98,7 @@ const OpenRouterModelPicker: React.FC = () => {
setIsDropdownVisible(true) setIsDropdownVisible(true)
}} }}
onFocus={() => setIsDropdownVisible(true)} onFocus={() => setIsDropdownVisible(true)}
style={{ width: "100%", zIndex: 1001 }}> style={{ width: "100%" }}>
{searchTerm && ( {searchTerm && (
<div <div
className="input-icon-button codicon codicon-close" className="input-icon-button codicon codicon-close"
@@ -83,10 +116,14 @@ const OpenRouterModelPicker: React.FC = () => {
</VSCodeTextField> </VSCodeTextField>
{isDropdownVisible && ( {isDropdownVisible && (
<DropdownList> <DropdownList>
{filteredModelIds.map((modelId) => ( {modelSearchResults.map((item) => (
<DropdownItem key={modelId} onClick={() => handleModelChange(modelId)}> <DropdownItem
{modelId} key={item.id}
</DropdownItem> onClick={() => handleModelChange(item.id)}
dangerouslySetInnerHTML={{
__html: item.html,
}}
/>
))} ))}
</DropdownList> </DropdownList>
)} )}