@fastino-ai/pioneer-cli 0.2.7 → 0.2.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/README.md +51 -71
- package/package.json +1 -1
- package/src/api.ts +670 -228
- package/src/client/WebSocketClient.ts +25 -7
- package/src/config.ts +8 -1
- package/src/index.tsx +2267 -1120
package/src/index.tsx
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Uses Ink (React) for terminal UI.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import React, { useState, useEffect } from "react";
|
|
7
|
+
import React, { useState, useEffect, useRef } from "react";
|
|
8
8
|
import { render, Box, Text, useApp, useInput, useStdin, Static } from "ink";
|
|
9
9
|
import Spinner from "ink-spinner";
|
|
10
10
|
import TextInput from "ink-text-input";
|
|
@@ -14,6 +14,7 @@ import * as path from "path";
|
|
|
14
14
|
import {
|
|
15
15
|
getApiKey,
|
|
16
16
|
getBaseUrl,
|
|
17
|
+
getMleModel,
|
|
17
18
|
saveConfig,
|
|
18
19
|
clearApiKey,
|
|
19
20
|
getHfToken,
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
clearHfToken,
|
|
22
23
|
} from "./config.js";
|
|
23
24
|
import * as api from "./api.js";
|
|
24
|
-
import {
|
|
25
|
+
import { WebSocketClient, type HistoryMessage } from "./client/WebSocketClient.js";
|
|
25
26
|
import {
|
|
26
27
|
isEnabled as isTelemetryEnabled,
|
|
27
28
|
hasChosenTelemetry,
|
|
@@ -150,9 +151,30 @@ function parseDatasetRef(datasetStr: string): api.DatasetRef | null {
|
|
|
150
151
|
};
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
|
|
154
|
+
const BOOLEAN_FLAGS = new Set([
|
|
155
|
+
"help",
|
|
156
|
+
"h",
|
|
157
|
+
"version",
|
|
158
|
+
"v",
|
|
159
|
+
"private",
|
|
160
|
+
"save",
|
|
161
|
+
"multi-label",
|
|
162
|
+
"include-spans",
|
|
163
|
+
"echo",
|
|
164
|
+
"include-confidence",
|
|
165
|
+
"format-results",
|
|
166
|
+
"reasoning-trace",
|
|
167
|
+
"use-meta-felix",
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
function parseArgs(argv: string[]): {
|
|
171
|
+
command: string[];
|
|
172
|
+
flags: Record<string, string>;
|
|
173
|
+
parseErrors: string[];
|
|
174
|
+
} {
|
|
154
175
|
const command: string[] = [];
|
|
155
176
|
const flags: Record<string, string> = {};
|
|
177
|
+
const parseErrors: string[] = [];
|
|
156
178
|
|
|
157
179
|
for (let i = 0; i < argv.length; i++) {
|
|
158
180
|
const arg = argv[i];
|
|
@@ -163,7 +185,12 @@ function parseArgs(argv: string[]): { command: string[]; flags: Record<string, s
|
|
|
163
185
|
flags[key] = next;
|
|
164
186
|
i++;
|
|
165
187
|
} else {
|
|
166
|
-
|
|
188
|
+
if (BOOLEAN_FLAGS.has(key)) {
|
|
189
|
+
flags[key] = "true";
|
|
190
|
+
} else {
|
|
191
|
+
parseErrors.push(`--${key}`);
|
|
192
|
+
flags[key] = "";
|
|
193
|
+
}
|
|
167
194
|
}
|
|
168
195
|
} else if (arg.startsWith("-") && arg.length === 2) {
|
|
169
196
|
// Handle short flags like -v, -h
|
|
@@ -173,14 +200,37 @@ function parseArgs(argv: string[]): { command: string[]; flags: Record<string, s
|
|
|
173
200
|
flags[key] = next;
|
|
174
201
|
i++;
|
|
175
202
|
} else {
|
|
176
|
-
|
|
203
|
+
if (BOOLEAN_FLAGS.has(key)) {
|
|
204
|
+
flags[key] = "true";
|
|
205
|
+
} else {
|
|
206
|
+
parseErrors.push(`-${key}`);
|
|
207
|
+
flags[key] = "";
|
|
208
|
+
}
|
|
177
209
|
}
|
|
178
210
|
} else {
|
|
179
211
|
command.push(arg);
|
|
180
212
|
}
|
|
181
213
|
}
|
|
182
214
|
|
|
183
|
-
return { command, flags };
|
|
215
|
+
return { command, flags, parseErrors };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
type CreateProjectExample = Record<string, unknown> | undefined;
|
|
219
|
+
|
|
220
|
+
function parseProjectExample(exampleStr: string | undefined): { value?: CreateProjectExample; error?: string } {
|
|
221
|
+
if (!exampleStr) {
|
|
222
|
+
return { value: undefined };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const parsed = JSON.parse(exampleStr) as unknown;
|
|
227
|
+
if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
228
|
+
return { error: "--example must be a JSON object" };
|
|
229
|
+
}
|
|
230
|
+
return { value: parsed as CreateProjectExample };
|
|
231
|
+
} catch {
|
|
232
|
+
return { error: "--example must be valid JSON" };
|
|
233
|
+
}
|
|
184
234
|
}
|
|
185
235
|
|
|
186
236
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -663,177 +713,758 @@ function ApiCommand<T>({ action, successMessage }: ApiCommandProps<T>) {
|
|
|
663
713
|
);
|
|
664
714
|
}
|
|
665
715
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
716
|
+
interface AgentInteractiveCommandProps {
|
|
717
|
+
message: string;
|
|
718
|
+
conversationId?: string;
|
|
719
|
+
history?: api.AgentChatHistoryItem[];
|
|
720
|
+
}
|
|
669
721
|
|
|
670
|
-
function
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
722
|
+
function toHistoryMessages(
|
|
723
|
+
items: Array<api.AgentChatHistoryItem | api.ChatSessionMessage>
|
|
724
|
+
): HistoryMessage[] {
|
|
725
|
+
return items
|
|
726
|
+
.filter((item) => Boolean(item && typeof item.content === "string" && typeof item.role === "string"))
|
|
727
|
+
.map((item) => {
|
|
728
|
+
const message: HistoryMessage = {
|
|
729
|
+
role: item.role as "user" | "assistant" | "tool" | string,
|
|
730
|
+
content: item.content,
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
const sessionMessage = item as api.ChatSessionMessage;
|
|
734
|
+
if (sessionMessage.tool_call_id) {
|
|
735
|
+
message.tool_call_id = sessionMessage.tool_call_id;
|
|
736
|
+
}
|
|
737
|
+
if (sessionMessage.tool_calls && Array.isArray(sessionMessage.tool_calls)) {
|
|
738
|
+
message.tool_calls = sessionMessage.tool_calls.filter((call) => Boolean(call));
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return message;
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function AgentInteractiveCommand({ message, conversationId, history }: AgentInteractiveCommandProps) {
|
|
746
|
+
const [state, setState] = useState<"connecting" | "running" | "done" | "error">("connecting");
|
|
747
|
+
const [stream, setStream] = useState("");
|
|
674
748
|
const [error, setError] = useState("");
|
|
749
|
+
const [statusHint, setStatusHint] = useState("");
|
|
750
|
+
const [client] = useState(() => new WebSocketClient());
|
|
751
|
+
const doneHistory = useRef<HistoryMessage[] | undefined>(undefined);
|
|
675
752
|
|
|
676
753
|
useEffect(() => {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
754
|
+
let isActive = true;
|
|
755
|
+
const model = getMleModel();
|
|
756
|
+
let wsSessionId: string | undefined;
|
|
757
|
+
const finish = (code: number) => {
|
|
758
|
+
setTimeout(() => process.exit(code), 300);
|
|
759
|
+
};
|
|
760
|
+
const getLatestAssistantContent = (
|
|
761
|
+
messages: HistoryMessage[] | undefined
|
|
762
|
+
): string => {
|
|
763
|
+
if (!messages?.length) {
|
|
764
|
+
return "";
|
|
765
|
+
}
|
|
766
|
+
const lastAssistant = [...messages]
|
|
767
|
+
.reverse()
|
|
768
|
+
.find(
|
|
769
|
+
(entry) =>
|
|
770
|
+
entry.role === "assistant" && typeof entry.content === "string" && entry.content.trim()
|
|
771
|
+
);
|
|
772
|
+
return lastAssistant?.content?.trim() ?? "";
|
|
773
|
+
};
|
|
774
|
+
let streamBuffer = "";
|
|
775
|
+
const appendStream = (content: string) => {
|
|
776
|
+
streamBuffer += content;
|
|
777
|
+
setStream(streamBuffer);
|
|
778
|
+
};
|
|
779
|
+
const runAgentFallback = async (
|
|
780
|
+
fallbackHistory: HistoryMessage[],
|
|
781
|
+
sessionIdHint?: string
|
|
782
|
+
) => {
|
|
783
|
+
const plainHistory: api.AgentChatHistoryItem[] = fallbackHistory.map((entry) => ({
|
|
784
|
+
role: entry.role,
|
|
785
|
+
content: entry.content,
|
|
786
|
+
}));
|
|
787
|
+
const fallbackTimeout = (ms: number) =>
|
|
788
|
+
new Promise<never>((_, reject) => {
|
|
789
|
+
setTimeout(() => reject(new Error(`REST fallback timed out after ${ms}ms`)), ms);
|
|
790
|
+
});
|
|
791
|
+
setState("running");
|
|
792
|
+
setStatusHint("WebSocket unavailable, using REST fallback...");
|
|
793
|
+
try {
|
|
794
|
+
const runRequest = (sessionIdOverride?: string) => {
|
|
795
|
+
const requestConversationId = sessionIdOverride;
|
|
796
|
+
return Promise.race([
|
|
797
|
+
api.agentChat({
|
|
798
|
+
message,
|
|
799
|
+
...(requestConversationId ? { conversation_id: requestConversationId } : {}),
|
|
800
|
+
...(plainHistory.length ? { history: plainHistory } : {}),
|
|
801
|
+
}),
|
|
802
|
+
fallbackTimeout(8000),
|
|
803
|
+
]);
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
const attempts = Array.from(
|
|
807
|
+
new Set([sessionIdHint, conversationId, undefined] as Array<string | undefined>)
|
|
808
|
+
);
|
|
809
|
+
let result: Awaited<ReturnType<typeof runRequest>> | undefined;
|
|
810
|
+
let lastError: string | undefined;
|
|
811
|
+
|
|
812
|
+
for (const attemptConversationId of attempts) {
|
|
813
|
+
const attemptResult = await runRequest(attemptConversationId);
|
|
814
|
+
if (attemptResult.ok && attemptResult.data?.answer) {
|
|
815
|
+
result = attemptResult;
|
|
816
|
+
break;
|
|
817
|
+
}
|
|
818
|
+
if (!attemptResult.ok && attemptResult.error) {
|
|
819
|
+
lastError = attemptResult.error;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (!result) {
|
|
824
|
+
if (lastError) {
|
|
825
|
+
throw new Error(lastError || "Agent fallback request failed.");
|
|
826
|
+
}
|
|
827
|
+
result = {
|
|
828
|
+
ok: false,
|
|
829
|
+
status: 0,
|
|
830
|
+
error: "Agent fallback request failed.",
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (!isActive) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (!result.ok) {
|
|
838
|
+
throw new Error(lastError || result.error || "Agent fallback request failed.");
|
|
839
|
+
}
|
|
840
|
+
setStream(result.data?.answer ?? "");
|
|
841
|
+
if (!result.data?.answer) {
|
|
842
|
+
setError("Agent returned no response content.");
|
|
843
|
+
setState("error");
|
|
844
|
+
setTimeout(() => process.exit(1), 300);
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
setStatusHint("");
|
|
681
848
|
setState("done");
|
|
682
|
-
|
|
683
|
-
|
|
849
|
+
setTimeout(() => process.exit(0), 300);
|
|
850
|
+
} catch (fallbackError) {
|
|
851
|
+
if (!isActive) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const fallbackErrorMessage =
|
|
855
|
+
fallbackError instanceof Error
|
|
856
|
+
? fallbackError.message
|
|
857
|
+
: String(fallbackError);
|
|
858
|
+
if (fallbackErrorMessage.includes("REST fallback timed out")) {
|
|
859
|
+
setError(
|
|
860
|
+
"Interactive mode timed out. Agent runtime is not reachable in this environment.\n" +
|
|
861
|
+
"Retry in standard mode (no mode flag needed), or set up a reachable WSS endpoint."
|
|
862
|
+
);
|
|
863
|
+
} else {
|
|
864
|
+
setError(fallbackErrorMessage);
|
|
865
|
+
}
|
|
684
866
|
setState("error");
|
|
867
|
+
setTimeout(() => process.exit(1), 300);
|
|
685
868
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
869
|
+
};
|
|
870
|
+
const start = async () => {
|
|
871
|
+
doneHistory.current = undefined;
|
|
872
|
+
const mergedHistory: HistoryMessage[] = [];
|
|
873
|
+
let commandCompleted = false;
|
|
874
|
+
let responseTimeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
875
|
+
const fallbackOnTimeout = async () => {
|
|
876
|
+
if (!isActive || commandCompleted) {
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
commandCompleted = true;
|
|
880
|
+
if (responseTimeoutId) {
|
|
881
|
+
clearTimeout(responseTimeoutId);
|
|
882
|
+
}
|
|
883
|
+
client.disconnect();
|
|
884
|
+
await runAgentFallback(mergedHistory, wsSessionId);
|
|
885
|
+
};
|
|
689
886
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
887
|
+
const connectWithTimeout = async (ms: number): Promise<void> => {
|
|
888
|
+
return new Promise<void>((resolve, reject) => {
|
|
889
|
+
const timeoutId = setTimeout(() => {
|
|
890
|
+
reject(new Error(`WebSocket connect timed out after ${ms}ms`));
|
|
891
|
+
}, ms);
|
|
892
|
+
|
|
893
|
+
client.connect()
|
|
894
|
+
.then(() => {
|
|
895
|
+
clearTimeout(timeoutId);
|
|
896
|
+
resolve();
|
|
897
|
+
})
|
|
898
|
+
.catch((err) => {
|
|
899
|
+
clearTimeout(timeoutId);
|
|
900
|
+
reject(err);
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
};
|
|
904
|
+
const withTimeout = async <T,>(promise: Promise<T>, ms: number, label: string): Promise<T> => {
|
|
905
|
+
return new Promise<T>((resolve, reject) => {
|
|
906
|
+
const timeoutId = setTimeout(() => {
|
|
907
|
+
reject(new Error(`${label} timed out after ${ms}ms`));
|
|
908
|
+
}, ms);
|
|
909
|
+
|
|
910
|
+
promise.then(
|
|
911
|
+
(value) => {
|
|
912
|
+
clearTimeout(timeoutId);
|
|
913
|
+
resolve(value);
|
|
914
|
+
},
|
|
915
|
+
(error) => {
|
|
916
|
+
clearTimeout(timeoutId);
|
|
917
|
+
reject(error);
|
|
918
|
+
}
|
|
919
|
+
);
|
|
920
|
+
});
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
try {
|
|
924
|
+
if (conversationId) {
|
|
925
|
+
setStatusHint(`Loading conversation ${conversationId}...`);
|
|
926
|
+
const sessionResult = await api.getAgentSession(conversationId);
|
|
927
|
+
if (!sessionResult.ok) {
|
|
928
|
+
throw new Error(
|
|
929
|
+
sessionResult.error ??
|
|
930
|
+
`Could not load conversation ${conversationId}. Please check the conversation id and credentials.`
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const session = sessionResult.data;
|
|
935
|
+
if (session?.messages?.length) {
|
|
936
|
+
mergedHistory.push(...toHistoryMessages(session.messages));
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
if (history?.length) {
|
|
941
|
+
mergedHistory.push(...toHistoryMessages(history));
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
const isWsHealthy = await withTimeout(client.health(), 3000, "WebSocket health check");
|
|
945
|
+
if (!isWsHealthy) {
|
|
946
|
+
throw new Error("WebSocket endpoint is not reachable.");
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
await connectWithTimeout(5000);
|
|
950
|
+
if (!isActive) {
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
setStatusHint("");
|
|
955
|
+
setState("running");
|
|
956
|
+
responseTimeoutId = setTimeout(() => {
|
|
957
|
+
void fallbackOnTimeout();
|
|
958
|
+
}, 5000);
|
|
959
|
+
|
|
960
|
+
await client.chat(message, {
|
|
961
|
+
onStream: (content) => {
|
|
962
|
+
appendStream(content);
|
|
963
|
+
},
|
|
964
|
+
onAssistantMessage: (content) => {
|
|
965
|
+
appendStream(content);
|
|
966
|
+
},
|
|
967
|
+
onToolCall: async (call) => {
|
|
968
|
+
setStatusHint(`Tool requested: ${call.tool}`);
|
|
969
|
+
return "CLI does not execute tool calls yet.";
|
|
970
|
+
},
|
|
971
|
+
onError: (err) => {
|
|
972
|
+
if (!isActive) return;
|
|
973
|
+
setError(err.message || "WebSocket agent error");
|
|
974
|
+
setState("error");
|
|
975
|
+
finish(1);
|
|
976
|
+
},
|
|
977
|
+
onDone: (messages, sessionId) => {
|
|
978
|
+
if (!isActive) return;
|
|
979
|
+
commandCompleted = true;
|
|
980
|
+
if (responseTimeoutId) {
|
|
981
|
+
clearTimeout(responseTimeoutId);
|
|
982
|
+
}
|
|
983
|
+
doneHistory.current = messages;
|
|
984
|
+
wsSessionId = sessionId;
|
|
985
|
+
},
|
|
986
|
+
}, {
|
|
987
|
+
history: mergedHistory,
|
|
988
|
+
...(model ? { config: { model } } : {}),
|
|
989
|
+
fileReferences: [],
|
|
990
|
+
});
|
|
991
|
+
if (!streamBuffer.trim()) {
|
|
992
|
+
const doneAssistantContent = getLatestAssistantContent(doneHistory.current);
|
|
993
|
+
if (doneAssistantContent) {
|
|
994
|
+
appendStream(doneAssistantContent);
|
|
995
|
+
setState("done");
|
|
996
|
+
finish(0);
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
setStatusHint("No assistant content in websocket response; retrying via REST fallback.");
|
|
1001
|
+
await runAgentFallback(mergedHistory, wsSessionId);
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
setState("done");
|
|
1006
|
+
finish(0);
|
|
1007
|
+
} catch (err) {
|
|
1008
|
+
if (!isActive) return;
|
|
1009
|
+
commandCompleted = true;
|
|
1010
|
+
if (responseTimeoutId) {
|
|
1011
|
+
clearTimeout(responseTimeoutId);
|
|
1012
|
+
}
|
|
1013
|
+
const rawError = err instanceof Error ? err.message : String(err);
|
|
1014
|
+
const unavailable =
|
|
1015
|
+
rawError.includes("WebSocket") ||
|
|
1016
|
+
rawError.includes("websocket") ||
|
|
1017
|
+
rawError.includes("timed out") ||
|
|
1018
|
+
rawError.includes("not reachable");
|
|
1019
|
+
|
|
1020
|
+
if (unavailable) {
|
|
1021
|
+
const wsUrl = client.getWebSocketUrl();
|
|
1022
|
+
setStatusHint(`WebSocket timed out at ${wsUrl}. Falling back to REST.`);
|
|
1023
|
+
await runAgentFallback(mergedHistory);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
setError(
|
|
1027
|
+
rawError || "Agent runtime unavailable. Retry by running `agent` in standard mode (omit --mode)."
|
|
1028
|
+
);
|
|
1029
|
+
setState("error");
|
|
1030
|
+
finish(1);
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
start();
|
|
1034
|
+
|
|
1035
|
+
return () => {
|
|
1036
|
+
isActive = false;
|
|
1037
|
+
if (state !== "done" && state !== "error") {
|
|
1038
|
+
client.disconnect();
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
}, [client, conversationId, history, message]);
|
|
1042
|
+
|
|
1043
|
+
useEffect(() => {
|
|
1044
|
+
if (state === "done") {
|
|
1045
|
+
setTimeout(() => process.exit(0), 300);
|
|
1046
|
+
} else if (state === "error") {
|
|
1047
|
+
setTimeout(() => process.exit(1), 300);
|
|
1048
|
+
}
|
|
1049
|
+
}, [state]);
|
|
693
1050
|
|
|
694
1051
|
if (state === "error") {
|
|
695
|
-
return
|
|
1052
|
+
return (
|
|
1053
|
+
<Box flexDirection="column">
|
|
1054
|
+
<ErrorMessage error={error || "WebSocket agent command failed."} />
|
|
1055
|
+
</Box>
|
|
1056
|
+
);
|
|
696
1057
|
}
|
|
697
1058
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
1059
|
+
if (state === "connecting") {
|
|
1060
|
+
return (
|
|
1061
|
+
<Box flexDirection="column">
|
|
1062
|
+
<Loading message={statusHint || "Connecting to agent runtime..."} />
|
|
1063
|
+
</Box>
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
702
1066
|
|
|
703
1067
|
return (
|
|
704
1068
|
<Box flexDirection="column">
|
|
705
|
-
{
|
|
706
|
-
<
|
|
1069
|
+
{state === "running" ? (
|
|
1070
|
+
<Loading message={stream ? "Streaming response..." : "Waiting for agent response..."} />
|
|
707
1071
|
) : (
|
|
708
|
-
|
|
709
|
-
<Text key={log.id} color={log.level === "ERROR" ? "red" : log.level === "WARNING" ? "yellow" : undefined}>
|
|
710
|
-
{formatLogEntry(log)}
|
|
711
|
-
</Text>
|
|
712
|
-
))
|
|
1072
|
+
<Success message="Agent response received" />
|
|
713
1073
|
)}
|
|
1074
|
+
{stream ? <Text>{stream}</Text> : <Text dimColor>No response content yet.</Text>}
|
|
1075
|
+
{conversationId ? <Text dimColor>Conversation: {conversationId}</Text> : null}
|
|
714
1076
|
</Box>
|
|
715
1077
|
);
|
|
716
1078
|
}
|
|
717
1079
|
|
|
718
|
-
|
|
719
|
-
// Local Dataset Helpers
|
|
720
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
721
|
-
|
|
722
|
-
const DATASETS_DIR = path.join(process.cwd(), "datasets");
|
|
1080
|
+
type AutoAgentTurnRole = "user" | "assistant";
|
|
723
1081
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
}
|
|
1082
|
+
interface AutoAgentTurn {
|
|
1083
|
+
role: AutoAgentTurnRole;
|
|
1084
|
+
content: string;
|
|
728
1085
|
}
|
|
729
1086
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
path: string;
|
|
734
|
-
type: string;
|
|
735
|
-
size: number;
|
|
736
|
-
sample_size: number;
|
|
737
|
-
created_at: string;
|
|
738
|
-
source: "local";
|
|
1087
|
+
function formatAutoAgentTurn(turn: AutoAgentTurn): string {
|
|
1088
|
+
const prefix = turn.role === "user" ? "You: " : "Agent: ";
|
|
1089
|
+
return `${prefix}${turn.content}`;
|
|
739
1090
|
}
|
|
740
1091
|
|
|
741
|
-
function
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
1092
|
+
function AutoAgentInteractiveSession({
|
|
1093
|
+
conversationId: initialConversationId,
|
|
1094
|
+
history,
|
|
1095
|
+
mode,
|
|
1096
|
+
firstMessage,
|
|
1097
|
+
}: {
|
|
1098
|
+
conversationId?: string;
|
|
1099
|
+
history?: api.AgentChatHistoryItem[];
|
|
1100
|
+
mode: "standard" | "research";
|
|
1101
|
+
firstMessage?: string;
|
|
1102
|
+
}) {
|
|
1103
|
+
const [input, setInput] = useState("");
|
|
1104
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1105
|
+
const [error, setError] = useState("");
|
|
1106
|
+
const [statusHint, setStatusHint] = useState("Start typing...");
|
|
1107
|
+
const [conversationId, setConversationId] = useState(initialConversationId);
|
|
1108
|
+
const [historyState, setHistoryState] = useState<api.AgentChatHistoryItem[]>(history ?? []);
|
|
1109
|
+
const [turns, setTurns] = useState<AutoAgentTurn[]>(
|
|
1110
|
+
(history ?? []).map((entry) =>
|
|
1111
|
+
entry.role === "assistant"
|
|
1112
|
+
? { role: "assistant", content: entry.content }
|
|
1113
|
+
: { role: "user", content: entry.content }
|
|
1114
|
+
)
|
|
1115
|
+
);
|
|
1116
|
+
const didSeedFirstMessage = useRef(false);
|
|
745
1117
|
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
let type = "unknown";
|
|
1118
|
+
const runAutoTurn = async (rawMessage: string) => {
|
|
1119
|
+
const trimmed = rawMessage.trim();
|
|
1120
|
+
if (!trimmed || isLoading) {
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
752
1123
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
1124
|
+
const nextHistory: api.AgentChatHistoryItem[] = [
|
|
1125
|
+
...historyState,
|
|
1126
|
+
{ role: "user", content: trimmed },
|
|
1127
|
+
];
|
|
1128
|
+
setHistoryState(nextHistory);
|
|
1129
|
+
setTurns((current) => [...current, { role: "user", content: trimmed }]);
|
|
1130
|
+
setInput("");
|
|
1131
|
+
setError("");
|
|
1132
|
+
setIsLoading(true);
|
|
1133
|
+
setStatusHint("Thinking...");
|
|
1134
|
+
|
|
1135
|
+
const result = await api.agentChat({
|
|
1136
|
+
message: trimmed,
|
|
1137
|
+
...(conversationId ? { conversation_id: conversationId } : {}),
|
|
1138
|
+
...(nextHistory.length ? { history: nextHistory } : {}),
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
if (!result.ok) {
|
|
1142
|
+
if (
|
|
1143
|
+
result.status === 403 &&
|
|
1144
|
+
(result.error ?? "").toLowerCase().includes("deep research mode")
|
|
1145
|
+
) {
|
|
1146
|
+
setError(
|
|
1147
|
+
"Research mode requires a Pro subscription.\n" +
|
|
1148
|
+
"To run this command, upgrade your account to Pro and retry.\n" +
|
|
1149
|
+
"Go to Manage subscription under Billing at https://agent.pioneer.ai/account."
|
|
1150
|
+
);
|
|
1151
|
+
} else {
|
|
1152
|
+
setError(result.error || "Agent request failed.");
|
|
1153
|
+
}
|
|
1154
|
+
setIsLoading(false);
|
|
1155
|
+
setStatusHint("Ready for next message.");
|
|
1156
|
+
return;
|
|
760
1157
|
}
|
|
761
1158
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
name: filename.replace(".json", ""),
|
|
765
|
-
path: filePath,
|
|
766
|
-
type,
|
|
767
|
-
size: stats.size,
|
|
768
|
-
sample_size: data.length,
|
|
769
|
-
created_at: stats.birthtime.toISOString(),
|
|
770
|
-
source: "local" as const,
|
|
771
|
-
};
|
|
772
|
-
});
|
|
773
|
-
}
|
|
1159
|
+
const answer = result.data.answer?.trim() || "No response content yet.";
|
|
1160
|
+
const nextConversationId = result.data.conversation_id || conversationId;
|
|
774
1161
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
1162
|
+
setConversationId(nextConversationId);
|
|
1163
|
+
setTurns((current) => [...current, { role: "assistant", content: answer }]);
|
|
1164
|
+
setHistoryState((current) => [...current, { role: "assistant", content: answer }]);
|
|
1165
|
+
setStatusHint("Ready for next message.");
|
|
1166
|
+
setIsLoading(false);
|
|
1167
|
+
};
|
|
782
1168
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1169
|
+
useEffect(() => {
|
|
1170
|
+
if (!firstMessage || didSeedFirstMessage.current) {
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
786
1173
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
datasetType: string;
|
|
791
|
-
saveToRemote: boolean;
|
|
792
|
-
}
|
|
1174
|
+
didSeedFirstMessage.current = true;
|
|
1175
|
+
void runAutoTurn(firstMessage);
|
|
1176
|
+
}, [firstMessage]);
|
|
793
1177
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1178
|
+
return (
|
|
1179
|
+
<Box flexDirection="column">
|
|
1180
|
+
<Text bold>Agent mode selected.</Text>
|
|
1181
|
+
<Text>
|
|
1182
|
+
Mode: {mode === "research" ? "research" : "standard"}{" "}
|
|
1183
|
+
{conversationId ? `(conversation ${conversationId})` : "(new conversation)"}
|
|
1184
|
+
</Text>
|
|
1185
|
+
<Box flexDirection="column" marginTop={1}>
|
|
1186
|
+
{turns.map((entry, idx) => (
|
|
1187
|
+
<Text key={`agent-turn-${idx}`} dimColor={entry.role === "user"}>
|
|
1188
|
+
{formatAutoAgentTurn(entry)}
|
|
1189
|
+
</Text>
|
|
1190
|
+
))}
|
|
1191
|
+
</Box>
|
|
1192
|
+
<Box marginTop={1}>
|
|
1193
|
+
{isLoading ? <Loading message={statusHint} /> : <Text color="cyan">> </Text>}
|
|
1194
|
+
{isLoading ? null : (
|
|
1195
|
+
<TextInput
|
|
1196
|
+
value={input}
|
|
1197
|
+
onChange={setInput}
|
|
1198
|
+
onSubmit={runAutoTurn}
|
|
1199
|
+
/>
|
|
1200
|
+
)}
|
|
1201
|
+
</Box>
|
|
1202
|
+
{error && (
|
|
1203
|
+
<Box marginTop={1}>
|
|
1204
|
+
<ErrorMessage error={error} />
|
|
1205
|
+
</Box>
|
|
1206
|
+
)}
|
|
1207
|
+
<Text dimColor>Type Ctrl+C to exit.</Text>
|
|
1208
|
+
</Box>
|
|
1209
|
+
);
|
|
798
1210
|
}
|
|
799
1211
|
|
|
800
|
-
function
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
const [
|
|
810
|
-
const [
|
|
811
|
-
const [
|
|
812
|
-
const
|
|
1212
|
+
function AgentInteractivePrompt({
|
|
1213
|
+
conversationId,
|
|
1214
|
+
history,
|
|
1215
|
+
mode,
|
|
1216
|
+
}: {
|
|
1217
|
+
conversationId?: string;
|
|
1218
|
+
history?: api.AgentChatHistoryItem[];
|
|
1219
|
+
mode?: "standard" | "research";
|
|
1220
|
+
}) {
|
|
1221
|
+
const [input, setInput] = useState("");
|
|
1222
|
+
const [isReady, setReady] = useState(false);
|
|
1223
|
+
const [message, setMessage] = useState("");
|
|
1224
|
+
const { isRawModeSupported } = useStdin();
|
|
1225
|
+
const shouldExitImmediately = !isRawModeSupported;
|
|
813
1226
|
|
|
814
1227
|
useEffect(() => {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
const data = result.data.data ?? [];
|
|
819
|
-
setCount(Array.isArray(data) ? data.length : 0);
|
|
1228
|
+
if (!shouldExitImmediately) {
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
820
1231
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1232
|
+
const timeout = setTimeout(() => {
|
|
1233
|
+
process.exit(0);
|
|
1234
|
+
}, 75);
|
|
1235
|
+
|
|
1236
|
+
return () => clearTimeout(timeout);
|
|
1237
|
+
}, [shouldExitImmediately]);
|
|
1238
|
+
|
|
1239
|
+
if (!isRawModeSupported) {
|
|
1240
|
+
return (
|
|
1241
|
+
<Box flexDirection="column">
|
|
1242
|
+
<ErrorMessage error="Interactive input is not supported in this terminal." />
|
|
1243
|
+
<Text>Use interactive mode for this environment:</Text>
|
|
1244
|
+
<Text dimColor>{` agent${mode === "research" ? " --mode research" : ""}`}</Text>
|
|
1245
|
+
<Text dimColor> agent --help</Text>
|
|
1246
|
+
</Box>
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
if (!isReady) {
|
|
1251
|
+
return (
|
|
1252
|
+
<Box flexDirection="column">
|
|
1253
|
+
<Text bold>Agent mode selected.</Text>
|
|
1254
|
+
<Text>Type your message and press enter to start:</Text>
|
|
1255
|
+
<Box>
|
|
1256
|
+
<Text color="cyan">> </Text>
|
|
1257
|
+
<TextInput
|
|
1258
|
+
value={input}
|
|
1259
|
+
onChange={setInput}
|
|
1260
|
+
onSubmit={(value) => {
|
|
1261
|
+
const trimmed = value.trim();
|
|
1262
|
+
if (!trimmed) {
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
setMessage(trimmed);
|
|
1266
|
+
setReady(true);
|
|
1267
|
+
}}
|
|
1268
|
+
/>
|
|
1269
|
+
</Box>
|
|
1270
|
+
<Text dimColor>Type Ctrl+C to cancel.</Text>
|
|
1271
|
+
</Box>
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
return (
|
|
1276
|
+
<AutoAgentInteractiveSession
|
|
1277
|
+
conversationId={conversationId}
|
|
1278
|
+
history={history}
|
|
1279
|
+
mode={mode === "research" ? "research" : "standard"}
|
|
1280
|
+
firstMessage={message}
|
|
1281
|
+
/>
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1286
|
+
// Interactive Model Create Selector
|
|
1287
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1288
|
+
|
|
1289
|
+
function ModelCreateInteractive({
|
|
1290
|
+
name,
|
|
1291
|
+
icon,
|
|
1292
|
+
repo,
|
|
1293
|
+
description,
|
|
1294
|
+
example,
|
|
1295
|
+
}: {
|
|
1296
|
+
name?: string;
|
|
1297
|
+
icon?: string;
|
|
1298
|
+
repo?: string;
|
|
1299
|
+
description?: string;
|
|
1300
|
+
example?: CreateProjectExample;
|
|
1301
|
+
}) {
|
|
1302
|
+
const [query, setQuery] = useState("");
|
|
1303
|
+
const [models, setModels] = useState<api.BaseModelInfo[]>([]);
|
|
1304
|
+
const [loading, setLoading] = useState(true);
|
|
1305
|
+
const [error, setError] = useState("");
|
|
1306
|
+
const [selectedModelId, setSelectedModelId] = useState<string | null>(null);
|
|
1307
|
+
const [highlightIndex, setHighlightIndex] = useState(0);
|
|
1308
|
+
|
|
1309
|
+
useEffect(() => {
|
|
1310
|
+
(async () => {
|
|
1311
|
+
const result = await api.listBaseModels();
|
|
1312
|
+
if (!result.ok) {
|
|
1313
|
+
setError(result.error ?? "Unable to load base models.");
|
|
1314
|
+
setLoading(false);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
const modelsData = result.data
|
|
1318
|
+
? Array.isArray((result.data as api.BaseModelsResponse).models)
|
|
1319
|
+
? (result.data as api.BaseModelsResponse).models
|
|
1320
|
+
: (result.data as api.BaseModelInfo[])
|
|
1321
|
+
: [];
|
|
1322
|
+
setModels(modelsData.sort((a, b) => a.id.localeCompare(b.id)));
|
|
1323
|
+
setLoading(false);
|
|
1324
|
+
})();
|
|
1325
|
+
}, []);
|
|
1326
|
+
|
|
1327
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
1328
|
+
const matchingModels = models.filter((model) => {
|
|
1329
|
+
const text = `${model.id} ${model.name ?? ""} ${model.label ?? ""} ${model.description ?? ""}`.toLowerCase();
|
|
1330
|
+
return normalizedQuery.length === 0 || text.includes(normalizedQuery);
|
|
1331
|
+
});
|
|
1332
|
+
const topMatches = matchingModels;
|
|
1333
|
+
useEffect(() => {
|
|
1334
|
+
setHighlightIndex(0);
|
|
1335
|
+
}, [query]);
|
|
1336
|
+
|
|
1337
|
+
const resolveModelId = (queryValue: string): string | undefined => {
|
|
1338
|
+
const trimmed = queryValue.trim();
|
|
1339
|
+
if (!trimmed) {
|
|
1340
|
+
return topMatches[highlightIndex]?.id;
|
|
1341
|
+
}
|
|
1342
|
+
const exact = models.find(
|
|
1343
|
+
(model) => model.id.toLowerCase() === trimmed.toLowerCase()
|
|
1344
|
+
);
|
|
1345
|
+
if (exact) return exact.id;
|
|
1346
|
+
return topMatches[highlightIndex]?.id;
|
|
1347
|
+
};
|
|
1348
|
+
|
|
1349
|
+
useEffect(() => {
|
|
1350
|
+
if (highlightIndex >= topMatches.length) {
|
|
1351
|
+
setHighlightIndex(topMatches.length > 0 ? topMatches.length - 1 : 0);
|
|
1352
|
+
}
|
|
1353
|
+
}, [highlightIndex, topMatches.length]);
|
|
1354
|
+
|
|
1355
|
+
useInput((_, key) => {
|
|
1356
|
+
if (!topMatches.length) return;
|
|
1357
|
+
if (key.upArrow) {
|
|
1358
|
+
setHighlightIndex((idx) => (idx === 0 ? topMatches.length - 1 : idx - 1));
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
if (key.downArrow) {
|
|
1362
|
+
setHighlightIndex((idx) => (idx === topMatches.length - 1 ? 0 : idx + 1));
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
if (key.return) {
|
|
1366
|
+
const resolvedModel = resolveModelId(query);
|
|
1367
|
+
if (!resolvedModel) {
|
|
1368
|
+
setError("No matching models found. Refine your search and try again.");
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
setSelectedModelId(resolvedModel);
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
|
|
1375
|
+
if (error) {
|
|
1376
|
+
return (
|
|
1377
|
+
<Box flexDirection="column">
|
|
1378
|
+
<ErrorMessage error={error} />
|
|
1379
|
+
<Text> </Text>
|
|
1380
|
+
<Text dimColor>Try again with --model explicitly, or check your API connectivity.</Text>
|
|
1381
|
+
</Box>
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
if (selectedModelId) {
|
|
1386
|
+
return (
|
|
1387
|
+
<ApiCommand
|
|
1388
|
+
action={() =>
|
|
1389
|
+
api.createProject({
|
|
1390
|
+
name: name ?? selectedModelId,
|
|
1391
|
+
...(icon ? { icon } : {}),
|
|
1392
|
+
...(repo ? { repo } : {}),
|
|
1393
|
+
...(description ? { description } : {}),
|
|
1394
|
+
active_model_id: selectedModelId,
|
|
1395
|
+
selected_model_id: selectedModelId,
|
|
1396
|
+
...(example ? { example } : {}),
|
|
1397
|
+
})
|
|
836
1398
|
}
|
|
1399
|
+
successMessage="Model entry created"
|
|
1400
|
+
/>
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
if (loading) {
|
|
1405
|
+
return <Loading message="Loading supported models..." />;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
return (
|
|
1409
|
+
<Box flexDirection="column">
|
|
1410
|
+
<Text bold>Choose a base model for this entry</Text>
|
|
1411
|
+
<Text>Type to filter. Use ↑/↓ to navigate and Enter to select.</Text>
|
|
1412
|
+
<Text dimColor>Type any part of model id, name, label, or description.</Text>
|
|
1413
|
+
<Text> </Text>
|
|
1414
|
+
<TextInput
|
|
1415
|
+
value={query}
|
|
1416
|
+
onChange={(value) => setQuery(value)}
|
|
1417
|
+
onSubmit={(value) => {
|
|
1418
|
+
const resolvedModel = resolveModelId(value);
|
|
1419
|
+
if (!resolvedModel) {
|
|
1420
|
+
setError("No matching models found. Refine your search and try again.");
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
setSelectedModelId(resolvedModel);
|
|
1424
|
+
}}
|
|
1425
|
+
placeholder="e.g. qwen/qwen3-8b"
|
|
1426
|
+
/>
|
|
1427
|
+
<Text> </Text>
|
|
1428
|
+
{topMatches.length === 0 ? (
|
|
1429
|
+
<Text dimColor>No matching models found.</Text>
|
|
1430
|
+
) : (
|
|
1431
|
+
<Box flexDirection="column">
|
|
1432
|
+
{topMatches.map((model, index) => {
|
|
1433
|
+
const suffix = [model.label, model.task_type, model.type].filter(Boolean).join(" · ");
|
|
1434
|
+
const modelLine = `${model.id}${suffix ? ` (${suffix})` : ""}`;
|
|
1435
|
+
const isHighlighted = index === highlightIndex;
|
|
1436
|
+
return (
|
|
1437
|
+
<Text key={model.id} color={isHighlighted ? "cyan" : undefined} bold={isHighlighted}>
|
|
1438
|
+
{isHighlighted ? "▶ " : " "}
|
|
1439
|
+
{modelLine}
|
|
1440
|
+
</Text>
|
|
1441
|
+
);
|
|
1442
|
+
})}
|
|
1443
|
+
</Box>
|
|
1444
|
+
)}
|
|
1445
|
+
<Text> </Text>
|
|
1446
|
+
<Text dimColor>Press Enter to select the highlighted model.</Text>
|
|
1447
|
+
<Text dimColor> </Text>
|
|
1448
|
+
<Text dimColor>Tip: You can still run {"model endpoints create --model \"<base-model-id>\""} for exact model ids.</Text>
|
|
1449
|
+
</Box>
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1454
|
+
// Job Logs Command (prettified output)
|
|
1455
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1456
|
+
|
|
1457
|
+
function JobLogsCommand({ jobId }: { jobId: string }) {
|
|
1458
|
+
const { exit } = useApp();
|
|
1459
|
+
const [state, setState] = useState<"loading" | "done" | "error">("loading");
|
|
1460
|
+
const [logs, setLogs] = useState<api.TrainingLog[]>([]);
|
|
1461
|
+
const [error, setError] = useState("");
|
|
1462
|
+
|
|
1463
|
+
useEffect(() => {
|
|
1464
|
+
(async () => {
|
|
1465
|
+
const result = await api.getJobLogs(jobId);
|
|
1466
|
+
if (result.ok && result.data) {
|
|
1467
|
+
setLogs(result.data.logs || []);
|
|
837
1468
|
setState("done");
|
|
838
1469
|
} else {
|
|
839
1470
|
setError(result.error ?? "Unknown error");
|
|
@@ -841,7 +1472,7 @@ function GenerateCommand<T extends GenerateResult>({
|
|
|
841
1472
|
}
|
|
842
1473
|
setTimeout(() => exit(), 500);
|
|
843
1474
|
})();
|
|
844
|
-
}, [
|
|
1475
|
+
}, [jobId, exit]);
|
|
845
1476
|
|
|
846
1477
|
if (state === "loading") {
|
|
847
1478
|
return <Loading />;
|
|
@@ -851,287 +1482,178 @@ function GenerateCommand<T extends GenerateResult>({
|
|
|
851
1482
|
return <ErrorMessage error={error} />;
|
|
852
1483
|
}
|
|
853
1484
|
|
|
1485
|
+
const formatLogEntry = (log: api.TrainingLog): string => {
|
|
1486
|
+
const ts = new Date(log.timestamp).toLocaleTimeString();
|
|
1487
|
+
return `[${ts}] [${log.level}] ${log.message}`;
|
|
1488
|
+
};
|
|
1489
|
+
|
|
854
1490
|
return (
|
|
855
1491
|
<Box flexDirection="column">
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
<Text color="cyan">Saved to: {savedPath}</Text>
|
|
1492
|
+
{logs.length === 0 ? (
|
|
1493
|
+
<Text dimColor>No logs available</Text>
|
|
1494
|
+
) : (
|
|
1495
|
+
logs.map((log) => (
|
|
1496
|
+
<Text key={log.id} color={log.level === "ERROR" ? "red" : log.level === "WARNING" ? "yellow" : undefined}>
|
|
1497
|
+
{formatLogEntry(log)}
|
|
1498
|
+
</Text>
|
|
1499
|
+
))
|
|
865
1500
|
)}
|
|
866
1501
|
</Box>
|
|
867
1502
|
);
|
|
868
1503
|
}
|
|
869
1504
|
|
|
870
1505
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
871
|
-
//
|
|
1506
|
+
// Local Dataset Helpers
|
|
872
1507
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
873
1508
|
|
|
874
|
-
|
|
875
|
-
cell_type: "code" | "markdown" | "raw";
|
|
876
|
-
source: string[];
|
|
877
|
-
metadata?: Record<string, unknown>;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
interface NotebookFile {
|
|
881
|
-
cells: NotebookCell[];
|
|
882
|
-
metadata?: Record<string, unknown>;
|
|
883
|
-
nbformat: number;
|
|
884
|
-
nbformat_minor: number;
|
|
885
|
-
}
|
|
1509
|
+
const DATASETS_DIR = path.join(process.cwd(), "datasets");
|
|
886
1510
|
|
|
887
|
-
function
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
throw new Error(`File not found: ${absPath}`);
|
|
1511
|
+
function ensureDatasetsDir(): void {
|
|
1512
|
+
if (!fs.existsSync(DATASETS_DIR)) {
|
|
1513
|
+
fs.mkdirSync(DATASETS_DIR, { recursive: true });
|
|
891
1514
|
}
|
|
892
|
-
const content = fs.readFileSync(absPath, "utf-8");
|
|
893
|
-
return JSON.parse(content) as NotebookFile;
|
|
894
1515
|
}
|
|
895
1516
|
|
|
896
|
-
interface
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1517
|
+
interface LocalDataset {
|
|
1518
|
+
id: string;
|
|
1519
|
+
name: string;
|
|
1520
|
+
path: string;
|
|
1521
|
+
type: string;
|
|
1522
|
+
size: number;
|
|
1523
|
+
sample_size: number;
|
|
1524
|
+
created_at: string;
|
|
1525
|
+
source: "local";
|
|
900
1526
|
}
|
|
901
1527
|
|
|
902
|
-
function
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
const [currentCell, setCurrentCell] = useState(0);
|
|
907
|
-
const [totalCells, setTotalCells] = useState(0);
|
|
908
|
-
const [outputs, setOutputs] = useState<Array<{ cell: number; source: string; result: api.ExecuteCodeResponse | null; error?: string }>>([]);
|
|
909
|
-
const [error, setError] = useState("");
|
|
910
|
-
|
|
911
|
-
useEffect(() => {
|
|
912
|
-
(async () => {
|
|
913
|
-
// 1. Read notebook
|
|
914
|
-
let notebook: NotebookFile;
|
|
915
|
-
try {
|
|
916
|
-
notebook = readNotebookFile(filePath);
|
|
917
|
-
} catch (e) {
|
|
918
|
-
setError(e instanceof Error ? e.message : String(e));
|
|
919
|
-
setPhase("error");
|
|
920
|
-
setTimeout(() => exit(), 500);
|
|
921
|
-
return;
|
|
922
|
-
}
|
|
1528
|
+
function listLocalDatasets(): LocalDataset[] {
|
|
1529
|
+
if (!fs.existsSync(DATASETS_DIR)) {
|
|
1530
|
+
return [];
|
|
1531
|
+
}
|
|
923
1532
|
|
|
924
|
-
|
|
925
|
-
|
|
1533
|
+
const files = fs.readdirSync(DATASETS_DIR).filter((f) => f.endsWith(".json"));
|
|
1534
|
+
return files.map((filename) => {
|
|
1535
|
+
const filePath = path.join(DATASETS_DIR, filename);
|
|
1536
|
+
const stats = fs.statSync(filePath);
|
|
1537
|
+
let data: unknown[] = [];
|
|
1538
|
+
let type = "unknown";
|
|
926
1539
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1540
|
+
try {
|
|
1541
|
+
const content = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
1542
|
+
data = Array.isArray(content) ? content : content.data ?? [];
|
|
1543
|
+
const firstItem = data[0] as Record<string, unknown> | undefined;
|
|
1544
|
+
type = content.task_type ?? (firstItem?.spans ? "ner" : firstItem?.label ? "classification" : "custom");
|
|
1545
|
+
} catch {
|
|
1546
|
+
// Ignore parse errors
|
|
1547
|
+
}
|
|
933
1548
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1549
|
+
return {
|
|
1550
|
+
id: filename.replace(".json", ""),
|
|
1551
|
+
name: filename.replace(".json", ""),
|
|
1552
|
+
path: filePath,
|
|
1553
|
+
type,
|
|
1554
|
+
size: stats.size,
|
|
1555
|
+
sample_size: data.length,
|
|
1556
|
+
created_at: stats.birthtime.toISOString(),
|
|
1557
|
+
source: "local" as const,
|
|
1558
|
+
};
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
940
1561
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1562
|
+
function saveLocalDataset(name: string, data: unknown, type: string): string {
|
|
1563
|
+
ensureDatasetsDir();
|
|
1564
|
+
const filename = `${name.replace(/[^a-zA-Z0-9-_]/g, "_")}.json`;
|
|
1565
|
+
const filePath = path.join(DATASETS_DIR, filename);
|
|
1566
|
+
fs.writeFileSync(filePath, JSON.stringify({ task_type: type, data }, null, 2));
|
|
1567
|
+
return filePath;
|
|
1568
|
+
}
|
|
947
1569
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
// Poll until session is ready (if status is 'creating')
|
|
952
|
-
if (sessionResult.data.status === "creating") {
|
|
953
|
-
let ready = false;
|
|
954
|
-
for (let attempt = 0; attempt < 60; attempt++) {
|
|
955
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
956
|
-
const status = await api.getNotebookSessionStatus(sid);
|
|
957
|
-
if (status.ok && status.data?.status === "ready") {
|
|
958
|
-
// Status response returns the real session_id when ready
|
|
959
|
-
sid = status.data.session_id;
|
|
960
|
-
setSessionId(sid);
|
|
961
|
-
ready = true;
|
|
962
|
-
break;
|
|
963
|
-
}
|
|
964
|
-
if (status.ok && status.data?.status === "failed") {
|
|
965
|
-
setError(status.data.error ?? "Session creation failed");
|
|
966
|
-
setPhase("error");
|
|
967
|
-
setTimeout(() => exit(), 500);
|
|
968
|
-
return;
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
if (!ready) {
|
|
972
|
-
setError("Session creation timed out after 2 minutes");
|
|
973
|
-
setPhase("error");
|
|
974
|
-
setTimeout(() => exit(), 500);
|
|
975
|
-
return;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
1570
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1571
|
+
// Generate Command (saves to local file when --save is false)
|
|
1572
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
978
1573
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1574
|
+
interface GenerateCommandProps<T> {
|
|
1575
|
+
action: () => Promise<api.ApiResult<T>>;
|
|
1576
|
+
datasetName: string;
|
|
1577
|
+
datasetType: string;
|
|
1578
|
+
saveToRemote: boolean;
|
|
1579
|
+
}
|
|
982
1580
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1581
|
+
interface GenerateResult {
|
|
1582
|
+
data?: unknown[];
|
|
1583
|
+
success?: boolean;
|
|
1584
|
+
dataset?: { id?: string; dataset_name?: string };
|
|
1585
|
+
}
|
|
987
1586
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1587
|
+
function GenerateCommand<T extends GenerateResult>({
|
|
1588
|
+
action,
|
|
1589
|
+
datasetName,
|
|
1590
|
+
datasetType,
|
|
1591
|
+
saveToRemote,
|
|
1592
|
+
}: GenerateCommandProps<T>) {
|
|
1593
|
+
const { exit } = useApp();
|
|
1594
|
+
const [state, setState] = useState<"loading" | "done" | "error">("loading");
|
|
1595
|
+
const [savedPath, setSavedPath] = useState<string | null>(null);
|
|
1596
|
+
const [savedDatasetId, setSavedDatasetId] = useState<string | null>(null);
|
|
1597
|
+
const [remoteSaveFailed, setRemoteSaveFailed] = useState(false);
|
|
1598
|
+
const [count, setCount] = useState(0);
|
|
1599
|
+
const [error, setError] = useState("");
|
|
992
1600
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1601
|
+
useEffect(() => {
|
|
1602
|
+
(async () => {
|
|
1603
|
+
const result = await action();
|
|
1604
|
+
if (result.ok && result.data) {
|
|
1605
|
+
const data = result.data.data ?? [];
|
|
1606
|
+
setCount(Array.isArray(data) ? data.length : 0);
|
|
998
1607
|
|
|
999
|
-
if
|
|
1000
|
-
|
|
1001
|
-
|
|
1608
|
+
// Check if dataset was saved remotely
|
|
1609
|
+
const savedDataset = result.data.dataset;
|
|
1610
|
+
if (savedDataset?.id) {
|
|
1611
|
+
setSavedDatasetId(savedDataset.id);
|
|
1612
|
+
} else if (!saveToRemote) {
|
|
1613
|
+
// Save to local file only if not saving remotely
|
|
1614
|
+
const name = datasetName || `${datasetType}-${Date.now()}`;
|
|
1615
|
+
const filePath = saveLocalDataset(name, data, datasetType);
|
|
1616
|
+
setSavedPath(filePath);
|
|
1002
1617
|
} else {
|
|
1003
|
-
|
|
1618
|
+
// Remote save was requested but no dataset returned - save locally as fallback
|
|
1619
|
+
setRemoteSaveFailed(true);
|
|
1620
|
+
const name = datasetName || `${datasetType}-${Date.now()}`;
|
|
1621
|
+
const filePath = saveLocalDataset(name, data, datasetType);
|
|
1622
|
+
setSavedPath(filePath);
|
|
1004
1623
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1624
|
+
setState("done");
|
|
1625
|
+
} else {
|
|
1626
|
+
setError(result.error ?? "Unknown error");
|
|
1627
|
+
setState("error");
|
|
1007
1628
|
}
|
|
1008
|
-
|
|
1009
|
-
setPhase("done");
|
|
1010
|
-
setTimeout(() => exit(), 1000);
|
|
1629
|
+
setTimeout(() => exit(), 500);
|
|
1011
1630
|
})();
|
|
1012
|
-
}, [
|
|
1013
|
-
|
|
1014
|
-
if (phase === "error") {
|
|
1015
|
-
return <ErrorMessage error={error} />;
|
|
1016
|
-
}
|
|
1631
|
+
}, [action, exit, datasetName, datasetType, saveToRemote]);
|
|
1017
1632
|
|
|
1018
|
-
if (
|
|
1019
|
-
return <Loading
|
|
1633
|
+
if (state === "loading") {
|
|
1634
|
+
return <Loading />;
|
|
1020
1635
|
}
|
|
1021
1636
|
|
|
1022
|
-
if (
|
|
1023
|
-
return <
|
|
1637
|
+
if (state === "error") {
|
|
1638
|
+
return <ErrorMessage error={error} />;
|
|
1024
1639
|
}
|
|
1025
1640
|
|
|
1026
|
-
// Render outputs
|
|
1027
|
-
const renderCellOutput = (entry: (typeof outputs)[0]) => {
|
|
1028
|
-
const lines: string[] = [];
|
|
1029
|
-
|
|
1030
|
-
if (entry.error) {
|
|
1031
|
-
lines.push(` Error: ${entry.error}`);
|
|
1032
|
-
return lines.join("\n");
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
if (!entry.result) {
|
|
1036
|
-
lines.push(" (empty cell)");
|
|
1037
|
-
return lines.join("\n");
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
for (const out of entry.result.outputs) {
|
|
1041
|
-
if (out.type === "stream" && out.text) {
|
|
1042
|
-
// Truncate long stream output
|
|
1043
|
-
const text = out.text.length > 2000 ? out.text.slice(0, 2000) + "\n ... (truncated)" : out.text;
|
|
1044
|
-
lines.push(text);
|
|
1045
|
-
} else if (out.type === "execute_result" || out.type === "display_data") {
|
|
1046
|
-
if (out.data?.["text/plain"]) {
|
|
1047
|
-
lines.push(out.data["text/plain"]);
|
|
1048
|
-
} else if (out.text) {
|
|
1049
|
-
lines.push(out.text);
|
|
1050
|
-
}
|
|
1051
|
-
if (out.data?.["image/png"]) {
|
|
1052
|
-
lines.push(" [image output]");
|
|
1053
|
-
}
|
|
1054
|
-
} else if (out.type === "error") {
|
|
1055
|
-
lines.push(` ${out.ename}: ${out.evalue}`);
|
|
1056
|
-
if (out.traceback) {
|
|
1057
|
-
// Strip ANSI codes for cleaner terminal output
|
|
1058
|
-
lines.push(out.traceback.map((l) => l.replace(/\x1b\[[0-9;]*m/g, "")).join("\n"));
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
if (entry.result.execution_time_ms) {
|
|
1064
|
-
lines.push(` (${(entry.result.execution_time_ms / 1000).toFixed(1)}s)`);
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
return lines.join("\n") || " (no output)";
|
|
1068
|
-
};
|
|
1069
|
-
|
|
1070
1641
|
return (
|
|
1071
1642
|
<Box flexDirection="column">
|
|
1072
|
-
{
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
<Text> Running cell {currentCell}/{totalCells}...</Text>
|
|
1076
|
-
</Box>
|
|
1643
|
+
<Success message={`${datasetType.toUpperCase()} dataset generated (${count} examples)`} />
|
|
1644
|
+
{savedDatasetId && (
|
|
1645
|
+
<Text color="green">Saved to remote: {savedDatasetId}</Text>
|
|
1077
1646
|
)}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
<Text dimColor>{entry.source.split("\n").slice(0, 3).join("\n")}{entry.source.split("\n").length > 3 ? "\n ..." : ""}</Text>
|
|
1084
|
-
<Text color={entry.error || (entry.result && !entry.result.success) ? "red" : undefined}>
|
|
1085
|
-
{renderCellOutput(entry)}
|
|
1086
|
-
</Text>
|
|
1087
|
-
</Box>
|
|
1088
|
-
)}
|
|
1089
|
-
</Static>
|
|
1090
|
-
|
|
1091
|
-
{phase === "done" && (
|
|
1092
|
-
<Box flexDirection="column" marginTop={1}>
|
|
1093
|
-
<Success message={`Notebook complete: ${outputs.length} cells executed`} />
|
|
1094
|
-
{sessionId && <Text dimColor>Session: {sessionId}</Text>}
|
|
1095
|
-
{outputs.some((o) => o.error || (o.result && !o.result.success)) && (
|
|
1096
|
-
<Text color="yellow">Some cells had errors — check output above</Text>
|
|
1097
|
-
)}
|
|
1098
|
-
</Box>
|
|
1647
|
+
{remoteSaveFailed && (
|
|
1648
|
+
<Text color="yellow">⚠ Remote save failed (check server logs). Saved locally instead.</Text>
|
|
1649
|
+
)}
|
|
1650
|
+
{savedPath && (
|
|
1651
|
+
<Text color="cyan">Saved to: {savedPath}</Text>
|
|
1099
1652
|
)}
|
|
1100
1653
|
</Box>
|
|
1101
1654
|
);
|
|
1102
1655
|
}
|
|
1103
1656
|
|
|
1104
|
-
function createBlankNotebook(name: string): string {
|
|
1105
|
-
const notebook: NotebookFile = {
|
|
1106
|
-
cells: [
|
|
1107
|
-
{
|
|
1108
|
-
cell_type: "markdown",
|
|
1109
|
-
source: [`# ${name}\n`],
|
|
1110
|
-
metadata: {},
|
|
1111
|
-
},
|
|
1112
|
-
{
|
|
1113
|
-
cell_type: "code",
|
|
1114
|
-
source: [],
|
|
1115
|
-
metadata: {},
|
|
1116
|
-
},
|
|
1117
|
-
],
|
|
1118
|
-
metadata: {
|
|
1119
|
-
kernelspec: { display_name: "Python 3", language: "python", name: "python3" },
|
|
1120
|
-
language_info: { name: "python", version: "3.12.0" },
|
|
1121
|
-
},
|
|
1122
|
-
nbformat: 4,
|
|
1123
|
-
nbformat_minor: 4,
|
|
1124
|
-
};
|
|
1125
|
-
|
|
1126
|
-
const filename = name.endsWith(".ipynb") ? name : `${name}.ipynb`;
|
|
1127
|
-
const filePath = path.resolve(filename);
|
|
1128
|
-
if (fs.existsSync(filePath)) {
|
|
1129
|
-
throw new Error(`File already exists: ${filePath}`);
|
|
1130
|
-
}
|
|
1131
|
-
fs.writeFileSync(filePath, JSON.stringify(notebook, null, 1));
|
|
1132
|
-
return filePath;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
1657
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1136
1658
|
// Helper: Infer format from file extension
|
|
1137
1659
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -1202,177 +1724,19 @@ function DatasetListCommand() {
|
|
|
1202
1724
|
</Text>
|
|
1203
1725
|
<Text dimColor> {ds.id}</Text>
|
|
1204
1726
|
</Box>
|
|
1205
|
-
))
|
|
1206
|
-
)}
|
|
1207
|
-
<Text> </Text>
|
|
1208
|
-
<Text bold color="cyan">Local Datasets ({localDatasets.length})</Text>
|
|
1209
|
-
{localDatasets.length === 0 ? (
|
|
1210
|
-
<Text dimColor> No local datasets in ./datasets/</Text>
|
|
1211
|
-
) : (
|
|
1212
|
-
localDatasets.map((ds) => (
|
|
1213
|
-
<Text key={ds.id}>
|
|
1214
|
-
{" "}<Text color="green">{ds.name}</Text> <Text dimColor>({ds.type}, {ds.sample_size} examples)</Text>
|
|
1215
|
-
</Text>
|
|
1216
|
-
))
|
|
1217
|
-
)}
|
|
1218
|
-
</Box>
|
|
1219
|
-
);
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1223
|
-
// Competition List Command
|
|
1224
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1225
|
-
|
|
1226
|
-
function CompetitionListCommand() {
|
|
1227
|
-
const { exit } = useApp();
|
|
1228
|
-
const [state, setState] = useState<"loading" | "done" | "error">("loading");
|
|
1229
|
-
const [competitions, setCompetitions] = useState<api.CompetitionInfo[]>([]);
|
|
1230
|
-
const [error, setError] = useState("");
|
|
1231
|
-
|
|
1232
|
-
useEffect(() => {
|
|
1233
|
-
(async () => {
|
|
1234
|
-
const result = await api.listCompetitions();
|
|
1235
|
-
if (result.ok && result.data) {
|
|
1236
|
-
setCompetitions(result.data.competitions || []);
|
|
1237
|
-
setState("done");
|
|
1238
|
-
} else {
|
|
1239
|
-
setError(result.error ?? "Unknown error");
|
|
1240
|
-
setState("error");
|
|
1241
|
-
}
|
|
1242
|
-
setTimeout(() => exit(), 500);
|
|
1243
|
-
})();
|
|
1244
|
-
}, [exit]);
|
|
1245
|
-
|
|
1246
|
-
if (state === "loading") {
|
|
1247
|
-
return <Loading message="Loading competitions..." />;
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
if (state === "error") {
|
|
1251
|
-
return <ErrorMessage error={error} />;
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
if (competitions.length === 0) {
|
|
1255
|
-
return <Text dimColor>No active competitions found.</Text>;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
return (
|
|
1259
|
-
<Box flexDirection="column">
|
|
1260
|
-
<Text bold color="cyan">Active Competitions ({competitions.length})</Text>
|
|
1261
|
-
<Text> </Text>
|
|
1262
|
-
{competitions.map((comp, idx) => (
|
|
1263
|
-
<Box key={comp.dataset_id} flexDirection="column" marginBottom={1}>
|
|
1264
|
-
<Text bold color="yellow">
|
|
1265
|
-
{idx + 1}. {comp.dataset_name}
|
|
1266
|
-
</Text>
|
|
1267
|
-
<Text>
|
|
1268
|
-
{" "}Type: <Text color="magenta">{comp.dataset_type}</Text>
|
|
1269
|
-
{" "}Samples: <Text color="blue">{comp.sample_count ?? "N/A"}</Text>
|
|
1270
|
-
{" "}Entries: <Text color="green">{comp.total_entries}</Text>
|
|
1271
|
-
</Text>
|
|
1272
|
-
{comp.description && (
|
|
1273
|
-
<Text dimColor>{" "}{comp.description}</Text>
|
|
1274
|
-
)}
|
|
1275
|
-
{comp.labels && comp.labels.length > 0 && (
|
|
1276
|
-
<Text>{" "}Labels: <Text color="cyan">{comp.labels.join(", ")}</Text></Text>
|
|
1277
|
-
)}
|
|
1278
|
-
{comp.winner && (
|
|
1279
|
-
<Text>
|
|
1280
|
-
{" "}Winner: <Text color="green" bold>{comp.winner.display_name}</Text>
|
|
1281
|
-
{" "}(F1: <Text color="yellow">{comp.winner.f1_score.toFixed(4)}</Text>)
|
|
1282
|
-
</Text>
|
|
1283
|
-
)}
|
|
1284
|
-
<Text dimColor>{" "}ID: {comp.dataset_id}</Text>
|
|
1285
|
-
</Box>
|
|
1286
|
-
))}
|
|
1287
|
-
</Box>
|
|
1288
|
-
);
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1292
|
-
// Leaderboard Command
|
|
1293
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1294
|
-
|
|
1295
|
-
function LeaderboardCommand({ datasetId, limit }: { datasetId: string; limit?: number }) {
|
|
1296
|
-
const { exit } = useApp();
|
|
1297
|
-
const [state, setState] = useState<"loading" | "done" | "error">("loading");
|
|
1298
|
-
const [data, setData] = useState<api.LeaderboardEntriesResponse | null>(null);
|
|
1299
|
-
const [error, setError] = useState("");
|
|
1300
|
-
|
|
1301
|
-
useEffect(() => {
|
|
1302
|
-
(async () => {
|
|
1303
|
-
const result = await api.getLeaderboardEntries(datasetId, limit);
|
|
1304
|
-
if (result.ok && result.data) {
|
|
1305
|
-
setData(result.data);
|
|
1306
|
-
setState("done");
|
|
1307
|
-
} else {
|
|
1308
|
-
setError(result.error ?? "Unknown error");
|
|
1309
|
-
setState("error");
|
|
1310
|
-
}
|
|
1311
|
-
setTimeout(() => exit(), 500);
|
|
1312
|
-
})();
|
|
1313
|
-
}, [datasetId, limit, exit]);
|
|
1314
|
-
|
|
1315
|
-
if (state === "loading") {
|
|
1316
|
-
return <Loading message="Loading leaderboard..." />;
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
if (state === "error") {
|
|
1320
|
-
return <ErrorMessage error={error} />;
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
if (!data || data.entries.length === 0) {
|
|
1324
|
-
return (
|
|
1325
|
-
<Box flexDirection="column">
|
|
1326
|
-
<Text bold color="cyan">Leaderboard: {data?.dataset_name ?? datasetId}</Text>
|
|
1327
|
-
<Text dimColor> No entries yet. Be the first to submit!</Text>
|
|
1328
|
-
</Box>
|
|
1329
|
-
);
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
return (
|
|
1333
|
-
<Box flexDirection="column">
|
|
1334
|
-
<Text bold color="cyan">Leaderboard: {data.dataset_name} ({data.total_entries} entries)</Text>
|
|
1335
|
-
<Text> </Text>
|
|
1336
|
-
{/* Header */}
|
|
1337
|
-
<Box>
|
|
1338
|
-
<Box width={6}><Text bold dimColor>Rank</Text></Box>
|
|
1339
|
-
<Box width={22}><Text bold dimColor>Name</Text></Box>
|
|
1340
|
-
<Box width={22}><Text bold dimColor>Model</Text></Box>
|
|
1341
|
-
<Box width={10}><Text bold dimColor>F1</Text></Box>
|
|
1342
|
-
<Box width={10}><Text bold dimColor>Precision</Text></Box>
|
|
1343
|
-
<Box width={10}><Text bold dimColor>Recall</Text></Box>
|
|
1344
|
-
</Box>
|
|
1345
|
-
{/* Entries */}
|
|
1346
|
-
{data.entries.map((entry, idx) => {
|
|
1347
|
-
const rank = entry.rank ?? idx + 1;
|
|
1348
|
-
const isFirst = rank === 1;
|
|
1349
|
-
return (
|
|
1350
|
-
<Box key={entry.id}>
|
|
1351
|
-
<Box width={6}>
|
|
1352
|
-
<Text color={isFirst ? "yellow" : undefined} bold={isFirst}>
|
|
1353
|
-
{isFirst ? `#${rank}` : `#${rank}`}
|
|
1354
|
-
</Text>
|
|
1355
|
-
</Box>
|
|
1356
|
-
<Box width={22}>
|
|
1357
|
-
<Text color={isFirst ? "yellow" : undefined} bold={isFirst}>
|
|
1358
|
-
{entry.display_name.substring(0, 20)}
|
|
1359
|
-
</Text>
|
|
1360
|
-
</Box>
|
|
1361
|
-
<Box width={22}>
|
|
1362
|
-
<Text color="cyan">{entry.model_name.substring(0, 20)}</Text>
|
|
1363
|
-
</Box>
|
|
1364
|
-
<Box width={10}>
|
|
1365
|
-
<Text color="green" bold>{entry.f1_score.toFixed(4)}</Text>
|
|
1366
|
-
</Box>
|
|
1367
|
-
<Box width={10}>
|
|
1368
|
-
<Text>{entry.precision_score?.toFixed(4) ?? "N/A"}</Text>
|
|
1369
|
-
</Box>
|
|
1370
|
-
<Box width={10}>
|
|
1371
|
-
<Text>{entry.recall_score?.toFixed(4) ?? "N/A"}</Text>
|
|
1372
|
-
</Box>
|
|
1373
|
-
</Box>
|
|
1374
|
-
);
|
|
1375
|
-
})}
|
|
1727
|
+
))
|
|
1728
|
+
)}
|
|
1729
|
+
<Text> </Text>
|
|
1730
|
+
<Text bold color="cyan">Local Datasets ({localDatasets.length})</Text>
|
|
1731
|
+
{localDatasets.length === 0 ? (
|
|
1732
|
+
<Text dimColor> No local datasets in ./datasets/</Text>
|
|
1733
|
+
) : (
|
|
1734
|
+
localDatasets.map((ds) => (
|
|
1735
|
+
<Text key={ds.id}>
|
|
1736
|
+
{" "}<Text color="green">{ds.name}</Text> <Text dimColor>({ds.type}, {ds.sample_size} examples)</Text>
|
|
1737
|
+
</Text>
|
|
1738
|
+
))
|
|
1739
|
+
)}
|
|
1376
1740
|
</Box>
|
|
1377
1741
|
);
|
|
1378
1742
|
}
|
|
@@ -2004,11 +2368,54 @@ function DeployedModelCard({ model, index }: DeployedModelCardProps) {
|
|
|
2004
2368
|
);
|
|
2005
2369
|
}
|
|
2006
2370
|
|
|
2371
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2372
|
+
// Registered Model (Project) Visualization Component
|
|
2373
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2374
|
+
|
|
2375
|
+
interface ProjectModelCardProps {
|
|
2376
|
+
model: api.ProjectResponse;
|
|
2377
|
+
index: number;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
function ProjectModelCard({ model, index }: ProjectModelCardProps) {
|
|
2381
|
+
const formatDateShort = (dateStr: string | null | undefined) => {
|
|
2382
|
+
if (!dateStr) return "N/A";
|
|
2383
|
+
const date = new Date(dateStr);
|
|
2384
|
+
return date.toLocaleDateString() + " " + date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
2385
|
+
};
|
|
2386
|
+
|
|
2387
|
+
return (
|
|
2388
|
+
<Box flexDirection="column" marginTop={index > 0 ? 0 : 0}>
|
|
2389
|
+
<Box>
|
|
2390
|
+
<Text bold color="green">●</Text>
|
|
2391
|
+
<Text> </Text>
|
|
2392
|
+
<Box width={28}>
|
|
2393
|
+
<Text bold color="cyan">{model.name?.substring(0, 26) || "Unnamed"}</Text>
|
|
2394
|
+
</Box>
|
|
2395
|
+
<Box width={38}>
|
|
2396
|
+
<Text dimColor>{model.id}</Text>
|
|
2397
|
+
</Box>
|
|
2398
|
+
<Box width={24}>
|
|
2399
|
+
<Text color="magenta">{model.selected_model_id || "N/A"}</Text>
|
|
2400
|
+
</Box>
|
|
2401
|
+
<Box width={18}>
|
|
2402
|
+
<Text dimColor>{formatDateShort(model.created_at)}</Text>
|
|
2403
|
+
</Box>
|
|
2404
|
+
</Box>
|
|
2405
|
+
{model.description && (
|
|
2406
|
+
<Box marginLeft={2}>
|
|
2407
|
+
<Text dimColor>{model.description}</Text>
|
|
2408
|
+
</Box>
|
|
2409
|
+
)}
|
|
2410
|
+
</Box>
|
|
2411
|
+
);
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2007
2414
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
2008
2415
|
// Model List Command
|
|
2009
2416
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
2010
2417
|
|
|
2011
|
-
type ModelListFilter = "
|
|
2418
|
+
type ModelListFilter = "registered" | "trained" | "deployed" | "artifacts";
|
|
2012
2419
|
|
|
2013
2420
|
interface ModelListCommandProps {
|
|
2014
2421
|
filter: ModelListFilter;
|
|
@@ -2042,11 +2449,42 @@ function ModelListCommand({ filter }: ModelListCommandProps) {
|
|
|
2042
2449
|
return <ErrorMessage error={error} />;
|
|
2043
2450
|
}
|
|
2044
2451
|
|
|
2045
|
-
const
|
|
2046
|
-
const
|
|
2452
|
+
const showProjects = filter === "registered";
|
|
2453
|
+
const showDeployed = filter === "deployed" || filter === "artifacts";
|
|
2454
|
+
const showTrained = filter === "trained" || filter === "artifacts";
|
|
2047
2455
|
|
|
2048
2456
|
return (
|
|
2049
2457
|
<Box flexDirection="column">
|
|
2458
|
+
{showProjects && (
|
|
2459
|
+
<>
|
|
2460
|
+
<Text bold color="cyan">Model Entries ({data?.projects.length ?? 0})</Text>
|
|
2461
|
+
{data?.projects.length === 0 ? (
|
|
2462
|
+
<Text dimColor> No model entries</Text>
|
|
2463
|
+
) : (
|
|
2464
|
+
<Box flexDirection="column" marginTop={1}>
|
|
2465
|
+
<Box marginBottom={0}>
|
|
2466
|
+
<Text bold dimColor> </Text>
|
|
2467
|
+
<Box width={28}>
|
|
2468
|
+
<Text bold dimColor>Name</Text>
|
|
2469
|
+
</Box>
|
|
2470
|
+
<Box width={38}>
|
|
2471
|
+
<Text bold dimColor>Model ID</Text>
|
|
2472
|
+
</Box>
|
|
2473
|
+
<Box width={24}>
|
|
2474
|
+
<Text bold dimColor>Base Model</Text>
|
|
2475
|
+
</Box>
|
|
2476
|
+
<Box width={18}>
|
|
2477
|
+
<Text bold dimColor>Created</Text>
|
|
2478
|
+
</Box>
|
|
2479
|
+
</Box>
|
|
2480
|
+
{data?.projects.map((model, index) => (
|
|
2481
|
+
<ProjectModelCard key={model.id || index} model={model} index={index} />
|
|
2482
|
+
))}
|
|
2483
|
+
</Box>
|
|
2484
|
+
)}
|
|
2485
|
+
</>
|
|
2486
|
+
)}
|
|
2487
|
+
{showProjects && (showDeployed || showTrained) && <Text> </Text>}
|
|
2050
2488
|
{showDeployed && (
|
|
2051
2489
|
<>
|
|
2052
2490
|
<Text bold color="cyan">Deployed Models ({data?.deployed.length ?? 0})</Text>
|
|
@@ -2135,6 +2573,9 @@ interface ModelGenerateCommandProps {
|
|
|
2135
2573
|
systemMsg?: string;
|
|
2136
2574
|
maxTokens: number;
|
|
2137
2575
|
temperature: number;
|
|
2576
|
+
topP?: number;
|
|
2577
|
+
includeReasoningTrace?: boolean;
|
|
2578
|
+
projectId?: string;
|
|
2138
2579
|
}
|
|
2139
2580
|
|
|
2140
2581
|
function normalizeBaseModels(
|
|
@@ -2157,18 +2598,74 @@ function formatDecoderSuggestions(decoderIds: string[]): string {
|
|
|
2157
2598
|
return `\nTry one of these decoder models: ${decoderIds.slice(0, 4).join(", ")}`;
|
|
2158
2599
|
}
|
|
2159
2600
|
|
|
2601
|
+
function normalizeModelId(modelId: string): string {
|
|
2602
|
+
return modelId.trim();
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
function getDecoderTaskType(model: api.BaseModelInfo | null | undefined): string {
|
|
2606
|
+
if (!model) return "";
|
|
2607
|
+
return `${model.task_type ?? model.type ?? ""}`.trim().toLowerCase();
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
function modelSupportsDecoderInference(model: api.BaseModelInfo | null | undefined): boolean {
|
|
2611
|
+
if (!model) return false;
|
|
2612
|
+
if (model.supports_inference !== undefined) {
|
|
2613
|
+
return model.supports_inference;
|
|
2614
|
+
}
|
|
2615
|
+
if (model.supports_on_demand_inference !== undefined) {
|
|
2616
|
+
return model.supports_on_demand_inference;
|
|
2617
|
+
}
|
|
2618
|
+
return true;
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
function isDecoderModel(model: api.BaseModelInfo | null | undefined): boolean {
|
|
2622
|
+
const taskType = getDecoderTaskType(model);
|
|
2623
|
+
return (
|
|
2624
|
+
taskType === "decoder" ||
|
|
2625
|
+
taskType === "llm" ||
|
|
2626
|
+
taskType === "generative"
|
|
2627
|
+
);
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
function shouldFallbackToTextCompletions(errorMessage: string): boolean {
|
|
2631
|
+
const normalized = errorMessage.toLowerCase();
|
|
2632
|
+
return (
|
|
2633
|
+
normalized.includes("/v1/completions") ||
|
|
2634
|
+
normalized.includes("without a chat template") ||
|
|
2635
|
+
normalized.includes("text completion")
|
|
2636
|
+
);
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
function buildTextCompletionPrompt(prompt: string, systemMsg?: string): string {
|
|
2640
|
+
if (!systemMsg) return prompt;
|
|
2641
|
+
return `System instruction:\n${systemMsg}\n\nUser prompt:\n${prompt}\n\nAssistant response:`;
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
function parseCommaSeparated(value?: string): string[] {
|
|
2645
|
+
if (!value) return [];
|
|
2646
|
+
return value
|
|
2647
|
+
.split(",")
|
|
2648
|
+
.map((item) => item.trim())
|
|
2649
|
+
.filter(Boolean);
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2160
2652
|
function ModelGenerateCommand({
|
|
2161
2653
|
modelId,
|
|
2162
2654
|
prompt,
|
|
2163
2655
|
systemMsg,
|
|
2164
2656
|
maxTokens,
|
|
2165
2657
|
temperature,
|
|
2658
|
+
topP,
|
|
2659
|
+
includeReasoningTrace,
|
|
2660
|
+
projectId,
|
|
2166
2661
|
}: ModelGenerateCommandProps) {
|
|
2167
2662
|
const { exit } = useApp();
|
|
2168
2663
|
const [state, setState] = useState<"checking" | "running" | "done" | "error">(
|
|
2169
2664
|
"checking"
|
|
2170
2665
|
);
|
|
2171
|
-
const [data, setData] = useState<api.InferenceResponse | null>(
|
|
2666
|
+
const [data, setData] = useState<api.InferenceResponse | api.TextCompletionResponse | null>(
|
|
2667
|
+
null
|
|
2668
|
+
);
|
|
2172
2669
|
const [error, setError] = useState("");
|
|
2173
2670
|
|
|
2174
2671
|
useEffect(() => {
|
|
@@ -2181,8 +2678,9 @@ function ModelGenerateCommand({
|
|
|
2181
2678
|
? normalizeBaseModels(baseModelsResult.data)
|
|
2182
2679
|
: [];
|
|
2183
2680
|
const decoderModelIds = baseModels
|
|
2184
|
-
.filter((model) => model
|
|
2681
|
+
.filter((model) => isDecoderModel(model) && modelSupportsDecoderInference(model))
|
|
2185
2682
|
.map((model) => model.id);
|
|
2683
|
+
const normalizedModelId = normalizeModelId(modelId);
|
|
2186
2684
|
|
|
2187
2685
|
const fail = (message: string) => {
|
|
2188
2686
|
if (!active) return;
|
|
@@ -2191,38 +2689,35 @@ function ModelGenerateCommand({
|
|
|
2191
2689
|
setTimeout(() => exit(), 500);
|
|
2192
2690
|
};
|
|
2193
2691
|
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
"Model 'base' is encoder-only and cannot run decoder generation. Use 'model predict' for encoder tasks." +
|
|
2197
|
-
formatDecoderSuggestions(decoderModelIds)
|
|
2198
|
-
);
|
|
2199
|
-
return;
|
|
2200
|
-
}
|
|
2201
|
-
|
|
2202
|
-
const matchedBaseModel = baseModels.find((model) => model.id === modelId);
|
|
2203
|
-
if (matchedBaseModel && matchedBaseModel.type !== "decoder") {
|
|
2692
|
+
const matchedBaseModel = baseModels.find((model) => model.id === normalizedModelId);
|
|
2693
|
+
if (matchedBaseModel && (!isDecoderModel(matchedBaseModel) || !modelSupportsDecoderInference(matchedBaseModel))) {
|
|
2204
2694
|
const modelName = matchedBaseModel.label || matchedBaseModel.name || matchedBaseModel.id;
|
|
2695
|
+
const taskType = getDecoderTaskType(matchedBaseModel) || "unknown";
|
|
2696
|
+
const supportsInference = modelSupportsDecoderInference(matchedBaseModel);
|
|
2205
2697
|
fail(
|
|
2206
|
-
`Model '${modelName}' is type '${
|
|
2698
|
+
`Model '${modelName}' is type '${taskType}' and is not decoder-compatible for inference` +
|
|
2699
|
+
`${supportsInference ? "" : " (supports_inference = false)"} ` +
|
|
2700
|
+
`(supports_inference: ${String(matchedBaseModel.supports_inference ?? "unknown")}, task_type: ${taskType}).` +
|
|
2207
2701
|
formatDecoderSuggestions(decoderModelIds)
|
|
2208
2702
|
);
|
|
2209
2703
|
return;
|
|
2210
2704
|
}
|
|
2211
2705
|
|
|
2212
|
-
if (
|
|
2706
|
+
if (baseModels.length > 0 && !isUuid(normalizedModelId) && !matchedBaseModel) {
|
|
2213
2707
|
fail(
|
|
2214
|
-
`
|
|
2708
|
+
`Model '${modelId}' is not a recognized decoder base model and is not a valid job UUID.` +
|
|
2709
|
+
` Use 'model list' to copy a full training job UUID or provide a known decoder catalog ID.`
|
|
2215
2710
|
);
|
|
2216
2711
|
return;
|
|
2217
2712
|
}
|
|
2218
2713
|
|
|
2219
|
-
if (isUuid(
|
|
2220
|
-
const jobResult = await api.getJob(
|
|
2714
|
+
if (isUuid(normalizedModelId)) {
|
|
2715
|
+
const jobResult = await api.getJob(normalizedModelId);
|
|
2221
2716
|
if (jobResult.ok && jobResult.data) {
|
|
2222
2717
|
const taskType = jobResult.data.task_type?.toLowerCase();
|
|
2223
|
-
if (taskType && taskType
|
|
2718
|
+
if (taskType && !isDecoderModel({ task_type: taskType } as api.BaseModelInfo)) {
|
|
2224
2719
|
fail(
|
|
2225
|
-
`Training job '${
|
|
2720
|
+
`Training job '${normalizedModelId}' has task_type '${taskType}', so it is not decoder-compatible.` +
|
|
2226
2721
|
formatDecoderSuggestions(decoderModelIds)
|
|
2227
2722
|
);
|
|
2228
2723
|
return;
|
|
@@ -2233,18 +2728,21 @@ function ModelGenerateCommand({
|
|
|
2233
2728
|
if (!active) return;
|
|
2234
2729
|
setState("running");
|
|
2235
2730
|
|
|
2236
|
-
const messages:
|
|
2731
|
+
const messages: api.InferenceMessage[] = [];
|
|
2237
2732
|
if (systemMsg) {
|
|
2238
2733
|
messages.push({ role: "system", content: systemMsg });
|
|
2239
2734
|
}
|
|
2240
2735
|
messages.push({ role: "user", content: prompt });
|
|
2241
2736
|
|
|
2242
2737
|
const result = await api.runInference({
|
|
2243
|
-
model_id:
|
|
2738
|
+
model_id: normalizedModelId,
|
|
2244
2739
|
task: "generate",
|
|
2245
2740
|
messages,
|
|
2246
2741
|
max_tokens: maxTokens,
|
|
2247
2742
|
temperature,
|
|
2743
|
+
...(topP !== undefined ? { top_p: topP } : {}),
|
|
2744
|
+
...(includeReasoningTrace ? { include_reasoning_trace: true } : {}),
|
|
2745
|
+
...(projectId ? { project_id: projectId } : {}),
|
|
2248
2746
|
});
|
|
2249
2747
|
|
|
2250
2748
|
if (!active) return;
|
|
@@ -2253,9 +2751,32 @@ function ModelGenerateCommand({
|
|
|
2253
2751
|
setData(result.data);
|
|
2254
2752
|
setState("done");
|
|
2255
2753
|
} else {
|
|
2256
|
-
|
|
2257
|
-
const normalizedError = formatApiError(
|
|
2258
|
-
|
|
2754
|
+
const primaryError = result.error ?? "Unknown error";
|
|
2755
|
+
const normalizedError = formatApiError(primaryError);
|
|
2756
|
+
let message = normalizedError;
|
|
2757
|
+
|
|
2758
|
+
if (shouldFallbackToTextCompletions(normalizedError)) {
|
|
2759
|
+
const completionResult = await api.runTextCompletion({
|
|
2760
|
+
model: normalizedModelId,
|
|
2761
|
+
prompt: buildTextCompletionPrompt(prompt, systemMsg),
|
|
2762
|
+
max_tokens: maxTokens,
|
|
2763
|
+
temperature,
|
|
2764
|
+
...(topP !== undefined ? { extra_body: { top_p: topP } } : {}),
|
|
2765
|
+
});
|
|
2766
|
+
|
|
2767
|
+
if (!active) return;
|
|
2768
|
+
|
|
2769
|
+
if (completionResult.ok && completionResult.data) {
|
|
2770
|
+
setData(completionResult.data);
|
|
2771
|
+
setState("done");
|
|
2772
|
+
setTimeout(() => exit(), 500);
|
|
2773
|
+
return;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
message =
|
|
2777
|
+
"Decoder chat inference failed, and raw text fallback via /v1/completions also failed.\n" +
|
|
2778
|
+
formatApiError(completionResult.error ?? "Unknown error");
|
|
2779
|
+
} else if (normalizedError.toLowerCase().includes("inference failed")) {
|
|
2259
2780
|
message =
|
|
2260
2781
|
`${normalizedError}\nDecoder inference request reached the backend but failed to execute.` +
|
|
2261
2782
|
formatDecoderSuggestions(decoderModelIds);
|
|
@@ -2270,7 +2791,17 @@ function ModelGenerateCommand({
|
|
|
2270
2791
|
return () => {
|
|
2271
2792
|
active = false;
|
|
2272
2793
|
};
|
|
2273
|
-
}, [
|
|
2794
|
+
}, [
|
|
2795
|
+
modelId,
|
|
2796
|
+
prompt,
|
|
2797
|
+
systemMsg,
|
|
2798
|
+
maxTokens,
|
|
2799
|
+
temperature,
|
|
2800
|
+
topP,
|
|
2801
|
+
includeReasoningTrace,
|
|
2802
|
+
projectId,
|
|
2803
|
+
exit,
|
|
2804
|
+
]);
|
|
2274
2805
|
|
|
2275
2806
|
if (state === "checking") {
|
|
2276
2807
|
return <Loading message="Checking decoder model compatibility..." />;
|
|
@@ -2301,16 +2832,12 @@ type HelpContext =
|
|
|
2301
2832
|
| "dataset"
|
|
2302
2833
|
| "dataset-analyze"
|
|
2303
2834
|
| "dataset-edit"
|
|
2304
|
-
| "project"
|
|
2305
2835
|
| "job"
|
|
2306
2836
|
| "model"
|
|
2307
|
-
| "chat"
|
|
2308
2837
|
| "eval"
|
|
2309
2838
|
| "benchmark"
|
|
2310
|
-
| "competition"
|
|
2311
|
-
| "notebook"
|
|
2312
2839
|
| "inference"
|
|
2313
|
-
| "
|
|
2840
|
+
| "agent";
|
|
2314
2841
|
|
|
2315
2842
|
interface HelpProps {
|
|
2316
2843
|
context?: HelpContext;
|
|
@@ -2356,6 +2883,7 @@ const Help: React.FC<HelpProps> = ({ context = "root" }) => {
|
|
|
2356
2883
|
<Text> dataset get {"<name[:version]>"} Get dataset details</Text>
|
|
2357
2884
|
<Text> dataset delete {"<name[:version]>"} Delete a dataset</Text>
|
|
2358
2885
|
<Text> dataset analyze {"<name[:version]>"} Analyze a dataset</Text>
|
|
2886
|
+
<Text> dataset analyze-llm {"<name[:version]>"} LLM-only dataset analysis</Text>
|
|
2359
2887
|
<Text> </Text>
|
|
2360
2888
|
<Text bold> Generate:</Text>
|
|
2361
2889
|
<Text> dataset generate ner</Text>
|
|
@@ -2386,12 +2914,53 @@ const Help: React.FC<HelpProps> = ({ context = "root" }) => {
|
|
|
2386
2914
|
<Text> --num {"<n>"} Number of examples (default: 10)</Text>
|
|
2387
2915
|
<Text> --save true Save to database</Text>
|
|
2388
2916
|
<Text> --name {"<name>"} Dataset name (required if --save)</Text>
|
|
2917
|
+
<Text> Advanced generation flags:</Text>
|
|
2918
|
+
<Text> --quality {"<light|medium|heavy>"} Generation quality profile</Text>
|
|
2919
|
+
<Text> --generation-profile {"<auto|fast|balanced|quality>"} Runtime profile</Text>
|
|
2920
|
+
<Text> --reasoning-trace {"true|false"} Include reasoning traces (decoder only)</Text>
|
|
2921
|
+
<Text> --reasoning-effort {"<low|medium|high>"} Reasoning effort</Text>
|
|
2922
|
+
<Text> --multiplicator {"<json>"} Multiplicator settings</Text>
|
|
2923
|
+
<Text> --use-meta-felix {"true|false"} Use MetaFelix metadata</Text>
|
|
2924
|
+
<Text> --min-criteria {"<n>"} Minimum diversity criteria</Text>
|
|
2925
|
+
<Text> --target-choices {"<n>"} Diversity target choices</Text>
|
|
2926
|
+
<Text> --project-id {"<id>"} Project ID</Text>
|
|
2927
|
+
<Text> --type {"training|evaluation|split"} Dataset type</Text>
|
|
2928
|
+
<Text> --visibility {"private|public"} Dataset visibility</Text>
|
|
2929
|
+
<Text> --split-ratio {"<train:eval>|{json>}"} Split dataset ratio</Text>
|
|
2930
|
+
<Text> --negative-ratio {"<n>"} Percent negative samples</Text>
|
|
2931
|
+
<Text> --classified-examples {"<json>"} Classified examples with feedback</Text>
|
|
2389
2932
|
<Text> </Text>
|
|
2390
2933
|
<Text bold> Infer Labels:</Text>
|
|
2391
2934
|
<Text> dataset infer ner Infer NER labels from description</Text>
|
|
2392
2935
|
<Text> dataset infer classification Infer classification labels</Text>
|
|
2393
2936
|
<Text> dataset infer fields Infer input/output fields</Text>
|
|
2394
2937
|
<Text> --domain {"<desc>"} Domain description (required)</Text>
|
|
2938
|
+
<Text> dataset infer infer-advanced Infer constraints and multiplicator from a prompt</Text>
|
|
2939
|
+
<Text> --prompt {"<prompt>"} Prompt for inference</Text>
|
|
2940
|
+
<Text> --labels {"<l1,l2,...>"} Optional labels to guide suggestions</Text>
|
|
2941
|
+
<Text> --data-type {"<type>"} entity_extraction|classification|json_extraction</Text>
|
|
2942
|
+
<Text> dataset infer improve-prompt Improve a generation prompt</Text>
|
|
2943
|
+
<Text> --prompt {"<prompt>"} Prompt to improve</Text>
|
|
2944
|
+
<Text> --data-type {"<type>"} Optional prompt domain hint</Text>
|
|
2945
|
+
<Text> dataset label-existing ner Label existing NER texts</Text>
|
|
2946
|
+
<Text> --labels {"<l1,l2,...>"} Labels for entities</Text>
|
|
2947
|
+
<Text> --inputs {"[{\"text\":\"...\"},...]"} Input texts JSON array (required)</Text>
|
|
2948
|
+
<Text> --name {"<name>"} Output dataset name (optional if --save false)</Text>
|
|
2949
|
+
<Text> --project-id {"<project_id>"} Assign output dataset to project</Text>
|
|
2950
|
+
<Text> --save {"<true|false>"} Save dataset (default: false)</Text>
|
|
2951
|
+
<Text> dataset label-existing classification Label existing classification texts</Text>
|
|
2952
|
+
<Text> --labels {"<l1,l2,...>"} Labels for classes</Text>
|
|
2953
|
+
<Text> --inputs {"[{\"text\":\"...\"},...]"} Input texts JSON array (required)</Text>
|
|
2954
|
+
<Text> --name {"<name>"} Output dataset name (optional if --save false)</Text>
|
|
2955
|
+
<Text> --project-id {"<project_id>"} Assign output dataset to project</Text>
|
|
2956
|
+
<Text> --save {"<true|false>"} Save dataset (default: false)</Text>
|
|
2957
|
+
<Text> dataset label-existing fields Label existing structured records</Text>
|
|
2958
|
+
<Text> --input-fields {"[{\"name\":\"...\"},...]"} Input schema fields (required)</Text>
|
|
2959
|
+
<Text> --output-fields {"[{\"name\":\"...\"},...]"} Output schema fields (required)</Text>
|
|
2960
|
+
<Text> --inputs {"[{\"f1\":\"v\"},...]"} Input records JSON array (required)</Text>
|
|
2961
|
+
<Text> --name {"<name>"} Output dataset name (optional if --save false)</Text>
|
|
2962
|
+
<Text> --project-id {"<project_id>"} Assign output dataset to project</Text>
|
|
2963
|
+
<Text> --save {"<true|false>"} Save dataset (default: false)</Text>
|
|
2395
2964
|
<Text> </Text>
|
|
2396
2965
|
<Text bold> Upload/Download:</Text>
|
|
2397
2966
|
<Text> dataset upload {"<file>"} Upload local file to Pioneer</Text>
|
|
@@ -2413,6 +2982,8 @@ const Help: React.FC<HelpProps> = ({ context = "root" }) => {
|
|
|
2413
2982
|
<Text bold> Data Editing:</Text>
|
|
2414
2983
|
<Text> dataset edit --help Show data editing commands</Text>
|
|
2415
2984
|
<Text> dataset edit scan-pii {"<name[:version]>"} Scan for PII</Text>
|
|
2985
|
+
<Text> dataset edit dismiss-outlier {"<name[:version]>"} Dismiss an outlier fingerprint</Text>
|
|
2986
|
+
<Text> --fingerprint {"<hash>"} Outlier fingerprint from dataset analysis</Text>
|
|
2416
2987
|
<Text> dataset edit subsample {"<name[:version]>"} Create a subsample</Text>
|
|
2417
2988
|
</Box>
|
|
2418
2989
|
);
|
|
@@ -2431,6 +3002,8 @@ const Help: React.FC<HelpProps> = ({ context = "root" }) => {
|
|
|
2431
3002
|
<Text> dataset edit scan-phd {"<name[:version]>"} Scan for prompt injection</Text>
|
|
2432
3003
|
<Text> --columns {"<col1,col2>"} Columns to scan (optional, scans all if omitted)</Text>
|
|
2433
3004
|
<Text> --threshold {"<n>"} Detection threshold (default: 0.5)</Text>
|
|
3005
|
+
<Text> dataset edit dismiss-outlier {"<name[:version]>"} Dismiss an outlier fingerprint</Text>
|
|
3006
|
+
<Text> --fingerprint {"<hash>"} Outlier fingerprint from dataset analysis</Text>
|
|
2434
3007
|
<Text> dataset edit subsample {"<name[:version]>"} Create a subsample</Text>
|
|
2435
3008
|
<Text> --n {"<count>"} Target sample count (required)</Text>
|
|
2436
3009
|
<Text> --method {"<type>"} Method: random, balanced, stratified</Text>
|
|
@@ -2458,32 +3031,13 @@ const Help: React.FC<HelpProps> = ({ context = "root" }) => {
|
|
|
2458
3031
|
<Text> Options: ner, classification, generative</Text>
|
|
2459
3032
|
<Text> --analyses {"<a1,a2,...>"} Analyses to run (required, comma-separated)</Text>
|
|
2460
3033
|
<Text> Options: distribution, duplicates, outliers, splits, diversity</Text>
|
|
3034
|
+
<Text> </Text>
|
|
3035
|
+
<Text> LLM-only analysis:</Text>
|
|
3036
|
+
<Text> dataset analyze-llm {"<id>"} --task-type {"<type>"} --description {"<text>"} --labels {"<l1,l2>"} </Text>
|
|
2461
3037
|
<Text> </Text>
|
|
2462
3038
|
<Text bold> Example:</Text>
|
|
2463
3039
|
<Text> dataset analyze abc123 --task-type ner --analyses distribution,duplicates</Text>
|
|
2464
|
-
|
|
2465
|
-
);
|
|
2466
|
-
}
|
|
2467
|
-
|
|
2468
|
-
// Project help
|
|
2469
|
-
if (context === "project") {
|
|
2470
|
-
return (
|
|
2471
|
-
<Box flexDirection="column">
|
|
2472
|
-
<Text bold>Project Commands:</Text>
|
|
2473
|
-
<Text> project list List all projects</Text>
|
|
2474
|
-
<Text> project get {"<project-id>"} Get project details</Text>
|
|
2475
|
-
<Text> project create Create a project</Text>
|
|
2476
|
-
<Text> --name {"<name>"} Project name (required)</Text>
|
|
2477
|
-
<Text> --icon {"<icon>"} Icon name (optional, default: folder)</Text>
|
|
2478
|
-
<Text> --repo {"<repo-url>"} Repository URL/reference (optional)</Text>
|
|
2479
|
-
<Text> --description {"<text>"} Description (optional)</Text>
|
|
2480
|
-
<Text> --model-id {"<id>"} Selected model ID (optional)</Text>
|
|
2481
|
-
<Text> --example {"<json>"} JSON example payload (optional)</Text>
|
|
2482
|
-
<Text> project update {"<project-id>"} Update project fields</Text>
|
|
2483
|
-
<Text> --name {"<name>"} --icon {"<icon>"} --repo {"<repo-url>"} --description {"<text>"} --model-id {"<id>"}</Text>
|
|
2484
|
-
<Text> project delete {"<project-id>"} Delete a project</Text>
|
|
2485
|
-
<Text> project dataset-count {"<project-id>"} Show attached dataset count</Text>
|
|
2486
|
-
<Text> project quality-metrics {"<project-id>"} Show LLMAJ pass/fail metrics</Text>
|
|
3040
|
+
<Text> dataset analyze-llm abc123 --task-type ner --description "NER quality analysis"</Text>
|
|
2487
3041
|
</Box>
|
|
2488
3042
|
);
|
|
2489
3043
|
}
|
|
@@ -2509,156 +3063,139 @@ const Help: React.FC<HelpProps> = ({ context = "root" }) => {
|
|
|
2509
3063
|
);
|
|
2510
3064
|
}
|
|
2511
3065
|
|
|
2512
|
-
// Model help
|
|
2513
|
-
if (context === "model") {
|
|
3066
|
+
// Model endpoints help
|
|
3067
|
+
if (context === "model-endpoints") {
|
|
2514
3068
|
return (
|
|
2515
3069
|
<Box flexDirection="column">
|
|
2516
|
-
<Text bold>Model Commands:</Text>
|
|
2517
|
-
<Text>
|
|
2518
|
-
<Text> model list
|
|
2519
|
-
<Text> model
|
|
2520
|
-
<Text>
|
|
2521
|
-
<Text>
|
|
2522
|
-
<Text> </Text>
|
|
2523
|
-
<Text
|
|
2524
|
-
<Text>
|
|
2525
|
-
<Text> --
|
|
2526
|
-
<Text>
|
|
2527
|
-
<Text> model
|
|
2528
|
-
<Text> --
|
|
2529
|
-
<Text>
|
|
2530
|
-
<Text>
|
|
2531
|
-
<Text
|
|
2532
|
-
<Text> </Text>
|
|
2533
|
-
<Text
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
3070
|
+
<Text bold>Model Endpoint Commands:</Text>
|
|
3071
|
+
<Text> Aliases: model_endpoints ...</Text>
|
|
3072
|
+
<Text> model endpoints list</Text>
|
|
3073
|
+
<Text> model endpoints create</Text>
|
|
3074
|
+
<Text> --name {"<name>"} Optional (defaults to model id)</Text>
|
|
3075
|
+
<Text> --icon {"<icon>"} Optional</Text>
|
|
3076
|
+
<Text> --repo {"<repo-url>"} Optional</Text>
|
|
3077
|
+
<Text> --description {"<text>"} Optional</Text>
|
|
3078
|
+
<Text> --model {"<base-model-id>"} Optional (starts interactive picker when omitted)</Text>
|
|
3079
|
+
<Text> --example {"<json>"} Optional</Text>
|
|
3080
|
+
<Text> model endpoints get {"<model-id>"} Get endpoint/model entry details</Text>
|
|
3081
|
+
<Text> model endpoints update {"<model-id>"} Update endpoint metadata</Text>
|
|
3082
|
+
<Text> --name {"<name>"} --icon {"<icon>"} --repo {"<repo-url>"} --description {"<text>"} --model-id {"<id>"}</Text>
|
|
3083
|
+
<Text> model endpoints delete {"<model-id>"} Delete an endpoint/model entry</Text>
|
|
3084
|
+
<Text> model endpoints dataset-count {"<model-id>"} Get attached dataset count</Text>
|
|
3085
|
+
<Text> model endpoints quality-metrics {"<model-id>"} Show LLMAJ pass/fail metrics</Text>
|
|
3086
|
+
<Text> model endpoints deploy {"<model-id>"} --job {"<training-job-id>"} [--reason {"<text>"}] Deploy a trained job to the endpoint</Text>
|
|
3087
|
+
<Text> model endpoints rollback {"<model-id>"} {"<deployment-id>"} Rollback endpoint to previous deployment</Text>
|
|
3088
|
+
</Box>
|
|
3089
|
+
);
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
// Model artifacts help
|
|
3093
|
+
if (context === "model-artifacts") {
|
|
3094
|
+
return (
|
|
3095
|
+
<Box flexDirection="column">
|
|
3096
|
+
<Text bold>Model Artifact Commands:</Text>
|
|
3097
|
+
<Text> Aliases: model_artifacts ...</Text>
|
|
3098
|
+
<Text> model artifacts list Show both trained and deployed artifacts</Text>
|
|
3099
|
+
<Text> model artifacts trained List trained artifacts</Text>
|
|
3100
|
+
<Text> model artifacts deployed List deployed artifacts</Text>
|
|
3101
|
+
<Text> model artifacts download {"<job-id>"} Download model artifact</Text>
|
|
3102
|
+
<Text> model artifacts delete {"<job-id>"} Delete deployed artifact record</Text>
|
|
3103
|
+
<Text> model artifacts upload {"<job-id>"} --to hf Upload trained model artifact to Hugging Face</Text>
|
|
3104
|
+
<Text> --repo {"<repo>"} HF repo (required, e.g., username/model)</Text>
|
|
3105
|
+
<Text> --private Make repo private</Text>
|
|
2537
3106
|
<Text dimColor> Note: Set HF token with 'pioneer auth hf' first</Text>
|
|
2538
3107
|
<Text dimColor> Note: Use full job ID (not partial ID shown in list)</Text>
|
|
2539
3108
|
</Box>
|
|
2540
3109
|
);
|
|
2541
3110
|
}
|
|
2542
3111
|
|
|
2543
|
-
//
|
|
2544
|
-
if (context === "
|
|
3112
|
+
// Model help
|
|
3113
|
+
if (context === "model") {
|
|
2545
3114
|
return (
|
|
2546
3115
|
<Box flexDirection="column">
|
|
2547
|
-
<Text bold>
|
|
2548
|
-
<Text> model
|
|
2549
|
-
<Text>
|
|
2550
|
-
<Text> Use "base" as model-id for the base GLiNER2 model</Text>
|
|
3116
|
+
<Text bold>Model Commands:</Text>
|
|
3117
|
+
<Text> model endpoints ... (alias: model_endpoints) Manage model catalog entries (from /projects)</Text>
|
|
3118
|
+
<Text> model artifacts ... (alias: model_artifacts) Manage trained/deployed artifacts (from /felix)</Text>
|
|
2551
3119
|
<Text> </Text>
|
|
2552
|
-
<Text> model
|
|
2553
|
-
<Text>
|
|
2554
|
-
<Text>
|
|
3120
|
+
<Text> model endpoints list</Text>
|
|
3121
|
+
<Text> model endpoints create</Text>
|
|
3122
|
+
<Text> model endpoints get {"<model-id>"}</Text>
|
|
3123
|
+
<Text> model endpoints deploy {"<model-id>"} --job {"<training-job-id>"} [--reason {"<text>"}]</Text>
|
|
3124
|
+
<Text> model endpoints rollback {"<model-id>"} {"<deployment-id>"}</Text>
|
|
3125
|
+
<Text> model artifacts list</Text>
|
|
3126
|
+
<Text> model artifacts trained</Text>
|
|
3127
|
+
<Text> model artifacts deployed</Text>
|
|
3128
|
+
<Text> model artifacts download {"<job-id>"}</Text>
|
|
2555
3129
|
<Text> </Text>
|
|
2556
|
-
<Text bold> Options:</Text>
|
|
2557
|
-
<Text> --task {"<task>"} extract_entities | classify_text | extract_json | schema</Text>
|
|
2558
|
-
<Text> --labels {"<labels>"} Comma-separated labels for extraction/classification</Text>
|
|
2559
|
-
<Text> --threshold {"<n>"} Confidence threshold (0-1, default: 0.5)</Text>
|
|
2560
|
-
<Text> --system {"<text>"} System message for decoder generation</Text>
|
|
2561
|
-
<Text> --max-tokens {"<n>"} Max tokens for generation (default: 256)</Text>
|
|
2562
|
-
<Text> --temperature {"<n>"} Sampling temperature (default: 0.7)</Text>
|
|
2563
3130
|
</Box>
|
|
2564
3131
|
);
|
|
2565
3132
|
}
|
|
2566
3133
|
|
|
2567
|
-
//
|
|
2568
|
-
if (context === "
|
|
3134
|
+
// Inference help
|
|
3135
|
+
if (context === "inference") {
|
|
2569
3136
|
return (
|
|
2570
3137
|
<Box flexDirection="column">
|
|
2571
|
-
<Text bold>
|
|
2572
|
-
<Text>
|
|
2573
|
-
<Text>
|
|
3138
|
+
<Text bold>Inference Commands:</Text>
|
|
3139
|
+
<Text> inference base-models</Text>
|
|
3140
|
+
<Text> List base models from /base-models</Text>
|
|
2574
3141
|
<Text> </Text>
|
|
2575
|
-
<Text
|
|
3142
|
+
<Text> inference encoder {"<model-id>"} --text {"<text>"} --labels {"<labels>"}</Text>
|
|
3143
|
+
<Text> Run encoder inference via /inference</Text>
|
|
3144
|
+
<Text> --task {"<task>"} extract_entities | classify_text | extract_json | schema</Text>
|
|
3145
|
+
<Text> --labels {"<labels>"} Comma-separated labels (or use --schema JSON)</Text>
|
|
3146
|
+
<Text> --schema {"<json>"} JSON schema object for advanced tasks</Text>
|
|
3147
|
+
<Text> --threshold {"<n>"} Confidence threshold (0-1, default: 0.5)</Text>
|
|
3148
|
+
<Text> --project-id {"<id>"} Associate inference with a project</Text>
|
|
3149
|
+
<Text> </Text>
|
|
3150
|
+
<Text> inference decoder {"<model-id>"} --prompt {"<text>"}</Text>
|
|
3151
|
+
<Text> Run decoder generation via /inference</Text>
|
|
3152
|
+
<Text> --system {"<text>"} System message (optional)</Text>
|
|
3153
|
+
<Text> --max-tokens {"<n>"} Max tokens (default: 256)</Text>
|
|
3154
|
+
<Text> --temperature {"<n>"} Sampling temperature (default: 0.7)</Text>
|
|
3155
|
+
<Text> --top-p {"<n>"} Top-p sampling (0-1)</Text>
|
|
3156
|
+
<Text> --reasoning-trace Include reasoning trace when supported</Text>
|
|
3157
|
+
<Text> --project-id {"<id>"} Associate inference with a project</Text>
|
|
3158
|
+
<Text> Example model IDs: Qwen/Qwen3-8B, meta-llama/Llama-3.1-8B-Instruct</Text>
|
|
3159
|
+
<Text> </Text>
|
|
3160
|
+
<Text> inference completions {"<model-id>"} --prompt {"<text>"}</Text>
|
|
3161
|
+
<Text> Run raw text completion via /v1/completions</Text>
|
|
3162
|
+
<Text> --max-tokens {"<n>"} Max tokens (default: 256)</Text>
|
|
3163
|
+
<Text> --temperature {"<n>"} Sampling temperature (default: 0.7)</Text>
|
|
3164
|
+
<Text> --top-p {"<n>"} Top-p (sent via extra_body)</Text>
|
|
3165
|
+
<Text> --stop {"<a,b,c>"} Stop sequences (comma-separated)</Text>
|
|
3166
|
+
<Text> --echo true Echo prompt in output</Text>
|
|
3167
|
+
<Text> --provider {"<name>"} Provider override (optional)</Text>
|
|
2576
3168
|
</Box>
|
|
2577
3169
|
);
|
|
2578
3170
|
}
|
|
2579
3171
|
|
|
2580
3172
|
// Eval help
|
|
2581
3173
|
if (context === "eval") {
|
|
2582
|
-
return
|
|
2583
|
-
<Box flexDirection="column">
|
|
2584
|
-
<Text bold>Evaluation Commands:</Text>
|
|
2585
|
-
<Text dimColor> Dataset format: name[:version] (version defaults to "latest")</Text>
|
|
2586
|
-
<Text> </Text>
|
|
2587
|
-
<Text> eval list {"<name[:version]>"} List evaluations for a dataset</Text>
|
|
2588
|
-
<Text> eval get {"<id>"} Get evaluation details</Text>
|
|
2589
|
-
<Text> eval create Create a new evaluation</Text>
|
|
2590
|
-
<Text> --model-id {"<id>"} Model to evaluate (required)</Text>
|
|
2591
|
-
<Text> --dataset {"<name[:version]>"} Dataset to evaluate on (required)</Text>
|
|
2592
|
-
<Text> --task-type {"<type>"} Task type: ner, classification</Text>
|
|
2593
|
-
<Text> --text-column {"<col>"} Text column name</Text>
|
|
2594
|
-
<Text> --label-column {"<col>"} Label column name</Text>
|
|
2595
|
-
</Box>
|
|
2596
|
-
);
|
|
3174
|
+
return <ErrorMessage error="The 'eval' command group is temporarily hidden for this version." />;
|
|
2597
3175
|
}
|
|
2598
3176
|
|
|
2599
3177
|
// Benchmark help
|
|
2600
3178
|
if (context === "benchmark") {
|
|
2601
|
-
return
|
|
2602
|
-
<Box flexDirection="column">
|
|
2603
|
-
<Text bold>Benchmark Commands:</Text>
|
|
2604
|
-
<Text> benchmark list List available benchmarks</Text>
|
|
2605
|
-
<Text> benchmark run Start a benchmark evaluation</Text>
|
|
2606
|
-
<Text> --model-id {"<id>"} Model to evaluate (required)</Text>
|
|
2607
|
-
<Text> --task {"<type>"} Task: ner, text_classification (required)</Text>
|
|
2608
|
-
<Text> --benchmark {"<name>"} Benchmark name (required)</Text>
|
|
2609
|
-
<Text> --max-samples {"<n>"} Max samples (default: 100)</Text>
|
|
2610
|
-
<Text> --split {"<name>"} Dataset split (default: test)</Text>
|
|
2611
|
-
<Text> benchmark get {"<id>"} Get evaluation status/results</Text>
|
|
2612
|
-
<Text> benchmark cancel {"<id>"} Cancel running evaluation</Text>
|
|
2613
|
-
</Box>
|
|
2614
|
-
);
|
|
3179
|
+
return <ErrorMessage error="The 'benchmark' command group is temporarily hidden for this version." />;
|
|
2615
3180
|
}
|
|
2616
3181
|
|
|
2617
|
-
//
|
|
2618
|
-
if (context === "
|
|
3182
|
+
// Agent help
|
|
3183
|
+
if (context === "agent") {
|
|
2619
3184
|
return (
|
|
2620
3185
|
<Box flexDirection="column">
|
|
2621
|
-
<Text bold>
|
|
2622
|
-
<Text>
|
|
2623
|
-
<Text> --
|
|
3186
|
+
<Text bold>Agent Commands:</Text>
|
|
3187
|
+
<Text> agent Start interactive agent chat</Text>
|
|
3188
|
+
<Text> --mode {"<research>"} --mode research uses Pro workflow</Text>
|
|
3189
|
+
<Text> </Text>
|
|
3190
|
+
<Text> Omit --mode to use the default standard interactive mode.</Text>
|
|
2624
3191
|
<Text> --conversation-id {"<id>"} Continue an existing conversation</Text>
|
|
2625
|
-
<Text> --filters {"<json>"}
|
|
3192
|
+
<Text> --filters {"<json>"} Reserved for future query filters</Text>
|
|
2626
3193
|
<Text> --history {"<json>"} Optional message history JSON</Text>
|
|
2627
3194
|
<Text> </Text>
|
|
2628
|
-
<Text dimColor>
|
|
2629
|
-
<Text dimColor>
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
}
|
|
2633
|
-
|
|
2634
|
-
// Competition help
|
|
2635
|
-
if (context === "competition") {
|
|
2636
|
-
return (
|
|
2637
|
-
<Box flexDirection="column">
|
|
2638
|
-
<Text bold>Competition Commands:</Text>
|
|
2639
|
-
<Text> competition list List active competitions</Text>
|
|
2640
|
-
<Text> competition show {"<dataset-id>"} View sample data for a competition</Text>
|
|
2641
|
-
<Text> competition leaderboard {"<dataset-id>"} View leaderboard rankings</Text>
|
|
2642
|
-
<Text> --limit {"<n>"} Number of entries (default: 10)</Text>
|
|
2643
|
-
<Text> competition submit {"<dataset-id>"} Submit evaluation to leaderboard</Text>
|
|
2644
|
-
<Text> --eval-id {"<id>"} Evaluation ID (required)</Text>
|
|
2645
|
-
<Text> --name {"<name>"} Display name (required)</Text>
|
|
2646
|
-
</Box>
|
|
2647
|
-
);
|
|
2648
|
-
}
|
|
2649
|
-
|
|
2650
|
-
// Notebook help
|
|
2651
|
-
if (context === "notebook") {
|
|
2652
|
-
return (
|
|
2653
|
-
<Box flexDirection="column">
|
|
2654
|
-
<Text bold>Notebook Commands:</Text>
|
|
2655
|
-
<Text> </Text>
|
|
2656
|
-
<Text> notebook run {"<file>"} Run all cells in a .ipynb notebook</Text>
|
|
2657
|
-
<Text> --gpu {"<type>"} GPU type: cpu, t4, a10g, a100, h100 (default: cpu)</Text>
|
|
2658
|
-
<Text> --no-felix Don't inject Felix helper functions</Text>
|
|
2659
|
-
<Text> notebook create {"<name>"} Create a blank notebook</Text>
|
|
2660
|
-
<Text> notebook sessions List active notebook sessions</Text>
|
|
2661
|
-
<Text> notebook stop {"<session-id>"} Terminate a notebook session</Text>
|
|
3195
|
+
<Text dimColor>Example: pioneer agent</Text>
|
|
3196
|
+
<Text dimColor>Example: pioneer agent --mode research</Text>
|
|
3197
|
+
<Text dimColor>Then type: Analyze failures and propose retraining plan</Text>
|
|
3198
|
+
<Text dimColor>Then type: Draft a short status summary</Text>
|
|
2662
3199
|
</Box>
|
|
2663
3200
|
);
|
|
2664
3201
|
}
|
|
@@ -2672,17 +3209,10 @@ const Help: React.FC<HelpProps> = ({ context = "root" }) => {
|
|
|
2672
3209
|
<Text> pioneer {"<command>"} {"[options]"}</Text>
|
|
2673
3210
|
<Text> </Text>
|
|
2674
3211
|
<Text bold>Commands:</Text>
|
|
2675
|
-
<Text> chat Start interactive chat agent</Text>
|
|
2676
3212
|
<Text> auth Authentication (login, logout, status)</Text>
|
|
2677
|
-
<Text>
|
|
2678
|
-
<Text> project Manage projects</Text>
|
|
3213
|
+
<Text> model Manage model endpoints and artifacts</Text>
|
|
2679
3214
|
<Text> job Manage training jobs</Text>
|
|
2680
|
-
|
|
2681
|
-
<Text> eval Model evaluations on datasets</Text>
|
|
2682
|
-
<Text> benchmark Run benchmark evaluations</Text>
|
|
2683
|
-
<Text> notebook Run and manage Jupyter notebooks</Text>
|
|
2684
|
-
<Text> competition Competitions and leaderboards</Text>
|
|
2685
|
-
<Text> adaptive-finetuning Adaptive fine-tuning agent</Text>
|
|
3215
|
+
<Text> agent Run agent chat (research is the only explicit alternate mode)</Text>
|
|
2686
3216
|
<Text> telemetry Manage anonymous usage analytics</Text>
|
|
2687
3217
|
<Text> </Text>
|
|
2688
3218
|
<Text dimColor>Run 'pioneer {"<command>"} --help' for details on a specific command.</Text>
|
|
@@ -2690,24 +3220,12 @@ const Help: React.FC<HelpProps> = ({ context = "root" }) => {
|
|
|
2690
3220
|
<Text dimColor>Get started:</Text>
|
|
2691
3221
|
<Text dimColor> 1. Sign up at https://app.pioneer.ai</Text>
|
|
2692
3222
|
<Text dimColor> 2. Run: pioneer auth login</Text>
|
|
2693
|
-
<Text dimColor> 3. Start building with: pioneer
|
|
3223
|
+
<Text dimColor> 3. Start building with: pioneer agent</Text>
|
|
2694
3224
|
</Box>
|
|
2695
3225
|
);
|
|
2696
3226
|
};
|
|
2697
3227
|
|
|
2698
3228
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
2699
|
-
// Chat Wrapper Component
|
|
2700
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
2701
|
-
|
|
2702
|
-
interface ChatWrapperProps {
|
|
2703
|
-
flags: Record<string, string>;
|
|
2704
|
-
}
|
|
2705
|
-
|
|
2706
|
-
const ChatWrapper: React.FC<ChatWrapperProps> = ({ flags }) => {
|
|
2707
|
-
const initialMessage = flags.message;
|
|
2708
|
-
return <ChatApp initialMessage={initialMessage} />;
|
|
2709
|
-
};
|
|
2710
|
-
|
|
2711
3229
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
2712
3230
|
// Main Router
|
|
2713
3231
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -2715,13 +3233,75 @@ const ChatWrapper: React.FC<ChatWrapperProps> = ({ flags }) => {
|
|
|
2715
3233
|
interface AppProps {
|
|
2716
3234
|
command: string[];
|
|
2717
3235
|
flags: Record<string, string>;
|
|
3236
|
+
parseErrors: string[];
|
|
2718
3237
|
}
|
|
2719
3238
|
|
|
2720
|
-
const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
3239
|
+
const App: React.FC<AppProps> = ({ command, flags, parseErrors }) => {
|
|
2721
3240
|
// Check if raw mode is supported for interactive prompts
|
|
2722
3241
|
const { isRawModeSupported } = useStdin();
|
|
2723
3242
|
const [showTelemetryPrompt, setShowTelemetryPrompt] = useState(!hasChosenTelemetry());
|
|
2724
3243
|
const [group, action, ...rest] = command;
|
|
3244
|
+
const normalizedAction =
|
|
3245
|
+
action === "model_endpoints"
|
|
3246
|
+
? "endpoints"
|
|
3247
|
+
: action === "model_artifacts"
|
|
3248
|
+
? "artifacts"
|
|
3249
|
+
: action;
|
|
3250
|
+
const hasParseErrors = parseErrors.length > 0;
|
|
3251
|
+
const isModelCreateMissingModel =
|
|
3252
|
+
group === "model" &&
|
|
3253
|
+
normalizedAction === "endpoints" &&
|
|
3254
|
+
rest[0] === "create" &&
|
|
3255
|
+
parseErrors.length === 1 &&
|
|
3256
|
+
parseErrors[0] === "--model";
|
|
3257
|
+
const isModelEndpointsDeployMissingJob =
|
|
3258
|
+
group === "model" &&
|
|
3259
|
+
normalizedAction === "endpoints" &&
|
|
3260
|
+
rest[0] === "deploy" &&
|
|
3261
|
+
!flags["job"];
|
|
3262
|
+
|
|
3263
|
+
if (group === "dataset" || group === "inference" || group === "eval" || group === "benchmark") {
|
|
3264
|
+
return (
|
|
3265
|
+
<ErrorMessage
|
|
3266
|
+
error={`The '${group}' command group is temporarily hidden for this version. Use 'pioneer --help' to see available commands.`}
|
|
3267
|
+
/>
|
|
3268
|
+
);
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
if (hasParseErrors && !isModelCreateMissingModel) {
|
|
3272
|
+
if (isModelEndpointsDeployMissingJob) {
|
|
3273
|
+
const errorMessage = rest[1]
|
|
3274
|
+
? `Training job ID required: model endpoints deploy ${rest[1]} --job <training-job-id>`
|
|
3275
|
+
: "Training job ID required: model endpoints deploy <model-id> --job <training-job-id>";
|
|
3276
|
+
|
|
3277
|
+
return (
|
|
3278
|
+
<ApiCommand
|
|
3279
|
+
action={() =>
|
|
3280
|
+
Promise.resolve<api.ApiResult<{ message: string }>>({
|
|
3281
|
+
ok: false,
|
|
3282
|
+
status: 400,
|
|
3283
|
+
error: errorMessage,
|
|
3284
|
+
data: {
|
|
3285
|
+
message: errorMessage,
|
|
3286
|
+
},
|
|
3287
|
+
})
|
|
3288
|
+
}
|
|
3289
|
+
successMessage="Validation failed"
|
|
3290
|
+
/>
|
|
3291
|
+
);
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
return (
|
|
3295
|
+
<Box flexDirection="column">
|
|
3296
|
+
<ErrorMessage error="One or more flags are missing values. Please provide values for: " />
|
|
3297
|
+
{parseErrors.map((flag) => (
|
|
3298
|
+
<Text dimColor key={flag}>
|
|
3299
|
+
- {flag} {"<value>"}
|
|
3300
|
+
</Text>
|
|
3301
|
+
))}
|
|
3302
|
+
</Box>
|
|
3303
|
+
);
|
|
3304
|
+
}
|
|
2725
3305
|
|
|
2726
3306
|
// In non-interactive mode, skip telemetry prompt and default to disabled
|
|
2727
3307
|
useEffect(() => {
|
|
@@ -2734,9 +3314,11 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
2734
3314
|
// Track command usage (must be before any conditional returns)
|
|
2735
3315
|
useEffect(() => {
|
|
2736
3316
|
if (group && !showTelemetryPrompt) {
|
|
2737
|
-
|
|
3317
|
+
const actionForTracking =
|
|
3318
|
+
group === "model" && normalizedAction !== action ? action || normalizedAction : normalizedAction;
|
|
3319
|
+
trackCommand(group, actionForTracking);
|
|
2738
3320
|
}
|
|
2739
|
-
}, [group, action, showTelemetryPrompt]);
|
|
3321
|
+
}, [group, action, normalizedAction, showTelemetryPrompt]);
|
|
2740
3322
|
|
|
2741
3323
|
// Show telemetry consent prompt on first run (but not for --help or --version, and only if interactive)
|
|
2742
3324
|
if (showTelemetryPrompt && isRawModeSupported && !flags.help && flags.version !== "true" && flags.v !== "true") {
|
|
@@ -2748,14 +3330,6 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
2748
3330
|
return <Help />;
|
|
2749
3331
|
}
|
|
2750
3332
|
|
|
2751
|
-
// Chat command - Interactive agent
|
|
2752
|
-
if (group === "chat") {
|
|
2753
|
-
if (flags.help === "true" || action === "help") {
|
|
2754
|
-
return <Help context="chat" />;
|
|
2755
|
-
}
|
|
2756
|
-
return <ChatWrapper flags={flags} />;
|
|
2757
|
-
}
|
|
2758
|
-
|
|
2759
3333
|
// Auth commands
|
|
2760
3334
|
if (group === "auth") {
|
|
2761
3335
|
if (flags.help === "true" || !action || action === "help") {
|
|
@@ -2895,6 +3469,31 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
2895
3469
|
);
|
|
2896
3470
|
}
|
|
2897
3471
|
|
|
3472
|
+
if (subAction === "dismiss-outlier" && rest[1]) {
|
|
3473
|
+
const dataset = parseDatasetRef(rest[1]);
|
|
3474
|
+
const fingerprint = flags["fingerprint"];
|
|
3475
|
+
if (!dataset) {
|
|
3476
|
+
return <ErrorMessage error={`Invalid dataset format: ${rest[1]}. Use name[:version] format (e.g., my-dataset or my-dataset:v1).`} />;
|
|
3477
|
+
}
|
|
3478
|
+
if (!fingerprint) {
|
|
3479
|
+
return (
|
|
3480
|
+
<ErrorMessage error="--fingerprint is required (from dataset analysis output)" />
|
|
3481
|
+
);
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
return (
|
|
3485
|
+
<ApiCommand
|
|
3486
|
+
action={() =>
|
|
3487
|
+
api.dismissOutlier({
|
|
3488
|
+
dataset,
|
|
3489
|
+
fingerprint,
|
|
3490
|
+
})
|
|
3491
|
+
}
|
|
3492
|
+
successMessage={`Outlier dismissed for dataset ${rest[1]}`}
|
|
3493
|
+
/>
|
|
3494
|
+
);
|
|
3495
|
+
}
|
|
3496
|
+
|
|
2898
3497
|
return <Help context="dataset-edit" />;
|
|
2899
3498
|
}
|
|
2900
3499
|
|
|
@@ -3076,6 +3675,41 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
3076
3675
|
/>
|
|
3077
3676
|
);
|
|
3078
3677
|
}
|
|
3678
|
+
if (action === "analyze-llm") {
|
|
3679
|
+
const datasetStr = rest[0];
|
|
3680
|
+
const taskType = flags["task-type"];
|
|
3681
|
+
const taskDescription = flags["description"] || flags["task-description"];
|
|
3682
|
+
const labels = parseCommaSeparated(flags["labels"]);
|
|
3683
|
+
|
|
3684
|
+
if (!datasetStr || !taskType) {
|
|
3685
|
+
return <Help context="dataset-analyze" />;
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
if (!["ner", "classification", "generative"].includes(taskType)) {
|
|
3689
|
+
return (
|
|
3690
|
+
<ErrorMessage error="--task-type must be one of: ner, classification, generative" />
|
|
3691
|
+
);
|
|
3692
|
+
}
|
|
3693
|
+
|
|
3694
|
+
const dataset = parseDatasetRef(datasetStr);
|
|
3695
|
+
if (!dataset) {
|
|
3696
|
+
return <ErrorMessage error={`Invalid dataset format: ${datasetStr}. Use name:version format.`} />;
|
|
3697
|
+
}
|
|
3698
|
+
|
|
3699
|
+
return (
|
|
3700
|
+
<ApiCommand
|
|
3701
|
+
action={() =>
|
|
3702
|
+
api.analyzeDatasetLLM({
|
|
3703
|
+
task_type: taskType as "ner" | "classification" | "generative",
|
|
3704
|
+
dataset_name: dataset.name,
|
|
3705
|
+
dataset_version: dataset.version,
|
|
3706
|
+
...(taskDescription ? { task_description: taskDescription } : {}),
|
|
3707
|
+
...(labels.length ? { labels } : {}),
|
|
3708
|
+
})
|
|
3709
|
+
}
|
|
3710
|
+
/>
|
|
3711
|
+
);
|
|
3712
|
+
}
|
|
3079
3713
|
if (action === "analyze") {
|
|
3080
3714
|
const datasetStr = rest[0];
|
|
3081
3715
|
const taskType = flags["task-type"];
|
|
@@ -3098,13 +3732,138 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
3098
3732
|
);
|
|
3099
3733
|
}
|
|
3100
3734
|
|
|
3101
|
-
// Generate commands
|
|
3102
|
-
if (action === "generate") {
|
|
3103
|
-
const subAction = rest[0];
|
|
3104
|
-
const numExamples = flags["num"] ? parseInt(flags["num"], 10) : undefined;
|
|
3105
|
-
const domainDescription = flags["domain"];
|
|
3106
|
-
const saveDataset = flags["save"]?.toLowerCase() === "true";
|
|
3107
|
-
const datasetName = flags["name"];
|
|
3735
|
+
// Generate commands
|
|
3736
|
+
if (action === "generate") {
|
|
3737
|
+
const subAction = rest[0];
|
|
3738
|
+
const numExamples = flags["num"] ? parseInt(flags["num"], 10) : undefined;
|
|
3739
|
+
const domainDescription = flags["domain"];
|
|
3740
|
+
const saveDataset = flags["save"]?.toLowerCase() === "true";
|
|
3741
|
+
const datasetName = flags["name"];
|
|
3742
|
+
const quality = flags["quality"] as "light" | "medium" | "heavy" | undefined;
|
|
3743
|
+
const generationProfile = flags["generation-profile"] as
|
|
3744
|
+
| "auto"
|
|
3745
|
+
| "fast"
|
|
3746
|
+
| "balanced"
|
|
3747
|
+
| "quality"
|
|
3748
|
+
| undefined;
|
|
3749
|
+
const includeReasoningTrace =
|
|
3750
|
+
flags["reasoning-trace"] === undefined
|
|
3751
|
+
? undefined
|
|
3752
|
+
: flags["reasoning-trace"].toLowerCase() !== "false";
|
|
3753
|
+
const reasoningEffort = flags["reasoning-effort"] as
|
|
3754
|
+
| "low"
|
|
3755
|
+
| "medium"
|
|
3756
|
+
| "high"
|
|
3757
|
+
| undefined;
|
|
3758
|
+
const validGenerationProfiles = ["auto", "fast", "balanced", "quality"];
|
|
3759
|
+
if (generationProfile && !validGenerationProfiles.includes(generationProfile)) {
|
|
3760
|
+
return <ErrorMessage error="--generation-profile must be one of: auto, fast, balanced, quality" />;
|
|
3761
|
+
}
|
|
3762
|
+
const validReasoningEfforts = ["low", "medium", "high"];
|
|
3763
|
+
if (reasoningEffort && !validReasoningEfforts.includes(reasoningEffort)) {
|
|
3764
|
+
return <ErrorMessage error="--reasoning-effort must be one of: low, medium, high" />;
|
|
3765
|
+
}
|
|
3766
|
+
const multiplicatorArg = flags["multiplicator"];
|
|
3767
|
+
const useMetaFelix = flags["use-meta-felix"]
|
|
3768
|
+
? flags["use-meta-felix"].toLowerCase() !== "false"
|
|
3769
|
+
: undefined;
|
|
3770
|
+
const minCriteria = flags["min-criteria"] ? parseInt(flags["min-criteria"], 10) : undefined;
|
|
3771
|
+
if (flags["min-criteria"] && Number.isNaN(minCriteria)) {
|
|
3772
|
+
return <ErrorMessage error="--min-criteria must be a number" />;
|
|
3773
|
+
}
|
|
3774
|
+
const targetChoices = flags["target-choices"]
|
|
3775
|
+
? parseInt(flags["target-choices"], 10)
|
|
3776
|
+
: undefined;
|
|
3777
|
+
if (flags["target-choices"] && Number.isNaN(targetChoices)) {
|
|
3778
|
+
return <ErrorMessage error="--target-choices must be a number" />;
|
|
3779
|
+
}
|
|
3780
|
+
const projectId = flags["project-id"];
|
|
3781
|
+
const generationType = flags["type"] as "training" | "evaluation" | "split" | undefined;
|
|
3782
|
+
if (generationType && !["training", "evaluation", "split"].includes(generationType)) {
|
|
3783
|
+
return <ErrorMessage error="--type must be one of: training, evaluation, split" />;
|
|
3784
|
+
}
|
|
3785
|
+
const visibility = flags["visibility"] as "private" | "public" | undefined;
|
|
3786
|
+
if (visibility && !["private", "public"].includes(visibility)) {
|
|
3787
|
+
return <ErrorMessage error="--visibility must be private or public" />;
|
|
3788
|
+
}
|
|
3789
|
+
const splitRatioArg = flags["split-ratio"];
|
|
3790
|
+
const splitRatio =
|
|
3791
|
+
splitRatioArg && splitRatioArg.includes(":")
|
|
3792
|
+
? (() => {
|
|
3793
|
+
const [training, evaluation] = splitRatioArg.split(":").map((v) => parseFloat(v));
|
|
3794
|
+
if (Number.isNaN(training) || Number.isNaN(evaluation)) {
|
|
3795
|
+
return undefined;
|
|
3796
|
+
}
|
|
3797
|
+
return { training, evaluation };
|
|
3798
|
+
})()
|
|
3799
|
+
: splitRatioArg
|
|
3800
|
+
? (() => {
|
|
3801
|
+
try {
|
|
3802
|
+
const parsed = JSON.parse(splitRatioArg);
|
|
3803
|
+
return parsed;
|
|
3804
|
+
} catch {
|
|
3805
|
+
return undefined;
|
|
3806
|
+
}
|
|
3807
|
+
})()
|
|
3808
|
+
: undefined;
|
|
3809
|
+
const negativeRatio = flags["negative-ratio"]
|
|
3810
|
+
? parseFloat(flags["negative-ratio"])
|
|
3811
|
+
: undefined;
|
|
3812
|
+
if (flags["negative-ratio"] && Number.isNaN(negativeRatio)) {
|
|
3813
|
+
return <ErrorMessage error="--negative-ratio must be a number" />;
|
|
3814
|
+
}
|
|
3815
|
+
const classifiedExamplesArg = flags["classified-examples"];
|
|
3816
|
+
const qualityArg = flags["quality"];
|
|
3817
|
+
if (qualityArg && !["light", "medium", "heavy"].includes(qualityArg)) {
|
|
3818
|
+
return <ErrorMessage error="--quality must be light, medium, or heavy" />;
|
|
3819
|
+
}
|
|
3820
|
+
if (splitRatioArg && splitRatio === undefined) {
|
|
3821
|
+
return <ErrorMessage error="--split-ratio must be training:evaluation or a JSON object" />;
|
|
3822
|
+
}
|
|
3823
|
+
let parsedMultiplicator: Record<string, unknown> | undefined;
|
|
3824
|
+
if (multiplicatorArg) {
|
|
3825
|
+
try {
|
|
3826
|
+
const parsed = JSON.parse(multiplicatorArg);
|
|
3827
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3828
|
+
return <ErrorMessage error="--multiplicator must be a valid JSON object" />;
|
|
3829
|
+
}
|
|
3830
|
+
parsedMultiplicator = parsed;
|
|
3831
|
+
} catch {
|
|
3832
|
+
return <ErrorMessage error="--multiplicator must be valid JSON" />;
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
let classifiedExamples: Record<string, unknown>[] | undefined;
|
|
3836
|
+
if (classifiedExamplesArg) {
|
|
3837
|
+
try {
|
|
3838
|
+
const parsed = JSON.parse(classifiedExamplesArg);
|
|
3839
|
+
if (!Array.isArray(parsed)) {
|
|
3840
|
+
return <ErrorMessage error="--classified-examples must be a JSON array" />;
|
|
3841
|
+
}
|
|
3842
|
+
classifiedExamples = parsed as Record<string, unknown>[];
|
|
3843
|
+
} catch {
|
|
3844
|
+
return <ErrorMessage error="--classified-examples must be valid JSON" />;
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
if (multiplicatorArg && parsedMultiplicator === undefined) {
|
|
3848
|
+
return <ErrorMessage error="--multiplicator must be a valid JSON object" />;
|
|
3849
|
+
}
|
|
3850
|
+
|
|
3851
|
+
const commonGenerationOptions = {
|
|
3852
|
+
quality,
|
|
3853
|
+
generation_profile: generationProfile,
|
|
3854
|
+
...(includeReasoningTrace !== undefined ? { include_reasoning_trace: includeReasoningTrace } : {}),
|
|
3855
|
+
...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
|
|
3856
|
+
...(multiplicatorArg ? { multiplicator: parsedMultiplicator } : {}),
|
|
3857
|
+
...(useMetaFelix !== undefined ? { use_meta_felix: useMetaFelix } : {}),
|
|
3858
|
+
...(minCriteria !== undefined ? { min_criteria: minCriteria } : {}),
|
|
3859
|
+
...(targetChoices !== undefined ? { target_choices: targetChoices } : {}),
|
|
3860
|
+
...(projectId ? { project_id: projectId } : {}),
|
|
3861
|
+
...(generationType ? { type: generationType } : {}),
|
|
3862
|
+
...(visibility ? { visibility } : {}),
|
|
3863
|
+
...(splitRatio ? { split_ratio: splitRatio } : {}),
|
|
3864
|
+
...(negativeRatio !== undefined ? { negative_ratio: negativeRatio } : {}),
|
|
3865
|
+
...(classifiedExamplesArg ? { classified_examples: classifiedExamples } : {}),
|
|
3866
|
+
};
|
|
3108
3867
|
|
|
3109
3868
|
if (subAction === "ner") {
|
|
3110
3869
|
const labels = flags["labels"]?.split(",");
|
|
@@ -3120,6 +3879,7 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
3120
3879
|
domain_description: domainDescription,
|
|
3121
3880
|
save_dataset: saveDataset,
|
|
3122
3881
|
dataset_name: datasetName,
|
|
3882
|
+
...commonGenerationOptions,
|
|
3123
3883
|
})
|
|
3124
3884
|
}
|
|
3125
3885
|
datasetName={datasetName || "ner-dataset"}
|
|
@@ -3145,6 +3905,7 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
3145
3905
|
multi_label: multiLabel,
|
|
3146
3906
|
save_dataset: saveDataset,
|
|
3147
3907
|
dataset_name: datasetName,
|
|
3908
|
+
...commonGenerationOptions,
|
|
3148
3909
|
})
|
|
3149
3910
|
}
|
|
3150
3911
|
datasetName={datasetName || "classification-dataset"}
|
|
@@ -3175,6 +3936,7 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
3175
3936
|
num_examples: numExamples,
|
|
3176
3937
|
save_dataset: saveDataset,
|
|
3177
3938
|
dataset_name: datasetName,
|
|
3939
|
+
...commonGenerationOptions,
|
|
3178
3940
|
})
|
|
3179
3941
|
}
|
|
3180
3942
|
datasetName={datasetName || "custom-dataset"}
|
|
@@ -3198,6 +3960,7 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
3198
3960
|
num_examples: numExamples,
|
|
3199
3961
|
save_dataset: saveDataset,
|
|
3200
3962
|
dataset_name: datasetName,
|
|
3963
|
+
...commonGenerationOptions,
|
|
3201
3964
|
})
|
|
3202
3965
|
}
|
|
3203
3966
|
datasetName={datasetName || "decoder-dataset"}
|
|
@@ -3213,6 +3976,44 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
3213
3976
|
// Infer labels commands
|
|
3214
3977
|
if (action === "infer") {
|
|
3215
3978
|
const subAction = rest[0];
|
|
3979
|
+
if (subAction === "improve-prompt") {
|
|
3980
|
+
const prompt = flags["prompt"] || flags["domain"];
|
|
3981
|
+
if (!prompt) {
|
|
3982
|
+
return <ErrorMessage error="--prompt is required for infer improve-prompt" />;
|
|
3983
|
+
}
|
|
3984
|
+
const dataType = flags["data-type"];
|
|
3985
|
+
return (
|
|
3986
|
+
<ApiCommand
|
|
3987
|
+
action={() =>
|
|
3988
|
+
api.improvePrompt({
|
|
3989
|
+
prompt,
|
|
3990
|
+
...(dataType ? { data_type: dataType } : {}),
|
|
3991
|
+
})
|
|
3992
|
+
}
|
|
3993
|
+
/>
|
|
3994
|
+
);
|
|
3995
|
+
}
|
|
3996
|
+
|
|
3997
|
+
if (subAction === "infer-advanced" || subAction === "advanced") {
|
|
3998
|
+
const prompt = flags["prompt"] || flags["domain"];
|
|
3999
|
+
const dataType = flags["data-type"];
|
|
4000
|
+
const labels = parseCommaSeparated(flags["labels"]);
|
|
4001
|
+
if (!prompt) {
|
|
4002
|
+
return <ErrorMessage error="--prompt is required for dataset infer infer-advanced" />;
|
|
4003
|
+
}
|
|
4004
|
+
return (
|
|
4005
|
+
<ApiCommand
|
|
4006
|
+
action={() =>
|
|
4007
|
+
api.inferAdvanced({
|
|
4008
|
+
prompt,
|
|
4009
|
+
...(dataType ? { data_type: dataType } : {}),
|
|
4010
|
+
...(labels.length > 0 ? { labels } : {}),
|
|
4011
|
+
})
|
|
4012
|
+
}
|
|
4013
|
+
/>
|
|
4014
|
+
);
|
|
4015
|
+
}
|
|
4016
|
+
|
|
3216
4017
|
const domainDescription = flags["domain"];
|
|
3217
4018
|
|
|
3218
4019
|
if (!domainDescription) {
|
|
@@ -3246,137 +4047,129 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
3246
4047
|
return <Help context="dataset" />;
|
|
3247
4048
|
}
|
|
3248
4049
|
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
return <ApiCommand action={api.listProjects} />;
|
|
3260
|
-
}
|
|
3261
|
-
|
|
3262
|
-
if (action === "get") {
|
|
3263
|
-
const projectId = rest[0];
|
|
3264
|
-
if (!projectId) {
|
|
3265
|
-
return <ErrorMessage error="Project ID required: project get <project-id>" />;
|
|
3266
|
-
}
|
|
3267
|
-
return <ApiCommand action={() => api.getProject(projectId)} />;
|
|
3268
|
-
}
|
|
3269
|
-
|
|
3270
|
-
if (action === "create") {
|
|
3271
|
-
const name = flags["name"];
|
|
3272
|
-
const icon = flags["icon"];
|
|
3273
|
-
const repo = flags["repo"];
|
|
3274
|
-
const description = flags["description"];
|
|
3275
|
-
const modelId = flags["model-id"];
|
|
3276
|
-
const exampleStr = flags["example"];
|
|
3277
|
-
|
|
3278
|
-
if (!name) {
|
|
3279
|
-
return <ErrorMessage error="--name is required for project creation" />;
|
|
4050
|
+
if (action === "label-existing") {
|
|
4051
|
+
const subAction = rest[0];
|
|
4052
|
+
const labels = parseCommaSeparated(flags["labels"]);
|
|
4053
|
+
const inputsArg = flags["inputs"];
|
|
4054
|
+
const datasetName = flags["name"];
|
|
4055
|
+
const saveDataset = flags["save"]?.toLowerCase() === "true";
|
|
4056
|
+
const domainDescription = flags["domain"];
|
|
4057
|
+
const projectId = flags["project-id"];
|
|
4058
|
+
if (!inputsArg) {
|
|
4059
|
+
return <ErrorMessage error="--inputs is required for label-existing commands" />;
|
|
3280
4060
|
}
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
if (exampleStr) {
|
|
3284
|
-
try {
|
|
3285
|
-
const parsed = JSON.parse(exampleStr) as unknown;
|
|
3286
|
-
if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
3287
|
-
return <ErrorMessage error="--example must be a JSON object" />;
|
|
3288
|
-
}
|
|
3289
|
-
example = parsed as Record<string, unknown>;
|
|
3290
|
-
} catch {
|
|
3291
|
-
return <ErrorMessage error="--example must be valid JSON" />;
|
|
3292
|
-
}
|
|
4061
|
+
if (saveDataset && !datasetName) {
|
|
4062
|
+
return <ErrorMessage error="--name is required when --save=true for label-existing" />;
|
|
3293
4063
|
}
|
|
3294
4064
|
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
...(icon ? { icon } : {}),
|
|
3301
|
-
...(repo ? { repo } : {}),
|
|
3302
|
-
...(description ? { description } : {}),
|
|
3303
|
-
...(modelId ? { selected_model_id: modelId } : {}),
|
|
3304
|
-
...(example ? { example } : {}),
|
|
3305
|
-
})
|
|
3306
|
-
}
|
|
3307
|
-
successMessage="Project created"
|
|
3308
|
-
/>
|
|
3309
|
-
);
|
|
3310
|
-
}
|
|
3311
|
-
|
|
3312
|
-
if (action === "update") {
|
|
3313
|
-
const projectId = rest[0];
|
|
3314
|
-
if (!projectId) {
|
|
3315
|
-
return <ErrorMessage error="Project ID required: project update <project-id>" />;
|
|
4065
|
+
let inputs: unknown;
|
|
4066
|
+
try {
|
|
4067
|
+
inputs = JSON.parse(inputsArg);
|
|
4068
|
+
} catch {
|
|
4069
|
+
return <ErrorMessage error="--inputs must be valid JSON" />;
|
|
3316
4070
|
}
|
|
3317
4071
|
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
4072
|
+
if (subAction === "ner" || subAction === "classification") {
|
|
4073
|
+
if (!labels || labels.length === 0) {
|
|
4074
|
+
return <ErrorMessage error="--labels is required for label-existing ner|classification" />;
|
|
4075
|
+
}
|
|
4076
|
+
if (
|
|
4077
|
+
!Array.isArray(inputs) ||
|
|
4078
|
+
inputs.length === 0 ||
|
|
4079
|
+
!inputs.every((item) => typeof item === "string")
|
|
4080
|
+
) {
|
|
4081
|
+
return <ErrorMessage error="--inputs must be a JSON array of strings for ner/classification" />;
|
|
4082
|
+
}
|
|
4083
|
+
const common = { labels, inputs: inputs as string[], dataset_name: datasetName };
|
|
4084
|
+
if (subAction === "ner") {
|
|
4085
|
+
return (
|
|
4086
|
+
<ApiCommand
|
|
4087
|
+
action={() =>
|
|
4088
|
+
api.labelExistingNER({
|
|
4089
|
+
...common,
|
|
4090
|
+
domain_description: domainDescription,
|
|
4091
|
+
save_dataset: saveDataset,
|
|
4092
|
+
project_id: projectId,
|
|
4093
|
+
})
|
|
4094
|
+
}
|
|
4095
|
+
/>
|
|
4096
|
+
);
|
|
4097
|
+
}
|
|
3325
4098
|
return (
|
|
3326
|
-
<
|
|
4099
|
+
<ApiCommand
|
|
4100
|
+
action={() =>
|
|
4101
|
+
api.labelExistingClassification({
|
|
4102
|
+
...common,
|
|
4103
|
+
domain_description: domainDescription,
|
|
4104
|
+
save_dataset: saveDataset,
|
|
4105
|
+
project_id: projectId,
|
|
4106
|
+
})
|
|
4107
|
+
}
|
|
4108
|
+
/>
|
|
3327
4109
|
);
|
|
3328
4110
|
}
|
|
3329
4111
|
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
);
|
|
3357
|
-
}
|
|
3358
|
-
|
|
3359
|
-
if (action === "dataset-count" || action === "count") {
|
|
3360
|
-
const projectId = rest[0];
|
|
3361
|
-
if (!projectId) {
|
|
3362
|
-
return (
|
|
3363
|
-
<ErrorMessage error="Project ID required: project dataset-count <project-id>" />
|
|
4112
|
+
if (subAction === "fields") {
|
|
4113
|
+
const inputFieldsArg = flags["input-fields"];
|
|
4114
|
+
const outputFieldsArg = flags["output-fields"];
|
|
4115
|
+
if (!inputFieldsArg || !outputFieldsArg) {
|
|
4116
|
+
return <ErrorMessage error="--input-fields and --output-fields are required for fields labeling" />;
|
|
4117
|
+
}
|
|
4118
|
+
let inputFields: unknown;
|
|
4119
|
+
let outputFields: unknown;
|
|
4120
|
+
try {
|
|
4121
|
+
inputFields = JSON.parse(inputFieldsArg);
|
|
4122
|
+
outputFields = JSON.parse(outputFieldsArg);
|
|
4123
|
+
} catch {
|
|
4124
|
+
return <ErrorMessage error="--input-fields and --output-fields must be valid JSON" />;
|
|
4125
|
+
}
|
|
4126
|
+
if (
|
|
4127
|
+
!Array.isArray(inputFields) ||
|
|
4128
|
+
!Array.isArray(outputFields) ||
|
|
4129
|
+
inputFields.length === 0 ||
|
|
4130
|
+
outputFields.length === 0
|
|
4131
|
+
) {
|
|
4132
|
+
return (
|
|
4133
|
+
<ErrorMessage error="--input-fields and --output-fields must be non-empty arrays" />
|
|
4134
|
+
);
|
|
4135
|
+
}
|
|
4136
|
+
const validInputFields = inputFields.every(
|
|
4137
|
+
(field) => field && typeof field === "object" && !Array.isArray(field)
|
|
3364
4138
|
);
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
4139
|
+
const validOutputFields = outputFields.every(
|
|
4140
|
+
(field) => field && typeof field === "object" && !Array.isArray(field)
|
|
4141
|
+
);
|
|
4142
|
+
if (!validInputFields || !validOutputFields) {
|
|
4143
|
+
return <ErrorMessage error="--input-fields and --output-fields must be arrays of objects" />;
|
|
4144
|
+
}
|
|
4145
|
+
if (
|
|
4146
|
+
!Array.isArray(inputs) ||
|
|
4147
|
+
inputs.length === 0 ||
|
|
4148
|
+
!inputs.every((item) => item && typeof item === "object" && !Array.isArray(item))
|
|
4149
|
+
) {
|
|
4150
|
+
return <ErrorMessage error="--inputs must be a JSON array of objects for fields" />;
|
|
4151
|
+
}
|
|
3372
4152
|
return (
|
|
3373
|
-
<
|
|
4153
|
+
<ApiCommand
|
|
4154
|
+
action={() =>
|
|
4155
|
+
api.labelExistingFields({
|
|
4156
|
+
input_fields: inputFields as api.RecordField[],
|
|
4157
|
+
output_fields: outputFields as api.RecordField[],
|
|
4158
|
+
inputs: inputs as Record<string, unknown>[],
|
|
4159
|
+
dataset_name: datasetName,
|
|
4160
|
+
save_dataset: saveDataset,
|
|
4161
|
+
domain_description: domainDescription,
|
|
4162
|
+
project_id: projectId,
|
|
4163
|
+
})
|
|
4164
|
+
}
|
|
4165
|
+
/>
|
|
3374
4166
|
);
|
|
3375
4167
|
}
|
|
3376
|
-
|
|
4168
|
+
|
|
4169
|
+
return <Help context="dataset" />;
|
|
3377
4170
|
}
|
|
3378
4171
|
|
|
3379
|
-
return <Help context="
|
|
4172
|
+
return <Help context="dataset" />;
|
|
3380
4173
|
}
|
|
3381
4174
|
|
|
3382
4175
|
// Job commands
|
|
@@ -3433,181 +4226,558 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
3433
4226
|
return <Help context="job" />;
|
|
3434
4227
|
}
|
|
3435
4228
|
|
|
3436
|
-
//
|
|
3437
|
-
if (group === "
|
|
4229
|
+
// Inference commands
|
|
4230
|
+
if (group === "inference") {
|
|
3438
4231
|
if (flags.help === "true" || !action || action === "help") {
|
|
3439
|
-
return <Help context="
|
|
4232
|
+
return <Help context="inference" />;
|
|
3440
4233
|
}
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
4234
|
+
|
|
4235
|
+
if (action === "base-models" || action === "models" || action === "list") {
|
|
4236
|
+
return <ApiCommand action={api.listBaseModels} />;
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4239
|
+
if (action === "encoder") {
|
|
4240
|
+
const rawModelId = rest[0];
|
|
4241
|
+
if (!rawModelId) {
|
|
4242
|
+
return (
|
|
4243
|
+
<ErrorMessage error="Model ID required. Usage: inference encoder <model-id> --text <text> --labels <labels>" />
|
|
4244
|
+
);
|
|
3445
4245
|
}
|
|
3446
|
-
|
|
3447
|
-
|
|
4246
|
+
const modelId = normalizeModelId(rawModelId);
|
|
4247
|
+
const text = flags["text"];
|
|
4248
|
+
if (!text) {
|
|
4249
|
+
return <ErrorMessage error="--text is required for encoder inference" />;
|
|
3448
4250
|
}
|
|
3449
|
-
|
|
3450
|
-
|
|
4251
|
+
|
|
4252
|
+
const task = (flags["task"] || "extract_entities") as
|
|
4253
|
+
| "extract_entities"
|
|
4254
|
+
| "classify_text"
|
|
4255
|
+
| "extract_json"
|
|
4256
|
+
| "schema";
|
|
4257
|
+
if (!["extract_entities", "classify_text", "extract_json", "schema"].includes(task)) {
|
|
4258
|
+
return (
|
|
4259
|
+
<ErrorMessage error="--task must be one of: extract_entities, classify_text, extract_json, schema" />
|
|
4260
|
+
);
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
const labels = parseCommaSeparated(flags["labels"]);
|
|
4264
|
+
const schemaStr = flags["schema"];
|
|
4265
|
+
let schema: string[] | Record<string, unknown> | null = null;
|
|
4266
|
+
|
|
4267
|
+
if (schemaStr) {
|
|
4268
|
+
try {
|
|
4269
|
+
const parsed = JSON.parse(schemaStr) as unknown;
|
|
4270
|
+
if (Array.isArray(parsed)) {
|
|
4271
|
+
if (!parsed.every((item) => typeof item === "string")) {
|
|
4272
|
+
return (
|
|
4273
|
+
<ErrorMessage error="--schema JSON array must contain only strings" />
|
|
4274
|
+
);
|
|
4275
|
+
}
|
|
4276
|
+
schema = parsed;
|
|
4277
|
+
} else if (parsed && typeof parsed === "object") {
|
|
4278
|
+
schema = parsed as Record<string, unknown>;
|
|
4279
|
+
} else {
|
|
4280
|
+
return (
|
|
4281
|
+
<ErrorMessage error="--schema must be a JSON object or array of strings" />
|
|
4282
|
+
);
|
|
4283
|
+
}
|
|
4284
|
+
} catch {
|
|
4285
|
+
return <ErrorMessage error="--schema must be valid JSON" />;
|
|
4286
|
+
}
|
|
4287
|
+
} else if (labels.length > 0) {
|
|
4288
|
+
schema = labels;
|
|
4289
|
+
}
|
|
4290
|
+
|
|
4291
|
+
if (!schema) {
|
|
4292
|
+
return <ErrorMessage error="Provide --labels or --schema for encoder inference" />;
|
|
4293
|
+
}
|
|
4294
|
+
|
|
4295
|
+
const threshold = flags["threshold"] ? parseFloat(flags["threshold"]) : 0.5;
|
|
4296
|
+
if (Number.isNaN(threshold) || threshold < 0 || threshold > 1) {
|
|
4297
|
+
return <ErrorMessage error="--threshold must be a number between 0 and 1" />;
|
|
4298
|
+
}
|
|
4299
|
+
|
|
4300
|
+
const includeConfidence =
|
|
4301
|
+
flags["include-confidence"] === undefined ||
|
|
4302
|
+
flags["include-confidence"].toLowerCase() !== "false";
|
|
4303
|
+
const includeSpans = flags["include-spans"]?.toLowerCase() === "true";
|
|
4304
|
+
const formatResults =
|
|
4305
|
+
flags["format-results"] === undefined ||
|
|
4306
|
+
flags["format-results"].toLowerCase() !== "false";
|
|
4307
|
+
const projectId = flags["project-id"];
|
|
4308
|
+
|
|
4309
|
+
return (
|
|
4310
|
+
<ApiCommand
|
|
4311
|
+
action={() =>
|
|
4312
|
+
api.runInference({
|
|
4313
|
+
model_id: modelId,
|
|
4314
|
+
task,
|
|
4315
|
+
text,
|
|
4316
|
+
schema,
|
|
4317
|
+
threshold,
|
|
4318
|
+
include_confidence: includeConfidence,
|
|
4319
|
+
include_spans: includeSpans,
|
|
4320
|
+
format_results: formatResults,
|
|
4321
|
+
...(projectId ? { project_id: projectId } : {}),
|
|
4322
|
+
})
|
|
4323
|
+
}
|
|
4324
|
+
/>
|
|
4325
|
+
);
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
if (action === "decoder") {
|
|
4329
|
+
const modelId = rest[0];
|
|
4330
|
+
if (!modelId) {
|
|
4331
|
+
return (
|
|
4332
|
+
<ErrorMessage error="Model ID required. Usage: inference decoder <model-id> --prompt <text>" />
|
|
4333
|
+
);
|
|
4334
|
+
}
|
|
4335
|
+
const normalizedModelId = normalizeModelId(modelId);
|
|
4336
|
+
const prompt = flags["prompt"];
|
|
4337
|
+
if (!prompt) {
|
|
4338
|
+
return <ErrorMessage error="--prompt is required for decoder inference" />;
|
|
4339
|
+
}
|
|
4340
|
+
const systemMsg = flags["system"];
|
|
4341
|
+
const maxTokens = flags["max-tokens"] ? parseInt(flags["max-tokens"], 10) : 256;
|
|
4342
|
+
if (Number.isNaN(maxTokens) || maxTokens < 1) {
|
|
4343
|
+
return <ErrorMessage error="--max-tokens must be a positive integer" />;
|
|
4344
|
+
}
|
|
4345
|
+
const temperature = flags["temperature"] ? parseFloat(flags["temperature"]) : 0.7;
|
|
4346
|
+
if (Number.isNaN(temperature) || temperature < 0 || temperature > 2) {
|
|
4347
|
+
return <ErrorMessage error="--temperature must be a number between 0 and 2" />;
|
|
4348
|
+
}
|
|
4349
|
+
const topP = flags["top-p"] ? parseFloat(flags["top-p"]) : undefined;
|
|
4350
|
+
if (topP !== undefined && (Number.isNaN(topP) || topP < 0 || topP > 1)) {
|
|
4351
|
+
return <ErrorMessage error="--top-p must be a number between 0 and 1" />;
|
|
4352
|
+
}
|
|
4353
|
+
const includeReasoningTrace =
|
|
4354
|
+
flags["reasoning-trace"] !== undefined &&
|
|
4355
|
+
flags["reasoning-trace"].toLowerCase() !== "false";
|
|
4356
|
+
const projectId = flags["project-id"];
|
|
4357
|
+
|
|
4358
|
+
return (
|
|
4359
|
+
<ModelGenerateCommand
|
|
4360
|
+
modelId={normalizedModelId}
|
|
4361
|
+
prompt={prompt}
|
|
4362
|
+
systemMsg={systemMsg}
|
|
4363
|
+
maxTokens={maxTokens}
|
|
4364
|
+
temperature={temperature}
|
|
4365
|
+
topP={topP}
|
|
4366
|
+
includeReasoningTrace={includeReasoningTrace}
|
|
4367
|
+
projectId={projectId}
|
|
4368
|
+
/>
|
|
4369
|
+
);
|
|
4370
|
+
}
|
|
4371
|
+
|
|
4372
|
+
if (action === "completions") {
|
|
4373
|
+
const rawModelId = rest[0];
|
|
4374
|
+
if (!rawModelId) {
|
|
4375
|
+
return (
|
|
4376
|
+
<ErrorMessage error="Model ID required. Usage: inference completions <model-id> --prompt <text>" />
|
|
4377
|
+
);
|
|
4378
|
+
}
|
|
4379
|
+
const modelId = normalizeModelId(rawModelId);
|
|
4380
|
+
const prompt = flags["prompt"];
|
|
4381
|
+
if (!prompt) {
|
|
4382
|
+
return <ErrorMessage error="--prompt is required for text completions" />;
|
|
4383
|
+
}
|
|
4384
|
+
|
|
4385
|
+
const systemMsg = flags["system"];
|
|
4386
|
+
const maxTokens = flags["max-tokens"] ? parseInt(flags["max-tokens"], 10) : 256;
|
|
4387
|
+
if (Number.isNaN(maxTokens) || maxTokens < 1) {
|
|
4388
|
+
return <ErrorMessage error="--max-tokens must be a positive integer" />;
|
|
3451
4389
|
}
|
|
3452
|
-
|
|
3453
|
-
|
|
4390
|
+
const temperature = flags["temperature"] ? parseFloat(flags["temperature"]) : 0.7;
|
|
4391
|
+
if (Number.isNaN(temperature) || temperature < 0 || temperature > 2) {
|
|
4392
|
+
return <ErrorMessage error="--temperature must be a number between 0 and 2" />;
|
|
4393
|
+
}
|
|
4394
|
+
const topP = flags["top-p"] ? parseFloat(flags["top-p"]) : undefined;
|
|
4395
|
+
if (topP !== undefined && (Number.isNaN(topP) || topP < 0 || topP > 1)) {
|
|
4396
|
+
return <ErrorMessage error="--top-p must be a number between 0 and 1" />;
|
|
4397
|
+
}
|
|
4398
|
+
const stopValues = parseCommaSeparated(flags["stop"]);
|
|
4399
|
+
const stop =
|
|
4400
|
+
stopValues.length === 0 ? undefined : stopValues.length === 1 ? stopValues[0] : stopValues;
|
|
4401
|
+
const echo = flags["echo"]?.toLowerCase() === "true";
|
|
4402
|
+
const provider = flags["provider"];
|
|
4403
|
+
|
|
4404
|
+
const extraBody: Record<string, unknown> = {};
|
|
4405
|
+
if (topP !== undefined) extraBody.top_p = topP;
|
|
4406
|
+
if (provider) extraBody.provider = provider;
|
|
4407
|
+
|
|
4408
|
+
return (
|
|
4409
|
+
<ApiCommand
|
|
4410
|
+
action={() =>
|
|
4411
|
+
api.runTextCompletion({
|
|
4412
|
+
model: modelId,
|
|
4413
|
+
prompt: buildTextCompletionPrompt(prompt, systemMsg),
|
|
4414
|
+
max_tokens: maxTokens,
|
|
4415
|
+
temperature,
|
|
4416
|
+
...(stop !== undefined ? { stop } : {}),
|
|
4417
|
+
...(echo ? { echo: true } : {}),
|
|
4418
|
+
...(Object.keys(extraBody).length > 0 ? { extra_body: extraBody } : {}),
|
|
4419
|
+
})
|
|
4420
|
+
}
|
|
4421
|
+
/>
|
|
4422
|
+
);
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
return <Help context="inference" />;
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
// Model commands
|
|
4429
|
+
if (group === "model") {
|
|
4430
|
+
if (flags.help === "true" || !action || action === "help") {
|
|
4431
|
+
return <Help context="model" />;
|
|
3454
4432
|
}
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
4433
|
+
|
|
4434
|
+
if (normalizedAction === "endpoints") {
|
|
4435
|
+
const endpointAction = rest[0];
|
|
4436
|
+
const endpointArgs = rest.slice(1);
|
|
4437
|
+
|
|
4438
|
+
if (flags.help === "true" || !endpointAction || endpointAction === "help") {
|
|
4439
|
+
return <Help context="model-endpoints" />;
|
|
3458
4440
|
}
|
|
3459
|
-
|
|
3460
|
-
if (
|
|
4441
|
+
|
|
4442
|
+
if (endpointAction === "create") {
|
|
4443
|
+
const name = flags["name"];
|
|
4444
|
+
const icon = flags["icon"];
|
|
4445
|
+
const repo = flags["repo"];
|
|
4446
|
+
const description = flags["description"];
|
|
4447
|
+
const modelId = flags["model"] || "";
|
|
4448
|
+
const exampleStr = flags["example"];
|
|
4449
|
+
|
|
4450
|
+
if (flags["model-id"]) {
|
|
4451
|
+
return (
|
|
4452
|
+
<ErrorMessage error="Use --model to specify the base model reference. --model-id is deprecated." />
|
|
4453
|
+
);
|
|
4454
|
+
}
|
|
4455
|
+
|
|
4456
|
+
if (flags["base-model"] || flags["active-model-id"]) {
|
|
4457
|
+
return (
|
|
4458
|
+
<ErrorMessage error="Use --model to specify the model reference. --base-model and --active-model-id are no longer supported." />
|
|
4459
|
+
);
|
|
4460
|
+
}
|
|
4461
|
+
|
|
4462
|
+
if (!modelId) {
|
|
4463
|
+
const parsedExample = parseProjectExample(exampleStr);
|
|
4464
|
+
if (parsedExample.error) {
|
|
4465
|
+
return <ErrorMessage error={parsedExample.error} />;
|
|
4466
|
+
}
|
|
4467
|
+
return (
|
|
4468
|
+
<ModelCreateInteractive
|
|
4469
|
+
name={name}
|
|
4470
|
+
icon={icon}
|
|
4471
|
+
repo={repo}
|
|
4472
|
+
description={description}
|
|
4473
|
+
example={parsedExample.value}
|
|
4474
|
+
/>
|
|
4475
|
+
);
|
|
4476
|
+
}
|
|
4477
|
+
|
|
4478
|
+
const parsedExample = parseProjectExample(exampleStr);
|
|
4479
|
+
if (parsedExample.error) {
|
|
4480
|
+
return <ErrorMessage error={parsedExample.error} />;
|
|
4481
|
+
}
|
|
4482
|
+
|
|
3461
4483
|
return (
|
|
3462
|
-
<
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
4484
|
+
<ApiCommand
|
|
4485
|
+
action={() =>
|
|
4486
|
+
api.createProject({
|
|
4487
|
+
name: name ?? modelId,
|
|
4488
|
+
...(icon ? { icon } : {}),
|
|
4489
|
+
...(repo ? { repo } : {}),
|
|
4490
|
+
...(description ? { description } : {}),
|
|
4491
|
+
...(modelId ? { active_model_id: modelId } : {}),
|
|
4492
|
+
...(modelId ? { selected_model_id: modelId } : {}),
|
|
4493
|
+
...(parsedExample.value ? { example: parsedExample.value } : {}),
|
|
4494
|
+
})
|
|
4495
|
+
}
|
|
4496
|
+
successMessage="Model entry created"
|
|
4497
|
+
/>
|
|
3467
4498
|
);
|
|
3468
4499
|
}
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
)
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
4500
|
+
|
|
4501
|
+
if (endpointAction === "list") {
|
|
4502
|
+
return <ModelListCommand filter="registered" />;
|
|
4503
|
+
}
|
|
4504
|
+
|
|
4505
|
+
if (endpointAction === "get") {
|
|
4506
|
+
const modelId = endpointArgs[0];
|
|
4507
|
+
if (!modelId) {
|
|
4508
|
+
return <ErrorMessage error="Model ID required: model endpoints get <model-id>" />;
|
|
4509
|
+
}
|
|
4510
|
+
return <ApiCommand action={() => api.getProject(modelId)} />;
|
|
3479
4511
|
}
|
|
3480
|
-
|
|
3481
|
-
if (
|
|
4512
|
+
|
|
4513
|
+
if (endpointAction === "update") {
|
|
4514
|
+
const modelId = endpointArgs[0];
|
|
4515
|
+
if (!modelId) {
|
|
4516
|
+
return <ErrorMessage error="Model ID required: model endpoints update <model-id>" />;
|
|
4517
|
+
}
|
|
4518
|
+
|
|
4519
|
+
const name = flags["name"];
|
|
4520
|
+
const icon = flags["icon"];
|
|
4521
|
+
const repo = flags["repo"];
|
|
4522
|
+
const description = flags["description"];
|
|
4523
|
+
const selectedModelId = flags["model-id"];
|
|
4524
|
+
|
|
4525
|
+
if (!name && !icon && !repo && !description && !selectedModelId) {
|
|
4526
|
+
return (
|
|
4527
|
+
<ErrorMessage error="Provide at least one field to update: --name, --icon, --repo, --description, or --model-id" />
|
|
4528
|
+
);
|
|
4529
|
+
}
|
|
4530
|
+
|
|
3482
4531
|
return (
|
|
3483
|
-
<
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
4532
|
+
<ApiCommand
|
|
4533
|
+
action={() =>
|
|
4534
|
+
api.updateProject(modelId, {
|
|
4535
|
+
...(name ? { name } : {}),
|
|
4536
|
+
...(icon ? { icon } : {}),
|
|
4537
|
+
...(repo ? { repo } : {}),
|
|
4538
|
+
...(description ? { description } : {}),
|
|
4539
|
+
...(selectedModelId ? { selected_model_id: selectedModelId } : {}),
|
|
4540
|
+
})
|
|
4541
|
+
}
|
|
4542
|
+
successMessage="Model updated"
|
|
4543
|
+
/>
|
|
3488
4544
|
);
|
|
3489
4545
|
}
|
|
3490
|
-
return <ApiCommand action={() => api.downloadModel(jobId)} />;
|
|
3491
|
-
}
|
|
3492
|
-
// Model upload command
|
|
3493
|
-
if (action === "upload") {
|
|
3494
|
-
const destination = flags["to"];
|
|
3495
4546
|
|
|
3496
|
-
|
|
3497
|
-
|
|
4547
|
+
if (endpointAction === "delete") {
|
|
4548
|
+
const modelId = endpointArgs[0];
|
|
4549
|
+
if (!modelId) {
|
|
4550
|
+
return <ErrorMessage error="Model ID required: model endpoints delete <model-id>" />;
|
|
4551
|
+
}
|
|
3498
4552
|
return (
|
|
3499
|
-
<
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
<Text> model upload {"<job-id>"} --to hf --repo {"<repo>"} [--hf-token {"<token>"}] [--private]</Text>
|
|
3504
|
-
<Text> </Text>
|
|
3505
|
-
<Text dimColor> Supported destinations: hf (more coming soon)</Text>
|
|
3506
|
-
</Box>
|
|
4553
|
+
<ApiCommand
|
|
4554
|
+
action={() => api.deleteProject(modelId)}
|
|
4555
|
+
successMessage={`Model ${modelId} deleted`}
|
|
4556
|
+
/>
|
|
3507
4557
|
);
|
|
3508
4558
|
}
|
|
3509
4559
|
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
if (!
|
|
3513
|
-
return <ErrorMessage error="
|
|
4560
|
+
if (endpointAction === "dataset-count" || endpointAction === "count") {
|
|
4561
|
+
const modelId = endpointArgs[0];
|
|
4562
|
+
if (!modelId) {
|
|
4563
|
+
return <ErrorMessage error="Model ID required: model endpoints dataset-count <model-id>" />;
|
|
3514
4564
|
}
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
const hfTokenFlag = flags["hf-token"];
|
|
3518
|
-
const isPrivate = flags["private"]?.toLowerCase() === "true";
|
|
4565
|
+
return <ApiCommand action={() => api.getProjectDatasetCount(modelId)} />;
|
|
4566
|
+
}
|
|
3519
4567
|
|
|
3520
|
-
|
|
3521
|
-
|
|
4568
|
+
if (endpointAction === "quality-metrics" || endpointAction === "quality") {
|
|
4569
|
+
const modelId = endpointArgs[0];
|
|
4570
|
+
if (!modelId) {
|
|
4571
|
+
return <ErrorMessage error="Model ID required: model endpoints quality-metrics <model-id>" />;
|
|
3522
4572
|
}
|
|
4573
|
+
return <ApiCommand action={() => api.getProjectQualityMetrics(modelId)} />;
|
|
4574
|
+
}
|
|
3523
4575
|
|
|
3524
|
-
|
|
3525
|
-
|
|
4576
|
+
if (endpointAction === "deploy") {
|
|
4577
|
+
const modelId = endpointArgs[0];
|
|
4578
|
+
const jobId = flags["job"];
|
|
4579
|
+
|
|
4580
|
+
if (!modelId) {
|
|
4581
|
+
return <ErrorMessage error="Model ID required: model endpoints deploy <model-id> --job <training-job-id>" />;
|
|
4582
|
+
}
|
|
4583
|
+
if (!jobId) {
|
|
4584
|
+
return <ErrorMessage error="Training job ID required: model endpoints deploy <model-id> --job <training-job-id>" />;
|
|
4585
|
+
}
|
|
4586
|
+
if (jobId.length !== 36) {
|
|
3526
4587
|
return (
|
|
3527
4588
|
<Box flexDirection="column">
|
|
3528
|
-
<ErrorMessage error="
|
|
3529
|
-
<Text> </Text>
|
|
3530
|
-
<Text>
|
|
3531
|
-
<Text color="cyan"> pioneer auth hf</Text>
|
|
3532
|
-
<Text> </Text>
|
|
3533
|
-
<Text dimColor>Get a token at: https://huggingface.co/settings/tokens</Text>
|
|
4589
|
+
<ErrorMessage error="Invalid job ID: must be full UUID (36 characters)" />
|
|
4590
|
+
<Text dimColor> Provided: {jobId} ({jobId.length} characters)</Text>
|
|
4591
|
+
<Text dimColor> Tip: Use 'pioneer model artifacts list' and 'pioneer model artifacts trained' to see full job IDs</Text>
|
|
3534
4592
|
</Box>
|
|
3535
4593
|
);
|
|
3536
4594
|
}
|
|
3537
4595
|
|
|
4596
|
+
const reason = flags["reason"];
|
|
4597
|
+
|
|
3538
4598
|
return (
|
|
3539
4599
|
<ApiCommand
|
|
3540
4600
|
action={() =>
|
|
3541
|
-
api.
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
private: isPrivate,
|
|
4601
|
+
api.deployTrainingJobToProject(modelId, {
|
|
4602
|
+
training_job_id: jobId,
|
|
4603
|
+
...(reason ? { reason } : {}),
|
|
3545
4604
|
})
|
|
3546
4605
|
}
|
|
3547
|
-
successMessage={`
|
|
4606
|
+
successMessage={`Deployment initiated for project ${modelId} from job ${jobId}`}
|
|
4607
|
+
/>
|
|
4608
|
+
);
|
|
4609
|
+
}
|
|
4610
|
+
|
|
4611
|
+
if (endpointAction === "rollback") {
|
|
4612
|
+
const modelId = endpointArgs[0];
|
|
4613
|
+
const deploymentId = endpointArgs[1];
|
|
4614
|
+
|
|
4615
|
+
if (!modelId) {
|
|
4616
|
+
return <ErrorMessage error="Model ID required: model endpoints rollback <model-id> <deployment-id>" />;
|
|
4617
|
+
}
|
|
4618
|
+
if (!deploymentId) {
|
|
4619
|
+
return (
|
|
4620
|
+
<ErrorMessage error="Deployment ID required: model endpoints rollback <model-id> <deployment-id>" />
|
|
4621
|
+
);
|
|
4622
|
+
}
|
|
4623
|
+
if (deploymentId.length !== 36) {
|
|
4624
|
+
return (
|
|
4625
|
+
<Box flexDirection="column">
|
|
4626
|
+
<ErrorMessage error="Invalid deployment ID: must be full UUID (36 characters)" />
|
|
4627
|
+
<Text dimColor> Provided: {deploymentId} ({deploymentId.length} characters)</Text>
|
|
4628
|
+
</Box>
|
|
4629
|
+
);
|
|
4630
|
+
}
|
|
4631
|
+
|
|
4632
|
+
return (
|
|
4633
|
+
<ApiCommand
|
|
4634
|
+
action={() => api.rollbackProjectDeployment(modelId, deploymentId)}
|
|
4635
|
+
successMessage={`Rollback initiated for endpoint ${modelId} using deployment ${deploymentId}`}
|
|
3548
4636
|
/>
|
|
3549
4637
|
);
|
|
3550
4638
|
}
|
|
3551
4639
|
|
|
3552
|
-
return <
|
|
4640
|
+
return <Help context="model-endpoints" />;
|
|
3553
4641
|
}
|
|
3554
|
-
|
|
3555
|
-
if (
|
|
3556
|
-
const
|
|
3557
|
-
|
|
3558
|
-
|
|
4642
|
+
|
|
4643
|
+
if (normalizedAction === "artifacts") {
|
|
4644
|
+
const artifactsAction = rest[0];
|
|
4645
|
+
const artifactArgs = rest.slice(1);
|
|
4646
|
+
|
|
4647
|
+
if (flags.help === "true" || !artifactsAction || artifactsAction === "help") {
|
|
4648
|
+
return <Help context="model-artifacts" />;
|
|
3559
4649
|
}
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
return <ErrorMessage error="--text is required for inference" />;
|
|
4650
|
+
|
|
4651
|
+
if (artifactsAction === "list") {
|
|
4652
|
+
return <ModelListCommand filter="artifacts" />;
|
|
3564
4653
|
}
|
|
3565
|
-
|
|
3566
|
-
|
|
4654
|
+
|
|
4655
|
+
if (artifactsAction === "trained") {
|
|
4656
|
+
return <ModelListCommand filter="trained" />;
|
|
3567
4657
|
}
|
|
3568
|
-
const labels = labelsStr.split(",").map((l: string) => l.trim());
|
|
3569
|
-
const task = (flags["task"] || "extract_entities") as "extract_entities" | "classify_text" | "extract_json" | "schema";
|
|
3570
|
-
const threshold = flags["threshold"] ? parseFloat(flags["threshold"]) : 0.5;
|
|
3571
4658
|
|
|
3572
|
-
|
|
3573
|
-
<
|
|
3574
|
-
action={() =>
|
|
3575
|
-
api.runInference({
|
|
3576
|
-
model_id: modelId,
|
|
3577
|
-
task,
|
|
3578
|
-
text,
|
|
3579
|
-
schema: labels,
|
|
3580
|
-
threshold,
|
|
3581
|
-
include_confidence: true,
|
|
3582
|
-
})
|
|
3583
|
-
}
|
|
3584
|
-
/>
|
|
3585
|
-
);
|
|
3586
|
-
}
|
|
3587
|
-
// Model generate command (decoder inference)
|
|
3588
|
-
if (action === "generate") {
|
|
3589
|
-
const modelId = rest[0];
|
|
3590
|
-
if (!modelId) {
|
|
3591
|
-
return <ErrorMessage error="Model ID required. Usage: model generate <model-id> --prompt <text>" />;
|
|
4659
|
+
if (artifactsAction === "deployed") {
|
|
4660
|
+
return <ModelListCommand filter="deployed" />;
|
|
3592
4661
|
}
|
|
3593
|
-
|
|
3594
|
-
if (
|
|
3595
|
-
|
|
4662
|
+
|
|
4663
|
+
if (artifactsAction === "download") {
|
|
4664
|
+
if (!artifactArgs[0]) {
|
|
4665
|
+
return <ErrorMessage error="Job ID required: model artifacts download <job-id>" />;
|
|
4666
|
+
}
|
|
4667
|
+
const jobId = artifactArgs[0];
|
|
4668
|
+
if (jobId.length !== 36) {
|
|
4669
|
+
return (
|
|
4670
|
+
<Box flexDirection="column">
|
|
4671
|
+
<ErrorMessage error="Invalid job ID: must be full UUID (36 characters)" />
|
|
4672
|
+
<Text dimColor> Provided: {jobId} ({jobId.length} characters)</Text>
|
|
4673
|
+
<Text dimColor> Tip: Use 'pioneer model artifacts trained' or 'pioneer model artifacts deployed' to see full job IDs</Text>
|
|
4674
|
+
</Box>
|
|
4675
|
+
);
|
|
4676
|
+
}
|
|
4677
|
+
return <ApiCommand action={() => api.downloadModel(jobId)} />;
|
|
3596
4678
|
}
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
4679
|
+
|
|
4680
|
+
if (artifactsAction === "delete") {
|
|
4681
|
+
if (!artifactArgs[0]) {
|
|
4682
|
+
return <ErrorMessage error="Model ID required: model artifacts delete <job-id>" />;
|
|
4683
|
+
}
|
|
4684
|
+
const jobId = artifactArgs[0];
|
|
4685
|
+
if (jobId.length !== 36) {
|
|
4686
|
+
return (
|
|
4687
|
+
<Box flexDirection="column">
|
|
4688
|
+
<ErrorMessage error="Invalid job ID: must be full UUID (36 characters)" />
|
|
4689
|
+
<Text dimColor> Provided: {jobId} ({jobId.length} characters)</Text>
|
|
4690
|
+
<Text dimColor> Tip: Use 'pioneer model artifacts list' to see full job IDs</Text>
|
|
4691
|
+
</Box>
|
|
4692
|
+
);
|
|
4693
|
+
}
|
|
4694
|
+
return (
|
|
4695
|
+
<ApiCommand
|
|
4696
|
+
action={() => api.deleteModel(jobId)}
|
|
4697
|
+
successMessage={`Model ${jobId} deleted`}
|
|
4698
|
+
/>
|
|
4699
|
+
);
|
|
4700
|
+
}
|
|
4701
|
+
|
|
4702
|
+
// Model upload command
|
|
4703
|
+
if (artifactsAction === "upload") {
|
|
4704
|
+
const destination = flags["to"];
|
|
4705
|
+
|
|
4706
|
+
if (!artifactArgs[0] && !destination) {
|
|
4707
|
+
return (
|
|
4708
|
+
<Box flexDirection="column">
|
|
4709
|
+
<Text bold>Model Upload:</Text>
|
|
4710
|
+
<Text> </Text>
|
|
4711
|
+
<Text> Upload to Hugging Face:</Text>
|
|
4712
|
+
<Text> model artifacts upload {"<job-id>"} --to hf --repo {"<repo>"} [--hf-token {"<token>"}] [--private]</Text>
|
|
4713
|
+
<Text> </Text>
|
|
4714
|
+
<Text dimColor> Supported destinations: hf (more coming soon)</Text>
|
|
4715
|
+
</Box>
|
|
4716
|
+
);
|
|
4717
|
+
}
|
|
4718
|
+
|
|
4719
|
+
if (destination === "hf") {
|
|
4720
|
+
if (!artifactArgs[0]) {
|
|
4721
|
+
return <ErrorMessage error="Job ID required: model artifacts upload <job-id> --to hf --repo <repo>" />;
|
|
4722
|
+
}
|
|
4723
|
+
const jobId = artifactArgs[0];
|
|
4724
|
+
const repo = flags["repo"];
|
|
4725
|
+
const hfTokenFlag = flags["hf-token"];
|
|
4726
|
+
const isPrivate = flags["private"]?.toLowerCase() === "true";
|
|
4727
|
+
|
|
4728
|
+
if (!repo) {
|
|
4729
|
+
return <ErrorMessage error="--repo is required (e.g., username/model-name)" />;
|
|
4730
|
+
}
|
|
4731
|
+
|
|
4732
|
+
const hfToken = getHfToken(hfTokenFlag);
|
|
4733
|
+
if (!hfToken) {
|
|
4734
|
+
return (
|
|
4735
|
+
<Box flexDirection="column">
|
|
4736
|
+
<ErrorMessage error="Hugging Face token required." />
|
|
4737
|
+
<Text> </Text>
|
|
4738
|
+
<Text>Set your token with:</Text>
|
|
4739
|
+
<Text color="cyan"> pioneer auth hf</Text>
|
|
4740
|
+
<Text> </Text>
|
|
4741
|
+
<Text dimColor>Get a token at: https://huggingface.co/settings/tokens</Text>
|
|
4742
|
+
</Box>
|
|
4743
|
+
);
|
|
4744
|
+
}
|
|
4745
|
+
|
|
4746
|
+
return (
|
|
4747
|
+
<ApiCommand
|
|
4748
|
+
action={() =>
|
|
4749
|
+
api.pushModelToHub(jobId, {
|
|
4750
|
+
hf_token: hfToken,
|
|
4751
|
+
repo_id: repo,
|
|
4752
|
+
private: isPrivate,
|
|
4753
|
+
})
|
|
4754
|
+
}
|
|
4755
|
+
successMessage={`Model uploaded to Hugging Face: ${repo}`}
|
|
4756
|
+
/>
|
|
4757
|
+
);
|
|
4758
|
+
}
|
|
4759
|
+
|
|
4760
|
+
return <ErrorMessage error="--to is required. Supported destinations: hf" />;
|
|
4761
|
+
}
|
|
4762
|
+
|
|
4763
|
+
return <Help context="model-artifacts" />;
|
|
4764
|
+
}
|
|
4765
|
+
|
|
4766
|
+
if (action === "predict" || action === "generate") {
|
|
3600
4767
|
return (
|
|
3601
|
-
<
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
maxTokens={maxTokens}
|
|
3606
|
-
temperature={temperature}
|
|
4768
|
+
<ErrorMessage
|
|
4769
|
+
error={
|
|
4770
|
+
"model predict and model generate are no longer supported. Use model endpoints/... and model artifacts/... instead."
|
|
4771
|
+
}
|
|
3607
4772
|
/>
|
|
3608
4773
|
);
|
|
3609
4774
|
}
|
|
3610
|
-
|
|
4775
|
+
|
|
4776
|
+
return (
|
|
4777
|
+
<ErrorMessage
|
|
4778
|
+
error={`Unknown model command: model ${action}. Use 'pioneer model_endpoints ...', 'pioneer model endpoints ...', 'pioneer model_artifacts ...', or 'pioneer model artifacts ...'.`}
|
|
4779
|
+
/>
|
|
4780
|
+
);
|
|
3611
4781
|
}
|
|
3612
4782
|
|
|
3613
4783
|
// Eval commands
|
|
@@ -3625,9 +4795,60 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
3625
4795
|
if (action === "list" && !rest[0]) {
|
|
3626
4796
|
return <ErrorMessage error="Dataset required: eval list <name[:version]>" />;
|
|
3627
4797
|
}
|
|
4798
|
+
if (action === "baseline-models") {
|
|
4799
|
+
return <ApiCommand action={api.listBaselineModels} />;
|
|
4800
|
+
}
|
|
3628
4801
|
if (action === "get" && rest[0]) {
|
|
3629
4802
|
return <ApiCommand action={() => api.getEvaluation(rest[0])} />;
|
|
3630
4803
|
}
|
|
4804
|
+
if (action === "delete" && rest[0]) {
|
|
4805
|
+
const evaluationId = rest[0];
|
|
4806
|
+
return (
|
|
4807
|
+
<ApiCommand
|
|
4808
|
+
action={async () => {
|
|
4809
|
+
const result = await api.deleteEvaluation(evaluationId);
|
|
4810
|
+
if (!result.ok && (result.status === 401 || result.status === 403) && result.error) {
|
|
4811
|
+
const lower = result.error.toLowerCase();
|
|
4812
|
+
const indicatesJwt = lower.includes("jwt") ||
|
|
4813
|
+
lower.includes("api key access") ||
|
|
4814
|
+
lower.includes("table") ||
|
|
4815
|
+
lower.includes("requires authentication");
|
|
4816
|
+
if (indicatesJwt) {
|
|
4817
|
+
return {
|
|
4818
|
+
ok: false,
|
|
4819
|
+
status: result.status,
|
|
4820
|
+
error:
|
|
4821
|
+
"Evaluation deletion requires a JWT-authenticated session. Run 'pioneer auth login' to sign in with your account credentials.",
|
|
4822
|
+
};
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
return result;
|
|
4826
|
+
}}
|
|
4827
|
+
/>
|
|
4828
|
+
);
|
|
4829
|
+
}
|
|
4830
|
+
if (action === "update") {
|
|
4831
|
+
const mode = rest[0];
|
|
4832
|
+
const evaluationId = mode === "project" ? rest[1] : rest[0];
|
|
4833
|
+
if (!evaluationId) {
|
|
4834
|
+
return <Help context="eval" />;
|
|
4835
|
+
}
|
|
4836
|
+
const projectId = flags["project-id"];
|
|
4837
|
+
if (!projectId) {
|
|
4838
|
+
return <ErrorMessage error="--project-id is required" />;
|
|
4839
|
+
}
|
|
4840
|
+
return (
|
|
4841
|
+
<ApiCommand
|
|
4842
|
+
action={() =>
|
|
4843
|
+
api.updateEvaluationProject({
|
|
4844
|
+
evaluation_id: evaluationId,
|
|
4845
|
+
project_id: projectId,
|
|
4846
|
+
})
|
|
4847
|
+
}
|
|
4848
|
+
successMessage={`Evaluation ${evaluationId} reassigned`}
|
|
4849
|
+
/>
|
|
4850
|
+
);
|
|
4851
|
+
}
|
|
3631
4852
|
if (action === "create") {
|
|
3632
4853
|
const modelId = flags["model-id"];
|
|
3633
4854
|
const datasetStr = flags["dataset"];
|
|
@@ -3671,21 +4892,35 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
3671
4892
|
return <ApiCommand action={api.listBenchmarks} />;
|
|
3672
4893
|
}
|
|
3673
4894
|
if (action === "run") {
|
|
3674
|
-
const
|
|
4895
|
+
const rawModelId = flags["model-id"];
|
|
3675
4896
|
const task = flags["task"] as "ner" | "text_classification";
|
|
3676
4897
|
const benchmark = flags["benchmark"];
|
|
3677
4898
|
const maxSamples = flags["max-samples"] ? parseInt(flags["max-samples"], 10) : undefined;
|
|
3678
4899
|
const split = flags["split"];
|
|
3679
4900
|
|
|
3680
|
-
if (!
|
|
4901
|
+
if (!rawModelId || !task || !benchmark) {
|
|
3681
4902
|
return <ErrorMessage error="--model-id, --task, and --benchmark are required" />;
|
|
3682
4903
|
}
|
|
4904
|
+
const normalizedModelId = normalizeModelId(rawModelId);
|
|
4905
|
+
if (!isUuid(normalizedModelId)) {
|
|
4906
|
+
return (
|
|
4907
|
+
<ErrorMessage
|
|
4908
|
+
error="Benchmark model-id must be a training job UUID (example: 72c1ac92-3a89-439d-afe3-687d8a935c06)."
|
|
4909
|
+
/>
|
|
4910
|
+
);
|
|
4911
|
+
}
|
|
4912
|
+
if (task !== "ner" && task !== "text_classification") {
|
|
4913
|
+
return <ErrorMessage error="--task must be either ner or text_classification" />;
|
|
4914
|
+
}
|
|
4915
|
+
if (maxSamples !== undefined && (Number.isNaN(maxSamples) || maxSamples < 1)) {
|
|
4916
|
+
return <ErrorMessage error="--max-samples must be a positive integer" />;
|
|
4917
|
+
}
|
|
3683
4918
|
|
|
3684
4919
|
return (
|
|
3685
4920
|
<ApiCommand
|
|
3686
4921
|
action={() =>
|
|
3687
4922
|
api.startBenchmarkEvaluation({
|
|
3688
|
-
model_id:
|
|
4923
|
+
model_id: normalizedModelId,
|
|
3689
4924
|
task,
|
|
3690
4925
|
benchmark,
|
|
3691
4926
|
max_samples: maxSamples,
|
|
@@ -3710,152 +4945,64 @@ const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
|
3710
4945
|
return <Help context="benchmark" />;
|
|
3711
4946
|
}
|
|
3712
4947
|
|
|
3713
|
-
//
|
|
3714
|
-
if (group === "
|
|
3715
|
-
if (flags.help === "true" ||
|
|
3716
|
-
return <Help context="
|
|
3717
|
-
}
|
|
3718
|
-
|
|
3719
|
-
if (action === "run") {
|
|
3720
|
-
const file = rest[0];
|
|
3721
|
-
if (!file) {
|
|
3722
|
-
return <ErrorMessage error="File path required: notebook run <file.ipynb>" />;
|
|
3723
|
-
}
|
|
3724
|
-
const gpu = flags.gpu || "cpu";
|
|
3725
|
-
const loadFelixHelpers = flags["no-felix"] !== "true";
|
|
3726
|
-
return <NotebookRunCommand filePath={file} gpu={gpu} loadFelixHelpers={loadFelixHelpers} />;
|
|
3727
|
-
}
|
|
3728
|
-
|
|
3729
|
-
if (action === "create") {
|
|
3730
|
-
const name = rest[0] || flags.name;
|
|
3731
|
-
if (!name) {
|
|
3732
|
-
return <ErrorMessage error="Name required: notebook create <name>" />;
|
|
3733
|
-
}
|
|
3734
|
-
try {
|
|
3735
|
-
const filePath = createBlankNotebook(name);
|
|
3736
|
-
return <Success message={`Created notebook: ${filePath}`} />;
|
|
3737
|
-
} catch (e) {
|
|
3738
|
-
return <ErrorMessage error={e instanceof Error ? e.message : String(e)} />;
|
|
3739
|
-
}
|
|
3740
|
-
}
|
|
3741
|
-
|
|
3742
|
-
if (action === "sessions") {
|
|
3743
|
-
return <ApiCommand action={api.listNotebookSessions} />;
|
|
4948
|
+
// Adaptive agent commands (new short command)
|
|
4949
|
+
if (group === "agent") {
|
|
4950
|
+
if (flags.help === "true" || action === "help") {
|
|
4951
|
+
return <Help context="agent" />;
|
|
3744
4952
|
}
|
|
3745
4953
|
|
|
3746
|
-
if (action
|
|
3747
|
-
const sessionId = rest[0];
|
|
3748
|
-
if (!sessionId) {
|
|
3749
|
-
return <ErrorMessage error="Session ID required: notebook stop <session-id>" />;
|
|
3750
|
-
}
|
|
4954
|
+
if (action && !action.startsWith("-")) {
|
|
3751
4955
|
return (
|
|
3752
|
-
<
|
|
3753
|
-
|
|
3754
|
-
|
|
4956
|
+
<ErrorMessage
|
|
4957
|
+
error={
|
|
4958
|
+
'Invalid agent command syntax. Use one of:\n' +
|
|
4959
|
+
"pioneer agent\n" +
|
|
4960
|
+
"pioneer agent --mode research"
|
|
4961
|
+
}
|
|
3755
4962
|
/>
|
|
3756
4963
|
);
|
|
3757
4964
|
}
|
|
3758
4965
|
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
// Adaptive fine-tuning commands
|
|
3763
|
-
if (group === "adaptive-finetuning" || group === "aft") {
|
|
3764
|
-
if (flags.help === "true" || action === "help") {
|
|
3765
|
-
return <Help context="adaptive-finetuning" />;
|
|
3766
|
-
}
|
|
3767
|
-
|
|
3768
|
-
let message = flags.message;
|
|
3769
|
-
if (!message) {
|
|
3770
|
-
const rawParts = action === "chat" ? rest : [action, ...rest];
|
|
3771
|
-
message = rawParts.filter(Boolean).join(" ").trim();
|
|
4966
|
+
if (flags.message) {
|
|
4967
|
+
return <ErrorMessage error="The --message flag has been removed for agent. Run `pioneer agent` or `pioneer agent --mode research` and provide input interactively." />;
|
|
3772
4968
|
}
|
|
3773
4969
|
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
);
|
|
4970
|
+
const rawMode = flags.mode;
|
|
4971
|
+
const validModes = ["research"];
|
|
4972
|
+
const normalizedMode = rawMode ? rawMode.toLowerCase() : undefined;
|
|
4973
|
+
if (normalizedMode && !validModes.includes(normalizedMode)) {
|
|
4974
|
+
return <ErrorMessage error="--mode must be one of: research" />;
|
|
3780
4975
|
}
|
|
4976
|
+
const mode = (normalizedMode === "research" ? "research" : "standard") as "standard" | "research";
|
|
3781
4977
|
|
|
3782
|
-
let history: api.
|
|
4978
|
+
let history: api.AgentChatHistoryItem[] | undefined;
|
|
3783
4979
|
if (flags.history) {
|
|
3784
4980
|
try {
|
|
3785
4981
|
const parsed = JSON.parse(flags.history) as unknown;
|
|
3786
4982
|
if (!Array.isArray(parsed)) {
|
|
3787
4983
|
return <ErrorMessage error="--history must be a JSON array" />;
|
|
3788
4984
|
}
|
|
3789
|
-
history = parsed as api.
|
|
4985
|
+
history = parsed as api.AgentChatHistoryItem[];
|
|
3790
4986
|
} catch {
|
|
3791
4987
|
return <ErrorMessage error="--history must be valid JSON" />;
|
|
3792
4988
|
}
|
|
3793
4989
|
}
|
|
3794
4990
|
|
|
3795
|
-
let filters: api.AdaptiveFinetuningQueryFilters | undefined;
|
|
3796
4991
|
if (flags.filters) {
|
|
3797
|
-
|
|
3798
|
-
const parsed = JSON.parse(flags.filters) as unknown;
|
|
3799
|
-
if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
3800
|
-
return <ErrorMessage error="--filters must be a JSON object" />;
|
|
3801
|
-
}
|
|
3802
|
-
filters = parsed as api.AdaptiveFinetuningQueryFilters;
|
|
3803
|
-
} catch {
|
|
3804
|
-
return <ErrorMessage error="--filters must be valid JSON" />;
|
|
3805
|
-
}
|
|
4992
|
+
return <ErrorMessage error='--filters is not supported for /auto-agent/clarify. Omit this flag for now.' />;
|
|
3806
4993
|
}
|
|
3807
4994
|
|
|
3808
4995
|
return (
|
|
3809
|
-
<
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
...(flags["conversation-id"] ? { conversation_id: flags["conversation-id"] } : {}),
|
|
3814
|
-
...(history ? { history } : {}),
|
|
3815
|
-
...(filters ? { filters } : {}),
|
|
3816
|
-
})
|
|
3817
|
-
}
|
|
3818
|
-
successMessage="Adaptive fine-tuning response received"
|
|
4996
|
+
<AgentInteractivePrompt
|
|
4997
|
+
conversationId={flags["conversation-id"]}
|
|
4998
|
+
history={history}
|
|
4999
|
+
mode={mode}
|
|
3819
5000
|
/>
|
|
3820
5001
|
);
|
|
3821
5002
|
}
|
|
3822
5003
|
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
if (flags.help === "true" || !action || action === "help") {
|
|
3826
|
-
return <Help context="competition" />;
|
|
3827
|
-
}
|
|
3828
|
-
if (action === "list") {
|
|
3829
|
-
return <CompetitionListCommand />;
|
|
3830
|
-
}
|
|
3831
|
-
if (action === "show" && rest[0]) {
|
|
3832
|
-
return <ApiCommand action={() => api.getCompetitionSamples(rest[0])} />;
|
|
3833
|
-
}
|
|
3834
|
-
if (action === "leaderboard" && rest[0]) {
|
|
3835
|
-
const limit = flags["limit"] ? parseInt(flags["limit"], 10) : undefined;
|
|
3836
|
-
return <LeaderboardCommand datasetId={rest[0]} limit={limit} />;
|
|
3837
|
-
}
|
|
3838
|
-
if (action === "submit" && rest[0]) {
|
|
3839
|
-
const evalId = flags["eval-id"];
|
|
3840
|
-
const displayName = flags["name"];
|
|
3841
|
-
|
|
3842
|
-
if (!evalId || !displayName) {
|
|
3843
|
-
return <ErrorMessage error="--eval-id and --name are required" />;
|
|
3844
|
-
}
|
|
3845
|
-
|
|
3846
|
-
return (
|
|
3847
|
-
<ApiCommand
|
|
3848
|
-
action={() =>
|
|
3849
|
-
api.submitToLeaderboard(rest[0], {
|
|
3850
|
-
evaluation_id: evalId,
|
|
3851
|
-
display_name: displayName,
|
|
3852
|
-
})
|
|
3853
|
-
}
|
|
3854
|
-
successMessage="Submitted to leaderboard"
|
|
3855
|
-
/>
|
|
3856
|
-
);
|
|
3857
|
-
}
|
|
3858
|
-
return <Help context="competition" />;
|
|
5004
|
+
if (group === "notebook") {
|
|
5005
|
+
return <ErrorMessage error="The notebook command is deprecated and has been removed from this CLI." />;
|
|
3859
5006
|
}
|
|
3860
5007
|
|
|
3861
5008
|
return <Help />;
|
|
@@ -3869,7 +5016,7 @@ import packageJson from "../package.json";
|
|
|
3869
5016
|
|
|
3870
5017
|
async function main() {
|
|
3871
5018
|
const argv = process.argv.slice(2);
|
|
3872
|
-
const { command, flags } = parseArgs(argv);
|
|
5019
|
+
const { command, flags, parseErrors } = parseArgs(argv);
|
|
3873
5020
|
|
|
3874
5021
|
// Handle version flag early (before React render)
|
|
3875
5022
|
if (flags.version === "true" || flags.v === "true") {
|
|
@@ -3877,7 +5024,7 @@ async function main() {
|
|
|
3877
5024
|
process.exit(0);
|
|
3878
5025
|
}
|
|
3879
5026
|
|
|
3880
|
-
await render(<App command={command} flags={flags} />).waitUntilExit();
|
|
5027
|
+
await render(<App command={command} flags={flags} parseErrors={parseErrors} />).waitUntilExit();
|
|
3881
5028
|
}
|
|
3882
5029
|
|
|
3883
5030
|
main();
|