fraude-code 0.1.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/README.md +68 -0
- package/dist/index.js +179297 -0
- package/package.json +88 -0
- package/src/agent/agent.ts +475 -0
- package/src/agent/contextManager.ts +141 -0
- package/src/agent/index.ts +14 -0
- package/src/agent/pendingChanges.ts +270 -0
- package/src/agent/prompts/AskPrompt.txt +10 -0
- package/src/agent/prompts/FastPrompt.txt +40 -0
- package/src/agent/prompts/PlannerPrompt.txt +51 -0
- package/src/agent/prompts/ReviewerPrompt.txt +57 -0
- package/src/agent/prompts/WorkerPrompt.txt +33 -0
- package/src/agent/subagents/askAgent.ts +37 -0
- package/src/agent/subagents/extractionAgent.ts +123 -0
- package/src/agent/subagents/fastAgent.ts +45 -0
- package/src/agent/subagents/managerAgent.ts +36 -0
- package/src/agent/subagents/relationAgent.ts +76 -0
- package/src/agent/subagents/researchSubAgent.ts +79 -0
- package/src/agent/subagents/reviewerSubAgent.ts +42 -0
- package/src/agent/subagents/workerSubAgent.ts +42 -0
- package/src/agent/tools/bashTool.ts +94 -0
- package/src/agent/tools/descriptions/bash.txt +47 -0
- package/src/agent/tools/descriptions/edit.txt +7 -0
- package/src/agent/tools/descriptions/glob.txt +4 -0
- package/src/agent/tools/descriptions/grep.txt +8 -0
- package/src/agent/tools/descriptions/lsp.txt +20 -0
- package/src/agent/tools/descriptions/plan.txt +3 -0
- package/src/agent/tools/descriptions/read.txt +9 -0
- package/src/agent/tools/descriptions/todo.txt +12 -0
- package/src/agent/tools/descriptions/write.txt +8 -0
- package/src/agent/tools/editTool.ts +44 -0
- package/src/agent/tools/globTool.ts +59 -0
- package/src/agent/tools/grepTool.ts +343 -0
- package/src/agent/tools/lspTool.ts +429 -0
- package/src/agent/tools/planTool.ts +118 -0
- package/src/agent/tools/readTool.ts +78 -0
- package/src/agent/tools/rememberTool.ts +91 -0
- package/src/agent/tools/testRunnerTool.ts +77 -0
- package/src/agent/tools/testTool.ts +44 -0
- package/src/agent/tools/todoTool.ts +224 -0
- package/src/agent/tools/writeTool.ts +33 -0
- package/src/commands/COMMANDS.ts +38 -0
- package/src/commands/cerebras/auth.ts +27 -0
- package/src/commands/cerebras/index.ts +31 -0
- package/src/commands/forget.ts +29 -0
- package/src/commands/google/auth.ts +24 -0
- package/src/commands/google/index.ts +31 -0
- package/src/commands/groq/add_model.ts +60 -0
- package/src/commands/groq/auth.ts +24 -0
- package/src/commands/groq/index.ts +33 -0
- package/src/commands/index.ts +65 -0
- package/src/commands/knowledge.ts +92 -0
- package/src/commands/log.ts +32 -0
- package/src/commands/mistral/auth.ts +27 -0
- package/src/commands/mistral/index.ts +31 -0
- package/src/commands/model/index.ts +145 -0
- package/src/commands/models/index.ts +16 -0
- package/src/commands/ollama/index.ts +29 -0
- package/src/commands/openrouter/add_model.ts +64 -0
- package/src/commands/openrouter/auth.ts +24 -0
- package/src/commands/openrouter/index.ts +33 -0
- package/src/commands/remember.ts +48 -0
- package/src/commands/serve.ts +31 -0
- package/src/commands/session/index.ts +21 -0
- package/src/commands/usage.ts +15 -0
- package/src/commands/visualize.ts +773 -0
- package/src/components/App.tsx +55 -0
- package/src/components/IntroComponent.tsx +70 -0
- package/src/components/LoaderComponent.tsx +68 -0
- package/src/components/OutputRenderer.tsx +88 -0
- package/src/components/SettingsRenderer.tsx +23 -0
- package/src/components/input/CommandSuggestions.tsx +41 -0
- package/src/components/input/FileSuggestions.tsx +61 -0
- package/src/components/input/InputBox.tsx +371 -0
- package/src/components/output/CheckpointView.tsx +13 -0
- package/src/components/output/CommandView.tsx +13 -0
- package/src/components/output/CommentView.tsx +12 -0
- package/src/components/output/ConfirmationView.tsx +179 -0
- package/src/components/output/ContextUsage.tsx +62 -0
- package/src/components/output/DiffView.tsx +202 -0
- package/src/components/output/ErrorView.tsx +14 -0
- package/src/components/output/InteractiveServerView.tsx +69 -0
- package/src/components/output/KnowledgeView.tsx +220 -0
- package/src/components/output/MarkdownView.tsx +15 -0
- package/src/components/output/ModelSelectView.tsx +71 -0
- package/src/components/output/ReasoningView.tsx +21 -0
- package/src/components/output/ToolCallView.tsx +45 -0
- package/src/components/settings/ModelList.tsx +250 -0
- package/src/components/settings/TokenUsage.tsx +274 -0
- package/src/config/schema.ts +19 -0
- package/src/config/settings.ts +229 -0
- package/src/index.tsx +100 -0
- package/src/parsers/tree-sitter-python.wasm +0 -0
- package/src/providers/providers.ts +71 -0
- package/src/services/PluginLoader.ts +123 -0
- package/src/services/cerebras.ts +69 -0
- package/src/services/embeddingService.ts +229 -0
- package/src/services/google.ts +65 -0
- package/src/services/graphSerializer.ts +248 -0
- package/src/services/groq.ts +23 -0
- package/src/services/knowledgeOrchestrator.ts +286 -0
- package/src/services/mistral.ts +79 -0
- package/src/services/ollama.ts +109 -0
- package/src/services/openrouter.ts +23 -0
- package/src/services/symbolExtractor.ts +277 -0
- package/src/store/useFraudeStore.ts +123 -0
- package/src/store/useSettingsStore.ts +38 -0
- package/src/theme.ts +26 -0
- package/src/types/Agent.ts +147 -0
- package/src/types/CommandDefinition.ts +8 -0
- package/src/types/Model.ts +94 -0
- package/src/types/OutputItem.ts +24 -0
- package/src/types/PluginContext.ts +55 -0
- package/src/types/TokenUsage.ts +5 -0
- package/src/types/assets.d.ts +4 -0
- package/src/utils/agentCognition.ts +1152 -0
- package/src/utils/fileSuggestions.ts +111 -0
- package/src/utils/index.ts +17 -0
- package/src/utils/initFraude.ts +8 -0
- package/src/utils/logger.ts +24 -0
- package/src/utils/lspClient.ts +1415 -0
- package/src/utils/paths.ts +24 -0
- package/src/utils/queryHandler.ts +227 -0
- package/src/utils/router.ts +278 -0
- package/src/utils/streamHandler.ts +132 -0
- package/src/utils/treeSitterQueries.ts +125 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import { useEffect, useMemo } from "react";
|
|
3
|
+
import { type Model, parseModelUniqueId } from "@/types/Model";
|
|
4
|
+
import useSettingsStore from "@/store/useSettingsStore";
|
|
5
|
+
|
|
6
|
+
import { THEME as SHARED_THEME } from "@/theme";
|
|
7
|
+
|
|
8
|
+
// Simplified Theme mapped to Shared Theme
|
|
9
|
+
const THEME = {
|
|
10
|
+
active: SHARED_THEME.primary,
|
|
11
|
+
primary: SHARED_THEME.primary,
|
|
12
|
+
secondary: SHARED_THEME.primaryDim,
|
|
13
|
+
text: SHARED_THEME.text,
|
|
14
|
+
dim: SHARED_THEME.dim,
|
|
15
|
+
border: SHARED_THEME.border,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const formatContext = (contextLength: number | undefined) => {
|
|
19
|
+
if (!contextLength) return "";
|
|
20
|
+
return `${(contextLength / 1000).toFixed(0)}k`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check if a model matches a stored model reference.
|
|
25
|
+
*/
|
|
26
|
+
const modelMatchesReference = (model: Model, reference: string): boolean => {
|
|
27
|
+
const parsed = parseModelUniqueId(reference);
|
|
28
|
+
if (parsed) {
|
|
29
|
+
return model.name === parsed.name && model.type === parsed.type;
|
|
30
|
+
}
|
|
31
|
+
return model.name === reference;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const ModelRow = ({
|
|
35
|
+
model,
|
|
36
|
+
isPrimary,
|
|
37
|
+
isSecondary,
|
|
38
|
+
}: {
|
|
39
|
+
model: Model;
|
|
40
|
+
isPrimary: boolean;
|
|
41
|
+
isSecondary: boolean;
|
|
42
|
+
}) => {
|
|
43
|
+
const isActive = isPrimary || isSecondary;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Box flexDirection="row" gap={1}>
|
|
47
|
+
<Box width={1}>{isActive && <Text color={THEME.active}>•</Text>}</Box>
|
|
48
|
+
|
|
49
|
+
<Box flexGrow={1}>
|
|
50
|
+
<Text color={isActive ? THEME.active : THEME.text} wrap="truncate-end">
|
|
51
|
+
{model.name}
|
|
52
|
+
</Text>
|
|
53
|
+
</Box>
|
|
54
|
+
|
|
55
|
+
{/* Tags */}
|
|
56
|
+
<Box gap={1} flexShrink={0}>
|
|
57
|
+
{/* Context Size */}
|
|
58
|
+
{model.details?.context_length && (
|
|
59
|
+
<Text color={THEME.dim}>
|
|
60
|
+
{formatContext(model.details.context_length)}
|
|
61
|
+
</Text>
|
|
62
|
+
)}
|
|
63
|
+
|
|
64
|
+
{isPrimary && (
|
|
65
|
+
<Text color={THEME.primary} bold>
|
|
66
|
+
PRI
|
|
67
|
+
</Text>
|
|
68
|
+
)}
|
|
69
|
+
{isSecondary && (
|
|
70
|
+
<Text color={THEME.secondary} bold>
|
|
71
|
+
SEC
|
|
72
|
+
</Text>
|
|
73
|
+
)}
|
|
74
|
+
</Box>
|
|
75
|
+
</Box>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const ProviderSection = ({
|
|
80
|
+
provider,
|
|
81
|
+
models,
|
|
82
|
+
primaryModelRef,
|
|
83
|
+
secondaryModelRef,
|
|
84
|
+
showAll = false,
|
|
85
|
+
}: {
|
|
86
|
+
provider: string;
|
|
87
|
+
models: Model[];
|
|
88
|
+
primaryModelRef: string;
|
|
89
|
+
secondaryModelRef: string;
|
|
90
|
+
showAll?: boolean;
|
|
91
|
+
}) => {
|
|
92
|
+
// Show all active models + up to 5 others
|
|
93
|
+
const { visibleModels, hiddenCount } = useMemo(() => {
|
|
94
|
+
const active: Model[] = [];
|
|
95
|
+
const others: Model[] = [];
|
|
96
|
+
|
|
97
|
+
models.forEach((m) => {
|
|
98
|
+
const isPri = modelMatchesReference(m, primaryModelRef);
|
|
99
|
+
const isSec = modelMatchesReference(m, secondaryModelRef);
|
|
100
|
+
if (isPri || isSec) {
|
|
101
|
+
active.push(m);
|
|
102
|
+
} else {
|
|
103
|
+
others.push(m);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Sort others alphabetically
|
|
108
|
+
others.sort((a, b) => a.name.localeCompare(b.name));
|
|
109
|
+
|
|
110
|
+
// Combine: Active first, then top 3 others
|
|
111
|
+
// If we have very few active models, show more others to fill space, but let's keep it simple.
|
|
112
|
+
// Let's just show up to 5 'others'.
|
|
113
|
+
const visibleOthers = showAll ? others : others.slice(0, 5);
|
|
114
|
+
return {
|
|
115
|
+
visibleModels: [...active, ...visibleOthers],
|
|
116
|
+
hiddenCount: others.length - visibleOthers.length,
|
|
117
|
+
};
|
|
118
|
+
}, [models, primaryModelRef, secondaryModelRef, showAll]);
|
|
119
|
+
|
|
120
|
+
if (models.length === 0) return null;
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Box flexDirection="column">
|
|
124
|
+
<Text color={THEME.dim} bold>
|
|
125
|
+
{provider.toUpperCase()}
|
|
126
|
+
</Text>
|
|
127
|
+
{visibleModels.map((model) => (
|
|
128
|
+
<ModelRow
|
|
129
|
+
key={model.digest || `${model.type}-${model.name}`}
|
|
130
|
+
model={model}
|
|
131
|
+
isPrimary={modelMatchesReference(model, primaryModelRef)}
|
|
132
|
+
isSecondary={modelMatchesReference(model, secondaryModelRef)}
|
|
133
|
+
/>
|
|
134
|
+
))}
|
|
135
|
+
{hiddenCount > 0 && (
|
|
136
|
+
<Box marginLeft={2}>
|
|
137
|
+
<Text color={THEME.dim} italic>
|
|
138
|
+
+ {hiddenCount} more models...
|
|
139
|
+
</Text>
|
|
140
|
+
</Box>
|
|
141
|
+
)}
|
|
142
|
+
</Box>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const CurrentConfig = ({
|
|
147
|
+
primary,
|
|
148
|
+
secondary,
|
|
149
|
+
}: {
|
|
150
|
+
primary: string;
|
|
151
|
+
secondary: string;
|
|
152
|
+
}) => (
|
|
153
|
+
<Box
|
|
154
|
+
flexDirection="column"
|
|
155
|
+
borderStyle="single"
|
|
156
|
+
borderColor={THEME.border}
|
|
157
|
+
paddingX={1}
|
|
158
|
+
>
|
|
159
|
+
<Box flexDirection="row" gap={2}>
|
|
160
|
+
<Text color={THEME.primary} bold>
|
|
161
|
+
PRIMARY
|
|
162
|
+
</Text>
|
|
163
|
+
<Text color={THEME.text}>{primary}</Text>
|
|
164
|
+
</Box>
|
|
165
|
+
<Box flexDirection="row" gap={2}>
|
|
166
|
+
<Text color={THEME.secondary} bold>
|
|
167
|
+
SECONDARY
|
|
168
|
+
</Text>
|
|
169
|
+
<Text color={THEME.text}>{secondary}</Text>
|
|
170
|
+
</Box>
|
|
171
|
+
</Box>
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const ModelList = ({
|
|
175
|
+
providerFilter,
|
|
176
|
+
showAll = false,
|
|
177
|
+
}: {
|
|
178
|
+
providerFilter?: string;
|
|
179
|
+
showAll?: boolean;
|
|
180
|
+
}) => {
|
|
181
|
+
const { primaryModel, secondaryModel, models, syncWithSettings } =
|
|
182
|
+
useSettingsStore();
|
|
183
|
+
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
syncWithSettings();
|
|
186
|
+
}, [syncWithSettings]);
|
|
187
|
+
|
|
188
|
+
// Group models
|
|
189
|
+
const groups = useMemo(() => {
|
|
190
|
+
const g: Record<string, Model[]> = {};
|
|
191
|
+
models.forEach((m) => {
|
|
192
|
+
// Clean up provider name
|
|
193
|
+
let type = (m.type || "ollama").toLowerCase();
|
|
194
|
+
|
|
195
|
+
// Filter by provider if requested
|
|
196
|
+
if (providerFilter && type !== providerFilter.toLowerCase()) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Skip ollama embedding models
|
|
201
|
+
if (
|
|
202
|
+
type === "ollama" &&
|
|
203
|
+
m.capabilities?.length === 1 &&
|
|
204
|
+
m.capabilities[0] === "embedding"
|
|
205
|
+
)
|
|
206
|
+
return;
|
|
207
|
+
|
|
208
|
+
if (!g[type]) g[type] = [];
|
|
209
|
+
g[type].push(m);
|
|
210
|
+
});
|
|
211
|
+
return g;
|
|
212
|
+
}, [models, providerFilter]);
|
|
213
|
+
|
|
214
|
+
if (models.length === 0) {
|
|
215
|
+
return (
|
|
216
|
+
<Text color={THEME.dim}>
|
|
217
|
+
No models found{providerFilter ? ` for ${providerFilter}` : ""}.
|
|
218
|
+
</Text>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<Box flexDirection="column" gap={1}>
|
|
224
|
+
<CurrentConfig primary={primaryModel} secondary={secondaryModel} />
|
|
225
|
+
|
|
226
|
+
<Box flexDirection="column" gap={1}>
|
|
227
|
+
{Object.entries(groups).map(([provider, providerModels]) => (
|
|
228
|
+
<ProviderSection
|
|
229
|
+
key={provider}
|
|
230
|
+
provider={provider}
|
|
231
|
+
models={providerModels}
|
|
232
|
+
primaryModelRef={primaryModel}
|
|
233
|
+
secondaryModelRef={secondaryModel}
|
|
234
|
+
showAll={showAll || !!providerFilter}
|
|
235
|
+
/>
|
|
236
|
+
))}
|
|
237
|
+
</Box>
|
|
238
|
+
|
|
239
|
+
{!providerFilter && (
|
|
240
|
+
<Box marginTop={1}>
|
|
241
|
+
<Text color={THEME.dim}>
|
|
242
|
+
Use <Text color="white">/model <name></Text> to switch
|
|
243
|
+
</Text>
|
|
244
|
+
</Box>
|
|
245
|
+
)}
|
|
246
|
+
</Box>
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export default ModelList;
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { type Model } from "@/types/Model";
|
|
4
|
+
import useSettingsStore from "@/store/useSettingsStore";
|
|
5
|
+
import log from "@/utils/logger";
|
|
6
|
+
|
|
7
|
+
import { THEME as SHARED_THEME } from "@/theme";
|
|
8
|
+
|
|
9
|
+
// Minimalist Theme mapped to Shared Theme
|
|
10
|
+
const THEME = {
|
|
11
|
+
text: SHARED_THEME.text,
|
|
12
|
+
dim: SHARED_THEME.dim,
|
|
13
|
+
accent: SHARED_THEME.primaryLight,
|
|
14
|
+
error: SHARED_THEME.error,
|
|
15
|
+
header: SHARED_THEME.text,
|
|
16
|
+
barFilled: SHARED_THEME.primary,
|
|
17
|
+
barEmpty: SHARED_THEME.border,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Format token count with K/M suffix (Compact)
|
|
21
|
+
const formatTokens = (tokens: number): string => {
|
|
22
|
+
if (tokens === 0) return "0";
|
|
23
|
+
if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`;
|
|
24
|
+
if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}k`;
|
|
25
|
+
return tokens.toLocaleString();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
interface ModelUsage {
|
|
29
|
+
name: string;
|
|
30
|
+
promptTokens: number;
|
|
31
|
+
completionTokens: number;
|
|
32
|
+
totalTokens: number;
|
|
33
|
+
percentOfProvider: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ProviderUsage {
|
|
37
|
+
provider: string;
|
|
38
|
+
models: ModelUsage[];
|
|
39
|
+
totalTokens: number;
|
|
40
|
+
percentOfTotal: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ProgressBar = ({
|
|
44
|
+
percent,
|
|
45
|
+
width = 15,
|
|
46
|
+
}: {
|
|
47
|
+
percent: number;
|
|
48
|
+
width?: number;
|
|
49
|
+
}) => {
|
|
50
|
+
const safePercent = Math.min(100, Math.max(0, percent));
|
|
51
|
+
const filled = Math.round((safePercent / 100) * width);
|
|
52
|
+
const empty = width - filled;
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Text>
|
|
56
|
+
<Text color={THEME.barFilled}>{"━".repeat(filled)}</Text>
|
|
57
|
+
<Text color={THEME.barEmpty}>{"─".repeat(empty)}</Text>
|
|
58
|
+
</Text>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Unified Column Widths
|
|
63
|
+
const COL_WIDTHS = {
|
|
64
|
+
NAME: 24,
|
|
65
|
+
BAR: 18, // Widen for bar + text
|
|
66
|
+
TOTAL: 10,
|
|
67
|
+
BREAKDOWN: 24,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const HeaderRow = () => (
|
|
71
|
+
<Box flexDirection="row" gap={2} marginBottom={0}>
|
|
72
|
+
<Box width={COL_WIDTHS.NAME}>
|
|
73
|
+
<Text color={THEME.dim}>Model Name</Text>
|
|
74
|
+
</Box>
|
|
75
|
+
<Box width={COL_WIDTHS.BAR}>
|
|
76
|
+
<Text color={THEME.dim}>% of Provider</Text>
|
|
77
|
+
</Box>
|
|
78
|
+
<Box width={COL_WIDTHS.TOTAL} justifyContent="flex-end">
|
|
79
|
+
<Text color={THEME.dim}>Total</Text>
|
|
80
|
+
</Box>
|
|
81
|
+
<Box width={COL_WIDTHS.BREAKDOWN} justifyContent="flex-end">
|
|
82
|
+
<Text color={THEME.dim}>Breakdown</Text>
|
|
83
|
+
</Box>
|
|
84
|
+
</Box>
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const ModelRow = ({ model }: { model: ModelUsage }) => {
|
|
88
|
+
return (
|
|
89
|
+
<Box flexDirection="row" gap={2}>
|
|
90
|
+
{/* Name */}
|
|
91
|
+
<Box width={COL_WIDTHS.NAME}>
|
|
92
|
+
<Text color={THEME.text} wrap="truncate-end">
|
|
93
|
+
{model.name}
|
|
94
|
+
</Text>
|
|
95
|
+
</Box>
|
|
96
|
+
|
|
97
|
+
{/* Bar */}
|
|
98
|
+
<Box width={COL_WIDTHS.BAR} flexDirection="row" gap={1}>
|
|
99
|
+
<ProgressBar percent={model.percentOfProvider} width={8} />
|
|
100
|
+
<Text color={THEME.dim}>{model.percentOfProvider.toFixed(0)}%</Text>
|
|
101
|
+
</Box>
|
|
102
|
+
|
|
103
|
+
{/* Total */}
|
|
104
|
+
<Box width={COL_WIDTHS.TOTAL} justifyContent="flex-end">
|
|
105
|
+
<Text color={THEME.text} bold>
|
|
106
|
+
{formatTokens(model.totalTokens)}
|
|
107
|
+
</Text>
|
|
108
|
+
</Box>
|
|
109
|
+
|
|
110
|
+
{/* Breakdown */}
|
|
111
|
+
<Box width={COL_WIDTHS.BREAKDOWN} justifyContent="flex-end">
|
|
112
|
+
<Text color={THEME.dim}>
|
|
113
|
+
P: {formatTokens(model.promptTokens)} · C:{" "}
|
|
114
|
+
{formatTokens(model.completionTokens)}
|
|
115
|
+
</Text>
|
|
116
|
+
</Box>
|
|
117
|
+
</Box>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const ProviderSection = ({ usage }: { usage: ProviderUsage }) => {
|
|
122
|
+
if (usage.totalTokens === 0) return null;
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Box flexDirection="column" marginTop={1}>
|
|
126
|
+
<Box marginBottom={0}>
|
|
127
|
+
<Text bold color={THEME.header}>
|
|
128
|
+
{usage.provider.toUpperCase()}
|
|
129
|
+
<Text color={THEME.dim}>
|
|
130
|
+
{" "}
|
|
131
|
+
· {usage.percentOfTotal.toFixed(1)}% of all usage
|
|
132
|
+
</Text>
|
|
133
|
+
</Text>
|
|
134
|
+
</Box>
|
|
135
|
+
|
|
136
|
+
{/* Column Headers for this section */}
|
|
137
|
+
<HeaderRow />
|
|
138
|
+
|
|
139
|
+
<Box flexDirection="column">
|
|
140
|
+
{usage.models
|
|
141
|
+
.sort((a, b) => b.totalTokens - a.totalTokens)
|
|
142
|
+
.map((model) => (
|
|
143
|
+
<ModelRow key={model.name} model={model} />
|
|
144
|
+
))}
|
|
145
|
+
</Box>
|
|
146
|
+
</Box>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const TokenUsage = () => {
|
|
151
|
+
const { models } = useSettingsStore();
|
|
152
|
+
log(JSON.stringify(models, null, 2));
|
|
153
|
+
|
|
154
|
+
const stats = useMemo(() => {
|
|
155
|
+
let globalTotal = 0;
|
|
156
|
+
let globalPrompt = 0;
|
|
157
|
+
let globalCompletion = 0;
|
|
158
|
+
|
|
159
|
+
const grouped: Record<string, Model[]> = {} as Record<string, Model[]>;
|
|
160
|
+
Object.keys(grouped).forEach((p) => (grouped[p] = []));
|
|
161
|
+
|
|
162
|
+
// Aggregate
|
|
163
|
+
for (const model of models) {
|
|
164
|
+
const provider = model.type || "ollama";
|
|
165
|
+
if (!grouped[provider]) grouped[provider] = []; // Safety
|
|
166
|
+
grouped[provider].push(model);
|
|
167
|
+
|
|
168
|
+
const u = model.usage || {
|
|
169
|
+
totalTokens: 0,
|
|
170
|
+
promptTokens: 0,
|
|
171
|
+
completionTokens: 0,
|
|
172
|
+
};
|
|
173
|
+
globalTotal += u.totalTokens;
|
|
174
|
+
globalPrompt += u.promptTokens;
|
|
175
|
+
globalCompletion += u.completionTokens;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Process per provider
|
|
179
|
+
const providers: ProviderUsage[] = Object.keys(grouped)
|
|
180
|
+
.map((provider) => {
|
|
181
|
+
const pModels = grouped[provider] || [];
|
|
182
|
+
let pTotal = 0;
|
|
183
|
+
|
|
184
|
+
const mUsages: ModelUsage[] = pModels
|
|
185
|
+
.filter((m) => (m.usage?.totalTokens ?? 0) > 0)
|
|
186
|
+
.map((m) => {
|
|
187
|
+
const t = m.usage?.totalTokens ?? 0;
|
|
188
|
+
pTotal += t;
|
|
189
|
+
return {
|
|
190
|
+
name: m.name,
|
|
191
|
+
totalTokens: t,
|
|
192
|
+
promptTokens: m.usage?.promptTokens ?? 0,
|
|
193
|
+
completionTokens: m.usage?.completionTokens ?? 0,
|
|
194
|
+
percentOfProvider: 0, // Calc later
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Calc percents
|
|
199
|
+
mUsages.forEach(
|
|
200
|
+
(m) =>
|
|
201
|
+
(m.percentOfProvider =
|
|
202
|
+
pTotal > 0 ? (m.totalTokens / pTotal) * 100 : 0),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
provider,
|
|
207
|
+
models: mUsages,
|
|
208
|
+
totalTokens: pTotal,
|
|
209
|
+
percentOfTotal: globalTotal > 0 ? (pTotal / globalTotal) * 100 : 0,
|
|
210
|
+
};
|
|
211
|
+
})
|
|
212
|
+
.filter((p) => p.totalTokens > 0)
|
|
213
|
+
.sort((a, b) => b.totalTokens - a.totalTokens);
|
|
214
|
+
|
|
215
|
+
return { globalTotal, globalPrompt, globalCompletion, providers };
|
|
216
|
+
}, [models]);
|
|
217
|
+
|
|
218
|
+
if (stats.globalTotal === 0) {
|
|
219
|
+
return (
|
|
220
|
+
<Box padding={1}>
|
|
221
|
+
<Text color={THEME.dim}>
|
|
222
|
+
No token usage data available. Start a chat to see stats!
|
|
223
|
+
</Text>
|
|
224
|
+
</Box>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<Box flexDirection="column" paddingX={1} paddingY={1}>
|
|
230
|
+
{/* Header Stats */}
|
|
231
|
+
<Box
|
|
232
|
+
borderStyle="single"
|
|
233
|
+
borderColor={THEME.dim}
|
|
234
|
+
paddingX={1}
|
|
235
|
+
marginBottom={1}
|
|
236
|
+
justifyContent="space-between"
|
|
237
|
+
>
|
|
238
|
+
<Text color={THEME.header} bold>
|
|
239
|
+
USAGE STATS
|
|
240
|
+
</Text>
|
|
241
|
+
<Box gap={2}>
|
|
242
|
+
<Text>
|
|
243
|
+
TOTAL:{" "}
|
|
244
|
+
<Text color={SHARED_THEME.primary} bold>
|
|
245
|
+
{formatTokens(stats.globalTotal)}
|
|
246
|
+
</Text>
|
|
247
|
+
</Text>
|
|
248
|
+
<Text color={THEME.dim}>P: {formatTokens(stats.globalPrompt)}</Text>
|
|
249
|
+
<Text color={THEME.dim}>
|
|
250
|
+
C: {formatTokens(stats.globalCompletion)}
|
|
251
|
+
</Text>
|
|
252
|
+
</Box>
|
|
253
|
+
</Box>
|
|
254
|
+
|
|
255
|
+
{/* Provider Lists */}
|
|
256
|
+
{stats.providers.map((p) => (
|
|
257
|
+
<ProviderSection key={p.provider} usage={p} />
|
|
258
|
+
))}
|
|
259
|
+
|
|
260
|
+
{/* Legend Footer */}
|
|
261
|
+
<Box marginTop={1} paddingX={1} flexDirection="column">
|
|
262
|
+
<Text color={THEME.dim} italic>
|
|
263
|
+
• P / C = Prompt vs Completion tokens
|
|
264
|
+
</Text>
|
|
265
|
+
<Text color={THEME.dim} italic>
|
|
266
|
+
• % of Provider = How much this model contributed to that provider's
|
|
267
|
+
total cost/usage
|
|
268
|
+
</Text>
|
|
269
|
+
</Box>
|
|
270
|
+
</Box>
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export default TokenUsage;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ModelSchema } from "../types/Model";
|
|
3
|
+
|
|
4
|
+
export const SettingsSchema = z.object({
|
|
5
|
+
lastOpened: z.iso.datetime().optional(),
|
|
6
|
+
ollamaUrl: z.string().default("http://localhost:11434"),
|
|
7
|
+
primaryModel: z.string().default("qwen3:8b|ollama"),
|
|
8
|
+
secondaryModel: z.string().default("llama3.1:latest|ollama"),
|
|
9
|
+
models: z.array(ModelSchema).default([]),
|
|
10
|
+
history: z.array(z.string()).default([]),
|
|
11
|
+
openrouter_api_key: z.string().optional(),
|
|
12
|
+
groq_api_key: z.string().optional(),
|
|
13
|
+
mistral_api_key: z.string().optional(),
|
|
14
|
+
cerebras_api_key: z.string().optional(),
|
|
15
|
+
google_api_key: z.string().optional(),
|
|
16
|
+
pluginSettings: z.any().default({}),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export type Config = z.infer<typeof SettingsSchema>;
|