pi-studio 0.9.42 → 0.9.44

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.
@@ -2,6 +2,10 @@
2
2
  const PANE_FOCUS_PARAM = "paneFocus";
3
3
  const PANE_FOCUS_OFF = "off";
4
4
  const PANE_FOCUS_TARGETS = Object.freeze(["left", "right"]);
5
+ const STUDIO_TAB_STATE_PARAM = "studioTabState";
6
+ const STUDIO_TAB_STATE_ID_PATTERN = /^[a-zA-Z0-9_-]{20,128}$/;
7
+ const STUDIO_WORKSPACE_STORAGE_PREFIX = "piStudio.workspaceState.v2:";
8
+ const STUDIO_WORKSPACE_LEGACY_STORAGE_KEY = "piStudio.workspaceState.v1";
5
9
  const STUDIO_LAUNCH_PROTOCOL_VERSION = 1;
6
10
  const STUDIO_LAUNCH_CHANNEL_PREFIX = "pi-studio-launch-v1:";
7
11
  const STUDIO_PENDING_KINDS = Object.freeze(["document", "preview", "export"]);
@@ -48,6 +52,98 @@
48
52
  return true;
49
53
  }
50
54
 
55
+ function isValidStudioTabStateId(value) {
56
+ return typeof value === "string" && STUDIO_TAB_STATE_ID_PATTERN.test(value);
57
+ }
58
+
59
+ function makeStudioTabStateId(cryptoLike) {
60
+ try {
61
+ const launchId = makeStudioLaunchId(cryptoLike);
62
+ const candidate = "tab_" + launchId;
63
+ if (isValidStudioTabStateId(candidate)) return candidate;
64
+ } catch {
65
+ // This ID isolates local browser state; it is not an authentication secret.
66
+ }
67
+ const candidate = "tab_" + Date.now().toString(36) + "_" + Math.random().toString(36).slice(2).padEnd(16, "0");
68
+ if (isValidStudioTabStateId(candidate)) return candidate;
69
+ throw new Error("Could not create a Studio tab-state ID.");
70
+ }
71
+
72
+ function ensureStudioTabStateId(windowLike) {
73
+ if (!windowLike || !windowLike.location) throw new Error("Studio tab-state URL is unavailable.");
74
+ const currentHref = String(windowLike.location.href || "");
75
+ const url = new URL(currentHref);
76
+ const existing = url.searchParams.getAll(STUDIO_TAB_STATE_PARAM);
77
+ if (existing.length === 1 && isValidStudioTabStateId(existing[0])) return existing[0];
78
+
79
+ const tabStateId = makeStudioTabStateId(windowLike.crypto);
80
+ url.searchParams.delete(STUDIO_TAB_STATE_PARAM);
81
+ url.searchParams.set(STUDIO_TAB_STATE_PARAM, tabStateId);
82
+ if (windowLike.history && typeof windowLike.history.replaceState === "function") {
83
+ windowLike.history.replaceState(windowLike.history.state, "", url.toString());
84
+ }
85
+ return tabStateId;
86
+ }
87
+
88
+ function getStudioWorkspaceStorageKey(tabStateId) {
89
+ if (!isValidStudioTabStateId(tabStateId)) throw new Error("Invalid Studio tab-state ID.");
90
+ return STUDIO_WORKSPACE_STORAGE_PREFIX + tabStateId;
91
+ }
92
+
93
+ function getStudioSessionStorage(windowLike) {
94
+ try {
95
+ return windowLike && windowLike.sessionStorage ? windowLike.sessionStorage : null;
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+
101
+ function parseStudioWorkspaceState(raw) {
102
+ try {
103
+ const parsed = JSON.parse(String(raw || ""));
104
+ if (!parsed || typeof parsed !== "object" || parsed.version !== 1 || typeof parsed.text !== "string") return null;
105
+ return parsed;
106
+ } catch {
107
+ return null;
108
+ }
109
+ }
110
+
111
+ function readStudioWorkspaceState(windowLike, tabStateId) {
112
+ const key = getStudioWorkspaceStorageKey(tabStateId);
113
+ const sessionStorage = getStudioSessionStorage(windowLike);
114
+ if (!sessionStorage || typeof sessionStorage.getItem !== "function") return null;
115
+ try {
116
+ const current = parseStudioWorkspaceState(sessionStorage.getItem(key));
117
+ if (current) return current;
118
+ return parseStudioWorkspaceState(sessionStorage.getItem(STUDIO_WORKSPACE_LEGACY_STORAGE_KEY));
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
123
+
124
+ function persistStudioWorkspaceState(windowLike, tabStateId, state) {
125
+ const key = getStudioWorkspaceStorageKey(tabStateId);
126
+ const sessionStorage = getStudioSessionStorage(windowLike);
127
+ if (!sessionStorage || typeof sessionStorage.setItem !== "function") return false;
128
+ try {
129
+ sessionStorage.setItem(key, JSON.stringify(state));
130
+ sessionStorage.removeItem(STUDIO_WORKSPACE_LEGACY_STORAGE_KEY);
131
+ return true;
132
+ } catch {
133
+ return false;
134
+ }
135
+ }
136
+
137
+ function clearStudioWorkspaceState(windowLike, tabStateId) {
138
+ const key = getStudioWorkspaceStorageKey(tabStateId);
139
+ const sessionStorage = getStudioSessionStorage(windowLike);
140
+ if (!sessionStorage || typeof sessionStorage.removeItem !== "function") return;
141
+ try {
142
+ sessionStorage.removeItem(key);
143
+ sessionStorage.removeItem(STUDIO_WORKSPACE_LEGACY_STORAGE_KEY);
144
+ } catch {}
145
+ }
146
+
51
147
  function normalizeStudioPendingKind(value) {
52
148
  return STUDIO_PENDING_KINDS.includes(value) ? value : null;
53
149
  }
@@ -490,6 +586,9 @@
490
586
  PANE_FOCUS_OFF,
491
587
  PANE_FOCUS_PARAM,
492
588
  PANE_FOCUS_TARGETS,
589
+ STUDIO_TAB_STATE_PARAM,
590
+ STUDIO_WORKSPACE_LEGACY_STORAGE_KEY,
591
+ STUDIO_WORKSPACE_STORAGE_PREFIX,
493
592
  STUDIO_LAUNCH_CHANNEL_PREFIX,
494
593
  STUDIO_LAUNCH_DELIVERY_TIMEOUT_MS,
495
594
  STUDIO_LAUNCH_MESSAGE_MAX_CHARS,
@@ -501,14 +600,21 @@
501
600
  buildPendingStudioUrl,
502
601
  canOpenPendingStudioLaunch,
503
602
  createPendingStudioLaunch,
603
+ clearStudioWorkspaceState,
604
+ ensureStudioTabStateId,
605
+ getStudioWorkspaceStorageKey,
504
606
  isValidStudioLaunchId,
607
+ isValidStudioTabStateId,
505
608
  makeStudioLaunchId,
609
+ makeStudioTabStateId,
506
610
  normalizePaneFocusTarget,
507
611
  normalizeStudioLaunchMessage,
508
612
  normalizeStudioPendingKind,
509
613
  normalizeStudioRelativeTarget,
510
614
  openStudioTabDirect,
615
+ persistStudioWorkspaceState,
511
616
  readPaneFocusTarget,
617
+ readStudioWorkspaceState,
512
618
  replacePaneFocusUrlState,
513
619
  startStudioPendingPage,
514
620
  studioLaunchChannelName,
@@ -0,0 +1,93 @@
1
+ (() => {
2
+ const STUDIO_SHOW_ME_SOURCE_MAX_CHARS = 16_000;
3
+
4
+ function truncateSource(value, maxChars) {
5
+ const source = String(value || "").trim();
6
+ const limit = Math.max(256, Math.floor(Number(maxChars) || STUDIO_SHOW_ME_SOURCE_MAX_CHARS));
7
+ if (source.length <= limit) {
8
+ return { text: source, truncated: false, omittedChars: 0 };
9
+ }
10
+
11
+ let omittedChars = Math.max(1, source.length - limit);
12
+ let marker = "";
13
+ let headChars = 0;
14
+ let tailChars = 0;
15
+ for (let attempt = 0; attempt < 4; attempt += 1) {
16
+ marker = "\n\n[Pi Studio omitted " + omittedChars.toLocaleString("en-US") + " characters from the middle of this source.]\n\n";
17
+ const contentBudget = Math.max(2, limit - marker.length);
18
+ headChars = Math.ceil(contentBudget * 0.6);
19
+ tailChars = Math.max(1, contentBudget - headChars);
20
+ const nextOmitted = Math.max(1, source.length - headChars - tailChars);
21
+ if (nextOmitted === omittedChars) break;
22
+ omittedChars = nextOmitted;
23
+ }
24
+
25
+ return {
26
+ text: (source.slice(0, headChars).trimEnd() + marker + source.slice(-tailChars).trimStart()).slice(0, limit),
27
+ truncated: true,
28
+ omittedChars: omittedChars,
29
+ };
30
+ }
31
+
32
+ function chooseStudioShowMeFocus(options) {
33
+ const input = options && typeof options === "object" ? options : {};
34
+ const target = input.target === "response" ? "response" : "editor";
35
+ const selectionText = String(input.selectionText || "");
36
+ const responseText = String(input.responseText || "");
37
+ const responseVisible = input.responseVisible === true;
38
+ const editorText = String(input.editorText || "");
39
+ const responseIndex = Math.max(0, Math.floor(Number(input.responseIndex) || 0));
40
+ const responseTotal = Math.max(0, Math.floor(Number(input.responseTotal) || 0));
41
+
42
+ if (target === "response") {
43
+ if (!responseVisible || !responseText.trim()) return null;
44
+ const bounded = truncateSource(responseText);
45
+ const historySuffix = responseIndex > 0 && responseTotal > 0
46
+ ? " " + responseIndex + "/" + responseTotal
47
+ : "";
48
+ return {
49
+ sourceKind: "response",
50
+ sourceLabel: "displayed Studio response" + historySuffix,
51
+ sourceText: bounded.text,
52
+ truncated: bounded.truncated,
53
+ actionLabel: "Explain displayed response",
54
+ };
55
+ }
56
+
57
+ if (selectionText.trim()) {
58
+ const bounded = truncateSource(selectionText);
59
+ return {
60
+ sourceKind: "selection",
61
+ sourceLabel: "Studio editor selection",
62
+ sourceText: bounded.text,
63
+ truncated: bounded.truncated,
64
+ actionLabel: "Explain selection",
65
+ };
66
+ }
67
+
68
+ if (editorText.trim()) {
69
+ const bounded = truncateSource(editorText);
70
+ return {
71
+ sourceKind: "editor",
72
+ sourceLabel: "Studio editor document",
73
+ sourceText: bounded.text,
74
+ truncated: bounded.truncated,
75
+ actionLabel: "Explain editor document",
76
+ };
77
+ }
78
+
79
+ return {
80
+ sourceKind: "context",
81
+ sourceLabel: "current conversation topic",
82
+ sourceText: "",
83
+ truncated: false,
84
+ actionLabel: "Explain current topic",
85
+ };
86
+ }
87
+
88
+ globalThis.PiStudioShowMeHelpers = Object.freeze({
89
+ STUDIO_SHOW_ME_SOURCE_MAX_CHARS: STUDIO_SHOW_ME_SOURCE_MAX_CHARS,
90
+ chooseStudioShowMeFocus: chooseStudioShowMeFocus,
91
+ truncateSource: truncateSource,
92
+ });
93
+ })();
package/client/studio.css CHANGED
@@ -198,7 +198,7 @@
198
198
  #sendRunBtn:not(:disabled):not(.request-stop-active):not(.repl-secondary-action),
199
199
  #queueSteerBtn:not(:disabled),
200
200
  #sendReplBtn.repl-primary-action:not(:disabled),
201
- #loadResponseBtn:not(:disabled):not([hidden]) {
201
+ #annotateResponseBtn:not(:disabled):not([hidden]) {
202
202
  background: var(--accent);
203
203
  border-color: var(--accent);
204
204
  color: var(--accent-contrast);
@@ -208,12 +208,13 @@
208
208
  #sendRunBtn:not(:disabled):not(.request-stop-active):not(.repl-secondary-action):hover,
209
209
  #queueSteerBtn:not(:disabled):hover,
210
210
  #sendReplBtn.repl-primary-action:not(:disabled):hover,
211
- #loadResponseBtn:not(:disabled):not([hidden]):hover {
211
+ #annotateResponseBtn:not(:disabled):not([hidden]):hover {
212
212
  filter: brightness(0.95);
213
213
  }
214
214
 
215
215
  #sendRunBtn.request-stop-active,
216
- #critiqueBtn.request-stop-active {
216
+ #critiqueBtn.request-stop-active,
217
+ #showMeBtn.request-stop-active {
217
218
  background: color-mix(in srgb, var(--error) 82%, var(--panel));
218
219
  border-color: color-mix(in srgb, var(--error) 88%, var(--panel));
219
220
  color: var(--error-contrast);
@@ -221,7 +222,8 @@
221
222
  }
222
223
 
223
224
  #sendRunBtn.request-stop-active:not(:disabled):hover,
224
- #critiqueBtn.request-stop-active:not(:disabled):hover {
225
+ #critiqueBtn.request-stop-active:not(:disabled):hover,
226
+ #showMeBtn.request-stop-active:not(:disabled):hover {
225
227
  background: var(--error);
226
228
  border-color: var(--error);
227
229
  filter: none;
@@ -406,6 +408,8 @@
406
408
  body[data-studio-mode="editor-only"] #sendEditorBtn,
407
409
  body[data-studio-mode="editor-only"] #lensSelect,
408
410
  body[data-studio-mode="editor-only"] #critiqueBtn,
411
+ body[data-studio-mode="editor-only"] #showMeBtn,
412
+ body[data-studio-mode="editor-only"] #showMeResponseBtn,
409
413
  body[data-studio-mode="editor-only"] #followSelect,
410
414
  body[data-studio-mode="editor-only"] #responseHighlightSelect,
411
415
  body[data-studio-mode="editor-only"] #pullLatestBtn,
@@ -414,6 +418,7 @@
414
418
  body[data-studio-mode="editor-only"] #historyNextBtn,
415
419
  body[data-studio-mode="editor-only"] #historyLastBtn,
416
420
  body[data-studio-mode="editor-only"] #loadResponseBtn,
421
+ body[data-studio-mode="editor-only"] #annotateResponseBtn,
417
422
  body[data-studio-mode="editor-only"] #loadCritiqueNotesBtn,
418
423
  body[data-studio-mode="editor-only"] #loadCritiqueFullBtn,
419
424
  body[data-studio-mode="editor-only"] #loadHistoryPromptBtn,
@@ -5369,39 +5374,6 @@
5369
5374
  padding: 9px;
5370
5375
  }
5371
5376
 
5372
- @container (max-width: 840px) {
5373
- body.studio-ui-refresh .studio-refresh-header-top,
5374
- body.studio-ui-refresh .studio-refresh-header-utility,
5375
- body.studio-ui-refresh #rightSectionHeader,
5376
- body.studio-ui-refresh .studio-refresh-toolbar-main,
5377
- body.studio-ui-refresh .studio-refresh-pane-identity {
5378
- grid-template-columns: minmax(0, 1fr);
5379
- justify-content: stretch;
5380
- }
5381
-
5382
- body.studio-ui-refresh .studio-refresh-pane-tools {
5383
- justify-content: flex-start;
5384
- justify-items: start;
5385
- min-width: 0;
5386
- }
5387
-
5388
- body.studio-ui-refresh .studio-refresh-toolbar-state {
5389
- justify-content: flex-end;
5390
- justify-items: end;
5391
- min-width: 0;
5392
- }
5393
-
5394
- body.studio-ui-refresh .studio-refresh-pane-tools,
5395
- body.studio-ui-refresh .studio-refresh-title-group,
5396
- body.studio-ui-refresh .studio-refresh-utility-left {
5397
- flex-wrap: wrap;
5398
- }
5399
-
5400
- body.studio-ui-refresh .studio-refresh-toolbar-state .studio-refresh-action-line {
5401
- justify-content: flex-end;
5402
- }
5403
- }
5404
-
5405
5377
  body.studio-ui-refresh footer {
5406
5378
  padding: 7px 9px;
5407
5379
  font-size: 11px;
@@ -5962,8 +5934,13 @@
5962
5934
  z-index: 100;
5963
5935
  }
5964
5936
 
5937
+ body.studio-ui-refresh .studio-refresh-review-anchor {
5938
+ position: static;
5939
+ }
5940
+
5965
5941
  body.studio-ui-refresh .studio-refresh-review-anchor .studio-refresh-menu {
5966
5942
  width: min(360px, calc(100vw - 48px));
5943
+ max-width: calc(100cqi - 18px);
5967
5944
  left: auto;
5968
5945
  right: 0;
5969
5946
  }
@@ -6016,6 +5993,10 @@
6016
5993
  min-width: 0;
6017
5994
  }
6018
5995
 
5996
+ body.studio-ui-refresh .studio-refresh-menu-item[hidden] {
5997
+ display: none;
5998
+ }
5999
+
6019
6000
  body.studio-ui-refresh .studio-refresh-menu-item > button,
6020
6001
  body.studio-ui-refresh .studio-refresh-menu-item > select {
6021
6002
  width: 100%;
@@ -6073,7 +6054,8 @@
6073
6054
  body.studio-ui-refresh #sendRunBtn,
6074
6055
  body.studio-ui-refresh #queueSteerBtn,
6075
6056
  body.studio-ui-refresh #sendReplBtn,
6076
- body.studio-ui-refresh #loadResponseBtn:not([hidden]) {
6057
+ body.studio-ui-refresh #loadResponseBtn:not([hidden]),
6058
+ body.studio-ui-refresh #annotateResponseBtn:not([hidden]) {
6077
6059
  height: 28px;
6078
6060
  min-height: 28px;
6079
6061
  padding: 4px 9px;
@@ -6111,7 +6093,8 @@
6111
6093
  content: none;
6112
6094
  }
6113
6095
 
6114
- body.studio-ui-refresh .studio-refresh-menu #critiqueBtn.request-stop-active {
6096
+ body.studio-ui-refresh .studio-refresh-menu #critiqueBtn.request-stop-active,
6097
+ body.studio-ui-refresh .studio-refresh-menu #showMeBtn.request-stop-active {
6115
6098
  background: color-mix(in srgb, var(--error) 82%, var(--panel));
6116
6099
  border-color: color-mix(in srgb, var(--error) 88%, var(--panel));
6117
6100
  color: var(--error-contrast);
@@ -6153,28 +6136,54 @@
6153
6136
  outline: none;
6154
6137
  }
6155
6138
 
6156
- @media (max-width: 1280px) {
6157
- body.studio-ui-refresh #rightSectionHeader,
6158
- body.studio-ui-refresh .studio-refresh-header-top,
6159
- body.studio-ui-refresh .studio-refresh-header-utility,
6160
- body.studio-ui-refresh .studio-refresh-toolbar-main,
6161
- body.studio-ui-refresh .studio-refresh-pane-identity {
6162
- grid-template-columns: 1fr;
6139
+ @container (max-width: 680px) {
6140
+ body.studio-ui-refresh #leftSectionHeader .studio-refresh-header-top {
6141
+ grid-template-columns: minmax(0, 1fr);
6142
+ justify-content: stretch;
6143
+ }
6144
+
6145
+ body.studio-ui-refresh #leftSectionHeader .studio-refresh-pane-tools {
6146
+ justify-content: flex-end;
6147
+ justify-items: end;
6148
+ min-width: 0;
6149
+ }
6150
+
6151
+ body.studio-ui-refresh #leftSectionHeader .studio-refresh-pane-tools,
6152
+ body.studio-ui-refresh #leftSectionHeader .studio-refresh-title-group {
6153
+ flex-wrap: wrap;
6154
+ }
6155
+ }
6156
+
6157
+ @container (max-width: 560px) {
6158
+ body.studio-ui-refresh .studio-refresh-toolbar-main {
6159
+ grid-template-columns: minmax(0, 1fr);
6163
6160
  }
6164
6161
 
6165
- body.studio-ui-refresh .studio-refresh-pane-tools,
6166
6162
  body.studio-ui-refresh .studio-refresh-toolbar-state {
6167
6163
  justify-content: flex-start;
6168
6164
  justify-items: start;
6169
6165
  }
6170
6166
 
6171
- body.studio-ui-refresh .studio-refresh-pane-tools,
6172
- body.studio-ui-refresh .studio-refresh-title-group,
6173
- body.studio-ui-refresh .studio-refresh-utility-left {
6174
- flex-wrap: wrap;
6167
+ body.studio-ui-refresh .studio-refresh-toolbar-state .studio-refresh-action-line {
6168
+ justify-content: flex-start;
6175
6169
  }
6170
+ }
6176
6171
 
6177
- body.studio-ui-refresh .studio-refresh-toolbar-state {
6172
+ @container (max-width: 440px) {
6173
+ body.studio-ui-refresh #rightSectionHeader,
6174
+ body.studio-ui-refresh #rightSectionHeader .studio-refresh-pane-identity {
6175
+ grid-template-columns: minmax(0, 1fr);
6176
+ justify-content: stretch;
6177
+ }
6178
+
6179
+ body.studio-ui-refresh #rightSectionHeader .studio-refresh-pane-tools {
6180
+ justify-content: flex-end;
6181
+ justify-items: end;
6178
6182
  min-width: 0;
6179
6183
  }
6184
+
6185
+ body.studio-ui-refresh #rightSectionHeader .studio-refresh-pane-tools,
6186
+ body.studio-ui-refresh #rightSectionHeader .studio-refresh-title-group {
6187
+ flex-wrap: wrap;
6188
+ }
6180
6189
  }