@sheepbun/yips 0.1.1
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/agent/commands/command-catalog.js +243 -0
- package/dist/agent/commands/commands.js +418 -0
- package/dist/agent/conductor.js +118 -0
- package/dist/agent/context/code-context.js +68 -0
- package/dist/agent/context/memory-store.js +159 -0
- package/dist/agent/context/session-store.js +211 -0
- package/dist/agent/protocol/tool-protocol.js +160 -0
- package/dist/agent/skills/skills.js +327 -0
- package/dist/agent/tools/tool-executor.js +415 -0
- package/dist/agent/tools/tool-safety.js +52 -0
- package/dist/app/index.js +35 -0
- package/dist/app/repl.js +105 -0
- package/dist/app/update-check.js +132 -0
- package/dist/app/version.js +51 -0
- package/dist/code-context.js +68 -0
- package/dist/colors.js +204 -0
- package/dist/command-catalog.js +242 -0
- package/dist/commands.js +350 -0
- package/dist/conductor.js +94 -0
- package/dist/config/config.js +335 -0
- package/dist/config/hooks.js +187 -0
- package/dist/config.js +335 -0
- package/dist/downloader-state.js +302 -0
- package/dist/downloader-ui.js +289 -0
- package/dist/gateway/adapters/discord.js +108 -0
- package/dist/gateway/adapters/formatting.js +96 -0
- package/dist/gateway/adapters/telegram.js +106 -0
- package/dist/gateway/adapters/types.js +2 -0
- package/dist/gateway/adapters/whatsapp.js +124 -0
- package/dist/gateway/auth-policy.js +66 -0
- package/dist/gateway/core.js +87 -0
- package/dist/gateway/headless-conductor.js +328 -0
- package/dist/gateway/message-router.js +23 -0
- package/dist/gateway/rate-limiter.js +48 -0
- package/dist/gateway/runtime/backend-policy.js +18 -0
- package/dist/gateway/runtime/discord-bot.js +104 -0
- package/dist/gateway/runtime/discord-main.js +69 -0
- package/dist/gateway/session-manager.js +77 -0
- package/dist/gateway/types.js +2 -0
- package/dist/hardware.js +92 -0
- package/dist/hooks.js +187 -0
- package/dist/index.js +34 -0
- package/dist/input-engine.js +250 -0
- package/dist/llama-client.js +227 -0
- package/dist/llama-server.js +620 -0
- package/dist/llm/llama-client.js +227 -0
- package/dist/llm/llama-server.js +620 -0
- package/dist/llm/token-counter.js +47 -0
- package/dist/memory-store.js +159 -0
- package/dist/messages.js +59 -0
- package/dist/model-downloader.js +382 -0
- package/dist/model-manager-state.js +118 -0
- package/dist/model-manager-ui.js +194 -0
- package/dist/model-manager.js +190 -0
- package/dist/models/hardware.js +92 -0
- package/dist/models/model-downloader.js +382 -0
- package/dist/models/model-manager.js +190 -0
- package/dist/prompt-box.js +78 -0
- package/dist/prompt-composer.js +498 -0
- package/dist/repl.js +105 -0
- package/dist/session-store.js +211 -0
- package/dist/spinner.js +76 -0
- package/dist/title-box.js +388 -0
- package/dist/token-counter.js +47 -0
- package/dist/tool-executor.js +415 -0
- package/dist/tool-protocol.js +121 -0
- package/dist/tool-safety.js +52 -0
- package/dist/tui/app.js +2553 -0
- package/dist/tui/startup.js +56 -0
- package/dist/tui-input-routing.js +53 -0
- package/dist/tui.js +51 -0
- package/dist/types/app-types.js +2 -0
- package/dist/types.js +2 -0
- package/dist/ui/colors.js +204 -0
- package/dist/ui/downloader/downloader-state.js +302 -0
- package/dist/ui/downloader/downloader-ui.js +289 -0
- package/dist/ui/input/input-engine.js +250 -0
- package/dist/ui/input/tui-input-routing.js +53 -0
- package/dist/ui/input/vt-session.js +168 -0
- package/dist/ui/messages.js +59 -0
- package/dist/ui/model-manager/model-manager-state.js +118 -0
- package/dist/ui/model-manager/model-manager-ui.js +194 -0
- package/dist/ui/prompt/prompt-box.js +78 -0
- package/dist/ui/prompt/prompt-composer.js +498 -0
- package/dist/ui/spinner.js +76 -0
- package/dist/ui/title-box.js +388 -0
- package/dist/ui/tui/app.js +6 -0
- package/dist/ui/tui/autocomplete.js +85 -0
- package/dist/ui/tui/constants.js +18 -0
- package/dist/ui/tui/history.js +29 -0
- package/dist/ui/tui/layout.js +341 -0
- package/dist/ui/tui/runtime-core.js +2584 -0
- package/dist/ui/tui/runtime-utils.js +53 -0
- package/dist/ui/tui/start-tui.js +54 -0
- package/dist/ui/tui/startup.js +56 -0
- package/dist/version.js +51 -0
- package/dist/vt-session.js +168 -0
- package/install.sh +457 -0
- package/package.json +128 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VirtualTerminalSession = void 0;
|
|
4
|
+
const node_path_1 = require("node:path");
|
|
5
|
+
const node_pty_1 = require("node-pty");
|
|
6
|
+
function escapeForSingleQuotedShell(value) {
|
|
7
|
+
return value.replace(/'/gu, "'\\''");
|
|
8
|
+
}
|
|
9
|
+
function stripAnsi(value) {
|
|
10
|
+
const esc = String.fromCharCode(27);
|
|
11
|
+
let output = "";
|
|
12
|
+
let index = 0;
|
|
13
|
+
while (index < value.length) {
|
|
14
|
+
if (value[index] === esc && value[index + 1] === "[") {
|
|
15
|
+
index += 2;
|
|
16
|
+
while (index < value.length) {
|
|
17
|
+
const code = value.charCodeAt(index);
|
|
18
|
+
const isFinalByte = code >= 0x40 && code <= 0x7e;
|
|
19
|
+
index += 1;
|
|
20
|
+
if (isFinalByte) {
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
output += value[index] ?? "";
|
|
27
|
+
index += 1;
|
|
28
|
+
}
|
|
29
|
+
return output;
|
|
30
|
+
}
|
|
31
|
+
function escapeRegExp(input) {
|
|
32
|
+
return input.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
33
|
+
}
|
|
34
|
+
class VirtualTerminalSession {
|
|
35
|
+
pty = null;
|
|
36
|
+
listeners = new Set();
|
|
37
|
+
lines = [];
|
|
38
|
+
currentLine = "";
|
|
39
|
+
ensureStarted(cols = 100, rows = 30) {
|
|
40
|
+
if (this.pty) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const shell = process.env["SHELL"]?.trim() || "/bin/bash";
|
|
44
|
+
this.pty = (0, node_pty_1.spawn)(shell, ["-i"], {
|
|
45
|
+
name: "xterm-256color",
|
|
46
|
+
cols,
|
|
47
|
+
rows,
|
|
48
|
+
cwd: process.cwd(),
|
|
49
|
+
env: { ...process.env }
|
|
50
|
+
});
|
|
51
|
+
this.pty.onData((chunk) => {
|
|
52
|
+
this.consumeDisplayChunk(chunk);
|
|
53
|
+
for (const listener of this.listeners) {
|
|
54
|
+
listener(chunk);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
onData(listener) {
|
|
59
|
+
this.listeners.add(listener);
|
|
60
|
+
return () => {
|
|
61
|
+
this.listeners.delete(listener);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
resize(cols, rows) {
|
|
65
|
+
this.ensureStarted(cols, rows);
|
|
66
|
+
this.pty?.resize(Math.max(20, cols), Math.max(8, rows));
|
|
67
|
+
}
|
|
68
|
+
write(data) {
|
|
69
|
+
this.ensureStarted();
|
|
70
|
+
this.pty?.write(data);
|
|
71
|
+
}
|
|
72
|
+
getDisplayLines(limit = 200) {
|
|
73
|
+
const all = this.currentLine.length > 0 ? [...this.lines, this.currentLine] : [...this.lines];
|
|
74
|
+
return all.slice(-Math.max(1, limit));
|
|
75
|
+
}
|
|
76
|
+
async runCommand(command, options) {
|
|
77
|
+
this.ensureStarted();
|
|
78
|
+
const cwd = (0, node_path_1.resolve)(options.cwd);
|
|
79
|
+
const commandTimeoutMs = Math.max(1_000, Math.min(120_000, options.timeoutMs));
|
|
80
|
+
const id = `${Date.now()}_${Math.floor(Math.random() * 1_000_000)}`;
|
|
81
|
+
const startMarker = `__YIPS_START_${id}__`;
|
|
82
|
+
const endPrefix = `__YIPS_END_${id}__:`;
|
|
83
|
+
const wrapped = [
|
|
84
|
+
`printf '%s\\n' '${startMarker}'`,
|
|
85
|
+
`cd '${escapeForSingleQuotedShell(cwd)}' && (${command})`,
|
|
86
|
+
`printf '%s%s\\n' '${endPrefix}' "$?"`
|
|
87
|
+
].join("; ");
|
|
88
|
+
return await new Promise((resolveResult) => {
|
|
89
|
+
let buffer = "";
|
|
90
|
+
let started = false;
|
|
91
|
+
let settled = false;
|
|
92
|
+
const cleanup = (result) => {
|
|
93
|
+
if (settled) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
settled = true;
|
|
97
|
+
off();
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
resolveResult(result);
|
|
100
|
+
};
|
|
101
|
+
const endPattern = new RegExp(`${escapeRegExp(endPrefix)}(\\d+)`, "u");
|
|
102
|
+
const off = this.onData((chunk) => {
|
|
103
|
+
if (settled) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
buffer += chunk;
|
|
107
|
+
if (!started) {
|
|
108
|
+
const startIndex = buffer.indexOf(startMarker);
|
|
109
|
+
if (startIndex < 0) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
started = true;
|
|
113
|
+
buffer = buffer.slice(startIndex + startMarker.length);
|
|
114
|
+
}
|
|
115
|
+
const endMatch = buffer.match(endPattern);
|
|
116
|
+
if (!endMatch || typeof endMatch.index !== "number") {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const outputRaw = buffer.slice(0, endMatch.index);
|
|
120
|
+
const output = stripAnsi(outputRaw).replace(/^\s+|\s+$/gu, "");
|
|
121
|
+
const exitCode = Number.parseInt(endMatch[1] ?? "1", 10);
|
|
122
|
+
cleanup({
|
|
123
|
+
exitCode: Number.isFinite(exitCode) ? exitCode : 1,
|
|
124
|
+
output,
|
|
125
|
+
timedOut: false
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
const timer = setTimeout(() => {
|
|
129
|
+
cleanup({
|
|
130
|
+
exitCode: 124,
|
|
131
|
+
output: "Command timed out.",
|
|
132
|
+
timedOut: true
|
|
133
|
+
});
|
|
134
|
+
}, commandTimeoutMs);
|
|
135
|
+
this.pty?.write(`${wrapped}\r`);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
dispose() {
|
|
139
|
+
this.listeners.clear();
|
|
140
|
+
this.pty?.kill();
|
|
141
|
+
this.pty = null;
|
|
142
|
+
this.lines = [];
|
|
143
|
+
this.currentLine = "";
|
|
144
|
+
}
|
|
145
|
+
consumeDisplayChunk(chunk) {
|
|
146
|
+
const plain = stripAnsi(chunk).replace(/\r\n/gu, "\n");
|
|
147
|
+
for (const char of Array.from(plain)) {
|
|
148
|
+
if (char === "\n") {
|
|
149
|
+
this.lines.push(this.currentLine);
|
|
150
|
+
this.currentLine = "";
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (char === "\r") {
|
|
154
|
+
this.currentLine = "";
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (char === "\b") {
|
|
158
|
+
this.currentLine = this.currentLine.slice(0, -1);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
this.currentLine += char;
|
|
162
|
+
}
|
|
163
|
+
if (this.lines.length > 1000) {
|
|
164
|
+
this.lines = this.lines.slice(-1000);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
exports.VirtualTerminalSession = VirtualTerminalSession;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Conversation message formatting with ANSI truecolor styling. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.formatUserMessage = formatUserMessage;
|
|
5
|
+
exports.formatAssistantMessage = formatAssistantMessage;
|
|
6
|
+
exports.formatErrorMessage = formatErrorMessage;
|
|
7
|
+
exports.formatWarningMessage = formatWarningMessage;
|
|
8
|
+
exports.formatSuccessMessage = formatSuccessMessage;
|
|
9
|
+
exports.formatDimMessage = formatDimMessage;
|
|
10
|
+
const colors_1 = require("#ui/colors");
|
|
11
|
+
function formatTimestamp(date) {
|
|
12
|
+
const hours = date.getHours();
|
|
13
|
+
const minutes = date.getMinutes().toString().padStart(2, "0");
|
|
14
|
+
const period = hours >= 12 ? "PM" : "AM";
|
|
15
|
+
const displayHours = hours % 12 || 12;
|
|
16
|
+
return `${displayHours}:${minutes} ${period}`;
|
|
17
|
+
}
|
|
18
|
+
function formatUserMessage(text) {
|
|
19
|
+
const lines = text.split("\n");
|
|
20
|
+
const first = (0, colors_1.colorText)(`>>> ${lines[0] ?? ""}`, colors_1.INPUT_PINK);
|
|
21
|
+
if (lines.length <= 1) {
|
|
22
|
+
return first;
|
|
23
|
+
}
|
|
24
|
+
const rest = lines.slice(1).map((line) => (0, colors_1.colorText)(line, colors_1.INPUT_PINK));
|
|
25
|
+
return [first, ...rest].join("\n");
|
|
26
|
+
}
|
|
27
|
+
function formatAssistantMessage(text, timestamp) {
|
|
28
|
+
const time = timestamp ?? new Date();
|
|
29
|
+
const timestampPlain = `[${formatTimestamp(time)}]`;
|
|
30
|
+
const namePlain = "Yips";
|
|
31
|
+
const prefixPlain = `${timestampPlain} ${namePlain}: `;
|
|
32
|
+
const timeStr = (0, colors_1.colorText)(timestampPlain, colors_1.GRADIENT_BLUE);
|
|
33
|
+
const name = (0, colors_1.horizontalGradient)(namePlain, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW);
|
|
34
|
+
const colon = (0, colors_1.colorText)(":", colors_1.GRADIENT_BLUE);
|
|
35
|
+
const lines = text.split("\n");
|
|
36
|
+
const firstLine = lines[0] ?? "";
|
|
37
|
+
const firstBody = (0, colors_1.horizontalGradient)(firstLine, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW);
|
|
38
|
+
const first = `${timeStr} ${name}${colon} ${firstBody}`;
|
|
39
|
+
if (lines.length <= 1) {
|
|
40
|
+
return first;
|
|
41
|
+
}
|
|
42
|
+
const indent = " ".repeat(prefixPlain.length);
|
|
43
|
+
const rest = lines
|
|
44
|
+
.slice(1)
|
|
45
|
+
.map((line) => `${indent}${(0, colors_1.horizontalGradient)(line, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW)}`);
|
|
46
|
+
return [first, ...rest].join("\n");
|
|
47
|
+
}
|
|
48
|
+
function formatErrorMessage(text) {
|
|
49
|
+
return (0, colors_1.colorText)(text, colors_1.ERROR_RED);
|
|
50
|
+
}
|
|
51
|
+
function formatWarningMessage(text) {
|
|
52
|
+
return (0, colors_1.colorText)(text, colors_1.WARNING_YELLOW);
|
|
53
|
+
}
|
|
54
|
+
function formatSuccessMessage(text) {
|
|
55
|
+
return (0, colors_1.colorText)(text, colors_1.SUCCESS_GREEN);
|
|
56
|
+
}
|
|
57
|
+
function formatDimMessage(text) {
|
|
58
|
+
return (0, colors_1.colorText)(text, colors_1.DIM_GRAY);
|
|
59
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createModelManagerState = createModelManagerState;
|
|
4
|
+
exports.setModelManagerLoading = setModelManagerLoading;
|
|
5
|
+
exports.setModelManagerError = setModelManagerError;
|
|
6
|
+
exports.setModelManagerModels = setModelManagerModels;
|
|
7
|
+
exports.setModelManagerSearchQuery = setModelManagerSearchQuery;
|
|
8
|
+
exports.moveModelManagerSelection = moveModelManagerSelection;
|
|
9
|
+
exports.removeModelById = removeModelById;
|
|
10
|
+
exports.getSelectedModel = getSelectedModel;
|
|
11
|
+
const model_manager_1 = require("#models/model-manager");
|
|
12
|
+
function createModelManagerState(memory) {
|
|
13
|
+
return {
|
|
14
|
+
isOpen: true,
|
|
15
|
+
searchQuery: "",
|
|
16
|
+
allModels: [],
|
|
17
|
+
models: [],
|
|
18
|
+
selectedModelIndex: 0,
|
|
19
|
+
scrollOffset: 0,
|
|
20
|
+
phase: "idle",
|
|
21
|
+
loading: false,
|
|
22
|
+
loadingMessage: "Loading models...",
|
|
23
|
+
errorMessage: "",
|
|
24
|
+
ramGb: memory.ramGb,
|
|
25
|
+
vramGb: memory.vramGb,
|
|
26
|
+
totalMemoryGb: memory.totalMemoryGb
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function clampSelection(state) {
|
|
30
|
+
const max = Math.max(0, state.models.length - 1);
|
|
31
|
+
const selectedModelIndex = Math.min(Math.max(0, state.selectedModelIndex), max);
|
|
32
|
+
const scrollOffset = Math.min(Math.max(0, state.scrollOffset), Math.max(0, max));
|
|
33
|
+
return {
|
|
34
|
+
...state,
|
|
35
|
+
selectedModelIndex,
|
|
36
|
+
scrollOffset
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function setModelManagerLoading(state, message) {
|
|
40
|
+
return {
|
|
41
|
+
...state,
|
|
42
|
+
phase: "loading",
|
|
43
|
+
loading: true,
|
|
44
|
+
loadingMessage: message,
|
|
45
|
+
errorMessage: ""
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function setModelManagerError(state, message) {
|
|
49
|
+
return {
|
|
50
|
+
...state,
|
|
51
|
+
phase: "error",
|
|
52
|
+
loading: false,
|
|
53
|
+
errorMessage: message
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function setModelManagerModels(state, models) {
|
|
57
|
+
const filtered = (0, model_manager_1.filterModels)(models, state.searchQuery);
|
|
58
|
+
return clampSelection({
|
|
59
|
+
...state,
|
|
60
|
+
allModels: models,
|
|
61
|
+
models: filtered,
|
|
62
|
+
phase: "idle",
|
|
63
|
+
loading: false,
|
|
64
|
+
errorMessage: "",
|
|
65
|
+
selectedModelIndex: filtered.length > 0 ? Math.min(state.selectedModelIndex, filtered.length - 1) : 0,
|
|
66
|
+
scrollOffset: 0
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function setModelManagerSearchQuery(state, searchQuery) {
|
|
70
|
+
const models = (0, model_manager_1.filterModels)(state.allModels, searchQuery);
|
|
71
|
+
return clampSelection({
|
|
72
|
+
...state,
|
|
73
|
+
searchQuery,
|
|
74
|
+
models,
|
|
75
|
+
selectedModelIndex: models.length > 0 ? Math.min(state.selectedModelIndex, models.length - 1) : 0,
|
|
76
|
+
scrollOffset: 0,
|
|
77
|
+
phase: state.phase === "error" ? "idle" : state.phase,
|
|
78
|
+
errorMessage: state.phase === "error" ? "" : state.errorMessage
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function moveModelManagerSelection(state, delta, viewportSize) {
|
|
82
|
+
if (state.models.length === 0) {
|
|
83
|
+
return state;
|
|
84
|
+
}
|
|
85
|
+
const max = state.models.length - 1;
|
|
86
|
+
const nextIndex = Math.min(max, Math.max(0, state.selectedModelIndex + delta));
|
|
87
|
+
let nextScroll = state.scrollOffset;
|
|
88
|
+
if (nextIndex < nextScroll) {
|
|
89
|
+
nextScroll = nextIndex;
|
|
90
|
+
}
|
|
91
|
+
else if (nextIndex >= nextScroll + viewportSize) {
|
|
92
|
+
nextScroll = nextIndex - (viewportSize - 1);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
...state,
|
|
96
|
+
selectedModelIndex: nextIndex,
|
|
97
|
+
scrollOffset: Math.max(0, nextScroll)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function removeModelById(state, id) {
|
|
101
|
+
const allModels = state.allModels.filter((model) => model.id !== id);
|
|
102
|
+
const models = (0, model_manager_1.filterModels)(allModels, state.searchQuery);
|
|
103
|
+
return clampSelection({
|
|
104
|
+
...state,
|
|
105
|
+
allModels,
|
|
106
|
+
models,
|
|
107
|
+
selectedModelIndex: models.length > 0 ? Math.min(state.selectedModelIndex, models.length - 1) : 0,
|
|
108
|
+
phase: "idle",
|
|
109
|
+
loading: false,
|
|
110
|
+
errorMessage: ""
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function getSelectedModel(state) {
|
|
114
|
+
if (state.models.length === 0) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return state.models[state.selectedModelIndex] ?? null;
|
|
118
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderModelManagerLines = renderModelManagerLines;
|
|
4
|
+
const colors_1 = require("#ui/colors");
|
|
5
|
+
const TAB_INACTIVE = { r: 0x88, g: 0x88, b: 0x88 };
|
|
6
|
+
const BLACK = { r: 0x00, g: 0x00, b: 0x00 };
|
|
7
|
+
const CURRENT_MODEL_BLUE = { r: 0x89, g: 0xcf, b: 0xf0 };
|
|
8
|
+
const FOCUS_ACCENT_BG = { r: 0xff, g: 0xcc, b: 0xff };
|
|
9
|
+
const MODEL_MANAGER_BODY_ROWS = 12;
|
|
10
|
+
const ANSI_BOLD_ON = "\u001b[1m";
|
|
11
|
+
const ANSI_BOLD_OFF = "\u001b[22m";
|
|
12
|
+
function charLength(text) {
|
|
13
|
+
return Array.from((0, colors_1.stripAnsi)(text)).length;
|
|
14
|
+
}
|
|
15
|
+
function fitLeft(text, width) {
|
|
16
|
+
const chars = Array.from(text);
|
|
17
|
+
if (chars.length <= width) {
|
|
18
|
+
return text;
|
|
19
|
+
}
|
|
20
|
+
if (width <= 3) {
|
|
21
|
+
return chars.slice(0, width).join("");
|
|
22
|
+
}
|
|
23
|
+
return `${chars.slice(0, width - 3).join("")}...`;
|
|
24
|
+
}
|
|
25
|
+
function fitRight(text, width) {
|
|
26
|
+
const chars = Array.from(text);
|
|
27
|
+
if (chars.length <= width) {
|
|
28
|
+
return text;
|
|
29
|
+
}
|
|
30
|
+
return chars.slice(chars.length - width).join("");
|
|
31
|
+
}
|
|
32
|
+
function makeBorderTop(width) {
|
|
33
|
+
if (width <= 1)
|
|
34
|
+
return "╭";
|
|
35
|
+
const prefix = "╭─── ";
|
|
36
|
+
const titleBrand = "Yips";
|
|
37
|
+
const titleDetail = " Model Manager";
|
|
38
|
+
const titleTail = " ";
|
|
39
|
+
const prefixLen = charLength(prefix);
|
|
40
|
+
const titleBrandLen = charLength(titleBrand);
|
|
41
|
+
const titleDetailLen = charLength(titleDetail);
|
|
42
|
+
const titleTailLen = charLength(titleTail);
|
|
43
|
+
const plainTitleLen = prefixLen + titleBrandLen + titleDetailLen + titleTailLen;
|
|
44
|
+
const fill = "─".repeat(Math.max(0, width - plainTitleLen - 1));
|
|
45
|
+
const fillOffset = prefixLen + titleBrandLen + titleDetailLen + titleTailLen;
|
|
46
|
+
const cornerOffset = width - 1;
|
|
47
|
+
return `${(0, colors_1.horizontalGradientAtOffset)(prefix, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, 0, width)}${ANSI_BOLD_ON}${(0, colors_1.horizontalGradient)(titleBrand, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW)}${(0, colors_1.colorText)(titleDetail, colors_1.GRADIENT_BLUE)}${ANSI_BOLD_OFF}${(0, colors_1.horizontalGradientAtOffset)(titleTail, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, prefixLen + titleBrandLen + titleDetailLen, width)}${(0, colors_1.horizontalGradientAtOffset)(fill, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, fillOffset, width)}${(0, colors_1.horizontalGradientAtOffset)("╮", colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, cornerOffset, width)}`;
|
|
48
|
+
}
|
|
49
|
+
function makeBorderBottom(width) {
|
|
50
|
+
if (width <= 1)
|
|
51
|
+
return "╰";
|
|
52
|
+
const mid = "─".repeat(Math.max(0, width - 2));
|
|
53
|
+
return `${(0, colors_1.colorText)("╰", colors_1.GRADIENT_PINK)}${(0, colors_1.horizontalGradient)(mid, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW)}${(0, colors_1.colorText)("╯", colors_1.GRADIENT_YELLOW)}`;
|
|
54
|
+
}
|
|
55
|
+
function lineWithBorders(inner, innerWidth) {
|
|
56
|
+
let fitted = inner;
|
|
57
|
+
if (charLength(fitted) > innerWidth) {
|
|
58
|
+
fitted = fitLeft((0, colors_1.stripAnsi)(inner), innerWidth);
|
|
59
|
+
}
|
|
60
|
+
const padding = " ".repeat(Math.max(0, innerWidth - charLength(fitted)));
|
|
61
|
+
return `${(0, colors_1.colorText)("│", colors_1.GRADIENT_PINK)}${fitted}${padding}${(0, colors_1.colorText)("│", colors_1.GRADIENT_YELLOW)}`;
|
|
62
|
+
}
|
|
63
|
+
function highlightedRow(row) {
|
|
64
|
+
const chars = Array.from(row);
|
|
65
|
+
if (chars.length === 0)
|
|
66
|
+
return row;
|
|
67
|
+
const first = (0, colors_1.bgColorText)(chars[0] ?? "", FOCUS_ACCENT_BG, BLACK);
|
|
68
|
+
const rest = chars.slice(1).join("");
|
|
69
|
+
if (rest.length === 0) {
|
|
70
|
+
return first;
|
|
71
|
+
}
|
|
72
|
+
return `${first}${(0, colors_1.horizontalGradientBackground)(rest, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, BLACK)}`;
|
|
73
|
+
}
|
|
74
|
+
function toSizeCell(sizeGb) {
|
|
75
|
+
return `${sizeGb.toFixed(1)}G`;
|
|
76
|
+
}
|
|
77
|
+
function getFileName(modelId) {
|
|
78
|
+
const parts = modelId.split("/");
|
|
79
|
+
return parts[parts.length - 1] ?? modelId;
|
|
80
|
+
}
|
|
81
|
+
function buildModelRows(state, contentWidth, rowCount, currentModel) {
|
|
82
|
+
if (state.models.length === 0) {
|
|
83
|
+
return [(0, colors_1.colorText)("No models found in local models directory.", colors_1.WARNING_YELLOW)];
|
|
84
|
+
}
|
|
85
|
+
const markerWidth = 3;
|
|
86
|
+
const separators = 4 * 3;
|
|
87
|
+
const minBackendWidth = 9;
|
|
88
|
+
const minProviderWidth = 14;
|
|
89
|
+
const minNameWidth = 16;
|
|
90
|
+
const minFileWidth = 18;
|
|
91
|
+
const minSizeWidth = 7;
|
|
92
|
+
let backendWidth = minBackendWidth;
|
|
93
|
+
let providerWidth = minProviderWidth;
|
|
94
|
+
let nameWidth = minNameWidth;
|
|
95
|
+
let fileWidth = minFileWidth;
|
|
96
|
+
let sizeWidth = minSizeWidth;
|
|
97
|
+
const minimumTotalColumnWidth = minBackendWidth + minProviderWidth + minNameWidth + minFileWidth + minSizeWidth;
|
|
98
|
+
const availableTotalColumnWidth = Math.max(5, contentWidth - markerWidth - separators);
|
|
99
|
+
if (availableTotalColumnWidth >= minimumTotalColumnWidth) {
|
|
100
|
+
const extra = availableTotalColumnWidth - minimumTotalColumnWidth;
|
|
101
|
+
const growBy = Math.floor(extra / 5);
|
|
102
|
+
const remainder = extra % 5;
|
|
103
|
+
backendWidth += growBy + (remainder > 0 ? 1 : 0);
|
|
104
|
+
providerWidth += growBy + (remainder > 1 ? 1 : 0);
|
|
105
|
+
nameWidth += growBy + (remainder > 2 ? 1 : 0);
|
|
106
|
+
fileWidth += growBy + (remainder > 3 ? 1 : 0);
|
|
107
|
+
sizeWidth += growBy;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const minAllowed = [4, 6, 8, 8, 4];
|
|
111
|
+
const widths = [
|
|
112
|
+
backendWidth,
|
|
113
|
+
providerWidth,
|
|
114
|
+
nameWidth,
|
|
115
|
+
fileWidth,
|
|
116
|
+
sizeWidth
|
|
117
|
+
];
|
|
118
|
+
const reduceOrder = [3, 2, 1, 0, 4];
|
|
119
|
+
let deficit = minimumTotalColumnWidth - availableTotalColumnWidth;
|
|
120
|
+
while (deficit > 0) {
|
|
121
|
+
let reducedThisPass = false;
|
|
122
|
+
for (const index of reduceOrder) {
|
|
123
|
+
if (deficit <= 0) {
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
if (widths[index] > minAllowed[index]) {
|
|
127
|
+
widths[index] -= 1;
|
|
128
|
+
deficit -= 1;
|
|
129
|
+
reducedThisPass = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!reducedThisPass) {
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
[backendWidth, providerWidth, nameWidth, fileWidth, sizeWidth] = widths;
|
|
137
|
+
}
|
|
138
|
+
const rows = [];
|
|
139
|
+
const header = ` ${"Backend".padEnd(backendWidth, " ")} | ${"Provider".padEnd(providerWidth, " ")} | ${"Name".padEnd(nameWidth, " ")} | ${"File".padEnd(fileWidth, " ")} | ${"Size".padStart(sizeWidth, " ")}`;
|
|
140
|
+
rows.push((0, colors_1.horizontalGradient)(header, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW));
|
|
141
|
+
const visibleRowCount = Math.max(0, rowCount - 1);
|
|
142
|
+
const start = state.scrollOffset;
|
|
143
|
+
const end = Math.min(state.models.length, start + visibleRowCount);
|
|
144
|
+
for (let index = start; index < end; index++) {
|
|
145
|
+
const model = state.models[index];
|
|
146
|
+
if (!model)
|
|
147
|
+
continue;
|
|
148
|
+
const selected = index === state.selectedModelIndex;
|
|
149
|
+
const current = model.id === currentModel;
|
|
150
|
+
const prefix = `${selected ? ">" : " "}${current ? "*" : " "} `;
|
|
151
|
+
const backend = fitLeft(model.friendlyBackend, backendWidth).padEnd(backendWidth, " ");
|
|
152
|
+
const provider = fitLeft(model.host, providerWidth).padEnd(providerWidth, " ");
|
|
153
|
+
const name = fitLeft(model.friendlyName, nameWidth).padEnd(nameWidth, " ");
|
|
154
|
+
const file = fitLeft(getFileName(model.id), fileWidth).padEnd(fileWidth, " ");
|
|
155
|
+
const sizeCell = toSizeCell(model.sizeGb).padStart(sizeWidth, " ");
|
|
156
|
+
const row = `${prefix}${backend} | ${provider} | ${name} | ${file} | ${sizeCell}`;
|
|
157
|
+
if (selected) {
|
|
158
|
+
rows.push(highlightedRow(row));
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
rows.push(current ? (0, colors_1.colorText)(row, CURRENT_MODEL_BLUE) : row);
|
|
162
|
+
}
|
|
163
|
+
return rows;
|
|
164
|
+
}
|
|
165
|
+
function fillRows(rows, rowCount) {
|
|
166
|
+
if (rows.length >= rowCount) {
|
|
167
|
+
return rows.slice(0, rowCount);
|
|
168
|
+
}
|
|
169
|
+
return [...rows, ...new Array(rowCount - rows.length).fill("")];
|
|
170
|
+
}
|
|
171
|
+
function renderModelManagerLines(options) {
|
|
172
|
+
const width = Math.max(20, options.width);
|
|
173
|
+
const innerWidth = Math.max(1, width - 2);
|
|
174
|
+
const rows = [];
|
|
175
|
+
rows.push(makeBorderTop(width));
|
|
176
|
+
const leftLabel = (0, colors_1.colorText)(" Local ", TAB_INACTIVE);
|
|
177
|
+
const specText = `RAM: ${options.state.ramGb.toFixed(1)}GB | VRAM: ${options.state.vramGb.toFixed(1)}GB`;
|
|
178
|
+
const gap = Math.max(1, innerWidth - charLength(leftLabel) - charLength(specText));
|
|
179
|
+
rows.push(lineWithBorders(`${leftLabel}${" ".repeat(gap)}${fitRight(specText, Math.max(1, innerWidth - charLength(leftLabel) - 1))}`, innerWidth));
|
|
180
|
+
let bodyRows;
|
|
181
|
+
if (options.state.phase === "loading") {
|
|
182
|
+
bodyRows = fillRows([`Loading: ${options.state.loadingMessage}`], MODEL_MANAGER_BODY_ROWS);
|
|
183
|
+
}
|
|
184
|
+
else if (options.state.phase === "error" && options.state.errorMessage.length > 0) {
|
|
185
|
+
bodyRows = fillRows([`Error: ${options.state.errorMessage}`], MODEL_MANAGER_BODY_ROWS);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
bodyRows = fillRows(buildModelRows(options.state, innerWidth, MODEL_MANAGER_BODY_ROWS, options.currentModel), MODEL_MANAGER_BODY_ROWS);
|
|
189
|
+
}
|
|
190
|
+
rows.push(...bodyRows.map((row) => lineWithBorders(row, innerWidth)));
|
|
191
|
+
rows.push(lineWithBorders((0, colors_1.horizontalGradient)("[Enter] Select [↑/↓] Move [Del] Delete Local [T] Downloader [Esc] Close", colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW), innerWidth));
|
|
192
|
+
rows.push(makeBorderBottom(width));
|
|
193
|
+
return rows;
|
|
194
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Prompt box layout helpers for the bottom input area. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.buildPromptBoxFrame = buildPromptBoxFrame;
|
|
5
|
+
exports.buildPromptBoxLayout = buildPromptBoxLayout;
|
|
6
|
+
const DEFAULT_PROMPT = ">>> ";
|
|
7
|
+
function toChars(text) {
|
|
8
|
+
return Array.from(text);
|
|
9
|
+
}
|
|
10
|
+
function charLength(text) {
|
|
11
|
+
return toChars(text).length;
|
|
12
|
+
}
|
|
13
|
+
function takeLeftChars(text, maxWidth) {
|
|
14
|
+
if (maxWidth <= 0)
|
|
15
|
+
return "";
|
|
16
|
+
const chars = toChars(text);
|
|
17
|
+
if (chars.length <= maxWidth)
|
|
18
|
+
return text;
|
|
19
|
+
return chars.slice(0, maxWidth).join("");
|
|
20
|
+
}
|
|
21
|
+
function takeRightChars(text, maxWidth) {
|
|
22
|
+
if (maxWidth <= 0)
|
|
23
|
+
return "";
|
|
24
|
+
const chars = toChars(text);
|
|
25
|
+
if (chars.length <= maxWidth)
|
|
26
|
+
return text;
|
|
27
|
+
return chars.slice(chars.length - maxWidth).join("");
|
|
28
|
+
}
|
|
29
|
+
function buildBorderLine(width, left, right, fill) {
|
|
30
|
+
if (width <= 0)
|
|
31
|
+
return "";
|
|
32
|
+
if (width === 1)
|
|
33
|
+
return left;
|
|
34
|
+
return `${left}${fill.repeat(Math.max(0, width - 2))}${right}`;
|
|
35
|
+
}
|
|
36
|
+
function buildContentLine(width, left, right, inner) {
|
|
37
|
+
if (width <= 0)
|
|
38
|
+
return "";
|
|
39
|
+
if (width === 1)
|
|
40
|
+
return left;
|
|
41
|
+
return `${left}${inner}${right}`;
|
|
42
|
+
}
|
|
43
|
+
function normalizeStatusText(statusText) {
|
|
44
|
+
const trimmed = statusText.trim();
|
|
45
|
+
return trimmed.length > 0 ? ` ${trimmed} ` : " ";
|
|
46
|
+
}
|
|
47
|
+
function buildPromptBoxFrame(width, statusText, middleRowCount) {
|
|
48
|
+
const safeWidth = Math.max(0, width);
|
|
49
|
+
const innerWidth = Math.max(0, safeWidth - 2);
|
|
50
|
+
const rowCount = Math.max(1, middleRowCount);
|
|
51
|
+
const middleInner = " ".repeat(innerWidth);
|
|
52
|
+
const normalizedStatus = normalizeStatusText(statusText);
|
|
53
|
+
// Keep the right edge on narrow terminals so model suffixes remain visible.
|
|
54
|
+
const clippedStatus = takeRightChars(normalizedStatus, innerWidth);
|
|
55
|
+
const clippedStatusWidth = charLength(clippedStatus);
|
|
56
|
+
const fill = "─".repeat(Math.max(0, innerWidth - clippedStatusWidth));
|
|
57
|
+
const bottomInner = `${fill}${clippedStatus}`;
|
|
58
|
+
return {
|
|
59
|
+
top: buildBorderLine(safeWidth, "╭", "╮", "─"),
|
|
60
|
+
middleRows: Array.from({ length: rowCount }, () => buildContentLine(safeWidth, "│", "│", middleInner)),
|
|
61
|
+
bottom: buildContentLine(safeWidth, "╰", "╯", bottomInner),
|
|
62
|
+
innerWidth
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function buildPromptBoxLayout(width, statusText, promptText = DEFAULT_PROMPT) {
|
|
66
|
+
const frame = buildPromptBoxFrame(width, statusText, 1);
|
|
67
|
+
const prompt = takeLeftChars(promptText, frame.innerWidth);
|
|
68
|
+
const promptPadding = " ".repeat(Math.max(0, frame.innerWidth - charLength(prompt)));
|
|
69
|
+
const middleInner = `${prompt}${promptPadding}`;
|
|
70
|
+
return {
|
|
71
|
+
top: frame.top,
|
|
72
|
+
middle: buildContentLine(Math.max(0, width), "│", "│", middleInner),
|
|
73
|
+
bottom: frame.bottom,
|
|
74
|
+
innerWidth: frame.innerWidth,
|
|
75
|
+
prompt,
|
|
76
|
+
promptPadding
|
|
77
|
+
};
|
|
78
|
+
}
|