@makefinks/daemon 0.3.1 → 0.4.0

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/package.json CHANGED
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "module": "src/index.tsx",
30
30
  "type": "module",
31
- "version": "0.3.1",
31
+ "version": "0.4.0",
32
32
  "bin": {
33
33
  "daemon": "dist/cli.js"
34
34
  },
@@ -1,8 +1,8 @@
1
1
  import type { ScrollBoxRenderable, TextareaRenderable } from "@opentui/core";
2
2
  import { useKeyboard } from "@opentui/react";
3
3
  import { useEffect, useMemo, useRef, useState } from "react";
4
- import type { ModelOption } from "../types";
5
4
  import { useMenuKeyboard } from "../hooks/use-menu-keyboard";
5
+ import type { ModelOption } from "../types";
6
6
  import { COLORS } from "../ui/constants";
7
7
  import { formatContextWindowK, formatPrice } from "../utils/formatters";
8
8
 
@@ -58,15 +58,26 @@ export function ModelMenu({
58
58
 
59
59
  const curatedIdSet = useMemo(() => new Set(sortedCurated.map((model) => model.id)), [sortedCurated]);
60
60
 
61
+ const savedModel = useMemo(() => {
62
+ if (!currentModelId) return null;
63
+ if (curatedIdSet.has(currentModelId)) return null;
64
+ const match = allModels.find((model) => model.id === currentModelId);
65
+ return match ?? { id: currentModelId, name: currentModelId };
66
+ }, [allModels, curatedIdSet, currentModelId]);
67
+
68
+ const savedModels = useMemo(() => (savedModel ? [savedModel] : []), [savedModel]);
69
+
61
70
  const allModelsWithFallback = useMemo(() => {
62
71
  if (!currentModelId) return allModels;
63
72
  if (curatedIdSet.has(currentModelId)) return allModels;
64
- if (allModels.some((model) => model.id === currentModelId)) return allModels;
73
+ if (savedModel) return allModels;
65
74
  return [...allModels, { id: currentModelId, name: currentModelId }];
66
- }, [allModels, curatedIdSet, currentModelId]);
75
+ }, [allModels, curatedIdSet, currentModelId, savedModel]);
67
76
 
68
77
  const filteredAllModels = useMemo(() => {
69
- const filtered = allModelsWithFallback.filter((model) => !curatedIdSet.has(model.id));
78
+ const filtered = allModelsWithFallback.filter(
79
+ (model) => !curatedIdSet.has(model.id) && model.id !== savedModel?.id
80
+ );
70
81
  const query = searchQuery.trim().toLowerCase();
71
82
  if (query.length < MIN_ALL_MODEL_QUERY_LENGTH) {
72
83
  return [];
@@ -78,28 +89,44 @@ export function ModelMenu({
78
89
  : filtered;
79
90
 
80
91
  return matching.sort((a, b) => a.name.localeCompare(b.name));
81
- }, [allModelsWithFallback, curatedIdSet, searchQuery]);
92
+ }, [allModelsWithFallback, curatedIdSet, savedModel?.id, searchQuery]);
82
93
 
83
- const totalItems = sortedCurated.length + filteredAllModels.length;
94
+ const totalItems = sortedCurated.length + savedModels.length + filteredAllModels.length;
84
95
 
85
96
  const initialIndex = useMemo(() => {
86
97
  if (totalItems === 0) return 0;
87
98
  const curatedIdx = sortedCurated.findIndex((model) => model.id === currentModelId);
88
99
  if (curatedIdx >= 0) return curatedIdx;
100
+ const savedIdx = savedModels.findIndex((model) => model.id === currentModelId);
101
+ if (savedIdx >= 0) return sortedCurated.length + savedIdx;
89
102
  const allIdx = filteredAllModels.findIndex((model) => model.id === currentModelId);
90
- if (allIdx >= 0) return sortedCurated.length + allIdx;
103
+ if (allIdx >= 0) return sortedCurated.length + savedModels.length + allIdx;
91
104
  return 0;
92
- }, [sortedCurated, filteredAllModels, currentModelId, totalItems]);
105
+ }, [sortedCurated, savedModels, filteredAllModels, currentModelId, totalItems]);
93
106
 
94
107
  const { selectedIndex } = useMenuKeyboard({
95
108
  itemCount: totalItems,
96
109
  initialIndex,
97
110
  onClose,
98
111
  onSelect: (selectedIdx) => {
99
- const isCurated = selectedIdx < sortedCurated.length;
100
- const model = isCurated
101
- ? sortedCurated[selectedIdx]
102
- : filteredAllModels[selectedIdx - sortedCurated.length];
112
+ if (selectedIdx < sortedCurated.length) {
113
+ const model = sortedCurated[selectedIdx];
114
+ if (model) {
115
+ onSelect(model);
116
+ }
117
+ return;
118
+ }
119
+
120
+ const afterCurated = selectedIdx - sortedCurated.length;
121
+ if (afterCurated < savedModels.length) {
122
+ const model = savedModels[afterCurated];
123
+ if (model) {
124
+ onSelect(model);
125
+ }
126
+ return;
127
+ }
128
+
129
+ const model = filteredAllModels[afterCurated - savedModels.length];
103
130
  if (model) {
104
131
  onSelect(model);
105
132
  }
@@ -124,7 +151,7 @@ export function ModelMenu({
124
151
  }
125
152
  });
126
153
 
127
- const allSelectedIndex = selectedIndex - sortedCurated.length;
154
+ const allSelectedIndex = selectedIndex - sortedCurated.length - savedModels.length;
128
155
  const isAllSectionSelected = allSelectedIndex >= 0;
129
156
 
130
157
  const scrollRef = useRef<ScrollBoxRenderable | null>(null);
@@ -345,6 +372,25 @@ export function ModelMenu({
345
372
  </>
346
373
  )}
347
374
 
375
+ {savedModels.length > 0 ? (
376
+ <>
377
+ <box marginBottom={1} marginTop={1}>
378
+ <text>
379
+ <span fg={COLORS.DAEMON_LABEL}>[ SAVED ]</span>
380
+ </text>
381
+ </box>
382
+ <box flexDirection="column">
383
+ {savedModels.map((model, idx) =>
384
+ renderModelRow(
385
+ model,
386
+ sortedCurated.length + idx === selectedIndex,
387
+ model.id === currentModelId
388
+ )
389
+ )}
390
+ </box>
391
+ </>
392
+ ) : null}
393
+
348
394
  <box marginBottom={1} marginTop={1}>
349
395
  <text>
350
396
  <span fg={COLORS.DAEMON_LABEL}>[ ALL MODELS ]</span>
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect, useRef } from "react";
2
- import { AVAILABLE_MODELS, setOpenRouterProviderTag, setResponseModel } from "../ai/model-config";
2
+ import { setOpenRouterProviderTag, setResponseModel } from "../ai/model-config";
3
3
  import type {
4
4
  AppPreferences,
5
5
  BashApprovalLevel,
@@ -94,11 +94,8 @@ export function useAppPreferencesBootstrap(
94
94
  }
95
95
 
96
96
  if (prefs?.modelId) {
97
- const modelIdx = AVAILABLE_MODELS.findIndex((m) => m.id === prefs.modelId);
98
- if (modelIdx >= 0) {
99
- setResponseModel(prefs.modelId);
100
- setCurrentModelId(prefs.modelId);
101
- }
97
+ setResponseModel(prefs.modelId);
98
+ setCurrentModelId(prefs.modelId);
102
99
  }
103
100
 
104
101
  if (prefs?.openRouterProviderTag) {
@@ -62,11 +62,11 @@ export function useReasoningAnimation(): UseReasoningAnimationReturn {
62
62
  const maxWidth = terminalWidth ? Math.max(20, terminalWidth - 12) : REASONING_ANIMATION.LINE_WIDTH;
63
63
  const lineWidth = Math.min(REASONING_ANIMATION.LINE_WIDTH, maxWidth);
64
64
 
65
- // Add to display, keeping it at max width by trimming from the left
65
+ // Add to display, restart when reaching the line width
66
66
  setReasoningDisplay((display: string) => {
67
67
  const newDisplay = display + movedChars;
68
- if (newDisplay.length > lineWidth) {
69
- return newDisplay.slice(-lineWidth);
68
+ if (newDisplay.length >= lineWidth) {
69
+ return movedChars;
70
70
  }
71
71
  return newDisplay;
72
72
  });
@@ -1,3 +1,4 @@
1
+ import { TextAttributes } from "@opentui/core";
1
2
  import { COLORS, REASONING_ANIMATION } from "./constants";
2
3
 
3
4
  export function renderReasoningTicker(reasoningDisplay: string) {
@@ -29,7 +30,7 @@ export function renderReasoningTicker(reasoningDisplay: string) {
29
30
  <text>
30
31
  <span fg={REASONING_ANIMATION.PREFIX_COLOR}>{"// "}</span>
31
32
  {segments.map((segment, index) => (
32
- <span fg={segment.color} key={`reasoning-seg-${index}`}>
33
+ <span fg={segment.color} key={`reasoning-seg-${index}`} attributes={TextAttributes.ITALIC}>
33
34
  {segment.text}
34
35
  </span>
35
36
  ))}