@narumitw/pi-btw 0.55.4 → 0.56.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/README.md +125 -88
- package/dist/index.ts +140 -45
- package/dist/index.ts.map +2 -2
- package/package.json +5 -8
- package/src/btw.ts +31 -26
- package/src/fullscreen-ui.ts +106 -17
- package/src/menu.ts +34 -2
- package/src/settings.ts +16 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narumitw/pi-btw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.0",
|
|
4
4
|
"description": "Pi extension that adds a /btw side-question command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,9 +23,6 @@
|
|
|
23
23
|
"./dist/index.ts"
|
|
24
24
|
]
|
|
25
25
|
},
|
|
26
|
-
"piExtension": {
|
|
27
|
-
"lifecycle": "stable"
|
|
28
|
-
},
|
|
29
26
|
"scripts": {
|
|
30
27
|
"build": "node scripts/build-runtime.mjs",
|
|
31
28
|
"check": "npm run build && biome check . && npm run typecheck",
|
|
@@ -34,10 +31,10 @@
|
|
|
34
31
|
"prepack": "npm run build"
|
|
35
32
|
},
|
|
36
33
|
"devDependencies": {
|
|
37
|
-
"@biomejs/biome": "2.5.
|
|
38
|
-
"@earendil-works/pi-ai": "0.84.
|
|
39
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
40
|
-
"@earendil-works/pi-tui": "0.84.
|
|
34
|
+
"@biomejs/biome": "2.5.11",
|
|
35
|
+
"@earendil-works/pi-ai": "0.84.4",
|
|
36
|
+
"@earendil-works/pi-coding-agent": "0.84.4",
|
|
37
|
+
"@earendil-works/pi-tui": "0.84.4",
|
|
41
38
|
"esbuild": "0.28.2",
|
|
42
39
|
"typescript": "7.0.2"
|
|
43
40
|
},
|
package/src/btw.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from "./menu.js";
|
|
34
34
|
import {
|
|
35
35
|
type BtwSettings,
|
|
36
|
+
effectiveFullscreenCopyOnSelect,
|
|
36
37
|
effectiveRememberThinkingLevelChanges,
|
|
37
38
|
parseBtwModelReference,
|
|
38
39
|
readBtwSettings,
|
|
@@ -193,7 +194,7 @@ export async function loadBtwThinkingLevel(
|
|
|
193
194
|
|
|
194
195
|
options.warn?.(
|
|
195
196
|
sanitizeSingleLine(
|
|
196
|
-
`pi-btw settings ignored: ${settings.reason}; expected optional model "provider/model-id", omitted thinkingLevel for Same as main thread or thinkingLevel "${BTW_THINKING_LEVELS.join('" | "')}", and boolean
|
|
197
|
+
`pi-btw settings ignored: ${settings.reason}; expected optional model "provider/model-id", omitted thinkingLevel for Same as main thread or thinkingLevel "${BTW_THINKING_LEVELS.join('" | "')}", boolean rememberThinkingLevelChanges, and boolean fullscreenCopyOnSelect. Using current Pi thinking level.`,
|
|
197
198
|
),
|
|
198
199
|
);
|
|
199
200
|
return currentThinkingLevel;
|
|
@@ -314,31 +315,35 @@ export default function btw(pi: ExtensionAPI, dependencies: BtwExtensionDependen
|
|
|
314
315
|
const startingTurnCount = state?.thread.turns.length ?? 0;
|
|
315
316
|
|
|
316
317
|
try {
|
|
317
|
-
await runFullscreen(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
state
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
318
|
+
await runFullscreen(
|
|
319
|
+
ctx,
|
|
320
|
+
(fullscreenCtx) => {
|
|
321
|
+
if (!state) {
|
|
322
|
+
const createdAt = Date.now();
|
|
323
|
+
state = {
|
|
324
|
+
id: `btw-${nextThreadNumber}`,
|
|
325
|
+
thread: createSideThread(
|
|
326
|
+
selectedConversationContext ??
|
|
327
|
+
buildConversationContext(fullscreenCtx.sessionManager.getBranch()),
|
|
328
|
+
),
|
|
329
|
+
thinkingLevel: settings.thinkingLevel ?? pi.getThinkingLevel(),
|
|
330
|
+
createdAt,
|
|
331
|
+
updatedAt: createdAt,
|
|
332
|
+
};
|
|
333
|
+
nextThreadNumber += 1;
|
|
334
|
+
}
|
|
335
|
+
return runThread({
|
|
336
|
+
initialQuestion: question || undefined,
|
|
337
|
+
selected: resolution.selected,
|
|
338
|
+
thinkingLevel: state.thinkingLevel,
|
|
339
|
+
rememberThinkingLevelChanges:
|
|
340
|
+
!sameAsMainThinkingLevel && effectiveRememberThinkingLevelChanges(settings),
|
|
341
|
+
state,
|
|
342
|
+
ctx: fullscreenCtx,
|
|
343
|
+
});
|
|
344
|
+
},
|
|
345
|
+
{ copyOnSelect: effectiveFullscreenCopyOnSelect(settings) },
|
|
346
|
+
);
|
|
342
347
|
} finally {
|
|
343
348
|
if (state?.title && state.thread.turns.length > 0) {
|
|
344
349
|
if (state.thread.turns.length > startingTurnCount) {
|
package/src/fullscreen-ui.ts
CHANGED
|
@@ -30,6 +30,7 @@ type BtwCustomFactory<T> = (
|
|
|
30
30
|
type BtwFullscreenTui = TUI & {
|
|
31
31
|
flash?: (message: string, durationMs?: number) => void;
|
|
32
32
|
setLayoutRoot(component: Component | undefined): void;
|
|
33
|
+
addInputListenerBeforeAll?(listener: TuiInputListener): () => void;
|
|
33
34
|
addInputListenerBeforeViewport?(listener: TuiInputListener): () => void;
|
|
34
35
|
};
|
|
35
36
|
|
|
@@ -37,17 +38,28 @@ export interface BtwFullscreenLayoutComponent extends Component {
|
|
|
37
38
|
getFullscreenLayout(): Component;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
export
|
|
41
|
+
export interface BtwFullscreenOptions {
|
|
42
|
+
copyOnSelect?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type BtwFullscreenTuiFactory = (
|
|
46
|
+
parent: TUI,
|
|
47
|
+
theme: Theme,
|
|
48
|
+
keybindings: KeybindingsManager,
|
|
49
|
+
options: BtwFullscreenOptions,
|
|
50
|
+
) => BtwFullscreenTui;
|
|
41
51
|
|
|
42
52
|
export interface BtwFullscreenDependencies {
|
|
43
53
|
createTui?: BtwFullscreenTuiFactory;
|
|
44
54
|
openUrl?: (url: string) => void;
|
|
45
55
|
copyToClipboard?: (text: string) => Promise<void>;
|
|
56
|
+
manualSelectionCopySupported?: boolean;
|
|
46
57
|
}
|
|
47
58
|
|
|
48
59
|
export type RunBtwFullscreen = <T>(
|
|
49
60
|
ctx: ExtensionCommandContext,
|
|
50
61
|
run: (ctx: ExtensionCommandContext) => Promise<T>,
|
|
62
|
+
options?: BtwFullscreenOptions,
|
|
51
63
|
) => Promise<T>;
|
|
52
64
|
|
|
53
65
|
type FullscreenOutcome<T> = { kind: "completed"; value: T } | { kind: "failed"; error: unknown };
|
|
@@ -62,14 +74,23 @@ class FullscreenUiDisposedError extends Error {
|
|
|
62
74
|
export async function runBtwFullscreen<T>(
|
|
63
75
|
ctx: ExtensionCommandContext,
|
|
64
76
|
run: (ctx: ExtensionCommandContext) => Promise<T>,
|
|
77
|
+
options: BtwFullscreenOptions = {},
|
|
65
78
|
dependencies: BtwFullscreenDependencies = {},
|
|
66
79
|
): Promise<T> {
|
|
67
80
|
const createTui =
|
|
68
81
|
dependencies.createTui ??
|
|
69
|
-
((
|
|
82
|
+
((
|
|
83
|
+
parent: TUI,
|
|
84
|
+
theme: Theme,
|
|
85
|
+
keybindings: KeybindingsManager,
|
|
86
|
+
fullscreenOptions: BtwFullscreenOptions,
|
|
87
|
+
) =>
|
|
70
88
|
createBtwFullscreenTui(
|
|
71
89
|
parent,
|
|
72
90
|
theme,
|
|
91
|
+
keybindings,
|
|
92
|
+
fullscreenOptions.copyOnSelect ?? true,
|
|
93
|
+
dependencies.manualSelectionCopySupported ?? hasManualSelectionCopyApi(),
|
|
73
94
|
dependencies.openUrl ?? openUrlInBrowser,
|
|
74
95
|
dependencies.copyToClipboard ?? copyToHostClipboard,
|
|
75
96
|
));
|
|
@@ -94,6 +115,7 @@ export async function runBtwFullscreen<T>(
|
|
|
94
115
|
done(value);
|
|
95
116
|
},
|
|
96
117
|
createTui,
|
|
118
|
+
options,
|
|
97
119
|
);
|
|
98
120
|
return host;
|
|
99
121
|
},
|
|
@@ -114,6 +136,7 @@ export async function runBtwFullscreen<T>(
|
|
|
114
136
|
}
|
|
115
137
|
|
|
116
138
|
type BtwInputListeners = {
|
|
139
|
+
beforeAll: Set<TuiInputListener>;
|
|
117
140
|
beforeViewport: Set<TuiInputListener>;
|
|
118
141
|
regular: Set<TuiInputListener>;
|
|
119
142
|
};
|
|
@@ -122,7 +145,7 @@ const btwInputListeners = new WeakMap<BtwTuiAltScreen, BtwInputListeners>();
|
|
|
122
145
|
|
|
123
146
|
function dispatchBtwInput(listeners: BtwInputListeners, data: string): TuiInputListenerResult {
|
|
124
147
|
let current = data;
|
|
125
|
-
for (const group of [listeners.beforeViewport, listeners.regular]) {
|
|
148
|
+
for (const group of [listeners.beforeAll, listeners.beforeViewport, listeners.regular]) {
|
|
126
149
|
for (const listener of group) {
|
|
127
150
|
const result = listener(current);
|
|
128
151
|
if (result?.consume) return result;
|
|
@@ -133,10 +156,15 @@ function dispatchBtwInput(listeners: BtwInputListeners, data: string): TuiInputL
|
|
|
133
156
|
}
|
|
134
157
|
|
|
135
158
|
class BtwTuiAltScreen extends TuiAltScreen {
|
|
159
|
+
hasFocusedOverlay(): boolean {
|
|
160
|
+
return this.isOverlayFocused();
|
|
161
|
+
}
|
|
162
|
+
|
|
136
163
|
override addInputListener(listener: TuiInputListener): () => void {
|
|
137
164
|
let listeners = btwInputListeners.get(this);
|
|
138
165
|
if (!listeners) {
|
|
139
166
|
const registeredListeners: BtwInputListeners = {
|
|
167
|
+
beforeAll: new Set(),
|
|
140
168
|
beforeViewport: new Set(),
|
|
141
169
|
regular: new Set(),
|
|
142
170
|
};
|
|
@@ -148,6 +176,13 @@ class BtwTuiAltScreen extends TuiAltScreen {
|
|
|
148
176
|
return () => listeners.regular.delete(listener);
|
|
149
177
|
}
|
|
150
178
|
|
|
179
|
+
addInputListenerBeforeAll(listener: TuiInputListener): () => void {
|
|
180
|
+
const listeners = btwInputListeners.get(this);
|
|
181
|
+
if (!listeners) return super.addInputListener(listener);
|
|
182
|
+
listeners.beforeAll.add(listener);
|
|
183
|
+
return () => listeners.beforeAll.delete(listener);
|
|
184
|
+
}
|
|
185
|
+
|
|
151
186
|
addInputListenerBeforeViewport(listener: TuiInputListener): () => void {
|
|
152
187
|
const listeners = btwInputListeners.get(this);
|
|
153
188
|
if (!listeners) return super.addInputListener(listener);
|
|
@@ -161,33 +196,85 @@ class BtwTuiAltScreen extends TuiAltScreen {
|
|
|
161
196
|
super.removeInputListener(listener);
|
|
162
197
|
return;
|
|
163
198
|
}
|
|
199
|
+
listeners.beforeAll.delete(listener);
|
|
164
200
|
listeners.beforeViewport.delete(listener);
|
|
165
201
|
listeners.regular.delete(listener);
|
|
166
202
|
}
|
|
167
203
|
}
|
|
168
204
|
|
|
205
|
+
const BRACKETED_PASTE_START = "\u001b[200~";
|
|
206
|
+
const BRACKETED_PASTE_END = "\u001b[201~";
|
|
207
|
+
|
|
208
|
+
function hasManualSelectionCopyApi(): boolean {
|
|
209
|
+
return (
|
|
210
|
+
typeof TuiAltScreen.prototype.hasActiveSelection === "function" &&
|
|
211
|
+
typeof TuiAltScreen.prototype.copyActiveSelectionToClipboard === "function"
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
169
215
|
function createBtwFullscreenTui(
|
|
170
216
|
parent: TUI,
|
|
171
217
|
theme: Theme,
|
|
218
|
+
keybindings: KeybindingsManager,
|
|
219
|
+
copyOnSelect: boolean,
|
|
220
|
+
manualSelectionCopySupported: boolean,
|
|
172
221
|
openUrl: (url: string) => void,
|
|
173
222
|
copyToClipboard: (text: string) => Promise<void>,
|
|
174
223
|
): BtwFullscreenTui {
|
|
224
|
+
if (!copyOnSelect && !manualSelectionCopySupported) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
"Manual fullscreen selection copying is unavailable in this Pi version; update Pi or enable automatic selection copying.",
|
|
227
|
+
);
|
|
228
|
+
}
|
|
175
229
|
const styleSearchMatch = (text: string) =>
|
|
176
230
|
theme.bg("searchMatchBg", theme.fg("searchMatchText", text));
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
231
|
+
const fullscreen = new BtwTuiAltScreen(
|
|
232
|
+
parent.terminal,
|
|
233
|
+
parent.getShowHardwareCursor(),
|
|
234
|
+
undefined,
|
|
235
|
+
{
|
|
236
|
+
mouse: true,
|
|
237
|
+
copyOnSelect,
|
|
238
|
+
searchMatchStyle: (text) => theme.underline(styleSearchMatch(text)),
|
|
239
|
+
searchCurrentMatchStyle: (text) => theme.bold(theme.inverse(styleSearchMatch(text))),
|
|
240
|
+
openUrl,
|
|
241
|
+
copySelection: async (text) => {
|
|
242
|
+
try {
|
|
243
|
+
await copyToClipboard(text);
|
|
244
|
+
return true;
|
|
245
|
+
} catch {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
},
|
|
189
249
|
},
|
|
190
|
-
|
|
250
|
+
);
|
|
251
|
+
if (!copyOnSelect) {
|
|
252
|
+
let isInBracketedPaste = false;
|
|
253
|
+
fullscreen.addInputListenerBeforeViewport((data) => {
|
|
254
|
+
const wasInBracketedPaste = isInBracketedPaste;
|
|
255
|
+
const startsBracketedPaste = data.includes(BRACKETED_PASTE_START);
|
|
256
|
+
if (startsBracketedPaste) isInBracketedPaste = true;
|
|
257
|
+
if (isInBracketedPaste && data.includes(BRACKETED_PASTE_END)) {
|
|
258
|
+
isInBracketedPaste = false;
|
|
259
|
+
}
|
|
260
|
+
if (
|
|
261
|
+
wasInBracketedPaste ||
|
|
262
|
+
startsBracketedPaste ||
|
|
263
|
+
fullscreen.hasFocusedOverlay() ||
|
|
264
|
+
isKeyRelease(data) ||
|
|
265
|
+
!keybindings.matches(data, "app.message.copy")
|
|
266
|
+
) {
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
if (!fullscreen.hasActiveSelection()) {
|
|
270
|
+
fullscreen.flash("No selection to copy");
|
|
271
|
+
return { consume: true };
|
|
272
|
+
}
|
|
273
|
+
void fullscreen.copyActiveSelectionToClipboard().catch(() => fullscreen.flash("Copy failed"));
|
|
274
|
+
return { consume: true };
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return fullscreen;
|
|
191
278
|
}
|
|
192
279
|
|
|
193
280
|
// Pi does not export its browser opener, so mirror its shell-free launcher for this isolated TUI.
|
|
@@ -226,6 +313,7 @@ class BtwFullscreenHost<T> implements Component {
|
|
|
226
313
|
private readonly run: (ctx: ExtensionCommandContext) => Promise<T>,
|
|
227
314
|
private readonly done: (outcome: FullscreenOutcome<T>) => void,
|
|
228
315
|
private readonly createTui: BtwFullscreenTuiFactory,
|
|
316
|
+
private readonly options: BtwFullscreenOptions,
|
|
229
317
|
) {
|
|
230
318
|
queueMicrotask(() => void this.start());
|
|
231
319
|
}
|
|
@@ -255,11 +343,12 @@ class BtwFullscreenHost<T> implements Component {
|
|
|
255
343
|
this.parent.stop({ preserveScreen: true });
|
|
256
344
|
this.parentStopped = true;
|
|
257
345
|
if (this.disposed) throw new FullscreenUiDisposedError();
|
|
258
|
-
this.fullscreen = this.createTui(this.parent, this.theme);
|
|
346
|
+
this.fullscreen = this.createTui(this.parent, this.theme, this.keybindings, this.options);
|
|
259
347
|
this.fullscreenCreated = true;
|
|
260
348
|
this.fullscreen.start();
|
|
261
349
|
// Waiting for the custom promise would leave follow-up keys bound to the side TUI.
|
|
262
350
|
const addHardCancelListener =
|
|
351
|
+
this.fullscreen.addInputListenerBeforeAll?.bind(this.fullscreen) ??
|
|
263
352
|
this.fullscreen.addInputListenerBeforeViewport?.bind(this.fullscreen) ??
|
|
264
353
|
this.fullscreen.addInputListener.bind(this.fullscreen);
|
|
265
354
|
this.removeHardCancelListener = addHardCancelListener((data) => {
|
package/src/menu.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type BtwSettings,
|
|
10
10
|
type BtwSettingsPatch,
|
|
11
11
|
btwSettingsPath,
|
|
12
|
+
effectiveFullscreenCopyOnSelect,
|
|
12
13
|
effectiveRememberThinkingLevelChanges,
|
|
13
14
|
readBtwSettings,
|
|
14
15
|
type UpdateBtwSettingsOptions,
|
|
@@ -48,7 +49,13 @@ export type BtwCommandMenuResult =
|
|
|
48
49
|
| { kind: "resume"; threadId: string };
|
|
49
50
|
|
|
50
51
|
type BtwMenuScreen = "main" | "resume" | "settings" | "invalid";
|
|
51
|
-
type BtwMenuAction =
|
|
52
|
+
type BtwMenuAction =
|
|
53
|
+
| "start"
|
|
54
|
+
| "start-tree"
|
|
55
|
+
| "resume"
|
|
56
|
+
| "set-thinking"
|
|
57
|
+
| "set-remember"
|
|
58
|
+
| "set-fullscreen-copy";
|
|
52
59
|
const SAME_AS_MAIN_THREAD = "Same as main thread";
|
|
53
60
|
type BtwCustomOptions = Parameters<ExtensionCommandContext["ui"]["custom"]>[1];
|
|
54
61
|
|
|
@@ -111,6 +118,7 @@ export async function showBtwCommandMenu(
|
|
|
111
118
|
title: "Pi BTW",
|
|
112
119
|
lines: [
|
|
113
120
|
`Thinking: ${displayThinkingSummary(state.settings)} · Remember changes: ${displayRememberSummary(state.settings)}`,
|
|
121
|
+
`Copy on select: ${effectiveFullscreenCopyOnSelect(state.settings) ? "On" : "Off"}`,
|
|
114
122
|
],
|
|
115
123
|
items: [
|
|
116
124
|
{
|
|
@@ -138,7 +146,7 @@ export async function showBtwCommandMenu(
|
|
|
138
146
|
{
|
|
139
147
|
id: "settings",
|
|
140
148
|
label: "Settings",
|
|
141
|
-
description: "Choose
|
|
149
|
+
description: "Choose thinking, shortcut memory, and selection copying",
|
|
142
150
|
to: state.kind === "invalid" ? "invalid" : "settings",
|
|
143
151
|
},
|
|
144
152
|
],
|
|
@@ -178,6 +186,15 @@ export async function showBtwCommandMenu(
|
|
|
178
186
|
values: ["On", "Off"],
|
|
179
187
|
action: "set-remember",
|
|
180
188
|
},
|
|
189
|
+
{
|
|
190
|
+
id: "fullscreenCopyOnSelect",
|
|
191
|
+
label: "Copy selection automatically",
|
|
192
|
+
description:
|
|
193
|
+
"Copy mouse selections immediately instead of with the configured copy key.",
|
|
194
|
+
currentValue: effectiveFullscreenCopyOnSelect(state.settings) ? "On" : "Off",
|
|
195
|
+
values: ["On", "Off"],
|
|
196
|
+
action: "set-fullscreen-copy",
|
|
197
|
+
},
|
|
181
198
|
],
|
|
182
199
|
}),
|
|
183
200
|
invalid: ({ state }) => ({
|
|
@@ -240,6 +257,21 @@ export async function showBtwCommandMenu(
|
|
|
240
257
|
return { kind: "rejected" };
|
|
241
258
|
}
|
|
242
259
|
},
|
|
260
|
+
"set-fullscreen-copy": async ({ value, signal }) => {
|
|
261
|
+
if (value !== "On" && value !== "Off") return { kind: "rejected" };
|
|
262
|
+
try {
|
|
263
|
+
await updateSettings(
|
|
264
|
+
{ fullscreenCopyOnSelect: value === "On" },
|
|
265
|
+
{ settingsPath, signal },
|
|
266
|
+
);
|
|
267
|
+
if (signal.aborted) return { kind: "rejected" };
|
|
268
|
+
notifySafely(ctx, `Copy selection automatically: ${value}.`, "info");
|
|
269
|
+
return { kind: "stay" };
|
|
270
|
+
} catch (error) {
|
|
271
|
+
if (!signal.aborted) notifySaveFailure(ctx, error);
|
|
272
|
+
return { kind: "rejected" };
|
|
273
|
+
}
|
|
274
|
+
},
|
|
243
275
|
},
|
|
244
276
|
});
|
|
245
277
|
|
package/src/settings.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
|
6
6
|
import { BTW_THINKING_LEVELS, type BtwThinkingLevel } from "./side-thread.js";
|
|
7
7
|
|
|
8
8
|
export const BTW_SETTINGS_FILE = "pi-btw.json";
|
|
9
|
+
export const DEFAULT_FULLSCREEN_COPY_ON_SELECT = true;
|
|
9
10
|
export const DEFAULT_REMEMBER_THINKING_LEVEL_CHANGES = true;
|
|
10
11
|
const MAX_SETTINGS_BYTES = 64 * 1024;
|
|
11
12
|
|
|
@@ -13,6 +14,7 @@ export interface BtwSettings {
|
|
|
13
14
|
model?: string;
|
|
14
15
|
thinkingLevel?: BtwThinkingLevel;
|
|
15
16
|
rememberThinkingLevelChanges?: boolean;
|
|
17
|
+
fullscreenCopyOnSelect?: boolean;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export type BtwSettingsLoadResult =
|
|
@@ -23,6 +25,7 @@ export type BtwSettingsLoadResult =
|
|
|
23
25
|
export interface BtwSettingsPatch {
|
|
24
26
|
thinkingLevel?: BtwThinkingLevel;
|
|
25
27
|
rememberThinkingLevelChanges?: boolean;
|
|
28
|
+
fullscreenCopyOnSelect?: boolean;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export interface UpdateBtwSettingsOptions {
|
|
@@ -58,6 +61,11 @@ export function normalizeBtwSettings(value: unknown): BtwSettings | undefined {
|
|
|
58
61
|
if (typeof remember !== "boolean") return undefined;
|
|
59
62
|
settings.rememberThinkingLevelChanges = remember;
|
|
60
63
|
}
|
|
64
|
+
if (Object.hasOwn(value, "fullscreenCopyOnSelect")) {
|
|
65
|
+
const copyOnSelect = Reflect.get(value, "fullscreenCopyOnSelect");
|
|
66
|
+
if (typeof copyOnSelect !== "boolean") return undefined;
|
|
67
|
+
settings.fullscreenCopyOnSelect = copyOnSelect;
|
|
68
|
+
}
|
|
61
69
|
return settings;
|
|
62
70
|
}
|
|
63
71
|
|
|
@@ -70,6 +78,10 @@ export function parseBtwModelReference(
|
|
|
70
78
|
return { provider: reference.slice(0, separator), modelId: reference.slice(separator + 1) };
|
|
71
79
|
}
|
|
72
80
|
|
|
81
|
+
export function effectiveFullscreenCopyOnSelect(settings: BtwSettings): boolean {
|
|
82
|
+
return settings.fullscreenCopyOnSelect ?? DEFAULT_FULLSCREEN_COPY_ON_SELECT;
|
|
83
|
+
}
|
|
84
|
+
|
|
73
85
|
export function effectiveRememberThinkingLevelChanges(settings: BtwSettings): boolean {
|
|
74
86
|
return settings.rememberThinkingLevelChanges ?? DEFAULT_REMEMBER_THINKING_LEVEL_CHANGES;
|
|
75
87
|
}
|
|
@@ -233,6 +245,10 @@ function applyBtwSettingsPatch(
|
|
|
233
245
|
if (Object.hasOwn(patch, "rememberThinkingLevelChanges")) {
|
|
234
246
|
updated.rememberThinkingLevelChanges = patch.rememberThinkingLevelChanges;
|
|
235
247
|
}
|
|
248
|
+
if (Object.hasOwn(patch, "fullscreenCopyOnSelect")) {
|
|
249
|
+
if (patch.fullscreenCopyOnSelect === undefined) delete updated.fullscreenCopyOnSelect;
|
|
250
|
+
else updated.fullscreenCopyOnSelect = patch.fullscreenCopyOnSelect;
|
|
251
|
+
}
|
|
236
252
|
return updated;
|
|
237
253
|
}
|
|
238
254
|
|