@duckmind/dm-windows-x64 0.60.4 → 0.60.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dm.exe +0 -0
- package/extensions/.dm-extensions.json +70 -123
- package/extensions/dm-9router-ext/src/index.js +410 -5
- package/extensions/dm-caveman/extensions/caveman.js +283 -12
- package/extensions/dm-cliproxy/index.js +182 -2
- package/extensions/dm-cliproxy/scripts/check-config-migration.js +66 -8
- package/extensions/dm-cliproxy/src/apply.js +228 -1
- package/extensions/dm-cliproxy/src/cache.js +42 -1
- package/extensions/dm-cliproxy/src/commands.js +50 -2
- package/extensions/dm-cliproxy/src/compat.js +81 -1
- package/extensions/dm-cliproxy/src/config.js +192 -2
- package/extensions/dm-cliproxy/src/conflicts.js +46 -1
- package/extensions/dm-cliproxy/src/fetch-models.js +190 -1
- package/extensions/dm-cliproxy/src/fetch-usage.js +41 -1
- package/extensions/dm-cliproxy/src/log.js +23 -1
- package/extensions/dm-cliproxy/src/status-quota.js +77 -1
- package/extensions/dm-cliproxy/src/ui-frame.js +50 -1
- package/extensions/dm-cliproxy/src/ui-hub/hub.js +199 -2
- package/extensions/dm-cliproxy/src/ui-hub/index.js +26 -2
- package/extensions/dm-cliproxy/src/ui-hub/shell.js +46 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-diagnostics.js +69 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-models.js +379 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-usage.js +106 -1
- package/extensions/dm-cliproxy/src/ui-picker/catalog.js +41 -1
- package/extensions/dm-cliproxy/src/ui-picker/mutate.js +115 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-confirm.js +35 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-name.js +62 -1
- package/extensions/dm-cliproxy/src/ui-picker/providers.js +45 -1
- package/extensions/dm-cliproxy/src/ui-picker/render-text.js +34 -1
- package/extensions/dm-cliproxy/src/ui-picker/rows.js +57 -1
- package/extensions/dm-cliproxy/src/ui-setup.js +260 -2
- package/extensions/dm-cliproxy/src/ui-usage.js +151 -1
- package/extensions/dm-cliproxy/src/usage-shared-cache.js +97 -1
- package/extensions/dm-context/src/context.js +144 -1
- package/extensions/dm-context/src/index.js +339 -7
- package/extensions/dm-context/src/utils.js +9 -1
- package/extensions/dm-cua/bin/browser-cua.mjs +73 -8
- package/extensions/dm-cua/index.js +75 -6
- package/extensions/dm-cua/src/browser-cua-lib.mjs +490 -6
- package/extensions/dm-cua/src/browser-install.mjs +331 -2
- package/extensions/dm-fff/src/index.js +688 -12
- package/extensions/dm-fff/src/query.js +60 -1
- package/extensions/dm-goal/src/goal.js +894 -23
- package/extensions/dm-image2/index.js +103 -9
- package/extensions/dm-image2/src/image-lib.mjs +1275 -8
- package/extensions/dm-subagents/install.mjs +62 -8
- package/extensions/dm-subagents/src/agents/agent-management.js +1190 -36
- package/extensions/dm-subagents/src/agents/agent-memory.js +216 -6
- package/extensions/dm-subagents/src/agents/agent-scope.js +5 -1
- package/extensions/dm-subagents/src/agents/agent-selection.js +20 -1
- package/extensions/dm-subagents/src/agents/agent-serializer.js +120 -5
- package/extensions/dm-subagents/src/agents/agents.js +1139 -11
- package/extensions/dm-subagents/src/agents/chain-serializer.js +299 -11
- package/extensions/dm-subagents/src/agents/frontmatter.js +65 -6
- package/extensions/dm-subagents/src/agents/identity.js +29 -1
- package/extensions/dm-subagents/src/agents/proactive-skills.js +141 -1
- package/extensions/dm-subagents/src/agents/skills.js +614 -6
- package/extensions/dm-subagents/src/extension/config.js +35 -2
- package/extensions/dm-subagents/src/extension/control-notices.js +69 -4
- package/extensions/dm-subagents/src/extension/doctor.js +172 -15
- package/extensions/dm-subagents/src/extension/fanout-child.js +158 -231
- package/extensions/dm-subagents/src/extension/index.js +521 -359
- package/extensions/dm-subagents/src/extension/rpc.js +266 -7
- package/extensions/dm-subagents/src/extension/schemas.js +275 -1
- package/extensions/dm-subagents/src/extension/tool-description.js +111 -6
- package/extensions/dm-subagents/src/intercom/intercom-bridge.js +126 -4
- package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +452 -5
- package/extensions/dm-subagents/src/intercom/result-intercom.js +319 -3
- package/extensions/dm-subagents/src/profiles/profiles.js +458 -3
- package/extensions/dm-subagents/src/runs/background/async-execution.js +834 -40
- package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +435 -14
- package/extensions/dm-subagents/src/runs/background/async-resume.js +334 -8
- package/extensions/dm-subagents/src/runs/background/async-status.js +313 -12
- package/extensions/dm-subagents/src/runs/background/chain-append.js +245 -2
- package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +136 -1
- package/extensions/dm-subagents/src/runs/background/completion-batcher.js +94 -1
- package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +54 -1
- package/extensions/dm-subagents/src/runs/background/control-channel.js +190 -1
- package/extensions/dm-subagents/src/runs/background/fleet-view.js +483 -17
- package/extensions/dm-subagents/src/runs/background/notify.js +129 -3
- package/extensions/dm-subagents/src/runs/background/parallel-groups.js +34 -1
- package/extensions/dm-subagents/src/runs/background/result-watcher.js +236 -6
- package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +76 -4
- package/extensions/dm-subagents/src/runs/background/run-status.js +427 -23
- package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +487 -4
- package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +306 -9
- package/extensions/dm-subagents/src/runs/background/subagent-runner.js +2849 -73
- package/extensions/dm-subagents/src/runs/background/top-level-async.js +5 -1
- package/extensions/dm-subagents/src/runs/background/wait.js +206 -11
- package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +1013 -12
- package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +980 -101
- package/extensions/dm-subagents/src/runs/foreground/execution.js +1165 -45
- package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +3157 -222
- package/extensions/dm-subagents/src/runs/shared/acceptance.js +835 -3
- package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +104 -1
- package/extensions/dm-subagents/src/runs/shared/completion-guard.js +116 -3
- package/extensions/dm-subagents/src/runs/shared/dm-args.js +208 -1
- package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +90 -1
- package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +282 -1
- package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +148 -1
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +305 -1
- package/extensions/dm-subagents/src/runs/shared/model-fallback.js +194 -1
- package/extensions/dm-subagents/src/runs/shared/model-scope.js +65 -1
- package/extensions/dm-subagents/src/runs/shared/nested-events.js +851 -8
- package/extensions/dm-subagents/src/runs/shared/nested-path.js +41 -1
- package/extensions/dm-subagents/src/runs/shared/nested-render.js +105 -1
- package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +81 -4
- package/extensions/dm-subagents/src/runs/shared/run-history.js +51 -4
- package/extensions/dm-subagents/src/runs/shared/single-output.js +149 -8
- package/extensions/dm-subagents/src/runs/shared/structured-output.js +58 -1
- package/extensions/dm-subagents/src/runs/shared/subagent-control.js +166 -5
- package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +329 -13
- package/extensions/dm-subagents/src/runs/shared/tool-budget.js +73 -1
- package/extensions/dm-subagents/src/runs/shared/turn-budget.js +47 -4
- package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +196 -1
- package/extensions/dm-subagents/src/runs/shared/worktree.js +435 -3
- package/extensions/dm-subagents/src/shared/artifacts.js +92 -2
- package/extensions/dm-subagents/src/shared/atomic-json.js +55 -1
- package/extensions/dm-subagents/src/shared/child-transcript.js +167 -5
- package/extensions/dm-subagents/src/shared/file-coalescer.js +25 -1
- package/extensions/dm-subagents/src/shared/fork-context.js +147 -4
- package/extensions/dm-subagents/src/shared/formatters.js +98 -7
- package/extensions/dm-subagents/src/shared/jsonl-writer.js +56 -2
- package/extensions/dm-subagents/src/shared/model-info.js +62 -1
- package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +68 -1
- package/extensions/dm-subagents/src/shared/session-identity.js +6 -1
- package/extensions/dm-subagents/src/shared/session-tokens.js +39 -2
- package/extensions/dm-subagents/src/shared/settings.js +198 -11
- package/extensions/dm-subagents/src/shared/status-format.js +53 -1
- package/extensions/dm-subagents/src/shared/types.js +184 -6
- package/extensions/dm-subagents/src/shared/utils.js +462 -2
- package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +288 -1
- package/extensions/dm-subagents/src/slash/prompt-workflows.js +297 -7
- package/extensions/dm-subagents/src/slash/slash-bridge.js +118 -1
- package/extensions/dm-subagents/src/slash/slash-commands.js +1287 -31
- package/extensions/dm-subagents/src/slash/slash-live-state.js +240 -4
- package/extensions/dm-subagents/src/tui/render-helpers.js +64 -1
- package/extensions/dm-subagents/src/tui/render.js +1542 -4
- package/extensions/dm-usage/index.js +1294 -9
- package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +40 -9
- package/extensions/greedysearch-dm/bin/cdp-headless.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp-visible.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp.mjs +896 -30
- package/extensions/greedysearch-dm/bin/gschrome.mjs +30 -2
- package/extensions/greedysearch-dm/bin/kill-visible.mjs +7 -2
- package/extensions/greedysearch-dm/bin/launch-visible.mjs +13 -2
- package/extensions/greedysearch-dm/bin/launch.mjs +282 -10
- package/extensions/greedysearch-dm/bin/mcp.mjs +386 -361
- package/extensions/greedysearch-dm/bin/search.mjs +620 -540
- package/extensions/greedysearch-dm/bin/visible.mjs +22 -2
- package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +329 -579
- package/extensions/greedysearch-dm/extractors/chatgpt.mjs +301 -583
- package/extensions/greedysearch-dm/extractors/common.mjs +408 -32
- package/extensions/greedysearch-dm/extractors/consensus.mjs +376 -365
- package/extensions/greedysearch-dm/extractors/consent.mjs +303 -14
- package/extensions/greedysearch-dm/extractors/gemini.mjs +228 -592
- package/extensions/greedysearch-dm/extractors/google-ai.mjs +78 -499
- package/extensions/greedysearch-dm/extractors/logically.mjs +270 -347
- package/extensions/greedysearch-dm/extractors/perplexity.mjs +243 -581
- package/extensions/greedysearch-dm/extractors/selectors.mjs +32 -1
- package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +130 -317
- package/extensions/greedysearch-dm/index.js +123 -23
- package/extensions/greedysearch-dm/src/fetcher.mjs +576 -2
- package/extensions/greedysearch-dm/src/formatters/results.js +95 -10
- package/extensions/greedysearch-dm/src/formatters/sources.js +57 -1
- package/extensions/greedysearch-dm/src/formatters/synthesis.js +49 -1
- package/extensions/greedysearch-dm/src/github.mjs +222 -7
- package/extensions/greedysearch-dm/src/reddit.mjs +145 -14
- package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +340 -9
- package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +112 -4
- package/extensions/greedysearch-dm/src/search/chrome.mjs +486 -285
- package/extensions/greedysearch-dm/src/search/constants.mjs +109 -8
- package/extensions/greedysearch-dm/src/search/defaults.mjs +10 -1
- package/extensions/greedysearch-dm/src/search/engines.mjs +79 -9
- package/extensions/greedysearch-dm/src/search/fetch-source.mjs +441 -349
- package/extensions/greedysearch-dm/src/search/file-sources.mjs +29 -7
- package/extensions/greedysearch-dm/src/search/minimize.mjs +86 -1
- package/extensions/greedysearch-dm/src/search/output.mjs +51 -5
- package/extensions/greedysearch-dm/src/search/paths.mjs +48 -1
- package/extensions/greedysearch-dm/src/search/pdf.mjs +63 -2
- package/extensions/greedysearch-dm/src/search/port-pid.mjs +69 -1
- package/extensions/greedysearch-dm/src/search/progress.mjs +109 -2
- package/extensions/greedysearch-dm/src/search/query.mjs +21 -1
- package/extensions/greedysearch-dm/src/search/recovery.mjs +49 -1
- package/extensions/greedysearch-dm/src/search/research.mjs +2227 -458
- package/extensions/greedysearch-dm/src/search/scale-aware.mjs +61 -11
- package/extensions/greedysearch-dm/src/search/simple-research.mjs +396 -805
- package/extensions/greedysearch-dm/src/search/sources.mjs +412 -1
- package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +127 -12
- package/extensions/greedysearch-dm/src/search/synthesis.mjs +202 -12
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +209 -23
- package/extensions/greedysearch-dm/src/tools/shared.js +226 -10
- package/extensions/greedysearch-dm/src/utils/content.mjs +35 -4
- package/extensions/greedysearch-dm/src/utils/helpers.js +22 -1
- package/extensions/greedysearch-dm/src/utils/node-runtime.mjs +10 -1
- package/extensions/greedysearch-dm/src/utils/system-cmds.mjs +61 -1
- package/package.json +1 -1
|
@@ -1 +1,379 @@
|
|
|
1
|
-
import{matchesKey as D}from"@duckmind/dm-tui";function $0($){let q=new Map,J=new Map;for(let z of $.builtinProviders){let j=[];for(let Y of z.models)q.set(Y.id,{id:Y.id,name:Y.name,suggestedApi:z.api,subtitle:Y.name&&Y.name!==Y.id?Y.name:void 0,ownedBy:z.name,reasoning:Y.reasoning,contextWindow:Y.contextWindow,maxTokens:Y.maxTokens,cost:Y.cost}),j.push(Y.id);J.set(z.name,j)}let Q=[];for(let z of $.customPool){if(!q.has(z.id))q.set(z.id,{id:z.id,name:z.name,suggestedApi:z.api,subtitle:z.name&&z.name!==z.id?z.name:void 0,origin:z.suggestedProvider,ownedBy:z.ownedBy,reasoning:z.reasoning,contextWindow:z.contextWindow,maxTokens:z.maxTokens,cost:z.cost});Q.push(z.id)}return{byId:q,builtinModelIds:J,customPoolIds:Q}}function i0($){let q=new Map;for(let[J,Q]of Object.entries($.customProviders))for(let z of Q.models)q.set(z.id,J);return q}function b($,q){if(q.kind==="builtin")return[...$.builtinProviders[q.name]?.models??[]];return $.customProviders[q.name]?.models.map((J)=>J.id)??[]}function K0($,q,J){if(q.kind==="builtin"){let j=J.builtinModelIds.get(q.name)??[],Y=new Set(b($,q));return j.filter((V)=>!Y.has(V))}let Q=i0($),z=new Set(b($,q));return J.customPoolIds.filter((j)=>{if(z.has(j))return!1;let Y=Q.get(j);return Y===void 0||Y===q.name})}function r0($){return{id:$.id,name:$.name,contextWindow:$.contextWindow,maxTokens:$.maxTokens,reasoning:$.reasoning,cost:$.cost}}function B0($,q,J){if(q.kind==="builtin"){let z=$.builtinProviders[q.name]??{enabled:!0,models:[]},j=new Set(z.models);j.add(J.id),$.builtinProviders[q.name]={enabled:!0,apiOverride:z.apiOverride??null,models:Array.from(j).sort()};return}for(let[z,j]of Object.entries($.customProviders)){if(z===q.name)continue;let Y=j.models.findIndex((V)=>V.id===J.id);if(Y>=0)j.models.splice(Y,1)}let Q=$.customProviders[q.name]??{api:q.api,models:[]};if(!Q.models.some((z)=>z.id===J.id))Q.models.push(r0(J));$.customProviders[q.name]=Q}function H0($,q,J){if(q.kind==="builtin"){let z=$.builtinProviders[q.name];if(!z)return;z.models=z.models.filter((j)=>j!==J),z.enabled=z.models.length>0;return}let Q=$.customProviders[q.name];if(!Q)return;Q.models=Q.models.filter((z)=>z.id!==J)}function z0($,q){if($===q)return!0;let J=["openai-completions","openai-responses"];if(J.includes($)&&J.includes(q))return!0;return!1}function P0($,q){let J=new Map;for(let Q of $){let z=q.byId.get(Q),j=z?.ownedBy||z?.origin||"misc",Y=J.get(j)??[];Y.push(Q),J.set(j,Y)}return Array.from(J.entries()).sort((Q,z)=>Q[0].localeCompare(z[0])).map(([Q,z])=>({label:Q,ids:z}))}function k0($,q,J){let Q=J.trim().toLowerCase();if(!Q)return $;return $.filter((z)=>{if(z.toLowerCase().includes(Q))return!0;let j=q.byId.get(z)?.name;return j?j.toLowerCase().includes(Q):!1})}import{getModels as t0,getProviders as o0}from"@duckmind/dm-ai/compat";function q0($,q){let J=[],Q=new Set(q.builtinModelIds.keys()),z=new Set(o0()),j=new Set([...Q,...z]);for(let Y of Array.from(j).sort()){let V=q.builtinModelIds.get(Y);if(!V||V.length===0)continue;let X;try{let R=t0(Y).find((E)=>V.includes(E.id));if(R)X=R.api}catch{}if(!X){let G=V[0];X=q.byId.get(G)?.suggestedApi??"openai-responses"}if(J.push({kind:"builtin",name:Y,api:X,subtitle:`built-in · ${X}`}),!$.builtinProviders[Y])$.builtinProviders[Y]={enabled:!1,models:[]}}for(let Y of Object.keys($.customProviders).sort()){let V=$.customProviders[Y];J.push({kind:"custom",name:Y,api:V.api,subtitle:`custom · ${V.api}`})}return J}import{getKeybindings as j$,matchesKey as w}from"@duckmind/dm-tui";import{visibleWidth as Q0}from"@duckmind/dm-tui";import{visibleWidth as F0}from"@duckmind/dm-tui";function A($,q){let J=F0($);if(J===q)return $;if(J<q)return $+" ".repeat(q-J);return J0($,q)}function J0($,q){if(q<=0)return"\x1B[0m";let J="",Q=0,z=0;while(z<$.length){let j=$[z];if(j==="\x1B"&&$[z+1]==="["){let V=$.indexOf("m",z+2);if(V>=0){J+=$.slice(z,V+1),z=V+1;continue}}let Y=F0(j);if(Q+Y>q)break;J+=j,Q+=Y,z++}return`${J}\x1B[0m`}var e0="│",$$="╭",z$="╮",q$="╰",J$="╯",u="─";function p($,q){let J=Math.max(4,q.width);return[Q$($,q.title,q.titleColor??"accent",J),...q.lines.map((Q)=>Y$($,Q,J)),Z$($,q.footer,J)]}function Q$($,q,J,Q){let z=q??"",j=Q0(z),Y=Math.max(0,Q-3-j),V=z?$.bold($.fg(J,z)):"",X=`${$$}${u}${V}${u.repeat(Y)}${z$}`;return $.fg("borderAccent",X)}function Y$($,q,J){let Q=Math.max(0,J-2),z=$.fg("borderAccent",e0);return`${z}${A(q,Q)}${z}`}function Z$($,q,J){let Q=`${q$}${u}`,z=`${J$}`,j=3;if(!q||!q.hint&&!q.badge){let I=Math.max(0,J-3);return $.fg("borderAccent",`${Q}${u.repeat(I)}${z}`)}let Y=q.hint??"",V=q.badge??"",X=Q0(Y),G=Q0(V),R=Math.max(0,J-3-X-G),E=$.fg("dim",Y),k=V?$.fg("muted",V):"",F=u.repeat(R);return`${$.fg("borderAccent",Q)}${E}${$.fg("borderAccent",F)}${k}${$.fg("borderAccent",z)}`}function I0($){return Math.max(0,$-2)}async function W0($,q){return $.ui.custom((J,Q,z,j)=>V$(Q,`Remove custom group “${q}”?`,"All model assignments in this group will be discarded.",j),{overlay:!0,overlayOptions:{width:80,maxHeight:"40%"}})}function V$($,q,J,Q){return{render(z){return p($,{width:z,title:" confirm ",titleColor:"error",lines:["",` ${$.bold(q)}`,` ${$.fg("dim",J)}`,""],footer:{hint:" y / enter = remove · n / esc = cancel "}})},invalidate(){},handleInput(z){if(j$().matches(z,"tui.select.cancel")||w(z,"escape")||w(z,"n")||w(z,"q")){Q(!1);return}if(w(z,"y")||w(z,"enter")||w(z,"return")){Q(!0);return}}}}import{getKeybindings as X$,Input as _$,matchesKey as G$}from"@duckmind/dm-tui";function S0($,q){let J=($??"").trim();return J?`${J}-${q}`:q}async function x0($,q){let J=S0(q,"group");return $.ui.custom((Q,z,j,Y)=>U$(Q,z,J,Y),{overlay:!0,overlayOptions:{width:80,maxHeight:"40%"}})}function U$($,q,J,Q){let z=new _$;if(J)z.setValue(J);z.focused=!0;let j=null;return z.onSubmit=(Y)=>{let V=Y.trim();if(!/^[a-z0-9][a-z0-9._-]*$/i.test(V)){j="letters / digits / dot / dash / underscore only",$.requestRender();return}Q(V)},z.onEscape=()=>Q(null),{render(Y){let V=I0(Y),X=J?`name shown in /model picker, e.g. ${J}`:"letters / digits / dot / dash / underscore",G=z.render(V-4),R=["",` ${q.fg("dim",X)}`,"",...G.map((E)=>` ${q.fg("accent",`❯ ${E}`)}`),"",j?` ${q.fg("error",`! ${j}`)}`:""];return p(q,{width:Y,title:" new custom group ",lines:R,footer:{hint:" enter = create · esc = cancel "}})},invalidate(){z.invalidate()},handleInput(Y){if(X$().matches(Y,"tui.select.cancel")||G$(Y,"escape")){Q(null);return}j=null,z.handleInput(Y),$.requestRender()}}}import{visibleWidth as D$}from"@duckmind/dm-tui";function Y0($,q,J){if(!q)return" ";if(J)return $.fg("accent","▶ ");return $.fg("muted","▸ ")}function L0($,q){let J=D$($);if(J===q)return $;if(J>q)return J0($,q);return A($,q)}function y0($,q,J){let{theme:Q,width:z,isCursor:j,isFocused:Y}=J,V=Y0(Q,j,Y),X=$.kind==="builtin"?q.builtinProviders[$.name]?.models.length??0:q.customProviders[$.name]?.models.length??0,G=$.kind==="builtin"?Q.fg("accent","B"):Q.fg("warning","C"),R=`${Q.fg("dim","[")}${G}${Q.fg("dim","]")}`,E=X>0?Q.fg("success","●"):Q.fg("dim","○"),k=X>0?Q.fg("success",String(X).padStart(2)):Q.fg("dim"," 0"),F=j&&Y?Q.bold(Q.fg("accent",$.name)):j?Q.bold($.name):$.name,I=Q.fg("dim",$.api),T=`${V}${R} ${F} ${E}${k} ${I}`;return L0(T,z)}function b0($,q,J,Q){let z=Y0($,q,J),j=q&&J?$.bold($.fg("accent","+ new custom group…")):$.fg("accent","+ new custom group…");return A(`${z}${j}`,Q)}function Z0($,q,J,Q,z){let{theme:j,width:Y,isCursor:V,isFocused:X}=z,G=Y0(j,V,X),R=J==="assigned"?j.fg("success","☑"):j.fg("dim","☐"),E=Q?` ${j.fg("warning","⚠")}`:"",k=V&&X?j.fg("accent",$):$,F=q?.reasoning?` ${j.fg("accent","✨")}`:"",I=q?j.fg("muted",` · ${q.suggestedApi}`):j.fg("error"," · not on proxy"),T=`${G}${R} ${k}${E}${F}${I}`;return L0(T,Y)}function C0($,q,J){let Q=`${$.fg("borderAccent","──")} ${$.fg("warning",q)} ${$.fg("borderAccent","──")}`;return A(` ${Q}`,J)}function j0($,q,J){return A(` ${$.fg("dim",q)}`,J)}function n0($,q,J,Q){let z=Q?$.bold($.fg("accent","❚ ")):$.fg("dim"," ");return A(`${z}${q}`,J)}import{visibleWidth as l}from"@duckmind/dm-tui";function a($,q,J){let Q=$.slice(q,q+J);while(Q.length<J)Q.push("");return Q}function s($,q,J,Q,z,j){if(z<=Q)return 0;let Y=Math.max(0,z-Q);if(q<j)return 0;let V=Math.max(0,Math.min(J,Y)),X=V+j;if($<X)V=Math.max(0,$-j);let G=V+Q-1;if(q>G)V=Math.min(Y,q-(Q-1));return V}function a$($){let{tui:q,theme:J,ctx:Q,cfg:z}=$,j=$.onChange??(()=>{}),Y=$0($.getDiscovery()),V=q0(z,Y),X="providers",G=0,R=0,E=0,k=0,F=0,I=0,T="",C=!1,v=()=>{if(V.length===0)return null;let Z=Math.max(0,Math.min(G,V.length-1));return V[Z]??null},V0=(Z)=>{let _=k0(K0(z,Z,Y),Y,T);return P0(_,Y)},d=(Z)=>V0(Z).flatMap((_)=>_.ids),f=()=>{V=q0(z,Y);let Z=Math.max(0,V.length);if(G>Z)G=Z;let _=v();if(_){let M=b(z,_).length,U=d(_).length;if(R>=M)R=Math.max(0,M-1);if(E>=U)E=Math.max(0,U-1)}},w0=()=>{Y=$0($.getDiscovery()),f()},X0=(Z)=>{let _=["providers","assigned","pool"],M=_.indexOf(X);X=Z?_[(M-1+_.length)%_.length]:_[(M+1)%_.length]},m=(Z)=>{if(X==="providers"){let U=V.length+1;G=Math.max(0,Math.min(G+Z,U-1));return}let _=v();if(!_)return;if(X==="assigned"){let U=b(z,_).length;if(U===0)return;R=Math.max(0,Math.min(R+Z,U-1));return}let M=d(_).length;if(M===0)return;E=Math.max(0,Math.min(E+Z,M-1))},v0=async()=>{if(X==="providers"){if(G===V.length){let U=await x0(Q,z.proxy.providerPrefix);if(U&&!z.customProviders[U]){if(z.customProviders[U]={api:"openai-completions",models:[]},j(),f(),G=V.findIndex((O)=>O.kind==="custom"&&O.name===U),G<0)G=V.length-1;X="pool"}q.requestRender();return}X="pool";return}let Z=v();if(!Z)return;if(X==="assigned"){let U=b(z,Z)[R];if(!U)return;H0(z,Z,U),j(),f();return}let _=d(Z)[E];if(!_)return;let M=Y.byId.get(_);if(!M)return;B0(z,Z,M),j(),f()},f0=async()=>{if(X!=="providers")return;let Z=v();if(!Z||Z.kind!=="custom")return;if(!await W0(Q,Z.name)){q.requestRender();return}if(delete z.customProviders[Z.name],j(),f(),G>=V.length)G=Math.max(0,V.length-1);q.requestRender()},g0=(Z,_)=>u0(Z,_);function u0(Z,_){let M=Math.max(70,Z),U=Math.min(56,Math.max(34,Math.floor(M*0.4))),O=M-U-1,W=Math.max(8,_),S=Math.max(5,Math.floor((W-1)/2)),G0=W-S,P=v(),g=0,i=0,r=0,U0=0,t=0,D0=0,L=[];L.push(_0(" providers ",U,X==="providers"));for(let N=0;N<V.length;N++){let B=V[N],K=X==="providers"&&N===G;if(K)g=L.length,i=g;L.push(y0(B,z,{theme:J,width:U,isCursor:K,isFocused:X==="providers"}))}{let N=X==="providers"&&G===V.length;if(N)g=L.length,i=g;L.push(b0(J,N,X==="providers",U))}let y=[],p0=P?`assigned to ${J.bold(P.name)} ${J.fg("dim",`· ${P.api}`)}`:J.fg("dim","no provider selected");if(y.push(_0(" assigned ",O,X==="assigned")),y.push(n0(J,p0,O,X==="assigned")),P){let N=b(z,P);if(N.length===0)y.push(j0(J,"(nothing assigned yet)",O));for(let B=0;B<N.length;B++){let K=N[B],H=Y.byId.get(K),c=X==="assigned"&&B===R;if(c)r=y.length,U0=r;let n=H?!z0(P.api,H.suggestedApi):!1;y.push(Z0(K,H,"assigned",n,{theme:J,width:O,isCursor:c,isFocused:X==="assigned"}))}}let x=[];if(x.push(m0(O,X==="pool")),P){let N=V0(P);if(N.reduce((K,H)=>K+H.ids.length,0)===0)x.push(j0(J,T?`(no matches for "${T}")`:"(no models available)",O));else{let K=0;for(let H of N){let c=x.length;x.push(C0(J,H.label,O));for(let n=0;n<H.ids.length;n++){let A0=H.ids[n],e=Y.byId.get(A0),T0=X==="pool"&&K===E;if(T0)t=x.length,D0=n===0?c:t;let d0=e?!z0(P.api,e.suggestedApi):!1;x.push(Z0(A0,e,"pool",d0,{theme:J,width:O,isCursor:T0,isFocused:X==="pool"})),K++}}}}let l0=Math.max(1,G0-1),N0=s(i,g,k,W,L.length,1),O0=s(X==="assigned"?U0:0,X==="assigned"?r:0,F,S,y.length,2),E0=s(X==="pool"?D0:0,X==="pool"?t:0,I,l0,x.length,1);k=N0,F=O0,I=E0;let R0=a(L,N0,W),a0=a(y,O0,S),s0=a(x,E0,G0),M0=J.fg("borderAccent","│"),o=[];for(let N=0;N<W;N++){let B=A(R0[N]??"",U),K=N<S?a0[N]:s0[N-S],H=A(K,O);o.push(`${B}${M0}${H}`)}let h=S;if(h>0&&h<W){let N=A(R0[h]??"",U),B=J.fg("borderAccent","─".repeat(O));o[h]=`${N}${M0}${B}`}return o}function _0(Z,_,M){let U=M?J.bold(J.fg("accent",Z)):J.fg("muted",Z),O=J.fg("borderAccent","─".repeat(Math.max(0,_-l(Z)-1)));return A(`${U}${O}`,_)}function m0(Z,_){let U=_?J.bold(J.fg("accent"," available pool ")):J.fg("muted"," available pool "),O="";if(C)O=`${J.fg("dim"," /")}${J.fg("accent",T)}${J.fg("accent","█")} `;else if(T)O=`${J.fg("dim"," /")}${J.fg("warning",T)} `;let W=l(" available pool ")+l(O),S=J.fg("borderAccent","─".repeat(Math.max(0,Z-W-1)));return A(`${U}${O}${S}`,Z)}function h0(Z){if(C){if(D(Z,"escape"))return T="",C=!1,E=0,!0;if(D(Z,"enter")||D(Z,"return"))return C=!1,!0;if(D(Z,"backspace")||D(Z,"delete"))return T=T.slice(0,-1),E=0,!0;if(Z.length===1&&Z>=" "&&Z!=="")return T+=Z,E=0,!0;return!0}if(D(Z,"tab"))return X0(!1),!0;if(D(Z,"shift+tab"))return X0(!0),!0;if(D(Z,"/"))return X="pool",C=!0,!0;if(D(Z,"enter")||D(Z,"return")||D(Z,"space"))return v0().then(()=>!0);if(X==="providers"&&(D(Z,"d")||D(Z,"delete")||D(Z,"backspace")))return f0().then(()=>!0);if(D(Z,"up")||D(Z,"k"))return m(-1),!0;if(D(Z,"down")||D(Z,"j"))return m(1),!0;if(D(Z,"pageUp")||D(Z,"b"))return m(-8),!0;if(D(Z,"pageDown")||D(Z,"f"))return m(8),!0;if(D(Z,"left")||D(Z,"h"))return X="providers",!0;if(D(Z,"right")||D(Z,"l")){if(X==="providers")X="assigned";else if(X==="assigned")X="pool";return!0}return!1}function c0(){if(C)return" type to filter · ↵ apply · esc clear ";return" tab ↔ panel · ↑↓ nav · ↵ move · / filter · d remove group "}return{id:"models",label:"Models",render:g0,handleInput:h0,footerHint:c0,rebuild:w0}}export{a$ as buildModelsView};
|
|
1
|
+
import { matchesKey } from "@duckmind/dm-tui";
|
|
2
|
+
import { buildCatalog } from "../ui-picker/catalog.js";
|
|
3
|
+
import {
|
|
4
|
+
apiCompatible,
|
|
5
|
+
assignedIdsFor,
|
|
6
|
+
attachModel,
|
|
7
|
+
detachModel,
|
|
8
|
+
filterModelIds,
|
|
9
|
+
groupPoolByOwnedBy,
|
|
10
|
+
poolFor
|
|
11
|
+
} from "../ui-picker/mutate.js";
|
|
12
|
+
import { collectProviders } from "../ui-picker/providers.js";
|
|
13
|
+
import { confirmRemoveProvider } from "../ui-picker/prompt-confirm.js";
|
|
14
|
+
import { promptNewProviderName } from "../ui-picker/prompt-name.js";
|
|
15
|
+
import { pad } from "../ui-picker/render-text.js";
|
|
16
|
+
import {
|
|
17
|
+
renderEmpty,
|
|
18
|
+
renderModelRow,
|
|
19
|
+
renderNewProviderRow,
|
|
20
|
+
renderPanelHeader,
|
|
21
|
+
renderProviderRow,
|
|
22
|
+
renderSubheader
|
|
23
|
+
} from "../ui-picker/rows.js";
|
|
24
|
+
import { clampScroll, takeSlice, visibleWidth } from "./shell.js";
|
|
25
|
+
export function buildModelsView(deps) {
|
|
26
|
+
const { tui, theme, ctx, cfg } = deps;
|
|
27
|
+
const onChange = deps.onChange ?? (() => {});
|
|
28
|
+
let catalog = buildCatalog(deps.getDiscovery());
|
|
29
|
+
let providers = collectProviders(cfg, catalog);
|
|
30
|
+
let focus = "providers";
|
|
31
|
+
let providerCursor = 0;
|
|
32
|
+
let assignedCursor = 0;
|
|
33
|
+
let poolCursor = 0;
|
|
34
|
+
let providerScroll = 0;
|
|
35
|
+
let assignedScroll = 0;
|
|
36
|
+
let poolScroll = 0;
|
|
37
|
+
let filter = "";
|
|
38
|
+
let filterEditing = false;
|
|
39
|
+
const selectedProvider = () => {
|
|
40
|
+
if (providers.length === 0)
|
|
41
|
+
return null;
|
|
42
|
+
const idx = Math.max(0, Math.min(providerCursor, providers.length - 1));
|
|
43
|
+
return providers[idx] ?? null;
|
|
44
|
+
};
|
|
45
|
+
const poolGroups = (prov) => {
|
|
46
|
+
const ids = filterModelIds(poolFor(cfg, prov, catalog), catalog, filter);
|
|
47
|
+
return groupPoolByOwnedBy(ids, catalog);
|
|
48
|
+
};
|
|
49
|
+
const poolOrder = (prov) => poolGroups(prov).flatMap((g) => g.ids);
|
|
50
|
+
const refresh = () => {
|
|
51
|
+
providers = collectProviders(cfg, catalog);
|
|
52
|
+
const maxProv = Math.max(0, providers.length);
|
|
53
|
+
if (providerCursor > maxProv)
|
|
54
|
+
providerCursor = maxProv;
|
|
55
|
+
const prov = selectedProvider();
|
|
56
|
+
if (prov) {
|
|
57
|
+
const aLen = assignedIdsFor(cfg, prov).length;
|
|
58
|
+
const pLen = poolOrder(prov).length;
|
|
59
|
+
if (assignedCursor >= aLen)
|
|
60
|
+
assignedCursor = Math.max(0, aLen - 1);
|
|
61
|
+
if (poolCursor >= pLen)
|
|
62
|
+
poolCursor = Math.max(0, pLen - 1);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const rebuild = () => {
|
|
66
|
+
catalog = buildCatalog(deps.getDiscovery());
|
|
67
|
+
refresh();
|
|
68
|
+
};
|
|
69
|
+
const onTab = (back) => {
|
|
70
|
+
const order = ["providers", "assigned", "pool"];
|
|
71
|
+
const i = order.indexOf(focus);
|
|
72
|
+
focus = back ? order[(i - 1 + order.length) % order.length] : order[(i + 1) % order.length];
|
|
73
|
+
};
|
|
74
|
+
const moveCursor = (delta) => {
|
|
75
|
+
if (focus === "providers") {
|
|
76
|
+
const total = providers.length + 1;
|
|
77
|
+
providerCursor = Math.max(0, Math.min(providerCursor + delta, total - 1));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const prov = selectedProvider();
|
|
81
|
+
if (!prov)
|
|
82
|
+
return;
|
|
83
|
+
if (focus === "assigned") {
|
|
84
|
+
const total = assignedIdsFor(cfg, prov).length;
|
|
85
|
+
if (total === 0)
|
|
86
|
+
return;
|
|
87
|
+
assignedCursor = Math.max(0, Math.min(assignedCursor + delta, total - 1));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const total = poolOrder(prov).length;
|
|
91
|
+
if (total === 0)
|
|
92
|
+
return;
|
|
93
|
+
poolCursor = Math.max(0, Math.min(poolCursor + delta, total - 1));
|
|
94
|
+
};
|
|
95
|
+
const onActivate = async () => {
|
|
96
|
+
if (focus === "providers") {
|
|
97
|
+
if (providerCursor === providers.length) {
|
|
98
|
+
const name = await promptNewProviderName(ctx, cfg.proxy.providerPrefix);
|
|
99
|
+
if (name && !cfg.customProviders[name]) {
|
|
100
|
+
cfg.customProviders[name] = { api: "openai-completions", models: [] };
|
|
101
|
+
onChange();
|
|
102
|
+
refresh();
|
|
103
|
+
providerCursor = providers.findIndex((p) => p.kind === "custom" && p.name === name);
|
|
104
|
+
if (providerCursor < 0)
|
|
105
|
+
providerCursor = providers.length - 1;
|
|
106
|
+
focus = "pool";
|
|
107
|
+
}
|
|
108
|
+
tui.requestRender();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
focus = "pool";
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const prov = selectedProvider();
|
|
115
|
+
if (!prov)
|
|
116
|
+
return;
|
|
117
|
+
if (focus === "assigned") {
|
|
118
|
+
const id = assignedIdsFor(cfg, prov)[assignedCursor];
|
|
119
|
+
if (!id)
|
|
120
|
+
return;
|
|
121
|
+
detachModel(cfg, prov, id);
|
|
122
|
+
onChange();
|
|
123
|
+
refresh();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const id = poolOrder(prov)[poolCursor];
|
|
127
|
+
if (!id)
|
|
128
|
+
return;
|
|
129
|
+
const m = catalog.byId.get(id);
|
|
130
|
+
if (!m)
|
|
131
|
+
return;
|
|
132
|
+
attachModel(cfg, prov, m);
|
|
133
|
+
onChange();
|
|
134
|
+
refresh();
|
|
135
|
+
};
|
|
136
|
+
const onDelete = async () => {
|
|
137
|
+
if (focus !== "providers")
|
|
138
|
+
return;
|
|
139
|
+
const prov = selectedProvider();
|
|
140
|
+
if (!prov || prov.kind !== "custom")
|
|
141
|
+
return;
|
|
142
|
+
const ok = await confirmRemoveProvider(ctx, prov.name);
|
|
143
|
+
if (!ok) {
|
|
144
|
+
tui.requestRender();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
delete cfg.customProviders[prov.name];
|
|
148
|
+
onChange();
|
|
149
|
+
refresh();
|
|
150
|
+
if (providerCursor >= providers.length)
|
|
151
|
+
providerCursor = Math.max(0, providers.length - 1);
|
|
152
|
+
tui.requestRender();
|
|
153
|
+
};
|
|
154
|
+
const render = (width, height) => renderBody(width, height);
|
|
155
|
+
function renderBody(width, height) {
|
|
156
|
+
const inner = Math.max(70, width);
|
|
157
|
+
const leftW = Math.min(56, Math.max(34, Math.floor(inner * 0.4)));
|
|
158
|
+
const rightW = inner - leftW - 1;
|
|
159
|
+
const bodyH = Math.max(8, height);
|
|
160
|
+
const upperH = Math.max(5, Math.floor((bodyH - 1) / 2));
|
|
161
|
+
const lowerH = bodyH - upperH;
|
|
162
|
+
const prov = selectedProvider();
|
|
163
|
+
let leftCursorLine = 0;
|
|
164
|
+
let leftCursorTop = 0;
|
|
165
|
+
let assignedCursorLine = 0;
|
|
166
|
+
let assignedCursorTop = 0;
|
|
167
|
+
let poolCursorLine = 0;
|
|
168
|
+
let poolCursorTop = 0;
|
|
169
|
+
const leftLines = [];
|
|
170
|
+
leftLines.push(panelHeaderBar(" providers ", leftW, focus === "providers"));
|
|
171
|
+
for (let i = 0;i < providers.length; i++) {
|
|
172
|
+
const p = providers[i];
|
|
173
|
+
const isCursor = focus === "providers" && i === providerCursor;
|
|
174
|
+
if (isCursor) {
|
|
175
|
+
leftCursorLine = leftLines.length;
|
|
176
|
+
leftCursorTop = leftCursorLine;
|
|
177
|
+
}
|
|
178
|
+
leftLines.push(renderProviderRow(p, cfg, {
|
|
179
|
+
theme,
|
|
180
|
+
width: leftW,
|
|
181
|
+
isCursor,
|
|
182
|
+
isFocused: focus === "providers"
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
{
|
|
186
|
+
const isCursor = focus === "providers" && providerCursor === providers.length;
|
|
187
|
+
if (isCursor) {
|
|
188
|
+
leftCursorLine = leftLines.length;
|
|
189
|
+
leftCursorTop = leftCursorLine;
|
|
190
|
+
}
|
|
191
|
+
leftLines.push(renderNewProviderRow(theme, isCursor, focus === "providers", leftW));
|
|
192
|
+
}
|
|
193
|
+
const assignedLines = [];
|
|
194
|
+
const assignedHeader = prov ? `assigned to ${theme.bold(prov.name)} ${theme.fg("dim", `· ${prov.api}`)}` : theme.fg("dim", "no provider selected");
|
|
195
|
+
assignedLines.push(panelHeaderBar(" assigned ", rightW, focus === "assigned"));
|
|
196
|
+
assignedLines.push(renderPanelHeader(theme, assignedHeader, rightW, focus === "assigned"));
|
|
197
|
+
if (prov) {
|
|
198
|
+
const ids = assignedIdsFor(cfg, prov);
|
|
199
|
+
if (ids.length === 0)
|
|
200
|
+
assignedLines.push(renderEmpty(theme, "(nothing assigned yet)", rightW));
|
|
201
|
+
for (let i = 0;i < ids.length; i++) {
|
|
202
|
+
const id = ids[i];
|
|
203
|
+
const m = catalog.byId.get(id);
|
|
204
|
+
const isCursor = focus === "assigned" && i === assignedCursor;
|
|
205
|
+
if (isCursor) {
|
|
206
|
+
assignedCursorLine = assignedLines.length;
|
|
207
|
+
assignedCursorTop = assignedCursorLine;
|
|
208
|
+
}
|
|
209
|
+
const compatWarn = m ? !apiCompatible(prov.api, m.suggestedApi) : false;
|
|
210
|
+
assignedLines.push(renderModelRow(id, m, "assigned", compatWarn, {
|
|
211
|
+
theme,
|
|
212
|
+
width: rightW,
|
|
213
|
+
isCursor,
|
|
214
|
+
isFocused: focus === "assigned"
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const poolLines = [];
|
|
219
|
+
poolLines.push(poolHeaderBar(rightW, focus === "pool"));
|
|
220
|
+
if (prov) {
|
|
221
|
+
const groups = poolGroups(prov);
|
|
222
|
+
const flatCount = groups.reduce((n, g) => n + g.ids.length, 0);
|
|
223
|
+
if (flatCount === 0) {
|
|
224
|
+
poolLines.push(renderEmpty(theme, filter ? `(no matches for "${filter}")` : "(no models available)", rightW));
|
|
225
|
+
} else {
|
|
226
|
+
let cursorIdx = 0;
|
|
227
|
+
for (const grp of groups) {
|
|
228
|
+
const groupHeaderLine = poolLines.length;
|
|
229
|
+
poolLines.push(renderSubheader(theme, grp.label, rightW));
|
|
230
|
+
for (let gi = 0;gi < grp.ids.length; gi++) {
|
|
231
|
+
const id = grp.ids[gi];
|
|
232
|
+
const m = catalog.byId.get(id);
|
|
233
|
+
const isCursor = focus === "pool" && cursorIdx === poolCursor;
|
|
234
|
+
if (isCursor) {
|
|
235
|
+
poolCursorLine = poolLines.length;
|
|
236
|
+
poolCursorTop = gi === 0 ? groupHeaderLine : poolCursorLine;
|
|
237
|
+
}
|
|
238
|
+
const compatWarn = m ? !apiCompatible(prov.api, m.suggestedApi) : false;
|
|
239
|
+
poolLines.push(renderModelRow(id, m, "pool", compatWarn, {
|
|
240
|
+
theme,
|
|
241
|
+
width: rightW,
|
|
242
|
+
isCursor,
|
|
243
|
+
isFocused: focus === "pool"
|
|
244
|
+
}));
|
|
245
|
+
cursorIdx++;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const poolUsableH = Math.max(1, lowerH - 1);
|
|
251
|
+
const leftScroll = clampScroll(leftCursorTop, leftCursorLine, providerScroll, bodyH, leftLines.length, 1);
|
|
252
|
+
const aScroll = clampScroll(focus === "assigned" ? assignedCursorTop : 0, focus === "assigned" ? assignedCursorLine : 0, assignedScroll, upperH, assignedLines.length, 2);
|
|
253
|
+
const pScroll = clampScroll(focus === "pool" ? poolCursorTop : 0, focus === "pool" ? poolCursorLine : 0, poolScroll, poolUsableH, poolLines.length, 1);
|
|
254
|
+
providerScroll = leftScroll;
|
|
255
|
+
assignedScroll = aScroll;
|
|
256
|
+
poolScroll = pScroll;
|
|
257
|
+
const leftSlice = takeSlice(leftLines, leftScroll, bodyH);
|
|
258
|
+
const aSlice = takeSlice(assignedLines, aScroll, upperH);
|
|
259
|
+
const pSlice = takeSlice(poolLines, pScroll, lowerH);
|
|
260
|
+
const vsplit = theme.fg("borderAccent", "│");
|
|
261
|
+
const out = [];
|
|
262
|
+
for (let i = 0;i < bodyH; i++) {
|
|
263
|
+
const l = pad(leftSlice[i] ?? "", leftW);
|
|
264
|
+
const rRaw = i < upperH ? aSlice[i] : pSlice[i - upperH];
|
|
265
|
+
const r = pad(rRaw, rightW);
|
|
266
|
+
out.push(`${l}${vsplit}${r}`);
|
|
267
|
+
}
|
|
268
|
+
const divIdx = upperH;
|
|
269
|
+
if (divIdx > 0 && divIdx < bodyH) {
|
|
270
|
+
const left = pad(leftSlice[divIdx] ?? "", leftW);
|
|
271
|
+
const horiz = theme.fg("borderAccent", "─".repeat(rightW));
|
|
272
|
+
out[divIdx] = `${left}${vsplit}${horiz}`;
|
|
273
|
+
}
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
function panelHeaderBar(label, width, isFocused) {
|
|
277
|
+
const bar = isFocused ? theme.bold(theme.fg("accent", label)) : theme.fg("muted", label);
|
|
278
|
+
const fill = theme.fg("borderAccent", "─".repeat(Math.max(0, width - visibleWidth(label) - 1)));
|
|
279
|
+
return pad(`${bar}${fill}`, width);
|
|
280
|
+
}
|
|
281
|
+
function poolHeaderBar(width, isFocused) {
|
|
282
|
+
const label = " available pool ";
|
|
283
|
+
const bar = isFocused ? theme.bold(theme.fg("accent", label)) : theme.fg("muted", label);
|
|
284
|
+
let filterChip = "";
|
|
285
|
+
if (filterEditing) {
|
|
286
|
+
filterChip = `${theme.fg("dim", " /")}${theme.fg("accent", filter)}${theme.fg("accent", "█")} `;
|
|
287
|
+
} else if (filter) {
|
|
288
|
+
filterChip = `${theme.fg("dim", " /")}${theme.fg("warning", filter)} `;
|
|
289
|
+
}
|
|
290
|
+
const used = visibleWidth(label) + visibleWidth(filterChip);
|
|
291
|
+
const fill = theme.fg("borderAccent", "─".repeat(Math.max(0, width - used - 1)));
|
|
292
|
+
return pad(`${bar}${filterChip}${fill}`, width);
|
|
293
|
+
}
|
|
294
|
+
function handleInput(data) {
|
|
295
|
+
if (filterEditing) {
|
|
296
|
+
if (matchesKey(data, "escape")) {
|
|
297
|
+
filter = "";
|
|
298
|
+
filterEditing = false;
|
|
299
|
+
poolCursor = 0;
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
if (matchesKey(data, "enter") || matchesKey(data, "return")) {
|
|
303
|
+
filterEditing = false;
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
if (matchesKey(data, "backspace") || matchesKey(data, "delete")) {
|
|
307
|
+
filter = filter.slice(0, -1);
|
|
308
|
+
poolCursor = 0;
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
if (data.length === 1 && data >= " " && data !== "") {
|
|
312
|
+
filter += data;
|
|
313
|
+
poolCursor = 0;
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
if (matchesKey(data, "tab")) {
|
|
319
|
+
onTab(false);
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
if (matchesKey(data, "shift+tab")) {
|
|
323
|
+
onTab(true);
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
if (matchesKey(data, "/")) {
|
|
327
|
+
focus = "pool";
|
|
328
|
+
filterEditing = true;
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
if (matchesKey(data, "enter") || matchesKey(data, "return") || matchesKey(data, "space")) {
|
|
332
|
+
return onActivate().then(() => true);
|
|
333
|
+
}
|
|
334
|
+
if (focus === "providers" && (matchesKey(data, "d") || matchesKey(data, "delete") || matchesKey(data, "backspace"))) {
|
|
335
|
+
return onDelete().then(() => true);
|
|
336
|
+
}
|
|
337
|
+
if (matchesKey(data, "up") || matchesKey(data, "k")) {
|
|
338
|
+
moveCursor(-1);
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
if (matchesKey(data, "down") || matchesKey(data, "j")) {
|
|
342
|
+
moveCursor(1);
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
if (matchesKey(data, "pageUp") || matchesKey(data, "b")) {
|
|
346
|
+
moveCursor(-8);
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
if (matchesKey(data, "pageDown") || matchesKey(data, "f")) {
|
|
350
|
+
moveCursor(8);
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
if (matchesKey(data, "left") || matchesKey(data, "h")) {
|
|
354
|
+
focus = "providers";
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
if (matchesKey(data, "right") || matchesKey(data, "l")) {
|
|
358
|
+
if (focus === "providers")
|
|
359
|
+
focus = "assigned";
|
|
360
|
+
else if (focus === "assigned")
|
|
361
|
+
focus = "pool";
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
function footerHint() {
|
|
367
|
+
if (filterEditing)
|
|
368
|
+
return " type to filter · ↵ apply · esc clear ";
|
|
369
|
+
return " tab ↔ panel · ↑↓ nav · ↵ move · / filter · d remove group ";
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
id: "models",
|
|
373
|
+
label: "Models",
|
|
374
|
+
render,
|
|
375
|
+
handleInput,
|
|
376
|
+
footerHint,
|
|
377
|
+
rebuild
|
|
378
|
+
};
|
|
379
|
+
}
|
|
@@ -1 +1,106 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { matchesKey } from "@duckmind/dm-tui";
|
|
2
|
+
import { resolveConfigValue } from "../config.js";
|
|
3
|
+
import {
|
|
4
|
+
clearUsageCache,
|
|
5
|
+
fetchUsage
|
|
6
|
+
} from "../fetch-usage.js";
|
|
7
|
+
import { pad } from "../ui-picker/render-text.js";
|
|
8
|
+
import { renderUsage } from "../ui-usage.js";
|
|
9
|
+
import { clampOffset, takeSlice } from "./shell.js";
|
|
10
|
+
export function buildUsageView(deps) {
|
|
11
|
+
const { tui, theme, cfg } = deps;
|
|
12
|
+
let status = "idle";
|
|
13
|
+
let doc = null;
|
|
14
|
+
let errMsg = "";
|
|
15
|
+
let offset = 0;
|
|
16
|
+
let showDisabled = false;
|
|
17
|
+
let verbose = false;
|
|
18
|
+
const load = (force) => {
|
|
19
|
+
status = "loading";
|
|
20
|
+
tui.requestRender();
|
|
21
|
+
const usageKey = resolveConfigValue(cfg.proxy.usageKey);
|
|
22
|
+
fetchUsage(cfg, usageKey, { force }).then((d) => {
|
|
23
|
+
doc = d;
|
|
24
|
+
status = "ready";
|
|
25
|
+
offset = 0;
|
|
26
|
+
tui.requestRender();
|
|
27
|
+
}).catch((e) => {
|
|
28
|
+
errMsg = e.message;
|
|
29
|
+
status = "error";
|
|
30
|
+
tui.requestRender();
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
const lines = () => {
|
|
34
|
+
if (status === "idle" || status === "loading")
|
|
35
|
+
return [theme.fg("dim", " loading usage…")];
|
|
36
|
+
if (status === "error")
|
|
37
|
+
return [theme.fg("error", ` usage failed: ${errMsg}`)];
|
|
38
|
+
if (!doc)
|
|
39
|
+
return [theme.fg("dim", " (no data)")];
|
|
40
|
+
return renderUsage(doc, { showDisabled, verbose });
|
|
41
|
+
};
|
|
42
|
+
const render = (width, height) => {
|
|
43
|
+
const body = lines();
|
|
44
|
+
offset = clampOffset(offset, height, body.length);
|
|
45
|
+
return takeSlice(body, offset, height).map((ln) => pad(` ${ln}`, width));
|
|
46
|
+
};
|
|
47
|
+
const handleInput = (data) => {
|
|
48
|
+
if (matchesKey(data, "d")) {
|
|
49
|
+
showDisabled = !showDisabled;
|
|
50
|
+
offset = 0;
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (matchesKey(data, "v")) {
|
|
54
|
+
verbose = !verbose;
|
|
55
|
+
offset = 0;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const total = lines().length;
|
|
59
|
+
if (matchesKey(data, "up") || matchesKey(data, "k")) {
|
|
60
|
+
offset = Math.max(0, offset - 1);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
if (matchesKey(data, "down") || matchesKey(data, "j")) {
|
|
64
|
+
offset = Math.min(Math.max(0, total - 1), offset + 1);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
if (matchesKey(data, "pageUp") || matchesKey(data, "b")) {
|
|
68
|
+
offset = Math.max(0, offset - 10);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if (matchesKey(data, "pageDown") || matchesKey(data, "f")) {
|
|
72
|
+
offset = Math.min(Math.max(0, total - 1), offset + 10);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (matchesKey(data, "home") || matchesKey(data, "g")) {
|
|
76
|
+
offset = 0;
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
if (matchesKey(data, "end") || matchesKey(data, "shift+g")) {
|
|
80
|
+
offset = Math.max(0, total - 1);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
};
|
|
85
|
+
const footerHint = () => {
|
|
86
|
+
const d = showDisabled ? theme.fg("success", "[d disabled]") : "d disabled";
|
|
87
|
+
const v = verbose ? theme.fg("success", "[v verbose]") : "v verbose";
|
|
88
|
+
return ` ↑↓ scroll · ${d} · ${v} `;
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
id: "usage",
|
|
92
|
+
label: "Usage",
|
|
93
|
+
render,
|
|
94
|
+
handleInput,
|
|
95
|
+
footerHint,
|
|
96
|
+
onActivate: () => {
|
|
97
|
+
if (status === "idle")
|
|
98
|
+
load(false);
|
|
99
|
+
},
|
|
100
|
+
reload: () => {
|
|
101
|
+
clearUsageCache();
|
|
102
|
+
if (status !== "idle")
|
|
103
|
+
load(true);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -1 +1,41 @@
|
|
|
1
|
-
function
|
|
1
|
+
export function buildCatalog(discovery) {
|
|
2
|
+
const byId = new Map;
|
|
3
|
+
const builtinModelIds = new Map;
|
|
4
|
+
for (const p of discovery.builtinProviders) {
|
|
5
|
+
const ids = [];
|
|
6
|
+
for (const m of p.models) {
|
|
7
|
+
byId.set(m.id, {
|
|
8
|
+
id: m.id,
|
|
9
|
+
name: m.name,
|
|
10
|
+
suggestedApi: p.api,
|
|
11
|
+
subtitle: m.name && m.name !== m.id ? m.name : undefined,
|
|
12
|
+
ownedBy: p.name,
|
|
13
|
+
reasoning: m.reasoning,
|
|
14
|
+
contextWindow: m.contextWindow,
|
|
15
|
+
maxTokens: m.maxTokens,
|
|
16
|
+
cost: m.cost
|
|
17
|
+
});
|
|
18
|
+
ids.push(m.id);
|
|
19
|
+
}
|
|
20
|
+
builtinModelIds.set(p.name, ids);
|
|
21
|
+
}
|
|
22
|
+
const customPoolIds = [];
|
|
23
|
+
for (const m of discovery.customPool) {
|
|
24
|
+
if (!byId.has(m.id)) {
|
|
25
|
+
byId.set(m.id, {
|
|
26
|
+
id: m.id,
|
|
27
|
+
name: m.name,
|
|
28
|
+
suggestedApi: m.api,
|
|
29
|
+
subtitle: m.name && m.name !== m.id ? m.name : undefined,
|
|
30
|
+
origin: m.suggestedProvider,
|
|
31
|
+
ownedBy: m.ownedBy,
|
|
32
|
+
reasoning: m.reasoning,
|
|
33
|
+
contextWindow: m.contextWindow,
|
|
34
|
+
maxTokens: m.maxTokens,
|
|
35
|
+
cost: m.cost
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
customPoolIds.push(m.id);
|
|
39
|
+
}
|
|
40
|
+
return { byId, builtinModelIds, customPoolIds };
|
|
41
|
+
}
|