parallel-codex-tui 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -7
- package/dist/cli.js +11 -1
- package/dist/core/session-manager.js +569 -13
- package/dist/domain/schemas.js +8 -0
- package/dist/tui/App.js +399 -9
- package/dist/tui/InputBar.js +30 -4
- package/dist/tui/MainConversationsView.js +162 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { compactEndByDisplayWidth, displayWidth } from "./display-width.js";
|
|
4
|
+
import { TUI_THEME } from "./theme.js";
|
|
5
|
+
export function MainConversationsView({ conversations, selectedIndex, includeArchived = false, notice = null, action = null, loading = false, error = null, height = 20, terminalWidth = process.stdout.columns || 120 }) {
|
|
6
|
+
const viewportHeight = Math.max(1, height);
|
|
7
|
+
const width = mainConversationsContentWidth(terminalWidth);
|
|
8
|
+
const lines = mainConversationsDisplayLines(conversations, selectedIndex, viewportHeight, terminalWidth, { includeArchived, notice, action, loading, error });
|
|
9
|
+
const blankRows = Math.max(0, viewportHeight - lines.length);
|
|
10
|
+
return (_jsxs(Box, { flexDirection: "column", height: viewportHeight, children: [lines.map((line, index) => (_jsx(MainConversationRow, { line: line, width: width }, `${line.conversationIndex ?? line.tone}-${index}`))), Array.from({ length: blankRows }, (_, index) => (_jsx(Text, { backgroundColor: TUI_THEME.surface, children: " ".repeat(width) }, `main-conversation-fill-${index}`)))] }));
|
|
11
|
+
}
|
|
12
|
+
export function mainConversationsDisplayLines(conversations, selectedIndex, height, terminalWidth, state = {}) {
|
|
13
|
+
const viewportHeight = Math.max(1, Math.trunc(height));
|
|
14
|
+
const width = mainConversationsContentWidth(terminalWidth);
|
|
15
|
+
const lines = [{
|
|
16
|
+
text: fitMainConversationCandidates([
|
|
17
|
+
state.includeArchived ? "Main conversations · archived shown" : "Main conversations",
|
|
18
|
+
state.includeArchived ? "Conversations · all" : "Conversations",
|
|
19
|
+
"Chats",
|
|
20
|
+
"C"
|
|
21
|
+
], width),
|
|
22
|
+
tone: "heading"
|
|
23
|
+
}];
|
|
24
|
+
if (viewportHeight >= 3) {
|
|
25
|
+
const messages = conversations.reduce((sum, conversation) => sum + conversation.messageCount, 0);
|
|
26
|
+
const nativeSessions = conversations.reduce((sum, conversation) => sum + conversation.nativeSessionCount, 0);
|
|
27
|
+
const archived = conversations.filter((conversation) => conversation.archivedAt).length;
|
|
28
|
+
lines.push({
|
|
29
|
+
text: fitMainConversationCandidates([
|
|
30
|
+
[
|
|
31
|
+
`${conversations.length} ${conversations.length === 1 ? "conversation" : "conversations"}`,
|
|
32
|
+
`${messages} messages`,
|
|
33
|
+
`${nativeSessions} native`,
|
|
34
|
+
...(archived > 0 ? [`${archived} archived`] : [])
|
|
35
|
+
].join(" · "),
|
|
36
|
+
`${conversations.length} conversations · ${messages} messages`,
|
|
37
|
+
`${conversations.length} conversations`,
|
|
38
|
+
`${conversations.length} chats`
|
|
39
|
+
], width),
|
|
40
|
+
tone: "muted"
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (viewportHeight >= 4 && state.action) {
|
|
44
|
+
lines.push({
|
|
45
|
+
text: fitMainConversationText(state.action.type === "rename"
|
|
46
|
+
? `rename · ${safeMainConversationText(state.action.title)} · Enter save · Esc cancel`
|
|
47
|
+
: `delete · ${safeMainConversationText(state.action.title)} · press D again · Esc cancel`, width),
|
|
48
|
+
tone: state.action.type === "delete" ? "danger" : "active"
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
else if (viewportHeight >= 4 && state.notice) {
|
|
52
|
+
lines.push({ text: fitMainConversationText(state.notice, width), tone: "success" });
|
|
53
|
+
}
|
|
54
|
+
const slots = Math.max(0, viewportHeight - lines.length);
|
|
55
|
+
if (state.loading) {
|
|
56
|
+
if (slots > 0) {
|
|
57
|
+
lines.push({ text: fitMainConversationText("loading Main conversations", width), tone: "muted" });
|
|
58
|
+
}
|
|
59
|
+
return lines;
|
|
60
|
+
}
|
|
61
|
+
if (state.error) {
|
|
62
|
+
if (slots > 0) {
|
|
63
|
+
lines.push({ text: fitMainConversationText(`error · ${safeMainConversationText(state.error)}`, width), tone: "danger" });
|
|
64
|
+
}
|
|
65
|
+
return lines;
|
|
66
|
+
}
|
|
67
|
+
if (conversations.length === 0) {
|
|
68
|
+
if (slots > 0) {
|
|
69
|
+
lines.push({ text: fitMainConversationText("No saved Main conversations", width), tone: "muted" });
|
|
70
|
+
}
|
|
71
|
+
return lines;
|
|
72
|
+
}
|
|
73
|
+
const selected = clampMainConversationIndex(selectedIndex, conversations.length);
|
|
74
|
+
const visibleCount = Math.min(slots, conversations.length);
|
|
75
|
+
const start = mainConversationWindowStart(selected, conversations.length, visibleCount);
|
|
76
|
+
for (let index = start; index < start + visibleCount; index += 1) {
|
|
77
|
+
const conversation = conversations[index];
|
|
78
|
+
if (!conversation) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
lines.push({
|
|
82
|
+
text: mainConversationRowText(conversation, index === selected, width),
|
|
83
|
+
tone: conversation.current ? "success" : index === selected ? "active" : "muted",
|
|
84
|
+
conversationIndex: index
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return lines;
|
|
88
|
+
}
|
|
89
|
+
export function moveMainConversationSelection(current, delta, conversationCount, wrap = false) {
|
|
90
|
+
if (conversationCount <= 0) {
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
const normalizedCurrent = clampMainConversationIndex(current, conversationCount);
|
|
94
|
+
const next = normalizedCurrent + Math.trunc(delta);
|
|
95
|
+
if (wrap) {
|
|
96
|
+
return ((next % conversationCount) + conversationCount) % conversationCount;
|
|
97
|
+
}
|
|
98
|
+
return Math.min(conversationCount - 1, Math.max(0, next));
|
|
99
|
+
}
|
|
100
|
+
function MainConversationRow({ line, width }) {
|
|
101
|
+
const trailingWidth = Math.max(0, width - displayWidth(line.text));
|
|
102
|
+
const theme = mainConversationLineTheme(line.tone);
|
|
103
|
+
return (_jsxs(Text, { children: [_jsx(Text, { ...theme, children: line.text }), trailingWidth > 0 ? _jsx(Text, { backgroundColor: TUI_THEME.surface, children: " ".repeat(trailingWidth) }) : null] }));
|
|
104
|
+
}
|
|
105
|
+
function mainConversationRowText(conversation, selected, width) {
|
|
106
|
+
const marker = `${selected ? ">" : " "} ${conversation.current ? "*" : " "} `;
|
|
107
|
+
const title = safeMainConversationText(conversation.title);
|
|
108
|
+
const messages = `${conversation.messageCount} ${conversation.messageCount === 1 ? "message" : "messages"}`;
|
|
109
|
+
const native = `${conversation.nativeSessionCount} native`;
|
|
110
|
+
const status = conversation.archivedAt ? "archived" : null;
|
|
111
|
+
const date = conversation.lastActivityAt.slice(5, 16).replace("T", " ");
|
|
112
|
+
const scope = conversation.id
|
|
113
|
+
? `#${conversation.id.replace(/^conversation-/, "")}`
|
|
114
|
+
: "legacy";
|
|
115
|
+
return fitMainConversationCandidates([
|
|
116
|
+
[marker + title, status, messages, native, date].filter(Boolean).join(" · "),
|
|
117
|
+
[marker + title, status, messages, native].filter(Boolean).join(" · "),
|
|
118
|
+
[marker + title, status, messages].filter(Boolean).join(" · "),
|
|
119
|
+
[marker + title, date].join(" · "),
|
|
120
|
+
[marker + scope, messages].join(" · "),
|
|
121
|
+
marker.trimEnd()
|
|
122
|
+
], width);
|
|
123
|
+
}
|
|
124
|
+
function mainConversationWindowStart(selected, count, visibleCount) {
|
|
125
|
+
if (visibleCount <= 0 || count <= visibleCount) {
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
return Math.min(count - visibleCount, Math.max(0, selected - Math.floor(visibleCount / 2)));
|
|
129
|
+
}
|
|
130
|
+
function clampMainConversationIndex(index, count) {
|
|
131
|
+
return Math.min(Math.max(0, count - 1), Math.max(0, Math.trunc(index)));
|
|
132
|
+
}
|
|
133
|
+
function fitMainConversationCandidates(candidates, width) {
|
|
134
|
+
return candidates.find((candidate) => displayWidth(candidate) <= width)
|
|
135
|
+
?? fitMainConversationText(candidates.at(-1) ?? "", width);
|
|
136
|
+
}
|
|
137
|
+
function fitMainConversationText(text, width) {
|
|
138
|
+
return compactEndByDisplayWidth(text, Math.max(1, width));
|
|
139
|
+
}
|
|
140
|
+
function safeMainConversationText(text) {
|
|
141
|
+
return text
|
|
142
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
143
|
+
.replace(/[\u0000-\u001f\u007f]/g, " ")
|
|
144
|
+
.replace(/\s+/g, " ")
|
|
145
|
+
.trim();
|
|
146
|
+
}
|
|
147
|
+
function mainConversationLineTheme(tone) {
|
|
148
|
+
return {
|
|
149
|
+
backgroundColor: TUI_THEME.surface,
|
|
150
|
+
color: tone === "heading" || tone === "active"
|
|
151
|
+
? TUI_THEME.accent
|
|
152
|
+
: tone === "success"
|
|
153
|
+
? TUI_THEME.success
|
|
154
|
+
: tone === "danger"
|
|
155
|
+
? TUI_THEME.danger
|
|
156
|
+
: TUI_THEME.muted,
|
|
157
|
+
...(tone === "heading" || tone === "danger" ? { bold: true } : {})
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function mainConversationsContentWidth(terminalWidth) {
|
|
161
|
+
return Math.max(1, terminalWidth - 2);
|
|
162
|
+
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "0.2.
|
|
1
|
+
export const version = "0.2.10";
|