refactor(tui): replace v2 sync with data context (#31826)
This commit is contained in:
parent
69623c2b79
commit
47a45601fd
16 changed files with 1113 additions and 2380 deletions
|
|
@ -1,17 +1,17 @@
|
|||
import { createMemo, createSignal } from "solid-js"
|
||||
import { useLocal } from "../context/local"
|
||||
import { useSync } from "../context/sync"
|
||||
import { map, pipe, flatMap, entries, filter, sortBy, take } from "remeda"
|
||||
import { map, pipe, filter, sortBy, take } from "remeda"
|
||||
import { DialogSelect } from "../ui/dialog-select"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
|
||||
import { DialogVariant } from "./dialog-variant"
|
||||
import * as fuzzysort from "fuzzysort"
|
||||
import { useConnected } from "./use-connected"
|
||||
import { useData } from "../context/data"
|
||||
|
||||
export function DialogModel(props: { providerID?: string }) {
|
||||
const local = useLocal()
|
||||
const sync = useSync()
|
||||
const data = useData()
|
||||
const dialog = useDialog()
|
||||
const [query, setQuery] = createSignal("")
|
||||
|
||||
|
|
@ -29,19 +29,21 @@ export function DialogModel(props: { providerID?: string }) {
|
|||
function toOptions(items: typeof favorites, category: string) {
|
||||
if (!showSections) return []
|
||||
return items.flatMap((item) => {
|
||||
const provider = sync.data.provider.find((x) => x.id === item.providerID)
|
||||
const provider = data.location.provider.list()?.find((provider) => provider.id === item.providerID)
|
||||
if (!provider) return []
|
||||
const model = provider.models[item.modelID]
|
||||
const model = data.location.model
|
||||
.list()
|
||||
?.find((model) => model.providerID === item.providerID && model.id === item.modelID)
|
||||
if (!model) return []
|
||||
return [
|
||||
{
|
||||
key: item,
|
||||
value: { providerID: provider.id, modelID: model.id },
|
||||
title: model.name ?? item.modelID,
|
||||
title: model.name,
|
||||
description: provider.name,
|
||||
category,
|
||||
disabled: provider.id === "opencode" && model.id.includes("-nano"),
|
||||
footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
|
||||
footer: model.cost[0]?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
|
||||
onSelect: () => {
|
||||
onSelect(provider.id, model.id)
|
||||
},
|
||||
|
|
@ -59,42 +61,45 @@ export function DialogModel(props: { providerID?: string }) {
|
|||
)
|
||||
|
||||
const providerOptions = pipe(
|
||||
sync.data.provider,
|
||||
data.location.model.list() ?? [],
|
||||
filter((model) => model.status !== "deprecated"),
|
||||
filter((model) => (props.providerID ? model.providerID === props.providerID : true)),
|
||||
sortBy(
|
||||
(provider) => provider.id !== "opencode",
|
||||
(provider) => provider.name,
|
||||
),
|
||||
flatMap((provider) =>
|
||||
pipe(
|
||||
provider.models,
|
||||
entries(),
|
||||
filter(([_, info]) => info.status !== "deprecated"),
|
||||
filter(([_, info]) => (props.providerID ? info.providerID === props.providerID : true)),
|
||||
map(([model, info]) => ({
|
||||
value: { providerID: provider.id, modelID: model },
|
||||
title: info.name ?? model,
|
||||
releaseDate: info.release_date,
|
||||
description: favorites.some((item) => item.providerID === provider.id && item.modelID === model)
|
||||
? "(Favorite)"
|
||||
: undefined,
|
||||
category: connected() ? provider.name : undefined,
|
||||
disabled: provider.id === "opencode" && model.includes("-nano"),
|
||||
footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
|
||||
onSelect() {
|
||||
onSelect(provider.id, model)
|
||||
},
|
||||
})),
|
||||
filter((x) => {
|
||||
if (!showSections) return true
|
||||
if (favorites.some((item) => item.providerID === x.value.providerID && item.modelID === x.value.modelID))
|
||||
return false
|
||||
if (recents.some((item) => item.providerID === x.value.providerID && item.modelID === x.value.modelID))
|
||||
return false
|
||||
return true
|
||||
}),
|
||||
(options) => sortModelOptions(options, props.providerID !== undefined),
|
||||
),
|
||||
(model) => model.providerID !== "opencode",
|
||||
(model) => data.location.provider.list()?.find((provider) => provider.id === model.providerID)?.name ?? "",
|
||||
[(model) => model.time.released, "desc"],
|
||||
),
|
||||
map((model) => ({
|
||||
value: { providerID: model.providerID, modelID: model.id },
|
||||
title: model.name,
|
||||
releaseDate: model.time.released,
|
||||
description: favorites.some((item) => item.providerID === model.providerID && item.modelID === model.id)
|
||||
? "(Favorite)"
|
||||
: undefined,
|
||||
category: connected()
|
||||
? data.location.provider.list()?.find((provider) => provider.id === model.providerID)?.name
|
||||
: undefined,
|
||||
disabled: !model.enabled || (model.providerID === "opencode" && model.id.includes("-nano")),
|
||||
footer: model.cost[0]?.input === 0 && model.providerID === "opencode" ? "Free" : undefined,
|
||||
onSelect() {
|
||||
onSelect(model.providerID, model.id)
|
||||
},
|
||||
})),
|
||||
filter((option) => {
|
||||
if (!showSections) return true
|
||||
if (
|
||||
favorites.some(
|
||||
(item) => item.providerID === option.value.providerID && item.modelID === option.value.modelID,
|
||||
)
|
||||
)
|
||||
return false
|
||||
if (
|
||||
recents.some((item) => item.providerID === option.value.providerID && item.modelID === option.value.modelID)
|
||||
)
|
||||
return false
|
||||
return true
|
||||
}),
|
||||
(options) => sortModelOptions(options, props.providerID !== undefined),
|
||||
)
|
||||
|
||||
const popularProviders = !connected()
|
||||
|
|
@ -119,7 +124,7 @@ export function DialogModel(props: { providerID?: string }) {
|
|||
})
|
||||
|
||||
const provider = createMemo(() =>
|
||||
props.providerID ? sync.data.provider.find((x) => x.id === props.providerID) : null,
|
||||
props.providerID ? data.location.provider.list()?.find((item) => item.id === props.providerID) : null,
|
||||
)
|
||||
|
||||
const title = createMemo(() => {
|
||||
|
|
@ -172,7 +177,7 @@ export function DialogModel(props: { providerID?: string }) {
|
|||
)
|
||||
}
|
||||
|
||||
export function sortModelOptions<T extends { footer?: string; releaseDate: string; title: string }>(
|
||||
export function sortModelOptions<T extends { footer?: string; releaseDate: string | number; title: string }>(
|
||||
options: T[],
|
||||
newestFirst: boolean,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ export async function warpWorkspaceSession(input: {
|
|||
|
||||
input.project.workspace.set(input.workspaceID)
|
||||
|
||||
await input.sync.bootstrap({ fatal: false }).catch(() => undefined)
|
||||
await input.sync.bootstrap()
|
||||
|
||||
const dir = input.project.instance.directory() || input.sync.path.directory
|
||||
if (dir) {
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export function DialogWorkspaceList() {
|
|||
route.navigate({ type: "home" })
|
||||
}
|
||||
await project.workspace.sync()
|
||||
await sync.bootstrap({ fatal: false }).catch(() => undefined)
|
||||
await sync.bootstrap()
|
||||
setRemoving(undefined)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { useEditorContext } from "../../context/editor"
|
|||
import { useProject } from "../../context/project"
|
||||
import { useSDK } from "../../context/sdk"
|
||||
import { useSync } from "../../context/sync"
|
||||
import { useSyncV2 } from "../../context/sync-v2"
|
||||
import { useData } from "../../context/data"
|
||||
import { getScrollAcceleration } from "../../util/scroll"
|
||||
import { useTuiPaths } from "../../context/runtime"
|
||||
import { useTuiConfig } from "../../config"
|
||||
|
|
@ -85,7 +85,7 @@ export function Autocomplete(props: {
|
|||
const editor = useEditorContext()
|
||||
const sdk = useSDK()
|
||||
const sync = useSync()
|
||||
const syncV2 = useSyncV2()
|
||||
const data = useData()
|
||||
const project = useProject()
|
||||
const slashes = useCommandSlashes()
|
||||
const modeStack = useOpencodeModeStack()
|
||||
|
|
@ -273,12 +273,14 @@ export function Autocomplete(props: {
|
|||
}
|
||||
}
|
||||
|
||||
const references = createMemo(() => data.location.reference.list() ?? [])
|
||||
|
||||
const referenceMatch = createMemo(() => {
|
||||
if (!store.visible || store.visible === "/") return
|
||||
const { baseQuery } = extractLineRange(search())
|
||||
const slash = baseQuery.indexOf("/")
|
||||
const alias = slash === -1 ? baseQuery : baseQuery.slice(0, slash)
|
||||
return syncV2.data.reference.find((item) => !item.hidden && item.name === alias)
|
||||
return references().find((item) => !item.hidden && item.name === alias)
|
||||
})
|
||||
|
||||
function normalizeMentionPath(filePath: string) {
|
||||
|
|
@ -312,14 +314,12 @@ export function Autocomplete(props: {
|
|||
async (query) => {
|
||||
if (!store.visible || store.visible === "/") return []
|
||||
if (referenceMatch()) return []
|
||||
|
||||
const { lineRange, baseQuery } = extractLineRange(query ?? "")
|
||||
|
||||
// Get files from SDK
|
||||
const result = await sdk.client.v2.fs.find({
|
||||
const result = await sdk.client.find.files({
|
||||
query: baseQuery,
|
||||
limit: "20",
|
||||
location: { workspace: project.workspace.current() },
|
||||
workspace: project.workspace.current(),
|
||||
})
|
||||
|
||||
const options: AutocompleteOption[] = []
|
||||
|
|
@ -329,14 +329,15 @@ export function Autocomplete(props: {
|
|||
if (!result.error && result.data) {
|
||||
const width = props.anchor().width - 4
|
||||
options.push(
|
||||
...result.data.data.map((item): AutocompleteOption => {
|
||||
const { filename, url, part } = createFilePart(item.path, lineRange)
|
||||
...result.data.map((item): AutocompleteOption => {
|
||||
const { filename, url, part } = createFilePart(item, lineRange)
|
||||
|
||||
const isDir = item.endsWith("/")
|
||||
return {
|
||||
display: Locale.truncateMiddle(filename, width),
|
||||
value: filename,
|
||||
isDirectory: item.type === "directory",
|
||||
path: item.path,
|
||||
isDirectory: isDir,
|
||||
path: item,
|
||||
onSelect: () => {
|
||||
insertPart(filename, part)
|
||||
},
|
||||
|
|
@ -389,16 +390,15 @@ export function Autocomplete(props: {
|
|||
})
|
||||
|
||||
const agents = createMemo(() => {
|
||||
const agents = sync.data.agent
|
||||
return agents
|
||||
return (data.location.agent.list() ?? [])
|
||||
.filter((agent) => !agent.hidden && agent.mode !== "primary")
|
||||
.map(
|
||||
(agent): AutocompleteOption => ({
|
||||
display: "@" + agent.name,
|
||||
display: "@" + agent.id,
|
||||
onSelect: () => {
|
||||
insertPart(agent.name, {
|
||||
insertPart(agent.id, {
|
||||
type: "agent",
|
||||
name: agent.name,
|
||||
name: agent.id,
|
||||
source: {
|
||||
start: 0,
|
||||
end: 0,
|
||||
|
|
@ -411,7 +411,7 @@ export function Autocomplete(props: {
|
|||
})
|
||||
|
||||
const referenceAliases = createMemo(() =>
|
||||
syncV2.data.reference
|
||||
references()
|
||||
.filter((reference) => !reference.hidden)
|
||||
.map(
|
||||
(reference): AutocompleteOption => ({
|
||||
|
|
@ -471,8 +471,6 @@ export function Autocomplete(props: {
|
|||
const commandsValue = commands()
|
||||
const searchValue = search()
|
||||
|
||||
// @<alias>/... — narrow to the matched reference, files come from fff
|
||||
// already ranked so there is no re-ranking here.
|
||||
if (store.visible === "@" && referenceMatchValue) {
|
||||
return referenceAliasesValue.filter((item) => item.display === `@${referenceMatchValue.name}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { createMemo } from "solid-js"
|
||||
import { useSync } from "../context/sync"
|
||||
import { useData } from "../context/data"
|
||||
|
||||
export function useConnected() {
|
||||
const sync = useSync()
|
||||
return createMemo(() =>
|
||||
sync.data.provider.some((x) => x.id !== "opencode" || Object.values(x.models).some((y) => y.cost?.input !== 0)),
|
||||
)
|
||||
const data = useData()
|
||||
return createMemo(() => (data.location.provider.list() ?? []).some((provider) => provider.enabled !== false))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue