| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- import { createStore } from "solid-js/store"
- import { batch, createEffect, createMemo } from "solid-js"
- import { useSync } from "@tui/context/sync"
- import { useTheme } from "@tui/context/theme"
- import { uniqueBy } from "remeda"
- import path from "path"
- import { Global } from "@/global"
- import { iife } from "@/util/iife"
- import { createSimpleContext } from "./helper"
- import { useToast } from "../ui/toast"
- import { Provider } from "@/provider/provider"
- import { useArgs } from "./args"
- import { RGBA } from "@opentui/core"
- export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
- name: "Local",
- init: () => {
- const sync = useSync()
- const toast = useToast()
- function isModelValid(model: { providerID: string; modelID: string }) {
- const provider = sync.data.provider.find((x) => x.id === model.providerID)
- return !!provider?.models[model.modelID]
- }
- function getFirstValidModel(...modelFns: (() => { providerID: string; modelID: string } | undefined)[]) {
- for (const modelFn of modelFns) {
- const model = modelFn()
- if (!model) continue
- if (isModelValid(model)) return model
- }
- }
- // Automatically update model when agent changes
- createEffect(() => {
- const value = agent.current()
- if (value.model) {
- if (isModelValid(value.model))
- model.set({
- providerID: value.model.providerID,
- modelID: value.model.modelID,
- })
- else
- toast.show({
- variant: "warning",
- message: `Agent ${value.name}'s configured model ${value.model.providerID}/${value.model.modelID} is not valid`,
- duration: 3000,
- })
- }
- })
- const agent = iife(() => {
- const agents = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent"))
- const [agentStore, setAgentStore] = createStore<{
- current: string
- }>({
- current: agents()[0].name,
- })
- const { theme } = useTheme()
- const colors = createMemo(() => [
- theme.secondary,
- theme.accent,
- theme.success,
- theme.warning,
- theme.primary,
- theme.error,
- ])
- return {
- list() {
- return agents()
- },
- current() {
- return agents().find((x) => x.name === agentStore.current)!
- },
- set(name: string) {
- if (!agents().some((x) => x.name === name))
- return toast.show({
- variant: "warning",
- message: `Agent not found: ${name}`,
- duration: 3000,
- })
- setAgentStore("current", name)
- },
- move(direction: 1 | -1) {
- batch(() => {
- let next = agents().findIndex((x) => x.name === agentStore.current) + direction
- if (next < 0) next = agents().length - 1
- if (next >= agents().length) next = 0
- const value = agents()[next]
- setAgentStore("current", value.name)
- })
- },
- color(name: string) {
- const agent = agents().find((x) => x.name === name)
- if (agent?.color) return RGBA.fromHex(agent.color)
- const index = agents().findIndex((x) => x.name === name)
- if (index === -1) return colors()[0]
- return colors()[index % colors().length]
- },
- }
- })
- const model = iife(() => {
- const [modelStore, setModelStore] = createStore<{
- ready: boolean
- model: Record<
- string,
- {
- providerID: string
- modelID: string
- }
- >
- recent: {
- providerID: string
- modelID: string
- }[]
- }>({
- ready: false,
- model: {},
- recent: [],
- })
- const file = Bun.file(path.join(Global.Path.state, "model.json"))
- file
- .json()
- .then((x) => {
- setModelStore("recent", x.recent)
- })
- .catch(() => {})
- .finally(() => {
- setModelStore("ready", true)
- })
- const args = useArgs()
- const fallbackModel = createMemo(() => {
- if (args.model) {
- const { providerID, modelID } = Provider.parseModel(args.model)
- if (isModelValid({ providerID, modelID })) {
- return {
- providerID,
- modelID,
- }
- }
- }
- if (sync.data.config.model) {
- const { providerID, modelID } = Provider.parseModel(sync.data.config.model)
- if (isModelValid({ providerID, modelID })) {
- return {
- providerID,
- modelID,
- }
- }
- }
- for (const item of modelStore.recent) {
- if (isModelValid(item)) {
- return item
- }
- }
- const provider = sync.data.provider[0]
- const model = sync.data.provider_default[provider.id] ?? Object.values(provider.models)[0].id
- return {
- providerID: provider.id,
- modelID: model,
- }
- })
- const currentModel = createMemo(() => {
- const a = agent.current()
- return getFirstValidModel(
- () => modelStore.model[a.name],
- () => a.model,
- fallbackModel,
- )!
- })
- return {
- current: currentModel,
- get ready() {
- return modelStore.ready
- },
- recent() {
- return modelStore.recent
- },
- parsed: createMemo(() => {
- const value = currentModel()
- const provider = sync.data.provider.find((x) => x.id === value.providerID)!
- const model = provider.models[value.modelID]
- return {
- provider: provider.name ?? value.providerID,
- model: model.name ?? value.modelID,
- }
- }),
- cycle(direction: 1 | -1) {
- const current = currentModel()
- if (!current) return
- const recent = modelStore.recent
- const index = recent.findIndex((x) => x.providerID === current.providerID && x.modelID === current.modelID)
- if (index === -1) return
- let next = index + direction
- if (next < 0) next = recent.length - 1
- if (next >= recent.length) next = 0
- const val = recent[next]
- if (!val) return
- setModelStore("model", agent.current().name, { ...val })
- },
- set(model: { providerID: string; modelID: string }, options?: { recent?: boolean }) {
- batch(() => {
- if (!isModelValid(model)) {
- toast.show({
- message: `Model ${model.providerID}/${model.modelID} is not valid`,
- variant: "warning",
- duration: 3000,
- })
- return
- }
- setModelStore("model", agent.current().name, model)
- if (options?.recent) {
- const uniq = uniqueBy([model, ...modelStore.recent], (x) => x.providerID + x.modelID)
- if (uniq.length > 5) uniq.pop()
- setModelStore("recent", uniq)
- Bun.write(
- file,
- JSON.stringify({
- recent: modelStore.recent,
- }),
- )
- }
- })
- },
- }
- })
- const result = {
- model,
- agent,
- }
- return result
- },
- })
|