openxgen 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -891,75 +891,6 @@ var init_provider = __esm({
891
891
  }
892
892
  });
893
893
 
894
- // src/agent/llm.ts
895
- var llm_exports = {};
896
- __export(llm_exports, {
897
- createLLMClient: () => createLLMClient,
898
- streamChat: () => streamChat
899
- });
900
- import OpenAI2 from "openai";
901
- function createLLMClient(provider) {
902
- const opts = {
903
- apiKey: provider.apiKey || "ollama"
904
- };
905
- if (provider.baseUrl) {
906
- opts.baseURL = provider.baseUrl;
907
- }
908
- return new OpenAI2(opts);
909
- }
910
- async function streamChat(client2, model, messages, tools2, onDelta) {
911
- const params = {
912
- model,
913
- messages,
914
- stream: true,
915
- stream_options: { include_usage: true }
916
- };
917
- if (tools2 && tools2.length > 0) {
918
- params.tools = tools2;
919
- }
920
- const stream = await client2.chat.completions.create(params);
921
- let content = "";
922
- let usage = null;
923
- const toolCallMap = /* @__PURE__ */ new Map();
924
- for await (const chunk of stream) {
925
- if (chunk.usage) {
926
- usage = {
927
- promptTokens: chunk.usage.prompt_tokens ?? 0,
928
- completionTokens: chunk.usage.completion_tokens ?? 0,
929
- totalTokens: chunk.usage.total_tokens ?? 0
930
- };
931
- }
932
- const delta = chunk.choices[0]?.delta;
933
- if (!delta) continue;
934
- if (delta.content) {
935
- content += delta.content;
936
- onDelta?.(delta.content);
937
- }
938
- if (delta.tool_calls) {
939
- for (const tc of delta.tool_calls) {
940
- const idx = tc.index;
941
- if (!toolCallMap.has(idx)) {
942
- toolCallMap.set(idx, { id: tc.id ?? "", name: tc.function?.name ?? "", arguments: "" });
943
- }
944
- const entry = toolCallMap.get(idx);
945
- if (tc.id) entry.id = tc.id;
946
- if (tc.function?.name) entry.name = tc.function.name;
947
- if (tc.function?.arguments) entry.arguments += tc.function.arguments;
948
- }
949
- }
950
- }
951
- return {
952
- content,
953
- toolCalls: [...toolCallMap.values()],
954
- usage
955
- };
956
- }
957
- var init_llm = __esm({
958
- "src/agent/llm.ts"() {
959
- "use strict";
960
- }
961
- });
962
-
963
894
  // src/api/xgen-extra.ts
964
895
  var xgen_extra_exports = {};
965
896
  __export(xgen_extra_exports, {
@@ -1177,334 +1108,283 @@ var init_ontology = __esm({
1177
1108
  }
1178
1109
  });
1179
1110
 
1180
- // src/dashboard/tui.ts
1181
- var tui_exports = {};
1182
- __export(tui_exports, {
1183
- startTui: () => startTui
1111
+ // src/dashboard/InkDashboard.tsx
1112
+ var InkDashboard_exports = {};
1113
+ __export(InkDashboard_exports, {
1114
+ startInkDashboard: () => startInkDashboard
1184
1115
  });
1185
- import blessed from "blessed";
1186
- async function startTui() {
1187
- const screen = blessed.screen({ smartCSR: true, title: "OPEN XGEN", fullUnicode: true });
1116
+ import { useState, useEffect } from "react";
1117
+ import { render, Box, Text, useInput, useApp, useStdout } from "ink";
1118
+ import TextInput from "ink-text-input";
1119
+ import { jsx, jsxs } from "react/jsx-runtime";
1120
+ function Header({ tab, serverDisplay, model }) {
1121
+ const tabStr = TABS.map((t) => {
1122
+ const active = tab === t.key;
1123
+ return `[${t.shortcut}]${active ? "\u25B8" : " "}${t.label}`;
1124
+ }).join(" ");
1125
+ return /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
1126
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "OPEN XGEN" }),
1127
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1128
+ " ",
1129
+ model,
1130
+ " ",
1131
+ serverDisplay,
1132
+ " \u2502 ",
1133
+ tabStr
1134
+ ] })
1135
+ ] });
1136
+ }
1137
+ function ListPanel({ items, selected, onSelect }) {
1138
+ const { stdout } = useStdout();
1139
+ const height = (stdout?.rows ?? 24) - 8;
1140
+ const visibleCount = Math.max(1, height);
1141
+ const start = Math.max(0, Math.min(selected - Math.floor(visibleCount / 2), items.length - visibleCount));
1142
+ const visible = items.slice(start, start + visibleCount);
1143
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: "50%", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
1144
+ visible.map((item, i) => {
1145
+ const realIndex = start + i;
1146
+ const isSelected = realIndex === selected;
1147
+ return /* @__PURE__ */ jsxs(Text, { inverse: isSelected, children: [
1148
+ isSelected ? "\u25B8 " : " ",
1149
+ item.label,
1150
+ item.dimLabel ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1151
+ " ",
1152
+ item.dimLabel
1153
+ ] }) : null
1154
+ ] }, `item-${realIndex}`);
1155
+ }),
1156
+ items.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (\uC5C6\uC74C)" })
1157
+ ] });
1158
+ }
1159
+ function DetailPanel({ lines }) {
1160
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: "50%", borderStyle: "single", borderColor: "gray", paddingX: 1, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text, { children: line }, `line-${i}`)) });
1161
+ }
1162
+ function StatusBar({ message }) {
1163
+ return /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: message }) });
1164
+ }
1165
+ function InputBar({ value, onChange, onSubmit, placeholder }) {
1166
+ return /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
1167
+ /* @__PURE__ */ jsx(Text, { children: "\u276F " }),
1168
+ /* @__PURE__ */ jsx(
1169
+ TextInput,
1170
+ {
1171
+ value,
1172
+ onChange,
1173
+ onSubmit,
1174
+ placeholder: placeholder ?? "\uC785\uB825..."
1175
+ }
1176
+ )
1177
+ ] });
1178
+ }
1179
+ function Dashboard() {
1180
+ const { exit } = useApp();
1188
1181
  const provider = getDefaultProvider();
1189
1182
  const server = getServer();
1190
1183
  const auth = getAuth();
1191
1184
  const serverDisplay = auth && server ? `${auth.username}@${server.replace("https://", "").replace("http://", "")}` : "\uBBF8\uC5F0\uACB0";
1192
- let activeTab = "workflows";
1193
- const header = blessed.box({
1194
- top: 0,
1195
- left: 0,
1196
- width: "100%",
1197
- height: 3,
1198
- tags: true,
1199
- style: { fg: "white", bg: "black" }
1200
- });
1201
- function renderHeader() {
1202
- const wf = activeTab === "workflows" ? "{bold}{underline}\uC6CC\uD06C\uD50C\uB85C\uC6B0{/underline}{/bold}" : "{gray-fg}\uC6CC\uD06C\uD50C\uB85C\uC6B0{/gray-fg}";
1203
- const col = activeTab === "collections" ? "{bold}{underline}\uCEEC\uB809\uC158{/underline}{/bold}" : "{gray-fg}\uCEEC\uB809\uC158{/gray-fg}";
1204
- header.setContent(` OPEN XGEN {gray-fg}${provider?.model ?? ""}{/gray-fg} ${serverDisplay} \u2502 [1]${wf} [2]${col}`);
1205
- screen.render();
1206
- }
1207
- const listPanel = blessed.list({
1208
- top: 3,
1209
- left: 0,
1210
- width: "50%",
1211
- height: "100%-9",
1212
- border: { type: "line" },
1213
- style: {
1214
- border: { fg: "gray" },
1215
- selected: { fg: "black", bg: "white" },
1216
- item: { fg: "white" },
1217
- label: { fg: "white", bold: true }
1218
- },
1219
- keys: true,
1220
- vi: true,
1221
- mouse: true,
1222
- scrollbar: { ch: "\u2502", style: { fg: "gray" } },
1223
- tags: true
1224
- });
1225
- const chatInput = blessed.textbox({
1226
- bottom: 3,
1227
- left: 0,
1228
- width: "50%",
1229
- height: 3,
1230
- label: " \u276F ",
1231
- border: { type: "line" },
1232
- inputOnFocus: true,
1233
- style: {
1234
- border: { fg: "gray" },
1235
- label: { fg: "white", bold: true }
1236
- }
1237
- });
1238
- const detailPanel = blessed.log({
1239
- top: 3,
1240
- left: "50%",
1241
- width: "50%",
1242
- height: "100%-6",
1243
- label: " \uC0C1\uC138 ",
1244
- border: { type: "line" },
1245
- scrollable: true,
1246
- alwaysScroll: true,
1247
- mouse: true,
1248
- tags: true,
1249
- style: {
1250
- border: { fg: "gray" },
1251
- label: { fg: "white", bold: true }
1252
- }
1253
- });
1254
- const statusBar = blessed.box({
1255
- bottom: 0,
1256
- left: 0,
1257
- width: "100%",
1258
- height: 3,
1259
- tags: true,
1260
- style: { fg: "gray", bg: "black" }
1261
- });
1262
- function renderStatusBar(msg) {
1263
- statusBar.setContent(msg ?? " {white-fg}{bold}1/2{/bold}{/white-fg}:\uD0ED {white-fg}{bold}Enter{/bold}{/white-fg}:\uC2E4\uD589 {white-fg}{bold}i{/bold}{/white-fg}:\uC785\uB825 {white-fg}{bold}r{/bold}{/white-fg}:\uC0C8\uB85C\uACE0\uCE68 {white-fg}{bold}Esc{/bold}{/white-fg}:\uBAA9\uB85D {white-fg}{bold}q{/bold}{/white-fg}:\uC885\uB8CC");
1264
- screen.render();
1265
- }
1266
- screen.append(header);
1267
- screen.append(listPanel);
1268
- screen.append(chatInput);
1269
- screen.append(detailPanel);
1270
- screen.append(statusBar);
1271
- let workflows = [];
1272
- let collections = [];
1273
- async function loadWorkflows() {
1274
- if (!server || !auth) return;
1185
+ const [tab, setTab] = useState("workflows");
1186
+ const [selected, setSelected] = useState(0);
1187
+ const [workflows, setWorkflows] = useState([]);
1188
+ const [collections, setCollections] = useState([]);
1189
+ const [nodes, setNodes] = useState([]);
1190
+ const [prompts, setPrompts] = useState([]);
1191
+ const [tools2, setTools] = useState([]);
1192
+ const [detail, setDetail] = useState(["\u2190 \uD56D\uBAA9\uC744 \uC120\uD0DD\uD558\uC138\uC694"]);
1193
+ const [inputValue, setInputValue] = useState("");
1194
+ const [inputMode, setInputMode] = useState(false);
1195
+ const [runTarget, setRunTarget] = useState(null);
1196
+ const [loading, setLoading] = useState(true);
1197
+ const [statusMsg, setStatusMsg] = useState("\uB85C\uB529...");
1198
+ useEffect(() => {
1199
+ loadAll();
1200
+ }, []);
1201
+ async function loadAll() {
1202
+ setLoading(true);
1203
+ setStatusMsg("\uB85C\uB529...");
1275
1204
  try {
1276
- const { getWorkflowListDetail: getWorkflowListDetail2 } = await Promise.resolve().then(() => (init_workflow(), workflow_exports));
1277
- const wfs = await getWorkflowListDetail2();
1278
- workflows = wfs.map((w) => ({
1279
- name: w.workflow_name,
1280
- id: (w.workflow_id ?? w.id ?? "").toString(),
1281
- deployed: !!w.is_deployed
1282
- }));
1205
+ if (server && auth) {
1206
+ const [wfMod, docMod, extraMod] = await Promise.all([
1207
+ Promise.resolve().then(() => (init_workflow(), workflow_exports)),
1208
+ Promise.resolve().then(() => (init_document(), document_exports)),
1209
+ Promise.resolve().then(() => (init_xgen_extra(), xgen_extra_exports))
1210
+ ]);
1211
+ const [wfs, cols, nodeList, promptList, toolList] = await Promise.allSettled([
1212
+ wfMod.getWorkflowListDetail(),
1213
+ docMod.listCollections(),
1214
+ extraMod.listNodes(),
1215
+ extraMod.listPrompts(),
1216
+ extraMod.listToolStore()
1217
+ ]);
1218
+ if (wfs.status === "fulfilled") {
1219
+ setWorkflows(wfs.value.map((w) => ({
1220
+ name: w.workflow_name,
1221
+ id: w.workflow_id ?? w.id ?? "",
1222
+ deployed: !!w.is_deployed
1223
+ })));
1224
+ }
1225
+ if (cols.status === "fulfilled") {
1226
+ setCollections(cols.value.map((c) => ({
1227
+ name: c.collection_make_name,
1228
+ docs: c.total_documents,
1229
+ chunks: c.total_chunks
1230
+ })));
1231
+ }
1232
+ if (nodeList.status === "fulfilled") setNodes(nodeList.value);
1233
+ if (promptList.status === "fulfilled") setPrompts(promptList.value);
1234
+ if (toolList.status === "fulfilled") setTools(toolList.value);
1235
+ }
1283
1236
  } catch {
1284
- workflows = [];
1285
1237
  }
1286
- }
1287
- async function loadCollections() {
1288
- if (!server || !auth) return;
1289
- try {
1290
- const { listCollections: listCollections2 } = await Promise.resolve().then(() => (init_document(), document_exports));
1291
- const cols = await listCollections2();
1292
- collections = cols.map((c) => ({
1293
- name: c.collection_make_name,
1294
- id: c.id,
1295
- docs: c.total_documents,
1296
- chunks: c.total_chunks,
1297
- shared: c.is_shared,
1298
- group: c.share_group ?? void 0,
1299
- model: c.init_embedding_model ?? void 0
1300
- }));
1301
- } catch {
1302
- collections = [];
1238
+ setLoading(false);
1239
+ setStatusMsg("\u2191\u2193:\uC774\uB3D9 Enter:\uC120\uD0DD 1-5:\uD0ED i:\uC785\uB825 r:\uC0C8\uB85C\uACE0\uCE68 q:\uC885\uB8CC");
1240
+ }
1241
+ function getListItems() {
1242
+ switch (tab) {
1243
+ case "workflows":
1244
+ return workflows.map((w) => ({ label: `${w.deployed ? "\u25CF" : "\u25CB"} ${w.name}`, dimLabel: "" }));
1245
+ case "collections":
1246
+ return collections.map((c) => ({ label: c.name, dimLabel: `${c.docs}\uBB38\uC11C ${c.chunks}\uCCAD\uD06C` }));
1247
+ case "nodes":
1248
+ return nodes.map((n) => ({ label: n.nodeName ?? n.name ?? "?", dimLabel: (n.description ?? "").slice(0, 30) }));
1249
+ case "prompts":
1250
+ return prompts.map((p) => ({ label: p.name ?? p.title ?? "?", dimLabel: `[${p.prompt_type ?? ""}]` }));
1251
+ case "tools":
1252
+ return tools2.map((t) => ({ label: t.name ?? t.tool_name ?? "?", dimLabel: (t.description ?? "").slice(0, 30) }));
1253
+ default:
1254
+ return [];
1255
+ }
1256
+ }
1257
+ function showDetail() {
1258
+ const items = getListItems();
1259
+ if (selected < 0 || selected >= items.length) return;
1260
+ if (tab === "workflows") {
1261
+ const w = workflows[selected];
1262
+ if (w) setDetail([w.name, "", `ID ${w.id}`, `\uBC30\uD3EC ${w.deployed ? "Yes" : "No"}`, "", "Enter \u2192 \uC2E4\uD589"]);
1263
+ } else if (tab === "collections") {
1264
+ const c = collections[selected];
1265
+ if (c) setDetail([c.name, "", `\uBB38\uC11C ${c.docs}\uAC1C`, `\uCCAD\uD06C ${c.chunks}\uAC1C`]);
1266
+ } else if (tab === "nodes") {
1267
+ const n = nodes[selected];
1268
+ if (n) setDetail([n.nodeName ?? n.name ?? "?", "", n.description ?? "", "", `ID: ${n.node_id ?? n.id ?? "?"}`]);
1269
+ } else if (tab === "prompts") {
1270
+ const p = prompts[selected];
1271
+ if (p) setDetail([p.name ?? "?", `[${p.prompt_type ?? ""}]`, "", (p.content ?? "").slice(0, 200)]);
1272
+ } else if (tab === "tools") {
1273
+ const t = tools2[selected];
1274
+ if (t) setDetail([t.name ?? t.tool_name ?? "?", "", t.description ?? ""]);
1275
+ }
1276
+ }
1277
+ useEffect(() => {
1278
+ showDetail();
1279
+ }, [selected, tab]);
1280
+ function switchTab(t) {
1281
+ setTab(t);
1282
+ setSelected(0);
1283
+ setInputMode(false);
1284
+ setRunTarget(null);
1285
+ }
1286
+ async function handleSubmit(value) {
1287
+ if (!value.trim()) {
1288
+ setInputMode(false);
1289
+ setRunTarget(null);
1290
+ return;
1303
1291
  }
1304
- }
1305
- function renderList() {
1306
- if (activeTab === "workflows") {
1307
- listPanel.label = ` \uC6CC\uD06C\uD50C\uB85C\uC6B0 (${workflows.length}) `;
1308
- listPanel.setItems(workflows.map((w, i) => {
1309
- const dot = w.deployed ? "\u25CF" : "\u25CB";
1310
- return ` ${dot} ${String(i + 1).padStart(2)}. ${w.name}`;
1311
- }));
1292
+ if (runTarget) {
1293
+ setDetail([`\uC2E4\uD589 \uC911: ${runTarget.name}`, `\uC785\uB825: ${value}`, "", "..."]);
1294
+ setInputValue("");
1295
+ setInputMode(false);
1296
+ try {
1297
+ const { executeWorkflow: executeWorkflow2 } = await Promise.resolve().then(() => (init_workflow(), workflow_exports));
1298
+ const { randomUUID: randomUUID4 } = await import("crypto");
1299
+ const result = await executeWorkflow2({
1300
+ workflow_id: runTarget.id,
1301
+ workflow_name: runTarget.name,
1302
+ input_data: value,
1303
+ interaction_id: `tui_${randomUUID4().slice(0, 8)}`,
1304
+ user_id: auth?.userId ? parseInt(auth.userId) : 1
1305
+ });
1306
+ const content = result.content ?? result.message ?? JSON.stringify(result).slice(0, 500);
1307
+ setDetail([`${runTarget.name}`, "", `\uC785\uB825: ${value}`, "", `\uACB0\uACFC:`, String(content)]);
1308
+ } catch (err) {
1309
+ setDetail([`\uC2E4\uD589 \uC2E4\uD328: ${err.message}`]);
1310
+ }
1311
+ setRunTarget(null);
1312
1312
  } else {
1313
- listPanel.label = ` \uCEEC\uB809\uC158 (${collections.length}) `;
1314
- listPanel.setItems(collections.map(
1315
- (c, i) => ` ${String(i + 1).padStart(2)}. ${c.name} {gray-fg}${c.docs}\uBB38\uC11C{/gray-fg}`
1316
- ));
1313
+ setInputValue("");
1314
+ setInputMode(false);
1317
1315
  }
1318
- screen.render();
1319
1316
  }
1320
- async function loadAll() {
1321
- renderStatusBar(" \uB85C\uB529...");
1322
- await Promise.all([loadWorkflows(), loadCollections()]);
1323
- renderList();
1324
- renderHeader();
1325
- renderStatusBar();
1326
- }
1327
- listPanel.on("select item", (_item, index) => {
1328
- if (activeTab === "workflows") {
1329
- const w = workflows[index];
1330
- if (!w) return;
1331
- detailPanel.setContent([
1332
- `{bold}${w.name}{/bold}`,
1333
- ``,
1334
- `ID ${w.id}`,
1335
- `\uBC30\uD3EC ${w.deployed ? "Yes" : "No"}`,
1336
- ``,
1337
- `Enter \u2192 \uC2E4\uD589 \uC785\uB825`
1338
- ].join("\n"));
1339
- } else {
1340
- const c = collections[index];
1341
- if (!c) return;
1342
- detailPanel.setContent([
1343
- `{bold}${c.name}{/bold}`,
1344
- ``,
1345
- `\uBB38\uC11C ${c.docs}\uAC1C`,
1346
- `\uCCAD\uD06C ${c.chunks}\uAC1C`,
1347
- `\uACF5\uC720 ${c.shared ? `Yes (${c.group})` : "No"}`,
1348
- `\uBAA8\uB378 ${c.model ?? "-"}`,
1349
- ``,
1350
- `Enter \u2192 \uBB38\uC11C \uBAA9\uB85D`
1351
- ].join("\n"));
1352
- }
1353
- screen.render();
1354
- });
1355
- listPanel.on("select", async (_item, index) => {
1356
- if (activeTab === "workflows") {
1357
- const w = workflows[index];
1358
- if (!w) return;
1359
- chatInput.label = ` ${w.name} \u276F `;
1360
- chatInput.focus();
1361
- screen.render();
1362
- chatInput.once("submit", async (value) => {
1363
- if (!value.trim()) {
1364
- chatInput.label = " \u276F ";
1365
- chatInput.clearValue();
1366
- listPanel.focus();
1367
- screen.render();
1368
- return;
1369
- }
1370
- detailPanel.log(`{gray-fg}\u2500\u2500 ${w.name} \u2500\u2500{/gray-fg}`);
1371
- detailPanel.log(`\uC785\uB825: ${value}`);
1372
- screen.render();
1373
- try {
1374
- const { executeWorkflow: executeWorkflow2 } = await Promise.resolve().then(() => (init_workflow(), workflow_exports));
1375
- const { randomUUID: randomUUID4 } = await import("crypto");
1376
- const result = await executeWorkflow2({
1377
- workflow_id: w.id,
1378
- workflow_name: w.name,
1379
- input_data: value,
1380
- interaction_id: `tui_${randomUUID4().slice(0, 8)}`,
1381
- user_id: auth?.userId ? parseInt(auth.userId) : 1
1382
- });
1383
- if (result.content) {
1384
- detailPanel.log(`${String(result.content)}
1385
- `);
1386
- } else if (result.error || result.message) {
1387
- detailPanel.log(`\uC624\uB958: ${result.error ?? result.message}
1388
- `);
1389
- } else {
1390
- detailPanel.log(JSON.stringify(result, null, 2).slice(0, 800) + "\n");
1391
- }
1392
- } catch (err) {
1393
- detailPanel.log(`\uC2E4\uD328: ${err.message}
1394
- `);
1395
- }
1396
- chatInput.label = " \u276F ";
1397
- chatInput.clearValue();
1398
- listPanel.focus();
1399
- screen.render();
1400
- });
1401
- } else {
1402
- const c = collections[index];
1403
- if (!c) return;
1404
- detailPanel.setContent(`${c.name} \uBB38\uC11C \uB85C\uB529...`);
1405
- screen.render();
1406
- try {
1407
- const { listDocuments: listDocuments2 } = await Promise.resolve().then(() => (init_document(), document_exports));
1408
- const docs = await listDocuments2(c.id?.toString());
1409
- if (!docs.length) {
1410
- detailPanel.setContent(`{bold}${c.name}{/bold}
1411
-
1412
- \uBB38\uC11C \uC5C6\uC74C`);
1413
- } else {
1414
- const docList = docs.map((d, i) => {
1415
- const name = d.name || d.file_name || "\uC774\uB984 \uC5C6\uC74C";
1416
- return ` ${i + 1}. ${name}`;
1417
- }).join("\n");
1418
- detailPanel.setContent(`{bold}${c.name}{/bold} \u2014 ${docs.length}\uAC1C \uBB38\uC11C
1419
-
1420
- ${docList}`);
1421
- }
1422
- } catch (err) {
1423
- detailPanel.setContent(`\uBB38\uC11C \uB85C\uB4DC \uC2E4\uD328: ${err.message}`);
1317
+ useInput((input, key) => {
1318
+ if (inputMode) {
1319
+ if (key.escape) {
1320
+ setInputMode(false);
1321
+ setRunTarget(null);
1424
1322
  }
1425
- screen.render();
1323
+ return;
1426
1324
  }
1427
- });
1428
- chatInput.on("submit", async (value) => {
1429
- if (!value.trim()) {
1430
- chatInput.clearValue();
1431
- listPanel.focus();
1432
- screen.render();
1325
+ if (input === "q" || key.ctrl && input === "c") {
1326
+ exit();
1433
1327
  return;
1434
1328
  }
1435
- detailPanel.log(`{white-fg}\u276F ${value}{/white-fg}`);
1436
- chatInput.clearValue();
1437
- screen.render();
1438
- try {
1439
- const p = getDefaultProvider();
1440
- if (!p) {
1441
- detailPanel.log("\uD504\uB85C\uBC14\uC774\uB354 \uBBF8\uC124\uC815");
1442
- chatInput.focus();
1443
- screen.render();
1444
- return;
1329
+ if (input === "r") {
1330
+ loadAll();
1331
+ return;
1332
+ }
1333
+ if (input === "i") {
1334
+ setInputMode(true);
1335
+ return;
1336
+ }
1337
+ if (input === "1") switchTab("workflows");
1338
+ else if (input === "2") switchTab("collections");
1339
+ else if (input === "3") switchTab("nodes");
1340
+ else if (input === "4") switchTab("prompts");
1341
+ else if (input === "5") switchTab("tools");
1342
+ const items = getListItems();
1343
+ if (key.upArrow) setSelected(Math.max(0, selected - 1));
1344
+ else if (key.downArrow) setSelected(Math.min(items.length - 1, selected + 1));
1345
+ else if (key.tab) switchTab(TABS[(TABS.findIndex((t) => t.key === tab) + 1) % TABS.length].key);
1346
+ if (key.return && items.length > 0) {
1347
+ if (tab === "workflows" && workflows[selected]) {
1348
+ setRunTarget(workflows[selected]);
1349
+ setInputMode(true);
1350
+ setStatusMsg(`${workflows[selected].name} \u2014 \uC785\uB825 \uD6C4 Enter\uB85C \uC2E4\uD589, Esc \uCDE8\uC18C`);
1445
1351
  }
1446
- const { createLLMClient: createLLMClient2, streamChat: streamChat2 } = await Promise.resolve().then(() => (init_llm(), llm_exports));
1447
- const client2 = createLLMClient2(p);
1448
- const result = await streamChat2(client2, p.model, [
1449
- { role: "system", content: "You are OPEN XGEN. Concise. Korean." },
1450
- { role: "user", content: value }
1451
- ]);
1452
- detailPanel.log(`${result.content || "(\uC751\uB2F5 \uC5C6\uC74C)"}
1453
- `);
1454
- } catch (err) {
1455
- detailPanel.log(`\uC624\uB958: ${err.message}
1456
- `);
1457
1352
  }
1458
- chatInput.focus();
1459
- screen.render();
1460
1353
  });
1461
- screen.key(["1"], () => {
1462
- activeTab = "workflows";
1463
- renderList();
1464
- renderHeader();
1465
- listPanel.focus();
1466
- });
1467
- screen.key(["2"], () => {
1468
- activeTab = "collections";
1469
- renderList();
1470
- renderHeader();
1471
- listPanel.focus();
1472
- });
1473
- screen.key(["tab"], () => {
1474
- if (screen.focused === listPanel) chatInput.focus();
1475
- else listPanel.focus();
1476
- screen.render();
1477
- });
1478
- screen.key(["i"], () => {
1479
- chatInput.focus();
1480
- screen.render();
1481
- });
1482
- screen.key(["r"], async () => {
1483
- await loadAll();
1484
- });
1485
- screen.key(["escape"], () => {
1486
- chatInput.label = " \u276F ";
1487
- listPanel.focus();
1488
- screen.render();
1489
- });
1490
- screen.key(["q", "C-c"], () => {
1491
- screen.destroy();
1492
- process.exit(0);
1493
- });
1494
- setInterval(async () => {
1495
- try {
1496
- await loadAll();
1497
- } catch {
1498
- }
1499
- }, 6e4);
1500
- await loadAll();
1501
- listPanel.focus();
1502
- screen.render();
1354
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1355
+ /* @__PURE__ */ jsx(Header, { tab, serverDisplay, model: provider?.model ?? "" }),
1356
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexGrow: 1, children: [
1357
+ /* @__PURE__ */ jsx(ListPanel, { items: getListItems(), selected }),
1358
+ /* @__PURE__ */ jsx(DetailPanel, { lines: detail })
1359
+ ] }),
1360
+ inputMode ? /* @__PURE__ */ jsx(
1361
+ InputBar,
1362
+ {
1363
+ value: inputValue,
1364
+ onChange: setInputValue,
1365
+ onSubmit: handleSubmit,
1366
+ placeholder: runTarget ? `${runTarget.name}\uC5D0 \uC785\uB825...` : "\uC785\uB825..."
1367
+ }
1368
+ ) : /* @__PURE__ */ jsx(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u276F i\uB97C \uB20C\uB7EC \uC785\uB825 \xB7 Enter\uB85C \uC2E4\uD589" }) }),
1369
+ /* @__PURE__ */ jsx(StatusBar, { message: loading ? "\uB85C\uB529..." : statusMsg })
1370
+ ] });
1503
1371
  }
1504
- var init_tui = __esm({
1505
- "src/dashboard/tui.ts"() {
1372
+ async function startInkDashboard() {
1373
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx(Dashboard, {}));
1374
+ await waitUntilExit();
1375
+ }
1376
+ var TABS;
1377
+ var init_InkDashboard = __esm({
1378
+ "src/dashboard/InkDashboard.tsx"() {
1506
1379
  "use strict";
1507
1380
  init_store();
1381
+ TABS = [
1382
+ { key: "workflows", label: "\uC6CC\uD06C\uD50C\uB85C\uC6B0", shortcut: "1" },
1383
+ { key: "collections", label: "\uCEEC\uB809\uC158", shortcut: "2" },
1384
+ { key: "nodes", label: "\uB178\uB4DC", shortcut: "3" },
1385
+ { key: "prompts", label: "\uD504\uB86C\uD504\uD2B8", shortcut: "4" },
1386
+ { key: "tools", label: "\uB3C4\uAD6C", shortcut: "5" }
1387
+ ];
1508
1388
  }
1509
1389
  });
1510
1390
 
@@ -2265,13 +2145,71 @@ init_provider();
2265
2145
 
2266
2146
  // src/commands/agent.ts
2267
2147
  init_store();
2268
- init_llm();
2269
2148
  import chalk12 from "chalk";
2270
2149
  import { createInterface as createInterface5 } from "readline";
2271
2150
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync5, readdirSync } from "fs";
2272
2151
  import { join as join4 } from "path";
2273
2152
  import { homedir as homedir2 } from "os";
2274
2153
 
2154
+ // src/agent/llm.ts
2155
+ import OpenAI2 from "openai";
2156
+ function createLLMClient(provider) {
2157
+ const opts = {
2158
+ apiKey: provider.apiKey || "ollama"
2159
+ };
2160
+ if (provider.baseUrl) {
2161
+ opts.baseURL = provider.baseUrl;
2162
+ }
2163
+ return new OpenAI2(opts);
2164
+ }
2165
+ async function streamChat(client2, model, messages, tools2, onDelta) {
2166
+ const params = {
2167
+ model,
2168
+ messages,
2169
+ stream: true,
2170
+ stream_options: { include_usage: true }
2171
+ };
2172
+ if (tools2 && tools2.length > 0) {
2173
+ params.tools = tools2;
2174
+ }
2175
+ const stream = await client2.chat.completions.create(params);
2176
+ let content = "";
2177
+ let usage = null;
2178
+ const toolCallMap = /* @__PURE__ */ new Map();
2179
+ for await (const chunk of stream) {
2180
+ if (chunk.usage) {
2181
+ usage = {
2182
+ promptTokens: chunk.usage.prompt_tokens ?? 0,
2183
+ completionTokens: chunk.usage.completion_tokens ?? 0,
2184
+ totalTokens: chunk.usage.total_tokens ?? 0
2185
+ };
2186
+ }
2187
+ const delta = chunk.choices[0]?.delta;
2188
+ if (!delta) continue;
2189
+ if (delta.content) {
2190
+ content += delta.content;
2191
+ onDelta?.(delta.content);
2192
+ }
2193
+ if (delta.tool_calls) {
2194
+ for (const tc of delta.tool_calls) {
2195
+ const idx = tc.index;
2196
+ if (!toolCallMap.has(idx)) {
2197
+ toolCallMap.set(idx, { id: tc.id ?? "", name: tc.function?.name ?? "", arguments: "" });
2198
+ }
2199
+ const entry = toolCallMap.get(idx);
2200
+ if (tc.id) entry.id = tc.id;
2201
+ if (tc.function?.name) entry.name = tc.function.name;
2202
+ if (tc.function?.arguments) entry.arguments += tc.function.arguments;
2203
+ }
2204
+ }
2205
+ }
2206
+ return {
2207
+ content,
2208
+ toolCalls: [...toolCallMap.values()],
2209
+ usage
2210
+ };
2211
+ }
2212
+
2275
2213
  // src/agent/tools/file-read.ts
2276
2214
  var file_read_exports = {};
2277
2215
  __export(file_read_exports, {
@@ -3374,8 +3312,8 @@ async function agentRepl() {
3374
3312
  mcpManager?.stopAll();
3375
3313
  rl.close();
3376
3314
  if (process.stdin.isTTY) process.stdin.setRawMode?.(false);
3377
- const { startTui: startTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
3378
- await startTui2();
3315
+ const { startInkDashboard: startInkDashboard2 } = await Promise.resolve().then(() => (init_InkDashboard(), InkDashboard_exports));
3316
+ await startInkDashboard2();
3379
3317
  return;
3380
3318
  }
3381
3319
  messages.push({ role: "user", content: input });