@xp266/dshtui 0.1.4 → 0.1.5
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/bin/dshtui.js +7 -1
- package/lib/contract/index.d.mts +1 -1
- package/lib/dialog.d.mts +1 -1
- package/lib/dialog.mjs +1 -1
- package/lib/{index-DqnaSXqP.d.mts → index-D4_nxuuR.d.mts} +3 -13
- package/lib/index.mjs +123 -123
- package/lib/{surface-DdxzIgIY.mjs → surface-B7w3R_kh.mjs} +2 -2
- package/package.json +1 -1
- package/src/contract/index.ts +1 -1
- package/src/model/message.ts +2 -13
- package/src/theme.ts +2 -2
- package/src/ui/app.tsx +4 -10
- package/src/ui/home-logo.ts +34 -27
- package/src/ui/message/layout.ts +0 -28
- package/src/ui/message/message-list.tsx +41 -1
- package/src/ui/message/warmup.ts +0 -1
package/bin/dshtui.js
CHANGED
|
@@ -43,6 +43,12 @@ function runDsh(args) {
|
|
|
43
43
|
: spawnSync('dsh', args, { stdio: 'inherit' })
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function probeDsh() {
|
|
47
|
+
return windows
|
|
48
|
+
? spawnSync(['dsh', '--version'].map(quote).join(' '), { stdio: 'ignore', shell: true })
|
|
49
|
+
: spawnSync('dsh', ['--version'], { stdio: 'ignore' })
|
|
50
|
+
}
|
|
51
|
+
|
|
46
52
|
function readJson(path) {
|
|
47
53
|
try {
|
|
48
54
|
return JSON.parse(readFileSync(path, 'utf8'))
|
|
@@ -107,7 +113,7 @@ function resolveProfile() {
|
|
|
107
113
|
|
|
108
114
|
const profile = resolveProfile()
|
|
109
115
|
const profileDir = join(profilesDir, profile)
|
|
110
|
-
const dshProbe =
|
|
116
|
+
const dshProbe = probeDsh()
|
|
111
117
|
if (dshProbe.error !== undefined || dshProbe.status !== 0) {
|
|
112
118
|
fail('the dsh CLI was not found on PATH; install it with: npm install -g @deepseek-ai/dsh')
|
|
113
119
|
}
|
package/lib/contract/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $ as ToolCallPresentation, A as MessageViewContribution, At as TuiToolsFace, B as PointerSession, Bt as MouseEventData, C as MarkdownBlockContext, Ct as TuiPaletteFace, D as MessageRendererContribution, Dt as TuiSessionSummary, E as MarkdownInlineContribution, Et as TuiServicesFace, F as PasteContext, Ft as WindowCommandSpec, G as SelectionExtractContext, H as PrismGrammarContribution, Ht as MessageKind, I as PasteHandlerContribution, It as WindowContribution, J as SpecialFieldCreateInput, K as SelectionTransformerContribution, L as PasteResult, Lt as WindowHandle, M as OverlayContribution, Mt as TuiWindowsFace, N as POINTER_PLUGIN_ORDER, Nt as WidgetDef, O as MessageRendererResult, Ot as TuiStartupFace, P as PaintArgs, Pt as WidgetKeyApi, Q as ThemePaletteContribution, R as PointerEventFrame, Rt as WindowProps, S as KeyBindingContribution, St as TuiMarkdownFace, T as MarkdownInlineContext, Tt as TuiSelectionFace, U as SelectionClipboardContribution, Ut as LineSelection, V as PointerUiContext, Vt as MouseEventType, W as SelectionDomainContribution, Wt as PendingImage, X as SpecialFieldStyle, Y as SpecialFieldFactory, Z as StatusLineContribution, _ as InputStatusContext, _t as TuiHomeLogoFace, a as CommandDef, at as ToolViewContribution, b as InteractionPanelComponentProps, bt as TuiKey, c as CustomMessageSeed, ct as TuiChatFace, d as DiffLine, dt as TuiCommandsFace, et as ToolReadLine, f as DiffLineKind, ft as TuiComposerFace, g as HomeLogoContribution, gt as TuiHintFace, h as HintMatcherContribution, ht as TuiFieldsFace, i as ClipboardBackendContribution, it as ToolViewCallContext, j as MessageViewRender, jt as TuiWidgetsFace, k as MessageViewContext, kt as TuiStartupSink, l as DialogItemKinds, lt as TuiChromeFace, m as HintEntry, mt as TuiExtensionPoint, n as ClickActions, nt as ToolResultPresentation, o as ComposerInsertSpec, ot as ToolViewDiff, p as HintArgsContribution, pt as TuiContentFace, q as SpecialFieldContribution, r as ClickHit, rt as ToolResultView, s as CustomMessage, st as ToolViewResultContext, t as ChatNodeDefinition, tt as ToolReadView, u as DiffHunk, ut as TuiClipboardFace, v as InputStatusContribution, vt as TuiInteractionPanelsFace, w as MarkdownBlockContribution, wt as TuiPointerFace, x as InteractionPanelContribution, xt as TuiKeymapFace, y as InputStatusPart, yt as TuiInteractionsFace, z as PointerHandlerContribution, zt as ScrollSnapshot } from "../index-
|
|
1
|
+
import { $ as ToolCallPresentation, A as MessageViewContribution, At as TuiToolsFace, B as PointerSession, Bt as MouseEventData, C as MarkdownBlockContext, Ct as TuiPaletteFace, D as MessageRendererContribution, Dt as TuiSessionSummary, E as MarkdownInlineContribution, Et as TuiServicesFace, F as PasteContext, Ft as WindowCommandSpec, G as SelectionExtractContext, H as PrismGrammarContribution, Ht as MessageKind, I as PasteHandlerContribution, It as WindowContribution, J as SpecialFieldCreateInput, K as SelectionTransformerContribution, L as PasteResult, Lt as WindowHandle, M as OverlayContribution, Mt as TuiWindowsFace, N as POINTER_PLUGIN_ORDER, Nt as WidgetDef, O as MessageRendererResult, Ot as TuiStartupFace, P as PaintArgs, Pt as WidgetKeyApi, Q as ThemePaletteContribution, R as PointerEventFrame, Rt as WindowProps, S as KeyBindingContribution, St as TuiMarkdownFace, T as MarkdownInlineContext, Tt as TuiSelectionFace, U as SelectionClipboardContribution, Ut as LineSelection, V as PointerUiContext, Vt as MouseEventType, W as SelectionDomainContribution, Wt as PendingImage, X as SpecialFieldStyle, Y as SpecialFieldFactory, Z as StatusLineContribution, _ as InputStatusContext, _t as TuiHomeLogoFace, a as CommandDef, at as ToolViewContribution, b as InteractionPanelComponentProps, bt as TuiKey, c as CustomMessageSeed, ct as TuiChatFace, d as DiffLine, dt as TuiCommandsFace, et as ToolReadLine, f as DiffLineKind, ft as TuiComposerFace, g as HomeLogoContribution, gt as TuiHintFace, h as HintMatcherContribution, ht as TuiFieldsFace, i as ClipboardBackendContribution, it as ToolViewCallContext, j as MessageViewRender, jt as TuiWidgetsFace, k as MessageViewContext, kt as TuiStartupSink, l as DialogItemKinds, lt as TuiChromeFace, m as HintEntry, mt as TuiExtensionPoint, n as ClickActions, nt as ToolResultPresentation, o as ComposerInsertSpec, ot as ToolViewDiff, p as HintArgsContribution, pt as TuiContentFace, q as SpecialFieldContribution, r as ClickHit, rt as ToolResultView, s as CustomMessage, st as ToolViewResultContext, t as ChatNodeDefinition, tt as ToolReadView, u as DiffHunk, ut as TuiClipboardFace, v as InputStatusContribution, vt as TuiInteractionPanelsFace, w as MarkdownBlockContribution, wt as TuiPointerFace, x as InteractionPanelContribution, xt as TuiKeymapFace, y as InputStatusPart, yt as TuiInteractionsFace, z as PointerHandlerContribution, zt as ScrollSnapshot } from "../index-D4_nxuuR.mjs";
|
|
2
2
|
export { ChatNodeDefinition, ClickActions, ClickHit, ClipboardBackendContribution, CommandDef, ComposerInsertSpec, CustomMessage, CustomMessageSeed, DialogItemKinds, DiffHunk, DiffLine, DiffLineKind, HintArgsContribution, HintEntry, HintMatcherContribution, HomeLogoContribution, InputStatusContext, InputStatusContribution, InputStatusPart, InteractionPanelComponentProps, InteractionPanelContribution, KeyBindingContribution, type LineSelection, MarkdownBlockContext, MarkdownBlockContribution, MarkdownInlineContext, MarkdownInlineContribution, type MessageKind, MessageRendererContribution, MessageRendererResult, MessageViewContext, MessageViewContribution, MessageViewRender, type MouseEventData, type MouseEventType, OverlayContribution, POINTER_PLUGIN_ORDER, PaintArgs, PasteContext, PasteHandlerContribution, PasteResult, type PendingImage, PointerEventFrame, PointerHandlerContribution, PointerSession, PointerUiContext, PrismGrammarContribution, type ScrollSnapshot, SelectionClipboardContribution, SelectionDomainContribution, SelectionExtractContext, SelectionTransformerContribution, SpecialFieldContribution, SpecialFieldCreateInput, SpecialFieldFactory, SpecialFieldStyle, StatusLineContribution, ThemePaletteContribution, ToolCallPresentation, ToolReadLine, ToolReadView, ToolResultPresentation, ToolResultView, ToolViewCallContext, ToolViewContribution, ToolViewDiff, ToolViewResultContext, TuiChatFace, TuiChromeFace, TuiClipboardFace, TuiCommandsFace, TuiComposerFace, TuiContentFace, TuiExtensionPoint, TuiFieldsFace, TuiHintFace, TuiHomeLogoFace, TuiInteractionPanelsFace, TuiInteractionsFace, TuiKey, TuiKeymapFace, TuiMarkdownFace, TuiPaletteFace, TuiPointerFace, TuiSelectionFace, TuiServicesFace, TuiSessionSummary, TuiStartupFace, TuiStartupSink, TuiToolsFace, TuiWidgetsFace, TuiWindowsFace, WidgetDef, WidgetKeyApi, WindowCommandSpec, WindowContribution, WindowHandle, WindowProps };
|
package/lib/dialog.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Gt as Segment, Lt as WindowHandle, Nt as WidgetDef, l as DialogItemKinds } from "./index-
|
|
1
|
+
import { Gt as Segment, Lt as WindowHandle, Nt as WidgetDef, l as DialogItemKinds } from "./index-D4_nxuuR.mjs";
|
|
2
2
|
import { ReactNode, Ref } from "react";
|
|
3
3
|
//#region src/ui/dialog/items.d.ts
|
|
4
4
|
interface DialogRow {
|
package/lib/dialog.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import "./prod-react-Dzof4qJx.mjs";
|
|
2
|
-
import { I as panelAnchorRow, c as asTextItem, i as Dialog, l as registerWidget, n as panelLegend, o as rowHeight, r as CloseGuardContext, s as wrapStatusLines, t as PanelSurface } from "./surface-
|
|
2
|
+
import { I as panelAnchorRow, c as asTextItem, i as Dialog, l as registerWidget, n as panelLegend, o as rowHeight, r as CloseGuardContext, s as wrapStatusLines, t as PanelSurface } from "./surface-B7w3R_kh.mjs";
|
|
3
3
|
export { CloseGuardContext, Dialog, PanelSurface, asTextItem, panelAnchorRow, panelLegend, registerWidget, rowHeight, wrapStatusLines };
|
|
@@ -124,18 +124,8 @@ interface CompactionMessage {
|
|
|
124
124
|
error?: string;
|
|
125
125
|
streaming?: boolean;
|
|
126
126
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
id: string;
|
|
130
|
-
/** Artwork lines after culling; empty when the terminal cannot fit the logo. */
|
|
131
|
-
lines: readonly string[];
|
|
132
|
-
/** One gradient color per artwork line, aligned with `lines`. */
|
|
133
|
-
colors: readonly string[];
|
|
134
|
-
/** Version banner lines rendered under the artwork inside the same bubble. */
|
|
135
|
-
info: readonly string[];
|
|
136
|
-
}
|
|
137
|
-
type MessageKind = 'bubble' | 'collapsible' | 'tool-card' | 'compaction' | 'custom' | 'home-logo';
|
|
138
|
-
type Message = BubbleMessage | CollapsibleMessage | ToolCardMessage | CompactionMessage | CustomMessage | HomeLogoMessage;
|
|
127
|
+
type MessageKind = 'bubble' | 'collapsible' | 'tool-card' | 'compaction' | 'custom';
|
|
128
|
+
type Message = BubbleMessage | CollapsibleMessage | ToolCardMessage | CompactionMessage | CustomMessage;
|
|
139
129
|
//#endregion
|
|
140
130
|
//#region src/terminal/mouse.d.ts
|
|
141
131
|
type MouseEventType = 'down' | 'up' | 'drag' | 'move' | 'scroll';
|
|
@@ -439,7 +429,7 @@ interface InputStatusContribution {
|
|
|
439
429
|
interface HomeLogoContribution {
|
|
440
430
|
id: string;
|
|
441
431
|
order?: number;
|
|
442
|
-
/** Logo artwork
|
|
432
|
+
/** Logo artwork centered on the empty home page, one string per terminal row. */
|
|
443
433
|
lines: readonly string[];
|
|
444
434
|
}
|
|
445
435
|
interface TuiHomeLogoFace {
|
package/lib/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./prod-react-Dzof4qJx.mjs";
|
|
2
|
-
import { $ as errorText, A as SelectableText, At as setColorLevel, B as inputFrameTop, C as caretNonceText, Ct as registerPalette, D as markKeymapConsumed, Dt as themeMode, E as isKeyConsumed, Et as subscribePalettes, F as messageBackgroundWidth, Ft as warn, G as wrapSegments, H as glyphs, I as panelAnchorRow, J as clampFocusRow, K as chromeSelectionText, L as panelContains, M as Region, Mt as configureLogs, N as hintSpan, Nt as error, O as createMouseController, Ot as keyedRegistry, P as inputContentContains, Pt as installCrashHandlers, Q as colToCharIndex, R as scrollbarColumn, S as caretNonceColor, St as permissionModeInfo, T as beginKeyEvent, Tt as setThemeMode, U as mergeRuns, V as inputStatusRow, W as segmentsKey, X as toScreenSelection, Y as selectedRange, Z as charWidth, _ as editDeleteWordRight, _t as releaseFields, a as DIALOG_MAX_HEIGHT, at as wrapLines, b as writeCursorShape, bt as COLORS, ct as fieldExpandOf, d as editCursorLeft, dt as hasFieldChar, et as lineBreaks, f as editCursorRight, ft as hasFieldSlots, g as editDeleteWordLeft, gt as registerFieldKind, h as editDelete, ht as linesChipLabel, i as Dialog, it as truncate, j as SelectionContext, jt as env, k as isMouseResidue, kt as colorLevel, l as registerWidget, lt as fieldSlotOf, m as editCursorWordRight, mt as isFieldChar, n as panelLegend, nt as segmentGraphemes, ot as allocateField, p as editCursorWordLeft, pt as imageChipLabel, q as sliceByColumns, r as CloseGuardContext, rt as textWidth, st as charactersChipLabel, t as PanelSurface, tt as locToPoint, u as editBackspace, ut as fieldStyleOf, v as editInsert, vt as releaseUnreferenced, w as useCaret, wt as replacePalettes, x as caretNonceBold, xt as paletteColor, y as CapText, yt as specialFieldFactory, z as hintBlockTop } from "./surface-
|
|
2
|
+
import { $ as errorText, A as SelectableText, At as setColorLevel, B as inputFrameTop, C as caretNonceText, Ct as registerPalette, D as markKeymapConsumed, Dt as themeMode, E as isKeyConsumed, Et as subscribePalettes, F as messageBackgroundWidth, Ft as warn, G as wrapSegments, H as glyphs, I as panelAnchorRow, J as clampFocusRow, K as chromeSelectionText, L as panelContains, M as Region, Mt as configureLogs, N as hintSpan, Nt as error, O as createMouseController, Ot as keyedRegistry, P as inputContentContains, Pt as installCrashHandlers, Q as colToCharIndex, R as scrollbarColumn, S as caretNonceColor, St as permissionModeInfo, T as beginKeyEvent, Tt as setThemeMode, U as mergeRuns, V as inputStatusRow, W as segmentsKey, X as toScreenSelection, Y as selectedRange, Z as charWidth, _ as editDeleteWordRight, _t as releaseFields, a as DIALOG_MAX_HEIGHT, at as wrapLines, b as writeCursorShape, bt as COLORS, ct as fieldExpandOf, d as editCursorLeft, dt as hasFieldChar, et as lineBreaks, f as editCursorRight, ft as hasFieldSlots, g as editDeleteWordLeft, gt as registerFieldKind, h as editDelete, ht as linesChipLabel, i as Dialog, it as truncate, j as SelectionContext, jt as env, k as isMouseResidue, kt as colorLevel, l as registerWidget, lt as fieldSlotOf, m as editCursorWordRight, mt as isFieldChar, n as panelLegend, nt as segmentGraphemes, ot as allocateField, p as editCursorWordLeft, pt as imageChipLabel, q as sliceByColumns, r as CloseGuardContext, rt as textWidth, st as charactersChipLabel, t as PanelSurface, tt as locToPoint, u as editBackspace, ut as fieldStyleOf, v as editInsert, vt as releaseUnreferenced, w as useCaret, wt as replacePalettes, x as caretNonceBold, xt as paletteColor, y as CapText, yt as specialFieldFactory, z as hintBlockTop } from "./surface-B7w3R_kh.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { readFileSync, statSync } from "node:fs";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
@@ -2499,23 +2499,6 @@ function renderBody(message, width) {
|
|
|
2499
2499
|
bgs: null
|
|
2500
2500
|
};
|
|
2501
2501
|
}
|
|
2502
|
-
case "home-logo": {
|
|
2503
|
-
const lines = [];
|
|
2504
|
-
const rows = [];
|
|
2505
|
-
message.lines.forEach((line, i) => {
|
|
2506
|
-
lines.push(line);
|
|
2507
|
-
rows.push([{
|
|
2508
|
-
text: line,
|
|
2509
|
-
style: { color: message.colors[i] }
|
|
2510
|
-
}]);
|
|
2511
|
-
});
|
|
2512
|
-
for (const line of message.info) lines.push(truncate(line, Math.max(8, inner)));
|
|
2513
|
-
return {
|
|
2514
|
-
lines,
|
|
2515
|
-
rows: rows.length === 0 ? null : rows,
|
|
2516
|
-
bgs: null
|
|
2517
|
-
};
|
|
2518
|
-
}
|
|
2519
2502
|
case "custom": {
|
|
2520
2503
|
const view = messageViewOf(message.view);
|
|
2521
2504
|
if (view === void 0) return {
|
|
@@ -2659,13 +2642,6 @@ function planFor(message, body) {
|
|
|
2659
2642
|
blankRow()
|
|
2660
2643
|
];
|
|
2661
2644
|
}
|
|
2662
|
-
case "home-logo": return [...bodyRows(body.lines.length, {
|
|
2663
|
-
colStart: 4,
|
|
2664
|
-
bg: false,
|
|
2665
|
-
muted: false,
|
|
2666
|
-
selectable: true,
|
|
2667
|
-
role: "assistant"
|
|
2668
|
-
}), blankRow()];
|
|
2669
2645
|
case "custom": return [
|
|
2670
2646
|
{
|
|
2671
2647
|
type: "header",
|
|
@@ -2734,12 +2710,6 @@ function fingerprintOf(message) {
|
|
|
2734
2710
|
running: message.running,
|
|
2735
2711
|
streaming: message.streaming
|
|
2736
2712
|
};
|
|
2737
|
-
case "home-logo": return {
|
|
2738
|
-
kind: "home-logo",
|
|
2739
|
-
lines: message.lines,
|
|
2740
|
-
colors: message.colors,
|
|
2741
|
-
info: message.info
|
|
2742
|
-
};
|
|
2743
2713
|
}
|
|
2744
2714
|
}
|
|
2745
2715
|
function fingerprintMatches(entry, message) {
|
|
@@ -2749,7 +2719,6 @@ function fingerprintMatches(entry, message) {
|
|
|
2749
2719
|
case "tool-card": return message.kind === "tool-card" && entry.tool === message.tool && entry.label === message.label && entry.argsBody === message.argsBody && entry.resultBody === message.resultBody && entry.diff === message.diff && entry.read === message.read && entry.nested === message.nested && entry.error === message.error && entry.failed === message.failed && entry.exitCode === message.exitCode && entry.signal === message.signal && entry.bodyCol === message.bodyCol && entry.running === message.running && entry.streaming === message.streaming;
|
|
2750
2720
|
case "compaction": return message.kind === "compaction" && entry.summary === message.summary && entry.running === message.running && entry.error === message.error;
|
|
2751
2721
|
case "custom": return message.kind === "custom" && entry.view === message.view && entry.data === message.data && entry.running === message.running && entry.streaming === message.streaming;
|
|
2752
|
-
case "home-logo": return message.kind === "home-logo" && entry.lines === message.lines && entry.colors === message.colors && entry.info === message.info;
|
|
2753
2722
|
}
|
|
2754
2723
|
}
|
|
2755
2724
|
function renderFor(message, width) {
|
|
@@ -6146,59 +6115,6 @@ function backgroundFor(role) {
|
|
|
6146
6115
|
return role === "user" ? COLORS.userBubbleBackground : COLORS.aiBubbleBackground;
|
|
6147
6116
|
}
|
|
6148
6117
|
//#endregion
|
|
6149
|
-
//#region src/ui/message/message-list.tsx
|
|
6150
|
-
function MessageList({ messages, height, width, scrollTop, onScroll, interactive = true, themeTick = 0 }) {
|
|
6151
|
-
const index = rowIndexFor(messages, width);
|
|
6152
|
-
const total = index.total;
|
|
6153
|
-
const halfPage = Math.max(1, Math.ceil(height / 2));
|
|
6154
|
-
useInput((input, key) => {
|
|
6155
|
-
if (!interactive || isKeyConsumed()) return;
|
|
6156
|
-
const maxScroll = Math.max(0, total - height);
|
|
6157
|
-
const scroll = (delta) => {
|
|
6158
|
-
onScroll(Math.max(0, Math.min(maxScroll, scrollTop + delta)));
|
|
6159
|
-
};
|
|
6160
|
-
if (key.pageUp) scroll(-(height - 2));
|
|
6161
|
-
if (key.pageDown) scroll(height - 2);
|
|
6162
|
-
if (key.ctrl && input === "u") scroll(-halfPage);
|
|
6163
|
-
if (key.ctrl && input === "d") scroll(halfPage);
|
|
6164
|
-
});
|
|
6165
|
-
const rows = [];
|
|
6166
|
-
const endRow = Math.min(scrollTop + height, total);
|
|
6167
|
-
for (let row = scrollTop; row < endRow; row++) {
|
|
6168
|
-
const info = index.rowAt(row);
|
|
6169
|
-
if (info) rows.push(/* @__PURE__ */ jsx(MessageRow, {
|
|
6170
|
-
info,
|
|
6171
|
-
row,
|
|
6172
|
-
themeTick
|
|
6173
|
-
}, `${info.messageId}:${info.lineNo}`));
|
|
6174
|
-
}
|
|
6175
|
-
const scrollbar = scrollbarGeometry(total, height, scrollTop);
|
|
6176
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
6177
|
-
width,
|
|
6178
|
-
height,
|
|
6179
|
-
flexDirection: "column",
|
|
6180
|
-
overflow: "hidden",
|
|
6181
|
-
children: [/* @__PURE__ */ jsx(Region, {
|
|
6182
|
-
y: -scrollTop,
|
|
6183
|
-
children: rows
|
|
6184
|
-
}), scrollbar !== null && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Box, {
|
|
6185
|
-
position: "absolute",
|
|
6186
|
-
top: 0,
|
|
6187
|
-
left: scrollbarColumn(width),
|
|
6188
|
-
width: 1,
|
|
6189
|
-
height,
|
|
6190
|
-
backgroundColor: COLORS.scrollTrackBackground
|
|
6191
|
-
}), /* @__PURE__ */ jsx(Box, {
|
|
6192
|
-
position: "absolute",
|
|
6193
|
-
top: scrollbar.top,
|
|
6194
|
-
left: scrollbarColumn(width),
|
|
6195
|
-
width: 1,
|
|
6196
|
-
height: scrollbar.height,
|
|
6197
|
-
backgroundColor: COLORS.scrollThumbBackground
|
|
6198
|
-
})] })]
|
|
6199
|
-
});
|
|
6200
|
-
}
|
|
6201
|
-
//#endregion
|
|
6202
6118
|
//#region src/contract/upstream.ts
|
|
6203
6119
|
const UPSTREAM_FLOOR_VERSION = "0.1.2-rc.1";
|
|
6204
6120
|
const UPSTREAM_CEILING_VERSION = "0.2.0";
|
|
@@ -6405,34 +6321,41 @@ function homeLogoLineColors(lines, top, bottom) {
|
|
|
6405
6321
|
const step = 1 / (count - 1);
|
|
6406
6322
|
return lines.map((_, i) => mixHex(top, bottom, i * step));
|
|
6407
6323
|
}
|
|
6408
|
-
const
|
|
6324
|
+
const BLOCK_GLYPHS = /* @__PURE__ */ new Set([
|
|
6325
|
+
"█",
|
|
6326
|
+
"▀",
|
|
6327
|
+
"▄",
|
|
6328
|
+
" "
|
|
6329
|
+
]);
|
|
6409
6330
|
/**
|
|
6410
|
-
*
|
|
6411
|
-
*
|
|
6412
|
-
*
|
|
6413
|
-
*
|
|
6414
|
-
*
|
|
6331
|
+
* Displace pure block-glyph artwork down by half a cell: every cell's
|
|
6332
|
+
* top/bottom half ink moves one pixel-row down and is re-encoded as
|
|
6333
|
+
* ▀/▄/█. The shape is pixel-identical, but the artwork's bottom edge lands
|
|
6334
|
+
* on a cell boundary, so vertically centered text beside it reads as
|
|
6335
|
+
* aligned with the bottom line. Artwork containing any non-block glyph is
|
|
6336
|
+
* returned unchanged — text glyphs cannot be split across cells.
|
|
6415
6337
|
*/
|
|
6416
|
-
function
|
|
6417
|
-
const
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
]
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6338
|
+
function shiftLogoDownHalfRow(lines) {
|
|
6339
|
+
const width = lines.reduce((max, line) => Math.max(max, line.length), 0);
|
|
6340
|
+
if (width === 0) return [...lines];
|
|
6341
|
+
for (const line of lines) for (const ch of line) if (!BLOCK_GLYPHS.has(ch)) return [...lines];
|
|
6342
|
+
const top = lines.map((line) => Array.from({ length: width }, (_, c) => line[c] === "█" || line[c] === "▀"));
|
|
6343
|
+
const bottom = lines.map((line) => Array.from({ length: width }, (_, c) => line[c] === "█" || line[c] === "▄"));
|
|
6344
|
+
const rows = [];
|
|
6345
|
+
for (let r = 0; r <= lines.length; r++) {
|
|
6346
|
+
let row = "";
|
|
6347
|
+
let ink = false;
|
|
6348
|
+
for (let c = 0; c < width; c++) {
|
|
6349
|
+
const t = r > 0 && bottom[r - 1][c];
|
|
6350
|
+
const b = r < lines.length && top[r][c];
|
|
6351
|
+
row += t && b ? "█" : t ? "▀" : b ? "▄" : " ";
|
|
6352
|
+
ink = ink || t || b;
|
|
6353
|
+
}
|
|
6354
|
+
if (ink) rows.push(row.trimEnd());
|
|
6355
|
+
}
|
|
6356
|
+
return rows;
|
|
6435
6357
|
}
|
|
6358
|
+
/** The running harness version, `dsh <version>`. */
|
|
6436
6359
|
function dshVersionLine() {
|
|
6437
6360
|
return `dsh ${installedUpstreamLines()[0] ?? "unknown"}`;
|
|
6438
6361
|
}
|
|
@@ -6448,6 +6371,92 @@ function tuiVersion() {
|
|
|
6448
6371
|
return cachedTuiVersion;
|
|
6449
6372
|
}
|
|
6450
6373
|
//#endregion
|
|
6374
|
+
//#region src/ui/message/message-list.tsx
|
|
6375
|
+
/** Gap between the artwork's right edge and an appended version label. */
|
|
6376
|
+
const VERSION_GAP = 2;
|
|
6377
|
+
function MessageList({ messages, height, width, scrollTop, onScroll, interactive = true, themeTick = 0 }) {
|
|
6378
|
+
const index = rowIndexFor(messages, width);
|
|
6379
|
+
const total = index.total;
|
|
6380
|
+
const halfPage = Math.max(1, Math.ceil(height / 2));
|
|
6381
|
+
useInput((input, key) => {
|
|
6382
|
+
if (!interactive || isKeyConsumed()) return;
|
|
6383
|
+
const maxScroll = Math.max(0, total - height);
|
|
6384
|
+
const scroll = (delta) => {
|
|
6385
|
+
onScroll(Math.max(0, Math.min(maxScroll, scrollTop + delta)));
|
|
6386
|
+
};
|
|
6387
|
+
if (key.pageUp) scroll(-(height - 2));
|
|
6388
|
+
if (key.pageDown) scroll(height - 2);
|
|
6389
|
+
if (key.ctrl && input === "u") scroll(-halfPage);
|
|
6390
|
+
if (key.ctrl && input === "d") scroll(halfPage);
|
|
6391
|
+
});
|
|
6392
|
+
const logo = useHomeLogo();
|
|
6393
|
+
if (messages.length === 0) {
|
|
6394
|
+
const clean = shiftLogoDownHalfRow(logo?.lines.map((line) => line.trimEnd()) ?? []);
|
|
6395
|
+
const metrics = homeLogoMetrics(clean);
|
|
6396
|
+
const dshtuiVersionLine = `dshtui ${tuiVersion()}`;
|
|
6397
|
+
const versionWidth = Math.max(dshVersionLine().length, dshtuiVersionLine.length);
|
|
6398
|
+
const anchorWidth = clean.length >= 2 ? Math.max(textWidth(clean[clean.length - 2].trimEnd()), textWidth(clean[clean.length - 1].trimEnd())) : metrics.columns;
|
|
6399
|
+
const fits = logo !== void 0 && width >= 2 * (anchorWidth + VERSION_GAP + versionWidth) - metrics.columns && height >= metrics.rows;
|
|
6400
|
+
const colors = homeLogoLineColors(clean, COLORS.homeLogoTop, COLORS.homeLogoBottom);
|
|
6401
|
+
const left = Math.floor((width - metrics.columns) / 2);
|
|
6402
|
+
const rowText = (i) => {
|
|
6403
|
+
const artwork = clean[i].padEnd(i >= clean.length - 2 ? anchorWidth : metrics.columns);
|
|
6404
|
+
if (i === clean.length - 1) return `${artwork}${" ".repeat(VERSION_GAP)}${dshtuiVersionLine}`;
|
|
6405
|
+
if (i === clean.length - 2) return `${artwork}${" ".repeat(VERSION_GAP)}${dshVersionLine()}`;
|
|
6406
|
+
return artwork;
|
|
6407
|
+
};
|
|
6408
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
6409
|
+
width,
|
|
6410
|
+
height,
|
|
6411
|
+
flexDirection: "column",
|
|
6412
|
+
justifyContent: "center",
|
|
6413
|
+
overflow: "hidden",
|
|
6414
|
+
children: fits && colors.map((color, i) => /* @__PURE__ */ jsx(Box, {
|
|
6415
|
+
marginLeft: left,
|
|
6416
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
6417
|
+
color,
|
|
6418
|
+
children: rowText(i)
|
|
6419
|
+
})
|
|
6420
|
+
}, i))
|
|
6421
|
+
});
|
|
6422
|
+
}
|
|
6423
|
+
const rows = [];
|
|
6424
|
+
const endRow = Math.min(scrollTop + height, total);
|
|
6425
|
+
for (let row = scrollTop; row < endRow; row++) {
|
|
6426
|
+
const info = index.rowAt(row);
|
|
6427
|
+
if (info) rows.push(/* @__PURE__ */ jsx(MessageRow, {
|
|
6428
|
+
info,
|
|
6429
|
+
row,
|
|
6430
|
+
themeTick
|
|
6431
|
+
}, `${info.messageId}:${info.lineNo}`));
|
|
6432
|
+
}
|
|
6433
|
+
const scrollbar = scrollbarGeometry(total, height, scrollTop);
|
|
6434
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
6435
|
+
width,
|
|
6436
|
+
height,
|
|
6437
|
+
flexDirection: "column",
|
|
6438
|
+
overflow: "hidden",
|
|
6439
|
+
children: [/* @__PURE__ */ jsx(Region, {
|
|
6440
|
+
y: -scrollTop,
|
|
6441
|
+
children: rows
|
|
6442
|
+
}), scrollbar !== null && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Box, {
|
|
6443
|
+
position: "absolute",
|
|
6444
|
+
top: 0,
|
|
6445
|
+
left: scrollbarColumn(width),
|
|
6446
|
+
width: 1,
|
|
6447
|
+
height,
|
|
6448
|
+
backgroundColor: COLORS.scrollTrackBackground
|
|
6449
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
6450
|
+
position: "absolute",
|
|
6451
|
+
top: scrollbar.top,
|
|
6452
|
+
left: scrollbarColumn(width),
|
|
6453
|
+
width: 1,
|
|
6454
|
+
height: scrollbar.height,
|
|
6455
|
+
backgroundColor: COLORS.scrollThumbBackground
|
|
6456
|
+
})] })]
|
|
6457
|
+
});
|
|
6458
|
+
}
|
|
6459
|
+
//#endregion
|
|
6451
6460
|
//#region src/chat/models.ts
|
|
6452
6461
|
const API_PROTOCOLS = [
|
|
6453
6462
|
"openai-completions",
|
|
@@ -9070,9 +9079,7 @@ function App({ bridge, screen, themeTick = 0 }) {
|
|
|
9070
9079
|
const permissionChrome = permissionModeInfo(permissionMode);
|
|
9071
9080
|
const inputHeight = panel === null ? layout.barHeight : panelHeight + 1;
|
|
9072
9081
|
const messageHeight = Math.max(1, rows - inputHeight - 1);
|
|
9073
|
-
const
|
|
9074
|
-
const displayMessages = messages.length === 0 ? [logoBubble] : messages;
|
|
9075
|
-
const total = rowIndexFor(displayMessages, columns).total;
|
|
9082
|
+
const total = rowIndexFor(messages, columns).total;
|
|
9076
9083
|
const { scrollTop, applyScroll, getScroll } = useScroll(total, messageHeight);
|
|
9077
9084
|
const startNewSession = () => {
|
|
9078
9085
|
if (!bridge) return;
|
|
@@ -9166,7 +9173,7 @@ function App({ bridge, screen, themeTick = 0 }) {
|
|
|
9166
9173
|
};
|
|
9167
9174
|
const selectionRef = useRef(null);
|
|
9168
9175
|
const { selection, messageAreaSelection, chromeSelection, setSelection, clearSelection } = useMouseSelection({
|
|
9169
|
-
messages
|
|
9176
|
+
messages,
|
|
9170
9177
|
columns,
|
|
9171
9178
|
rows,
|
|
9172
9179
|
scrollTop,
|
|
@@ -9211,7 +9218,7 @@ function App({ bridge, screen, themeTick = 0 }) {
|
|
|
9211
9218
|
if (key.ctrl && input === "c") {
|
|
9212
9219
|
if (selection) {
|
|
9213
9220
|
const text = copySelection(selection, {
|
|
9214
|
-
messageText: (sel) => selectionText(
|
|
9221
|
+
messageText: (sel) => selectionText(messages, columns, sel),
|
|
9215
9222
|
chromeText: (sel) => chromeSelectionText(sel)
|
|
9216
9223
|
});
|
|
9217
9224
|
if (text) writeClipboardText(text);
|
|
@@ -9236,7 +9243,7 @@ function App({ bridge, screen, themeTick = 0 }) {
|
|
|
9236
9243
|
children: [
|
|
9237
9244
|
/* @__PURE__ */ jsx(KeymapGate, {}),
|
|
9238
9245
|
/* @__PURE__ */ jsx(MessageList, {
|
|
9239
|
-
messages
|
|
9246
|
+
messages,
|
|
9240
9247
|
height: messageHeight,
|
|
9241
9248
|
width: columns,
|
|
9242
9249
|
scrollTop,
|
|
@@ -10973,13 +10980,6 @@ function warmRenderPipeline() {
|
|
|
10973
10980
|
renderMarkdown(MARKDOWN_FIXTURE, 96);
|
|
10974
10981
|
renderMarkdown(MARKDOWN_FIXTURE, 72);
|
|
10975
10982
|
const index = buildRowIndex([
|
|
10976
|
-
{
|
|
10977
|
-
kind: "home-logo",
|
|
10978
|
-
id: "warm-logo",
|
|
10979
|
-
lines: ["██ ████"],
|
|
10980
|
-
colors: ["#7aa2f7"],
|
|
10981
|
-
info: ["dsh 0.0.0", "dshtui 0.0.0"]
|
|
10982
|
-
},
|
|
10983
10983
|
{
|
|
10984
10984
|
kind: "bubble",
|
|
10985
10985
|
id: "warm-user",
|
|
@@ -505,8 +505,8 @@ function buildPalette(ladder, hues, code, mode) {
|
|
|
505
505
|
thinkCodeType: thinkCode.type,
|
|
506
506
|
thinkCodeVariable: thinkCode.variable,
|
|
507
507
|
thinkCodeConstant: thinkCode.variable,
|
|
508
|
-
homeLogoTop: mode === "dark" ? "#
|
|
509
|
-
homeLogoBottom: mode === "dark" ? "#
|
|
508
|
+
homeLogoTop: mode === "dark" ? "#e1e1e1" : "#7f7f7f",
|
|
509
|
+
homeLogoBottom: mode === "dark" ? "#6c6c6c" : "#292929"
|
|
510
510
|
};
|
|
511
511
|
}
|
|
512
512
|
const darkPalette = buildPalette(DARK_LADDER, DARK_HUES, DARK_CODE, "dark");
|
package/package.json
CHANGED
package/src/contract/index.ts
CHANGED
|
@@ -304,7 +304,7 @@ export interface InputStatusContribution {
|
|
|
304
304
|
export interface HomeLogoContribution {
|
|
305
305
|
id: string
|
|
306
306
|
order?: number
|
|
307
|
-
/** Logo artwork
|
|
307
|
+
/** Logo artwork centered on the empty home page, one string per terminal row. */
|
|
308
308
|
lines: readonly string[]
|
|
309
309
|
}
|
|
310
310
|
|
package/src/model/message.ts
CHANGED
|
@@ -73,17 +73,6 @@ export interface CompactionMessage {
|
|
|
73
73
|
streaming?: boolean
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
export
|
|
77
|
-
kind: 'home-logo'
|
|
78
|
-
id: string
|
|
79
|
-
/** Artwork lines after culling; empty when the terminal cannot fit the logo. */
|
|
80
|
-
lines: readonly string[]
|
|
81
|
-
/** One gradient color per artwork line, aligned with `lines`. */
|
|
82
|
-
colors: readonly string[]
|
|
83
|
-
/** Version banner lines rendered under the artwork inside the same bubble. */
|
|
84
|
-
info: readonly string[]
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export type MessageKind = 'bubble' | 'collapsible' | 'tool-card' | 'compaction' | 'custom' | 'home-logo'
|
|
76
|
+
export type MessageKind = 'bubble' | 'collapsible' | 'tool-card' | 'compaction' | 'custom'
|
|
88
77
|
|
|
89
|
-
export type Message = BubbleMessage | CollapsibleMessage | ToolCardMessage | CompactionMessage | CustomMessage
|
|
78
|
+
export type Message = BubbleMessage | CollapsibleMessage | ToolCardMessage | CompactionMessage | CustomMessage
|
package/src/theme.ts
CHANGED
|
@@ -246,8 +246,8 @@ function buildPalette(ladder: GrayLadder, hues: SemanticHues, code: CodeHues, mo
|
|
|
246
246
|
thinkCodeVariable: thinkCode.variable,
|
|
247
247
|
thinkCodeConstant: thinkCode.variable,
|
|
248
248
|
|
|
249
|
-
homeLogoTop: mode === 'dark' ? '#
|
|
250
|
-
homeLogoBottom: mode === 'dark' ? '#
|
|
249
|
+
homeLogoTop: mode === 'dark' ? '#e1e1e1' : '#7f7f7f',
|
|
250
|
+
homeLogoBottom: mode === 'dark' ? '#6c6c6c' : '#292929',
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
|
package/src/ui/app.tsx
CHANGED
|
@@ -30,7 +30,6 @@ import { useComposer } from './input/use-composer.ts'
|
|
|
30
30
|
import type { ComposerSubmission } from './input/composer-fields.ts'
|
|
31
31
|
import { Region } from './region.tsx'
|
|
32
32
|
import { MessageList } from './message/message-list.tsx'
|
|
33
|
-
import { useHomeLogoBubble } from './home-logo.ts'
|
|
34
33
|
import { SpinnerTickProvider } from './spinner-tick.tsx'
|
|
35
34
|
import { CloseGuardContext } from './dialog/dialog.tsx'
|
|
36
35
|
import type { DialogHandle } from './dialog/dialog.tsx'
|
|
@@ -185,12 +184,7 @@ export function App({ bridge, screen, themeTick = 0 }: AppProps) {
|
|
|
185
184
|
const bottomHeight = panel === null ? layout.barHeight : panelHeight + 1
|
|
186
185
|
const inputHeight = bottomHeight
|
|
187
186
|
const messageHeight = Math.max(1, rows - inputHeight - MESSAGE_INPUT_GAP_ROWS)
|
|
188
|
-
|
|
189
|
-
// bubble is its content. Any real message switches to the chat list and
|
|
190
|
-
// the logo disappears.
|
|
191
|
-
const logoBubble = useHomeLogoBubble(columns, messageHeight)
|
|
192
|
-
const displayMessages = messages.length === 0 ? [logoBubble] : messages
|
|
193
|
-
const total = rowIndexFor(displayMessages, columns).total
|
|
187
|
+
const total = rowIndexFor(messages, columns).total
|
|
194
188
|
const { scrollTop, applyScroll, getScroll } = useScroll(total, messageHeight)
|
|
195
189
|
const startNewSession = () => {
|
|
196
190
|
if (!bridge) return
|
|
@@ -284,7 +278,7 @@ export function App({ bridge, screen, themeTick = 0 }: AppProps) {
|
|
|
284
278
|
}
|
|
285
279
|
const selectionRef = useRef<LineSelection | null>(null)
|
|
286
280
|
const { selection, messageAreaSelection, chromeSelection, setSelection, clearSelection } = useMouseSelection({
|
|
287
|
-
messages
|
|
281
|
+
messages,
|
|
288
282
|
columns,
|
|
289
283
|
rows,
|
|
290
284
|
scrollTop,
|
|
@@ -329,7 +323,7 @@ export function App({ bridge, screen, themeTick = 0 }: AppProps) {
|
|
|
329
323
|
if (key.ctrl && input === 'c') {
|
|
330
324
|
if (selection) {
|
|
331
325
|
const text = copySelection(selection, {
|
|
332
|
-
messageText: sel => selectionText(
|
|
326
|
+
messageText: sel => selectionText(messages, columns, sel),
|
|
333
327
|
chromeText: sel => chromeSelectionText(sel),
|
|
334
328
|
})
|
|
335
329
|
if (text) writeClipboardText(text)
|
|
@@ -349,7 +343,7 @@ export function App({ bridge, screen, themeTick = 0 }: AppProps) {
|
|
|
349
343
|
<Box flexDirection="column" width={columns} height={rows}>
|
|
350
344
|
<KeymapGate />
|
|
351
345
|
<MessageList
|
|
352
|
-
messages={
|
|
346
|
+
messages={messages}
|
|
353
347
|
height={messageHeight}
|
|
354
348
|
width={columns}
|
|
355
349
|
scrollTop={scrollTop}
|
package/src/ui/home-logo.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs'
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
-
import {
|
|
3
|
+
import { useSyncExternalStore } from 'react'
|
|
4
4
|
import { installedUpstreamLines } from '../contract/upstream.ts'
|
|
5
|
-
import { BUBBLE_WIDTH_OFFSET } from '../core/metrics.ts'
|
|
6
|
-
import type { HomeLogoMessage } from '../model/message.ts'
|
|
7
5
|
import { keyedRegistry } from '../kernel/registry.ts'
|
|
8
6
|
import { textWidth } from '../core/text.ts'
|
|
9
7
|
import { COLORS } from '../theme.ts'
|
|
@@ -83,34 +81,43 @@ export function homeLogoLineColors(lines: readonly string[], top: string, bottom
|
|
|
83
81
|
return lines.map((_, i) => mixHex(top, bottom, i * step))
|
|
84
82
|
}
|
|
85
83
|
|
|
86
|
-
const
|
|
84
|
+
const BLOCK_GLYPHS = new Set(['█', '▀', '▄', ' '])
|
|
87
85
|
|
|
88
86
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
87
|
+
* Displace pure block-glyph artwork down by half a cell: every cell's
|
|
88
|
+
* top/bottom half ink moves one pixel-row down and is re-encoded as
|
|
89
|
+
* ▀/▄/█. The shape is pixel-identical, but the artwork's bottom edge lands
|
|
90
|
+
* on a cell boundary, so vertically centered text beside it reads as
|
|
91
|
+
* aligned with the bottom line. Artwork containing any non-block glyph is
|
|
92
|
+
* returned unchanged — text glyphs cannot be split across cells.
|
|
94
93
|
*/
|
|
95
|
-
export function
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
94
|
+
export function shiftLogoDownHalfRow(lines: readonly string[]): string[] {
|
|
95
|
+
const width = lines.reduce((max, line) => Math.max(max, line.length), 0)
|
|
96
|
+
if (width === 0) return [...lines]
|
|
97
|
+
for (const line of lines) {
|
|
98
|
+
for (const ch of line) {
|
|
99
|
+
if (!BLOCK_GLYPHS.has(ch)) return [...lines]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const top: boolean[][] = lines.map(line => Array.from({ length: width }, (_, c) => line[c] === '█' || line[c] === '▀'))
|
|
103
|
+
const bottom: boolean[][] = lines.map(line => Array.from({ length: width }, (_, c) => line[c] === '█' || line[c] === '▄'))
|
|
104
|
+
const rows: string[] = []
|
|
105
|
+
for (let r = 0; r <= lines.length; r++) {
|
|
106
|
+
let row = ''
|
|
107
|
+
let ink = false
|
|
108
|
+
for (let c = 0; c < width; c++) {
|
|
109
|
+
const t = r > 0 && bottom[r - 1]![c]!
|
|
110
|
+
const b = r < lines.length && top[r]![c]!
|
|
111
|
+
row += t && b ? '█' : t ? '▀' : b ? '▄' : ' '
|
|
112
|
+
ink = ink || t || b
|
|
113
|
+
}
|
|
114
|
+
if (ink) rows.push(row.trimEnd())
|
|
110
115
|
}
|
|
116
|
+
return rows
|
|
111
117
|
}
|
|
112
118
|
|
|
113
|
-
|
|
119
|
+
/** The running harness version, `dsh <version>`. */
|
|
120
|
+
export function dshVersionLine(): string {
|
|
114
121
|
// dsh-agent itself does not export ./package.json, so the version comes
|
|
115
122
|
// from the lockstepped harness line any sibling package exposes.
|
|
116
123
|
return `dsh ${installedUpstreamLines()[0] ?? 'unknown'}`
|
|
@@ -118,14 +125,14 @@ function dshVersionLine(): string {
|
|
|
118
125
|
|
|
119
126
|
let cachedTuiVersion: string | undefined
|
|
120
127
|
|
|
121
|
-
function tuiVersion(): string {
|
|
128
|
+
export function tuiVersion(): string {
|
|
122
129
|
if (cachedTuiVersion !== undefined) return cachedTuiVersion
|
|
123
130
|
try {
|
|
124
131
|
const path = fileURLToPath(import.meta.resolve('@xp266/dshtui/package.json'))
|
|
125
132
|
const manifest = JSON.parse(readFileSync(path, 'utf8')) as { version?: string }
|
|
126
133
|
cachedTuiVersion = manifest.version ?? 'unknown'
|
|
127
134
|
} catch {
|
|
128
|
-
// The manifest may be unreadable in unusual install layouts; the
|
|
135
|
+
// The manifest may be unreadable in unusual install layouts; the label still renders.
|
|
129
136
|
cachedTuiVersion = 'unknown'
|
|
130
137
|
}
|
|
131
138
|
return cachedTuiVersion
|
package/src/ui/message/layout.ts
CHANGED
|
@@ -248,16 +248,6 @@ function renderBody(message: Message, width: number): BodyRendered {
|
|
|
248
248
|
}
|
|
249
249
|
return { lines, rows, bgs: null }
|
|
250
250
|
}
|
|
251
|
-
case 'home-logo': {
|
|
252
|
-
const lines: string[] = []
|
|
253
|
-
const rows: Segment[][] = []
|
|
254
|
-
message.lines.forEach((line, i) => {
|
|
255
|
-
lines.push(line)
|
|
256
|
-
rows.push([{ text: line, style: { color: message.colors[i] } }])
|
|
257
|
-
})
|
|
258
|
-
for (const line of message.info) lines.push(truncate(line, Math.max(8, inner)))
|
|
259
|
-
return { lines, rows: rows.length === 0 ? null : rows, bgs: null }
|
|
260
|
-
}
|
|
261
251
|
case 'custom': {
|
|
262
252
|
const view = messageViewOf(message.view)
|
|
263
253
|
if (view === undefined) {
|
|
@@ -369,11 +359,6 @@ function planFor(message: Message, body: BodyRendered): PlanRow[] {
|
|
|
369
359
|
blankRow(),
|
|
370
360
|
]
|
|
371
361
|
}
|
|
372
|
-
case 'home-logo':
|
|
373
|
-
return [
|
|
374
|
-
...bodyRows(body.lines.length, { colStart: 4, bg: false, muted: false, selectable: true, role: 'assistant' }),
|
|
375
|
-
blankRow(),
|
|
376
|
-
]
|
|
377
362
|
case 'custom': {
|
|
378
363
|
const header: PlanRow = {
|
|
379
364
|
type: 'header',
|
|
@@ -403,7 +388,6 @@ type RenderFingerprint =
|
|
|
403
388
|
| { kind: 'tool-card'; tool: string; label: string; argsBody: string; resultBody: string | undefined; diff: ToolCardMessage['diff']; read: ToolCardMessage['read']; nested: ToolCardMessage['nested']; error: string | undefined; failed: boolean | undefined; exitCode: number | undefined; signal: string | undefined; bodyCol: number | undefined; running: boolean; streaming: boolean | undefined }
|
|
404
389
|
| { kind: 'compaction'; summary: string; running: boolean; error: string | undefined }
|
|
405
390
|
| { kind: 'custom'; view: string; data: unknown; running: boolean | undefined; streaming: boolean | undefined }
|
|
406
|
-
| { kind: 'home-logo'; lines: readonly string[]; colors: readonly string[]; info: readonly string[] }
|
|
407
391
|
|
|
408
392
|
interface RenderMemoEntry {
|
|
409
393
|
epoch: number
|
|
@@ -467,13 +451,6 @@ function fingerprintOf(message: Message): RenderFingerprint {
|
|
|
467
451
|
running: message.running,
|
|
468
452
|
streaming: message.streaming,
|
|
469
453
|
}
|
|
470
|
-
case 'home-logo':
|
|
471
|
-
return {
|
|
472
|
-
kind: 'home-logo',
|
|
473
|
-
lines: message.lines,
|
|
474
|
-
colors: message.colors,
|
|
475
|
-
info: message.info,
|
|
476
|
-
}
|
|
477
454
|
}
|
|
478
455
|
}
|
|
479
456
|
|
|
@@ -520,11 +497,6 @@ function fingerprintMatches(entry: RenderFingerprint, message: Message): boolean
|
|
|
520
497
|
&& entry.data === message.data
|
|
521
498
|
&& entry.running === message.running
|
|
522
499
|
&& entry.streaming === message.streaming
|
|
523
|
-
case 'home-logo':
|
|
524
|
-
return message.kind === 'home-logo'
|
|
525
|
-
&& entry.lines === message.lines
|
|
526
|
-
&& entry.colors === message.colors
|
|
527
|
-
&& entry.info === message.info
|
|
528
500
|
}
|
|
529
501
|
}
|
|
530
502
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { Box, useInput } from 'ink'
|
|
1
|
+
import { Box, Text, useInput } from 'ink'
|
|
2
2
|
import { isKeyConsumed } from '../key-arbiter.ts'
|
|
3
3
|
import type { Message } from '../../model/message.ts'
|
|
4
4
|
import { rowIndexFor, scrollbarGeometry } from './layout.ts'
|
|
5
5
|
import { MessageRow } from './message-row.tsx'
|
|
6
|
+
import { textWidth } from '../../core/text.ts'
|
|
6
7
|
import { COLORS } from '../../theme.ts'
|
|
7
8
|
import { scrollbarColumn } from '../layout-service.ts'
|
|
8
9
|
import { Region } from '../region.tsx'
|
|
10
|
+
import { dshVersionLine, homeLogoLineColors, homeLogoMetrics, shiftLogoDownHalfRow, tuiVersion, useHomeLogo } from '../home-logo.ts'
|
|
9
11
|
|
|
10
12
|
interface MessageListProps {
|
|
11
13
|
messages: Message[]
|
|
@@ -17,6 +19,9 @@ interface MessageListProps {
|
|
|
17
19
|
themeTick?: number
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
/** Gap between the artwork's right edge and an appended version label. */
|
|
23
|
+
const VERSION_GAP = 2
|
|
24
|
+
|
|
20
25
|
export function MessageList({ messages, height, width, scrollTop, onScroll, interactive = true, themeTick = 0 }: MessageListProps) {
|
|
21
26
|
const index = rowIndexFor(messages, width)
|
|
22
27
|
const total = index.total
|
|
@@ -32,6 +37,41 @@ export function MessageList({ messages, height, width, scrollTop, onScroll, inte
|
|
|
32
37
|
if (key.ctrl && input === 'u') scroll(-halfPage)
|
|
33
38
|
if (key.ctrl && input === 'd') scroll(halfPage)
|
|
34
39
|
})
|
|
40
|
+
const logo = useHomeLogo()
|
|
41
|
+
if (messages.length === 0) {
|
|
42
|
+
// Shift the block artwork down half a cell so its bottom edge meets the
|
|
43
|
+
// vertically centered version text.
|
|
44
|
+
const clean = shiftLogoDownHalfRow(logo?.lines.map(line => line.trimEnd()) ?? [])
|
|
45
|
+
const metrics = homeLogoMetrics(clean)
|
|
46
|
+
const dshtuiVersionLine = `dshtui ${tuiVersion()}`
|
|
47
|
+
const versionWidth = Math.max(dshVersionLine().length, dshtuiVersionLine.length)
|
|
48
|
+
// The labels hug the bottom word's ink (narrower than the widest row);
|
|
49
|
+
// the anchor is the widest of the two label rows so the labels line up
|
|
50
|
+
// even when the shifted artwork leaves them different lengths.
|
|
51
|
+
const anchorWidth = clean.length >= 2
|
|
52
|
+
? Math.max(textWidth(clean[clean.length - 2]!.trimEnd()), textWidth(clean[clean.length - 1]!.trimEnd()))
|
|
53
|
+
: metrics.columns
|
|
54
|
+
// The version labels hang right of the centered artwork block, so the
|
|
55
|
+
// width must hold the label at its overhanging position.
|
|
56
|
+
const fits = logo !== undefined && width >= 2 * (anchorWidth + VERSION_GAP + versionWidth) - metrics.columns && height >= metrics.rows
|
|
57
|
+
const colors = homeLogoLineColors(clean, COLORS.homeLogoTop, COLORS.homeLogoBottom)
|
|
58
|
+
const left = Math.floor((width - metrics.columns) / 2)
|
|
59
|
+
const rowText = (i: number): string => {
|
|
60
|
+
const artwork = clean[i]!.padEnd(i >= clean.length - 2 ? anchorWidth : metrics.columns)
|
|
61
|
+
if (i === clean.length - 1) return `${artwork}${' '.repeat(VERSION_GAP)}${dshtuiVersionLine}`
|
|
62
|
+
if (i === clean.length - 2) return `${artwork}${' '.repeat(VERSION_GAP)}${dshVersionLine()}`
|
|
63
|
+
return artwork
|
|
64
|
+
}
|
|
65
|
+
return (
|
|
66
|
+
<Box width={width} height={height} flexDirection="column" justifyContent="center" overflow="hidden">
|
|
67
|
+
{fits && colors.map((color, i) => (
|
|
68
|
+
<Box key={i} marginLeft={left}>
|
|
69
|
+
<Text color={color}>{rowText(i)}</Text>
|
|
70
|
+
</Box>
|
|
71
|
+
))}
|
|
72
|
+
</Box>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
35
75
|
const rows = []
|
|
36
76
|
const endRow = Math.min(scrollTop + height, total)
|
|
37
77
|
for (let row = scrollTop; row < endRow; row++) {
|
package/src/ui/message/warmup.ts
CHANGED
|
@@ -28,7 +28,6 @@ export function warmRenderPipeline(): void {
|
|
|
28
28
|
renderMarkdown(MARKDOWN_FIXTURE, 96)
|
|
29
29
|
renderMarkdown(MARKDOWN_FIXTURE, 72)
|
|
30
30
|
const messages: Message[] = [
|
|
31
|
-
{ kind: 'home-logo', id: 'warm-logo', lines: ['██ ████'], colors: ['#7aa2f7'], info: ['dsh 0.0.0', 'dshtui 0.0.0'] },
|
|
32
31
|
{ kind: 'bubble', id: 'warm-user', role: 'user', content: 'warm up the render pipeline' },
|
|
33
32
|
{ kind: 'bubble', id: 'warm-ai', role: 'assistant', content: MARKDOWN_FIXTURE },
|
|
34
33
|
{ kind: 'collapsible', id: 'warm-think', label: 'Thinking', body: 'thinking fixture body', running: false, collapsed: false, thinking: true },
|