pi-studio 0.5.59 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +3 -1
- package/client/studio-client.js +260 -25
- package/client/studio.css +306 -137
- package/index.ts +382 -79
- package/package.json +8 -2
- package/themes/pi-studio-dark.json +78 -0
- package/themes/pi-studio-light.json +77 -0
package/index.ts
CHANGED
|
@@ -593,6 +593,8 @@ interface StudioPalette {
|
|
|
593
593
|
interface StudioThemeStyle {
|
|
594
594
|
mode: StudioThemeMode;
|
|
595
595
|
palette: StudioPalette;
|
|
596
|
+
accentContrast?: string;
|
|
597
|
+
errorContrast?: string;
|
|
596
598
|
}
|
|
597
599
|
|
|
598
600
|
const DARK_STUDIO_PALETTE: StudioPalette = {
|
|
@@ -673,9 +675,39 @@ const LIGHT_STUDIO_PALETTE: StudioPalette = {
|
|
|
673
675
|
syntaxPunctuation: "#000000",
|
|
674
676
|
};
|
|
675
677
|
|
|
678
|
+
function inferThemeModeFromName(name: string): StudioThemeMode | undefined {
|
|
679
|
+
const lower = name.toLowerCase();
|
|
680
|
+
if (/\b(light|dawn|day|latte)\b/.test(lower) || lower.includes("-light")) return "light";
|
|
681
|
+
if (/\b(dark|night|moon|mocha)\b/.test(lower) || lower.includes("-dark")) return "dark";
|
|
682
|
+
return undefined;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function inferThemeModeFromColorCandidates(...colors: Array<string | undefined>): StudioThemeMode | undefined {
|
|
686
|
+
for (const color of colors) {
|
|
687
|
+
const inferred = inferThemeModeFromColor(color);
|
|
688
|
+
if (inferred) return inferred;
|
|
689
|
+
}
|
|
690
|
+
return undefined;
|
|
691
|
+
}
|
|
692
|
+
|
|
676
693
|
function getStudioThemeMode(theme?: Theme): StudioThemeMode {
|
|
677
|
-
const
|
|
678
|
-
|
|
694
|
+
const exported = readThemeExportPalette(theme);
|
|
695
|
+
const inferredFromExport = inferThemeModeFromColorCandidates(exported?.pageBg, exported?.cardBg);
|
|
696
|
+
if (inferredFromExport) return inferredFromExport;
|
|
697
|
+
|
|
698
|
+
const inferredFromSurface = inferThemeModeFromColorCandidates(
|
|
699
|
+
inferThemeSurfaceColor(theme, "page"),
|
|
700
|
+
inferThemeSurfaceColor(theme, "card"),
|
|
701
|
+
readThemeColorToken(theme, "userMessageBg"),
|
|
702
|
+
readThemeColorToken(theme, "customMessageBg"),
|
|
703
|
+
readThemeColorToken(theme, "toolPendingBg"),
|
|
704
|
+
);
|
|
705
|
+
if (inferredFromSurface) return inferredFromSurface;
|
|
706
|
+
|
|
707
|
+
const inferredFromName = inferThemeModeFromName(theme?.name ?? "");
|
|
708
|
+
if (inferredFromName) return inferredFromName;
|
|
709
|
+
|
|
710
|
+
return "dark";
|
|
679
711
|
}
|
|
680
712
|
|
|
681
713
|
function toHexByte(value: number): string {
|
|
@@ -809,6 +841,29 @@ function blendColors(a: string, b: string, t: number): string {
|
|
|
809
841
|
);
|
|
810
842
|
}
|
|
811
843
|
|
|
844
|
+
function wcagRelativeLuminance(color: string): number {
|
|
845
|
+
const rgb = hexToRgb(color);
|
|
846
|
+
if (!rgb) return 0;
|
|
847
|
+
const linear = [rgb.r, rgb.g, rgb.b].map((channel) => {
|
|
848
|
+
const value = channel / 255;
|
|
849
|
+
return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
850
|
+
});
|
|
851
|
+
return 0.2126 * linear[0]! + 0.7152 * linear[1]! + 0.0722 * linear[2]!;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function contrastRatio(a: string, b: string): number {
|
|
855
|
+
const lumA = wcagRelativeLuminance(a);
|
|
856
|
+
const lumB = wcagRelativeLuminance(b);
|
|
857
|
+
const lighter = Math.max(lumA, lumB);
|
|
858
|
+
const darker = Math.min(lumA, lumB);
|
|
859
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function readableTextOn(background: string, darkText = "#0e1616", lightText = "#ffffff"): string {
|
|
863
|
+
if (!hexToRgb(background)) return lightText;
|
|
864
|
+
return contrastRatio(background, darkText) >= contrastRatio(background, lightText) ? darkText : lightText;
|
|
865
|
+
}
|
|
866
|
+
|
|
812
867
|
function deriveCanvasColors(
|
|
813
868
|
baseColor: string,
|
|
814
869
|
mode: StudioThemeMode,
|
|
@@ -848,7 +903,17 @@ interface ThemeExportPalette {
|
|
|
848
903
|
infoBg?: string;
|
|
849
904
|
}
|
|
850
905
|
|
|
851
|
-
|
|
906
|
+
interface ThemeSourceJson {
|
|
907
|
+
name?: string;
|
|
908
|
+
vars?: Record<string, string | number>;
|
|
909
|
+
colors?: Record<string, string | number>;
|
|
910
|
+
export?: { pageBg?: string | number; cardBg?: string | number; infoBg?: string | number };
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const themeSourceJsonCache = new Map<string, { mtimeMs: number; json: ThemeSourceJson | null }>();
|
|
914
|
+
|
|
915
|
+
const DEFAULT_UI_FONT_STACK = "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif";
|
|
916
|
+
const DEFAULT_PROSE_FONT_STACK = DEFAULT_UI_FONT_STACK;
|
|
852
917
|
|
|
853
918
|
const DEFAULT_MONO_FONT_FAMILIES = [
|
|
854
919
|
"ui-monospace",
|
|
@@ -1049,6 +1114,14 @@ function getStudioMonoFontStack(): string {
|
|
|
1049
1114
|
return cachedStudioMonoFontStack;
|
|
1050
1115
|
}
|
|
1051
1116
|
|
|
1117
|
+
function getStudioUiFontStack(): string {
|
|
1118
|
+
return sanitizeCssValue(process.env.PI_STUDIO_FONT_UI ?? "") || DEFAULT_UI_FONT_STACK;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function getStudioProseFontStack(): string {
|
|
1122
|
+
return sanitizeCssValue(process.env.PI_STUDIO_FONT_PROSE ?? "") || DEFAULT_PROSE_FONT_STACK;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1052
1125
|
function resolveThemeExportValue(
|
|
1053
1126
|
value: string | number | undefined,
|
|
1054
1127
|
vars: Record<string, string | number>,
|
|
@@ -1071,37 +1144,109 @@ function resolveThemeExportValue(
|
|
|
1071
1144
|
return resolveThemeExportValue(referenced, vars, seen) ?? token;
|
|
1072
1145
|
}
|
|
1073
1146
|
|
|
1074
|
-
function
|
|
1147
|
+
function isCssColorValue(value: string | undefined): value is string {
|
|
1148
|
+
if (!value) return false;
|
|
1149
|
+
const trimmed = value.trim();
|
|
1150
|
+
return /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(trimmed) || /^rgba?\(/i.test(trimmed);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function normalizeResolvedThemeColor(value: string | undefined): string | undefined {
|
|
1154
|
+
if (!isCssColorValue(value)) return undefined;
|
|
1155
|
+
return value.trim();
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
function readThemeSourceJson(theme?: Theme): ThemeSourceJson | undefined {
|
|
1075
1159
|
const sourcePath = theme?.sourcePath?.trim();
|
|
1076
1160
|
if (!sourcePath) return undefined;
|
|
1077
1161
|
|
|
1078
|
-
if (themeExportPaletteCache.has(sourcePath)) {
|
|
1079
|
-
const cached = themeExportPaletteCache.get(sourcePath);
|
|
1080
|
-
return cached ?? undefined;
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
1162
|
try {
|
|
1084
|
-
const
|
|
1085
|
-
const
|
|
1086
|
-
|
|
1087
|
-
vars?: Record<string, string | number>;
|
|
1088
|
-
};
|
|
1089
|
-
const vars = parsed.vars ?? {};
|
|
1090
|
-
const exportSection = parsed.export ?? {};
|
|
1091
|
-
const resolved: ThemeExportPalette = {
|
|
1092
|
-
pageBg: resolveThemeExportValue(exportSection.pageBg, vars),
|
|
1093
|
-
cardBg: resolveThemeExportValue(exportSection.cardBg, vars),
|
|
1094
|
-
infoBg: resolveThemeExportValue(exportSection.infoBg, vars),
|
|
1095
|
-
};
|
|
1163
|
+
const mtimeMs = statSync(sourcePath).mtimeMs;
|
|
1164
|
+
const cached = themeSourceJsonCache.get(sourcePath);
|
|
1165
|
+
if (cached && cached.mtimeMs === mtimeMs) return cached.json ?? undefined;
|
|
1096
1166
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1167
|
+
const raw = readFileSync(sourcePath, "utf-8");
|
|
1168
|
+
const parsed = JSON.parse(raw) as ThemeSourceJson;
|
|
1169
|
+
themeSourceJsonCache.set(sourcePath, { mtimeMs, json: parsed });
|
|
1170
|
+
return parsed;
|
|
1099
1171
|
} catch {
|
|
1100
|
-
|
|
1172
|
+
themeSourceJsonCache.set(sourcePath, { mtimeMs: -1, json: null });
|
|
1101
1173
|
return undefined;
|
|
1102
1174
|
}
|
|
1103
1175
|
}
|
|
1104
1176
|
|
|
1177
|
+
function resolveThemeJsonValue(
|
|
1178
|
+
value: string | number | undefined,
|
|
1179
|
+
vars: Record<string, string | number>,
|
|
1180
|
+
): string | undefined {
|
|
1181
|
+
return normalizeResolvedThemeColor(resolveThemeExportValue(value, vars));
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
function readThemeExportPalette(theme?: Theme): ThemeExportPalette | undefined {
|
|
1185
|
+
const parsed = readThemeSourceJson(theme);
|
|
1186
|
+
if (!parsed) return undefined;
|
|
1187
|
+
const vars = parsed.vars ?? {};
|
|
1188
|
+
const exportSection = parsed.export ?? {};
|
|
1189
|
+
const resolved: ThemeExportPalette = {
|
|
1190
|
+
pageBg: resolveThemeJsonValue(exportSection.pageBg, vars),
|
|
1191
|
+
cardBg: resolveThemeJsonValue(exportSection.cardBg, vars),
|
|
1192
|
+
infoBg: resolveThemeJsonValue(exportSection.infoBg, vars),
|
|
1193
|
+
};
|
|
1194
|
+
return resolved.pageBg || resolved.cardBg || resolved.infoBg ? resolved : undefined;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function readThemeColorToken(theme: Theme | undefined, token: string): string | undefined {
|
|
1198
|
+
const parsed = readThemeSourceJson(theme);
|
|
1199
|
+
if (!parsed) return undefined;
|
|
1200
|
+
return resolveThemeJsonValue(parsed.colors?.[token], parsed.vars ?? {});
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function readThemeVarColor(theme: Theme | undefined, keys: string[]): string | undefined {
|
|
1204
|
+
const parsed = readThemeSourceJson(theme);
|
|
1205
|
+
if (!parsed) return undefined;
|
|
1206
|
+
const vars = parsed.vars ?? {};
|
|
1207
|
+
for (const key of keys) {
|
|
1208
|
+
const color = resolveThemeJsonValue(vars[key], vars);
|
|
1209
|
+
if (color) return color;
|
|
1210
|
+
}
|
|
1211
|
+
return undefined;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function readThemeAnyColor(theme: Theme | undefined, keys: string[]): string | undefined {
|
|
1215
|
+
const parsed = readThemeSourceJson(theme);
|
|
1216
|
+
if (!parsed) return undefined;
|
|
1217
|
+
const vars = parsed.vars ?? {};
|
|
1218
|
+
for (const key of keys) {
|
|
1219
|
+
const color = resolveThemeJsonValue(parsed.colors?.[key], vars);
|
|
1220
|
+
if (color) return color;
|
|
1221
|
+
}
|
|
1222
|
+
return undefined;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function inferThemeModeFromColor(color: string | undefined): StudioThemeMode | undefined {
|
|
1226
|
+
if (!color || !hexToRgb(color)) return undefined;
|
|
1227
|
+
return relativeLuminance(color) >= 0.58 ? "light" : "dark";
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
function inferThemeTextColor(theme: Theme | undefined, mode: StudioThemeMode): string | undefined {
|
|
1231
|
+
return readThemeAnyColor(theme, ["text", "userMessageText", "customMessageText", "mdCodeBlock"])
|
|
1232
|
+
?? readThemeVarColor(
|
|
1233
|
+
theme,
|
|
1234
|
+
mode === "light"
|
|
1235
|
+
? ["text", "fg", "foreground", "textDark1", "fg0", "fg1", "nord0"]
|
|
1236
|
+
: ["text", "fg", "foreground", "text", "fg0", "fg1", "subtext1", "subtext0", "nord4", "gray3"],
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
function inferThemeSurfaceColor(theme: Theme | undefined, role: "page" | "card" | "panel2"): string | undefined {
|
|
1241
|
+
if (role === "page") {
|
|
1242
|
+
return readThemeVarColor(theme, ["pageBg", "bg", "base", "background", "mantle", "bg_dark", "bg0", "nord0"]);
|
|
1243
|
+
}
|
|
1244
|
+
if (role === "card") {
|
|
1245
|
+
return readThemeVarColor(theme, ["cardBg", "surface", "base", "bg", "bg1", "nord1"]);
|
|
1246
|
+
}
|
|
1247
|
+
return readThemeVarColor(theme, ["infoBg", "surfaceAlt", "surface0", "overlay", "bg_hl", "bg2", "nord2"]);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1105
1250
|
function getStudioThemeStyle(theme?: Theme): StudioThemeStyle {
|
|
1106
1251
|
const mode = getStudioThemeMode(theme);
|
|
1107
1252
|
const fallback = mode === "light" ? LIGHT_STUDIO_PALETTE : DARK_STUDIO_PALETTE;
|
|
@@ -1116,36 +1261,49 @@ function getStudioThemeStyle(theme?: Theme): StudioThemeStyle {
|
|
|
1116
1261
|
const accent =
|
|
1117
1262
|
safeThemeColor(() => theme.getFgAnsi("mdLink"))
|
|
1118
1263
|
?? safeThemeColor(() => theme.getFgAnsi("accent"))
|
|
1264
|
+
?? readThemeColorToken(theme, "mdLink")
|
|
1265
|
+
?? readThemeColorToken(theme, "accent")
|
|
1119
1266
|
?? fallback.accent;
|
|
1120
|
-
const warn = safeThemeColor(() => theme.getFgAnsi("warning")) ?? fallback.warn;
|
|
1121
|
-
const error = safeThemeColor(() => theme.getFgAnsi("error")) ?? fallback.error;
|
|
1122
|
-
const ok = safeThemeColor(() => theme.getFgAnsi("success")) ?? fallback.ok;
|
|
1267
|
+
const warn = safeThemeColor(() => theme.getFgAnsi("warning")) ?? readThemeColorToken(theme, "warning") ?? fallback.warn;
|
|
1268
|
+
const error = safeThemeColor(() => theme.getFgAnsi("error")) ?? readThemeColorToken(theme, "error") ?? fallback.error;
|
|
1269
|
+
const ok = safeThemeColor(() => theme.getFgAnsi("success")) ?? readThemeColorToken(theme, "success") ?? fallback.ok;
|
|
1270
|
+
const text = safeThemeColor(() => theme.getFgAnsi("text")) ?? inferThemeTextColor(theme, mode) ?? fallback.text;
|
|
1123
1271
|
const exported = readThemeExportPalette(theme);
|
|
1124
1272
|
|
|
1125
1273
|
const surfaceBase =
|
|
1126
1274
|
safeThemeColor(() => theme.getBgAnsi("userMessageBg"))
|
|
1127
|
-
?? safeThemeColor(() => theme.getBgAnsi("customMessageBg"))
|
|
1275
|
+
?? safeThemeColor(() => theme.getBgAnsi("customMessageBg"))
|
|
1276
|
+
?? readThemeColorToken(theme, "userMessageBg")
|
|
1277
|
+
?? readThemeColorToken(theme, "customMessageBg");
|
|
1128
1278
|
const derived = surfaceBase ? deriveCanvasColors(surfaceBase, mode) : undefined;
|
|
1279
|
+
const themePageBg = inferThemeSurfaceColor(theme, "page");
|
|
1280
|
+
const themeCardBg = inferThemeSurfaceColor(theme, "card");
|
|
1281
|
+
const themePanel2 = inferThemeSurfaceColor(theme, "panel2");
|
|
1129
1282
|
|
|
1130
1283
|
const palette: StudioPalette = {
|
|
1131
1284
|
bg:
|
|
1132
1285
|
exported?.pageBg
|
|
1286
|
+
?? themePageBg
|
|
1133
1287
|
?? derived?.pageBg
|
|
1134
1288
|
?? fallback.bg,
|
|
1135
1289
|
panel:
|
|
1136
1290
|
exported?.cardBg
|
|
1291
|
+
?? themeCardBg
|
|
1137
1292
|
?? derived?.cardBg
|
|
1138
1293
|
?? safeThemeColor(() => theme.getBgAnsi("toolPendingBg"))
|
|
1294
|
+
?? readThemeColorToken(theme, "toolPendingBg")
|
|
1139
1295
|
?? fallback.panel,
|
|
1140
1296
|
panel2:
|
|
1141
|
-
|
|
1297
|
+
themePanel2
|
|
1298
|
+
?? derived?.panel2
|
|
1142
1299
|
?? safeThemeColor(() => theme.getBgAnsi("selectedBg"))
|
|
1300
|
+
?? readThemeColorToken(theme, "selectedBg")
|
|
1143
1301
|
?? exported?.infoBg
|
|
1144
1302
|
?? fallback.panel2,
|
|
1145
|
-
border: safeThemeColor(() => theme.getFgAnsi("border")) ?? fallback.border,
|
|
1146
|
-
borderMuted: safeThemeColor(() => theme.getFgAnsi("borderMuted")) ?? fallback.borderMuted,
|
|
1147
|
-
text
|
|
1148
|
-
muted: safeThemeColor(() => theme.getFgAnsi("muted")) ?? fallback.muted,
|
|
1303
|
+
border: safeThemeColor(() => theme.getFgAnsi("border")) ?? readThemeColorToken(theme, "border") ?? fallback.border,
|
|
1304
|
+
borderMuted: safeThemeColor(() => theme.getFgAnsi("borderMuted")) ?? readThemeColorToken(theme, "borderMuted") ?? fallback.borderMuted,
|
|
1305
|
+
text,
|
|
1306
|
+
muted: safeThemeColor(() => theme.getFgAnsi("muted")) ?? readThemeColorToken(theme, "muted") ?? fallback.muted,
|
|
1149
1307
|
accent,
|
|
1150
1308
|
warn,
|
|
1151
1309
|
error,
|
|
@@ -1156,28 +1314,33 @@ function getStudioThemeStyle(theme?: Theme): StudioThemeStyle {
|
|
|
1156
1314
|
accentSoftStrong: withAlpha(accent, mode === "light" ? 0.35 : 0.40, fallback.accentSoftStrong),
|
|
1157
1315
|
okBorder: withAlpha(ok, mode === "light" ? 0.55 : 0.70, fallback.okBorder),
|
|
1158
1316
|
warnBorder: withAlpha(warn, mode === "light" ? 0.55 : 0.70, fallback.warnBorder),
|
|
1159
|
-
mdHeading: safeThemeColor(() => theme.getFgAnsi("mdHeading")) ?? fallback.mdHeading,
|
|
1160
|
-
mdLink: safeThemeColor(() => theme.getFgAnsi("mdLink")) ?? fallback.mdLink,
|
|
1161
|
-
mdLinkUrl: safeThemeColor(() => theme.getFgAnsi("mdLinkUrl")) ?? fallback.mdLinkUrl,
|
|
1162
|
-
mdCode: safeThemeColor(() => theme.getFgAnsi("mdCode")) ?? fallback.mdCode,
|
|
1163
|
-
mdCodeBlock: safeThemeColor(() => theme.getFgAnsi("mdCodeBlock")) ??
|
|
1164
|
-
mdCodeBlockBorder: safeThemeColor(() => theme.getFgAnsi("mdCodeBlockBorder")) ?? fallback.mdCodeBlockBorder,
|
|
1165
|
-
mdQuote: safeThemeColor(() => theme.getFgAnsi("mdQuote")) ?? fallback.mdQuote,
|
|
1166
|
-
mdQuoteBorder: safeThemeColor(() => theme.getFgAnsi("mdQuoteBorder")) ?? fallback.mdQuoteBorder,
|
|
1167
|
-
mdHr: safeThemeColor(() => theme.getFgAnsi("mdHr")) ?? fallback.mdHr,
|
|
1168
|
-
mdListBullet: safeThemeColor(() => theme.getFgAnsi("mdListBullet")) ?? fallback.mdListBullet,
|
|
1169
|
-
syntaxComment: safeThemeColor(() => theme.getFgAnsi("syntaxComment")) ?? fallback.syntaxComment,
|
|
1170
|
-
syntaxKeyword: safeThemeColor(() => theme.getFgAnsi("syntaxKeyword")) ?? fallback.syntaxKeyword,
|
|
1171
|
-
syntaxFunction: safeThemeColor(() => theme.getFgAnsi("syntaxFunction")) ?? fallback.syntaxFunction,
|
|
1172
|
-
syntaxVariable: safeThemeColor(() => theme.getFgAnsi("syntaxVariable")) ?? fallback.syntaxVariable,
|
|
1173
|
-
syntaxString: safeThemeColor(() => theme.getFgAnsi("syntaxString")) ?? fallback.syntaxString,
|
|
1174
|
-
syntaxNumber: safeThemeColor(() => theme.getFgAnsi("syntaxNumber")) ?? fallback.syntaxNumber,
|
|
1175
|
-
syntaxType: safeThemeColor(() => theme.getFgAnsi("syntaxType")) ?? fallback.syntaxType,
|
|
1176
|
-
syntaxOperator: safeThemeColor(() => theme.getFgAnsi("syntaxOperator")) ?? fallback.syntaxOperator,
|
|
1177
|
-
syntaxPunctuation: safeThemeColor(() => theme.getFgAnsi("syntaxPunctuation")) ?? fallback.syntaxPunctuation,
|
|
1317
|
+
mdHeading: safeThemeColor(() => theme.getFgAnsi("mdHeading")) ?? readThemeColorToken(theme, "mdHeading") ?? fallback.mdHeading,
|
|
1318
|
+
mdLink: safeThemeColor(() => theme.getFgAnsi("mdLink")) ?? readThemeColorToken(theme, "mdLink") ?? fallback.mdLink,
|
|
1319
|
+
mdLinkUrl: safeThemeColor(() => theme.getFgAnsi("mdLinkUrl")) ?? readThemeColorToken(theme, "mdLinkUrl") ?? fallback.mdLinkUrl,
|
|
1320
|
+
mdCode: safeThemeColor(() => theme.getFgAnsi("mdCode")) ?? readThemeColorToken(theme, "mdCode") ?? fallback.mdCode,
|
|
1321
|
+
mdCodeBlock: safeThemeColor(() => theme.getFgAnsi("mdCodeBlock")) ?? readThemeColorToken(theme, "mdCodeBlock") ?? text,
|
|
1322
|
+
mdCodeBlockBorder: safeThemeColor(() => theme.getFgAnsi("mdCodeBlockBorder")) ?? readThemeColorToken(theme, "mdCodeBlockBorder") ?? fallback.mdCodeBlockBorder,
|
|
1323
|
+
mdQuote: safeThemeColor(() => theme.getFgAnsi("mdQuote")) ?? readThemeColorToken(theme, "mdQuote") ?? fallback.mdQuote,
|
|
1324
|
+
mdQuoteBorder: safeThemeColor(() => theme.getFgAnsi("mdQuoteBorder")) ?? readThemeColorToken(theme, "mdQuoteBorder") ?? fallback.mdQuoteBorder,
|
|
1325
|
+
mdHr: safeThemeColor(() => theme.getFgAnsi("mdHr")) ?? readThemeColorToken(theme, "mdHr") ?? fallback.mdHr,
|
|
1326
|
+
mdListBullet: safeThemeColor(() => theme.getFgAnsi("mdListBullet")) ?? readThemeColorToken(theme, "mdListBullet") ?? fallback.mdListBullet,
|
|
1327
|
+
syntaxComment: safeThemeColor(() => theme.getFgAnsi("syntaxComment")) ?? readThemeColorToken(theme, "syntaxComment") ?? fallback.syntaxComment,
|
|
1328
|
+
syntaxKeyword: safeThemeColor(() => theme.getFgAnsi("syntaxKeyword")) ?? readThemeColorToken(theme, "syntaxKeyword") ?? fallback.syntaxKeyword,
|
|
1329
|
+
syntaxFunction: safeThemeColor(() => theme.getFgAnsi("syntaxFunction")) ?? readThemeColorToken(theme, "syntaxFunction") ?? fallback.syntaxFunction,
|
|
1330
|
+
syntaxVariable: safeThemeColor(() => theme.getFgAnsi("syntaxVariable")) ?? readThemeColorToken(theme, "syntaxVariable") ?? fallback.syntaxVariable,
|
|
1331
|
+
syntaxString: safeThemeColor(() => theme.getFgAnsi("syntaxString")) ?? readThemeColorToken(theme, "syntaxString") ?? fallback.syntaxString,
|
|
1332
|
+
syntaxNumber: safeThemeColor(() => theme.getFgAnsi("syntaxNumber")) ?? readThemeColorToken(theme, "syntaxNumber") ?? fallback.syntaxNumber,
|
|
1333
|
+
syntaxType: safeThemeColor(() => theme.getFgAnsi("syntaxType")) ?? readThemeColorToken(theme, "syntaxType") ?? fallback.syntaxType,
|
|
1334
|
+
syntaxOperator: safeThemeColor(() => theme.getFgAnsi("syntaxOperator")) ?? readThemeColorToken(theme, "syntaxOperator") ?? fallback.syntaxOperator,
|
|
1335
|
+
syntaxPunctuation: safeThemeColor(() => theme.getFgAnsi("syntaxPunctuation")) ?? readThemeColorToken(theme, "syntaxPunctuation") ?? fallback.syntaxPunctuation,
|
|
1178
1336
|
};
|
|
1179
1337
|
|
|
1180
|
-
return {
|
|
1338
|
+
return {
|
|
1339
|
+
mode,
|
|
1340
|
+
palette,
|
|
1341
|
+
accentContrast: readThemeVarColor(theme, ["studioAccentText", "studioAccentContrast"]),
|
|
1342
|
+
errorContrast: readThemeVarColor(theme, ["studioErrorText", "studioErrorContrast"]),
|
|
1343
|
+
};
|
|
1181
1344
|
}
|
|
1182
1345
|
|
|
1183
1346
|
function createSessionToken(): string {
|
|
@@ -5764,34 +5927,74 @@ function createEmptyStudioTraceState(): StudioTraceState {
|
|
|
5764
5927
|
};
|
|
5765
5928
|
}
|
|
5766
5929
|
|
|
5930
|
+
function sanitizeStudioTraceOutputText(text: string): string {
|
|
5931
|
+
return String(text || "")
|
|
5932
|
+
.replace(/data:image\/([a-zA-Z0-9.+-]+);base64,[A-Za-z0-9+/=\r\n]+/g, (_match, subtype: string) => `[Image: image/${subtype || "unknown"} data omitted]`)
|
|
5933
|
+
.replace(/(\"(?:data|image|base64|content)\"\s*:\s*\")[A-Za-z0-9+/=]{1000,}(\")/g, "$1[base64 data omitted]$2")
|
|
5934
|
+
.replace(/\b[A-Za-z0-9+/]{3000,}={0,2}\b/g, "[base64 data omitted]");
|
|
5935
|
+
}
|
|
5936
|
+
|
|
5937
|
+
function isStudioTraceImageBlock(block: unknown): boolean {
|
|
5938
|
+
if (!block || typeof block !== "object") return false;
|
|
5939
|
+
const payload = block as Record<string, unknown>;
|
|
5940
|
+
const type = typeof payload.type === "string" ? payload.type.toLowerCase() : "";
|
|
5941
|
+
if (type.includes("image")) return true;
|
|
5942
|
+
const mime = typeof payload.mimeType === "string"
|
|
5943
|
+
? payload.mimeType
|
|
5944
|
+
: (typeof payload.media_type === "string" ? payload.media_type : "");
|
|
5945
|
+
if (mime.toLowerCase().startsWith("image/")) return true;
|
|
5946
|
+
const source = payload.source && typeof payload.source === "object" ? payload.source as Record<string, unknown> : null;
|
|
5947
|
+
const sourceMime = source && typeof source.media_type === "string" ? source.media_type : "";
|
|
5948
|
+
return sourceMime.toLowerCase().startsWith("image/");
|
|
5949
|
+
}
|
|
5950
|
+
|
|
5951
|
+
function describeStudioTraceImageBlock(block: unknown): string {
|
|
5952
|
+
const payload = (block && typeof block === "object") ? block as Record<string, unknown> : {};
|
|
5953
|
+
const source = payload.source && typeof payload.source === "object" ? payload.source as Record<string, unknown> : null;
|
|
5954
|
+
const mime = typeof payload.mimeType === "string"
|
|
5955
|
+
? payload.mimeType
|
|
5956
|
+
: (typeof payload.media_type === "string"
|
|
5957
|
+
? payload.media_type
|
|
5958
|
+
: (source && typeof source.media_type === "string" ? source.media_type : "image"));
|
|
5959
|
+
return `[Image: ${mime || "image"} output omitted from Working view]`;
|
|
5960
|
+
}
|
|
5961
|
+
|
|
5962
|
+
function stringifyStudioTraceObject(value: unknown): string {
|
|
5963
|
+
try {
|
|
5964
|
+
return sanitizeStudioTraceOutputText(JSON.stringify(value, (_key, item) => {
|
|
5965
|
+
if (typeof item === "string") {
|
|
5966
|
+
if (/^data:image\//i.test(item)) return "[image data URI omitted]";
|
|
5967
|
+
if (/^[A-Za-z0-9+/=]{1000,}$/.test(item)) return "[base64 data omitted]";
|
|
5968
|
+
}
|
|
5969
|
+
return item;
|
|
5970
|
+
}, 2));
|
|
5971
|
+
} catch {
|
|
5972
|
+
return sanitizeStudioTraceOutputText(String(value));
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5975
|
+
|
|
5767
5976
|
function formatStudioTraceOutput(result: unknown): string {
|
|
5768
5977
|
if (result == null) return "";
|
|
5769
|
-
if (typeof result === "string") return result;
|
|
5978
|
+
if (typeof result === "string") return sanitizeStudioTraceOutputText(result);
|
|
5770
5979
|
if (Array.isArray(result)) {
|
|
5771
5980
|
return result.map((item) => formatStudioTraceOutput(item)).filter(Boolean).join("\n");
|
|
5772
5981
|
}
|
|
5773
5982
|
if (typeof result === "object") {
|
|
5983
|
+
if (isStudioTraceImageBlock(result)) return describeStudioTraceImageBlock(result);
|
|
5774
5984
|
const payload = result as { content?: Array<{ type?: string; text?: string }> };
|
|
5775
5985
|
if (Array.isArray(payload.content)) {
|
|
5776
5986
|
return payload.content
|
|
5777
5987
|
.map((block) => {
|
|
5778
|
-
if (block
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
} catch {
|
|
5782
|
-
return String(block);
|
|
5783
|
-
}
|
|
5988
|
+
if (isStudioTraceImageBlock(block)) return describeStudioTraceImageBlock(block);
|
|
5989
|
+
if (block && block.type === "text" && typeof block.text === "string") return sanitizeStudioTraceOutputText(block.text);
|
|
5990
|
+
return stringifyStudioTraceObject(block);
|
|
5784
5991
|
})
|
|
5785
5992
|
.filter(Boolean)
|
|
5786
5993
|
.join("\n");
|
|
5787
5994
|
}
|
|
5788
|
-
|
|
5789
|
-
return JSON.stringify(result, null, 2);
|
|
5790
|
-
} catch {
|
|
5791
|
-
return String(result);
|
|
5792
|
-
}
|
|
5995
|
+
return stringifyStudioTraceObject(result);
|
|
5793
5996
|
}
|
|
5794
|
-
return String(result);
|
|
5997
|
+
return sanitizeStudioTraceOutputText(String(result));
|
|
5795
5998
|
}
|
|
5796
5999
|
|
|
5797
6000
|
function summarizeStudioTraceToolArgs(toolName: string, args: unknown): string | null {
|
|
@@ -5926,6 +6129,17 @@ function buildTerminalSessionLabel(cwd: string, sessionName?: string): string {
|
|
|
5926
6129
|
return parts.join(" · ");
|
|
5927
6130
|
}
|
|
5928
6131
|
|
|
6132
|
+
function buildTerminalSessionDetail(cwd: string, sessionName?: string): string {
|
|
6133
|
+
const termProgram = String(process.env.TERM_PROGRAM ?? "").trim() || "unknown";
|
|
6134
|
+
const name = String(sessionName ?? "").trim() || "unknown";
|
|
6135
|
+
const workingDir = String(cwd || process.cwd() || "").trim() || "unknown";
|
|
6136
|
+
return [
|
|
6137
|
+
`Terminal: ${termProgram}`,
|
|
6138
|
+
`Session: ${name}`,
|
|
6139
|
+
`Working dir: ${workingDir}`,
|
|
6140
|
+
].join("\n");
|
|
6141
|
+
}
|
|
6142
|
+
|
|
5929
6143
|
function sanitizePdfFilename(input: string | undefined): string {
|
|
5930
6144
|
const fallback = "studio-preview.pdf";
|
|
5931
6145
|
const raw = String(input ?? "").trim();
|
|
@@ -5944,12 +6158,19 @@ function sanitizePdfFilename(input: string | undefined): string {
|
|
|
5944
6158
|
}
|
|
5945
6159
|
|
|
5946
6160
|
function buildThemeCssVars(style: StudioThemeStyle): Record<string, string> {
|
|
6161
|
+
const shadowColor = style.mode === "light"
|
|
6162
|
+
? withAlpha(style.palette.text, 0.10, "rgba(15, 23, 42, 0.08)")
|
|
6163
|
+
: "rgba(0, 0, 0, 0.32)";
|
|
5947
6164
|
const panelShadow =
|
|
5948
6165
|
style.mode === "light"
|
|
5949
|
-
?
|
|
5950
|
-
: "0 1px 2px rgba(0, 0, 0, 0.
|
|
5951
|
-
const
|
|
5952
|
-
const
|
|
6166
|
+
? `0 1px 2px ${withAlpha(style.palette.text, 0.035, "rgba(15, 23, 42, 0.03)")}, 0 4px 14px ${withAlpha(style.palette.text, 0.055, "rgba(15, 23, 42, 0.04)")}`
|
|
6167
|
+
: "0 1px 2px rgba(0, 0, 0, 0.30), 0 6px 18px rgba(0, 0, 0, 0.18)";
|
|
6168
|
+
const borderSubtle = blendColors(style.palette.borderMuted, style.palette.panel, style.mode === "light" ? 0.58 : 0.48);
|
|
6169
|
+
const panelBorder = blendColors(style.palette.borderMuted, style.palette.panel, style.mode === "light" ? 0.42 : 0.36);
|
|
6170
|
+
const controlBorder = blendColors(style.palette.borderMuted, style.palette.panel, style.mode === "light" ? 0.30 : 0.22);
|
|
6171
|
+
const paneActiveBorder = blendColors(style.palette.border, style.palette.panel, style.mode === "light" ? 0.34 : 0.48);
|
|
6172
|
+
const accentContrast = style.accentContrast ?? (style.mode === "light" ? "#ffffff" : "#0e1616");
|
|
6173
|
+
const errorContrast = style.errorContrast ?? readableTextOn(style.palette.error);
|
|
5953
6174
|
const blockquoteBg = withAlpha(
|
|
5954
6175
|
style.palette.mdQuoteBorder,
|
|
5955
6176
|
style.mode === "light" ? 0.10 : 0.16,
|
|
@@ -5960,10 +6181,29 @@ function buildThemeCssVars(style: StudioThemeStyle): Record<string, string> {
|
|
|
5960
6181
|
style.mode === "light" ? 0.10 : 0.14,
|
|
5961
6182
|
style.mode === "light" ? "rgba(15, 23, 42, 0.03)" : "rgba(255, 255, 255, 0.04)",
|
|
5962
6183
|
);
|
|
5963
|
-
const
|
|
5964
|
-
|
|
6184
|
+
const inlineCodeBg = withAlpha(
|
|
6185
|
+
style.palette.mdCodeBlockBorder,
|
|
6186
|
+
style.mode === "light" ? 0.13 : 0.18,
|
|
6187
|
+
style.mode === "light" ? "rgba(15, 23, 42, 0.06)" : "rgba(255, 255, 255, 0.07)",
|
|
6188
|
+
);
|
|
6189
|
+
const diffAddedBg = withAlpha(style.palette.ok, style.mode === "light" ? 0.10 : 0.14, "rgba(46, 160, 67, 0.12)");
|
|
6190
|
+
const diffRemovedBg = withAlpha(style.palette.error, style.mode === "light" ? 0.10 : 0.14, "rgba(248, 81, 73, 0.12)");
|
|
6191
|
+
const okSoft = withAlpha(style.palette.ok, style.mode === "light" ? 0.10 : 0.12, "rgba(115, 209, 61, 0.08)");
|
|
6192
|
+
const errorSoft = withAlpha(style.palette.error, style.mode === "light" ? 0.10 : 0.12, "rgba(255, 107, 107, 0.08)");
|
|
6193
|
+
const backdropBg = style.mode === "light" ? "rgba(15, 23, 42, 0.20)" : "rgba(0, 0, 0, 0.48)";
|
|
6194
|
+
const panelLum = hexToRgb(style.palette.panel) ? relativeLuminance(style.palette.panel) : null;
|
|
6195
|
+
const panel2Lum = hexToRgb(style.palette.panel2) ? relativeLuminance(style.palette.panel2) : null;
|
|
6196
|
+
const lightPrimarySurface = panelLum != null && panel2Lum != null && panel2Lum > panelLum
|
|
6197
|
+
? style.palette.panel2
|
|
5965
6198
|
: style.palette.panel;
|
|
6199
|
+
const lightSecondarySurface = lightPrimarySurface === style.palette.panel ? style.palette.panel2 : style.palette.panel;
|
|
6200
|
+
const editorBg = style.mode === "light" ? lightPrimarySurface : style.palette.panel;
|
|
6201
|
+
const editorGutterBg = style.mode === "light" ? lightSecondarySurface : style.palette.panel2;
|
|
6202
|
+
const referenceMetaBg = style.mode === "light" ? lightSecondarySurface : style.palette.panel2;
|
|
6203
|
+
const referenceBadgeBg = style.mode === "light" ? lightPrimarySurface : style.palette.panel;
|
|
5966
6204
|
const monoFontStack = getStudioMonoFontStack();
|
|
6205
|
+
const uiFontStack = getStudioUiFontStack();
|
|
6206
|
+
const proseFontStack = getStudioProseFontStack();
|
|
5967
6207
|
|
|
5968
6208
|
return {
|
|
5969
6209
|
"color-scheme": style.mode,
|
|
@@ -5972,6 +6212,10 @@ function buildThemeCssVars(style: StudioThemeStyle): Record<string, string> {
|
|
|
5972
6212
|
"--panel-2": style.palette.panel2,
|
|
5973
6213
|
"--border": style.palette.border,
|
|
5974
6214
|
"--border-muted": style.palette.borderMuted,
|
|
6215
|
+
"--border-subtle": borderSubtle,
|
|
6216
|
+
"--panel-border": panelBorder,
|
|
6217
|
+
"--control-border": controlBorder,
|
|
6218
|
+
"--pane-active-border": paneActiveBorder,
|
|
5975
6219
|
"--text": style.palette.text,
|
|
5976
6220
|
"--muted": style.palette.muted,
|
|
5977
6221
|
"--accent": style.palette.accent,
|
|
@@ -6004,11 +6248,24 @@ function buildThemeCssVars(style: StudioThemeStyle): Record<string, string> {
|
|
|
6004
6248
|
"--syntax-operator": style.palette.syntaxOperator,
|
|
6005
6249
|
"--syntax-punctuation": style.palette.syntaxPunctuation,
|
|
6006
6250
|
"--panel-shadow": panelShadow,
|
|
6251
|
+
"--shadow-color": shadowColor,
|
|
6007
6252
|
"--accent-contrast": accentContrast,
|
|
6008
6253
|
"--error-contrast": errorContrast,
|
|
6009
6254
|
"--blockquote-bg": blockquoteBg,
|
|
6255
|
+
"--inline-code-bg": inlineCodeBg,
|
|
6010
6256
|
"--table-alt-bg": tableAltBg,
|
|
6257
|
+
"--md-table-border": borderSubtle,
|
|
6258
|
+
"--diff-added-bg": diffAddedBg,
|
|
6259
|
+
"--diff-removed-bg": diffRemovedBg,
|
|
6260
|
+
"--ok-soft": okSoft,
|
|
6261
|
+
"--error-soft": errorSoft,
|
|
6262
|
+
"--backdrop-bg": backdropBg,
|
|
6011
6263
|
"--editor-bg": editorBg,
|
|
6264
|
+
"--editor-gutter-bg": editorGutterBg,
|
|
6265
|
+
"--reference-meta-bg": referenceMetaBg,
|
|
6266
|
+
"--reference-badge-bg": referenceBadgeBg,
|
|
6267
|
+
"--font-ui": uiFontStack,
|
|
6268
|
+
"--font-prose": proseFontStack,
|
|
6012
6269
|
"--font-mono": monoFontStack,
|
|
6013
6270
|
};
|
|
6014
6271
|
}
|
|
@@ -6029,6 +6286,7 @@ function buildStudioHtml(
|
|
|
6029
6286
|
theme?: Theme,
|
|
6030
6287
|
initialModelLabel?: string,
|
|
6031
6288
|
initialTerminalLabel?: string,
|
|
6289
|
+
initialTerminalDetail?: string,
|
|
6032
6290
|
initialContextUsage?: StudioContextUsageSnapshot,
|
|
6033
6291
|
studioMode: StudioUiMode = "full",
|
|
6034
6292
|
): string {
|
|
@@ -6039,6 +6297,7 @@ function buildStudioHtml(
|
|
|
6039
6297
|
const initialDraftId = escapeHtmlForInline(initialDocument?.draftId ?? "");
|
|
6040
6298
|
const initialModel = escapeHtmlForInline(initialModelLabel ?? "none");
|
|
6041
6299
|
const initialTerminal = escapeHtmlForInline(initialTerminalLabel ?? "unknown");
|
|
6300
|
+
const initialTerminalDetailAttr = escapeHtmlForInline(initialTerminalDetail ?? initialTerminalLabel ?? "unknown");
|
|
6042
6301
|
const initialContextTokens =
|
|
6043
6302
|
typeof initialContextUsage?.tokens === "number" && Number.isFinite(initialContextUsage.tokens)
|
|
6044
6303
|
? String(initialContextUsage.tokens)
|
|
@@ -6105,7 +6364,7 @@ ${cssVarsBlock}
|
|
|
6105
6364
|
</style>
|
|
6106
6365
|
<link rel="stylesheet" href="${stylesheetHref}" />
|
|
6107
6366
|
</head>
|
|
6108
|
-
<body data-initial-source="${initialSource}" data-initial-label="${initialLabel}" data-initial-path="${initialPath}" data-initial-draft-id="${initialDraftId}" data-model-label="${initialModel}" data-terminal-label="${initialTerminal}" data-context-tokens="${initialContextTokens}" data-context-window="${initialContextWindow}" data-context-percent="${initialContextPercent}" data-studio-mode="${studioMode}">
|
|
6367
|
+
<body data-initial-source="${initialSource}" data-initial-label="${initialLabel}" data-initial-path="${initialPath}" data-initial-draft-id="${initialDraftId}" data-model-label="${initialModel}" data-terminal-label="${initialTerminal}" data-terminal-detail="${initialTerminalDetailAttr}" data-context-tokens="${initialContextTokens}" data-context-window="${initialContextWindow}" data-context-percent="${initialContextPercent}" data-studio-mode="${studioMode}">
|
|
6109
6368
|
<header>
|
|
6110
6369
|
<h1><span class="app-logo" aria-hidden="true">π</span> Studio <span class="app-subtitle">${appSubtitle}</span></h1>
|
|
6111
6370
|
<div class="controls">
|
|
@@ -6201,6 +6460,16 @@ ${cssVarsBlock}
|
|
|
6201
6460
|
<option value="off">Line numbers: Off</option>
|
|
6202
6461
|
<option value="on" selected>Line numbers: On</option>
|
|
6203
6462
|
</select>
|
|
6463
|
+
<select id="editorFontSizeSelect" aria-label="Editor text size" title="Adjust raw editor text size.">
|
|
6464
|
+
<option value="10">Editor text: 10px</option>
|
|
6465
|
+
<option value="11">Editor text: 11px</option>
|
|
6466
|
+
<option value="12" selected>Editor text: 12px</option>
|
|
6467
|
+
<option value="13">Editor text: 13px</option>
|
|
6468
|
+
<option value="14">Editor text: 14px</option>
|
|
6469
|
+
<option value="15">Editor text: 15px</option>
|
|
6470
|
+
<option value="16">Editor text: 16px</option>
|
|
6471
|
+
<option value="18">Editor text: 18px</option>
|
|
6472
|
+
</select>
|
|
6204
6473
|
</div>
|
|
6205
6474
|
</div>
|
|
6206
6475
|
</div>
|
|
@@ -6260,8 +6529,9 @@ ${cssVarsBlock}
|
|
|
6260
6529
|
<div id="reviewNotesList" class="review-notes-list" aria-live="polite"></div>
|
|
6261
6530
|
<div class="review-notes-dock-footer">
|
|
6262
6531
|
<div class="scratchpad-actions">
|
|
6263
|
-
<button id="reviewNotesAddBtn" type="button" title="Create a new local comment on the current editor line.">Line
|
|
6264
|
-
<button id="
|
|
6532
|
+
<button id="reviewNotesAddBtn" type="button" title="Create a new local comment on the current editor line.">Line</button>
|
|
6533
|
+
<button id="reviewNotesPromptBtn" type="button" title="Load local comments, line numbers, and file labels into the editor as a prompt.">Comments → prompt</button>
|
|
6534
|
+
<button id="reviewNotesInlineAllBtn" type="button" title="Toggle inline annotations for all non-empty comments.">Inline: Off</button>
|
|
6265
6535
|
<button id="reviewNotesDeleteAllBtn" type="button" title="Delete all local comments for this document or draft.">Delete all</button>
|
|
6266
6536
|
<button id="reviewNotesDoneBtn" type="button" title="Hide the comments rail.">Hide</button>
|
|
6267
6537
|
</div>
|
|
@@ -6293,7 +6563,7 @@ ${cssVarsBlock}
|
|
|
6293
6563
|
<div id="critiqueView" class="panel-scroll rendered-markdown"><pre class="plain-markdown">No response yet.</pre></div>
|
|
6294
6564
|
<div class="response-wrap">
|
|
6295
6565
|
<div id="responseActions" class="response-actions">
|
|
6296
|
-
<div class="response-actions-row">
|
|
6566
|
+
<div class="response-actions-row response-options-row">
|
|
6297
6567
|
<select id="followSelect" aria-label="Auto-update response">
|
|
6298
6568
|
<option value="on" selected>Auto-update response: On</option>
|
|
6299
6569
|
<option value="off">Auto-update response: Off</option>
|
|
@@ -6302,6 +6572,20 @@ ${cssVarsBlock}
|
|
|
6302
6572
|
<option value="off">Syntax highlight: Off</option>
|
|
6303
6573
|
<option value="on" selected>Syntax highlight: On</option>
|
|
6304
6574
|
</select>
|
|
6575
|
+
<select id="responseFontSizeSelect" aria-label="Response text size" title="Adjust right-pane response, preview, and working text size.">
|
|
6576
|
+
<option value="11">Response text: 11px</option>
|
|
6577
|
+
<option value="12">Response text: 12px</option>
|
|
6578
|
+
<option value="12.5">Response text: 12.5px</option>
|
|
6579
|
+
<option value="13">Response text: 13px</option>
|
|
6580
|
+
<option value="13.5" selected>Response text: 13.5px</option>
|
|
6581
|
+
<option value="14">Response text: 14px</option>
|
|
6582
|
+
<option value="14.5">Response text: 14.5px</option>
|
|
6583
|
+
<option value="15">Response text: 15px</option>
|
|
6584
|
+
<option value="15.5">Response text: 15.5px</option>
|
|
6585
|
+
<option value="16">Response text: 16px</option>
|
|
6586
|
+
<option value="18">Response text: 18px</option>
|
|
6587
|
+
<option value="20">Response text: 20px</option>
|
|
6588
|
+
</select>
|
|
6305
6589
|
</div>
|
|
6306
6590
|
<div class="response-actions-row history-row">
|
|
6307
6591
|
<button id="pullLatestBtn" type="button" title="Fetch the latest assistant response when auto-update is off.">Fetch latest response</button>
|
|
@@ -6310,7 +6594,7 @@ ${cssVarsBlock}
|
|
|
6310
6594
|
<button id="historyNextBtn" type="button" title="Show next response in history.">Next response ▶</button>
|
|
6311
6595
|
<button id="historyLastBtn" type="button" title="Jump to the latest loaded response in history.">Last response ▶|</button>
|
|
6312
6596
|
</div>
|
|
6313
|
-
<div class="response-actions-row">
|
|
6597
|
+
<div class="response-actions-row response-result-row">
|
|
6314
6598
|
<button id="loadResponseBtn" type="button">Load response into editor</button>
|
|
6315
6599
|
<button id="loadCritiqueNotesBtn" type="button" hidden>Load critique notes into editor</button>
|
|
6316
6600
|
<button id="loadCritiqueFullBtn" type="button" hidden>Load full critique into editor</button>
|
|
@@ -6324,7 +6608,7 @@ ${cssVarsBlock}
|
|
|
6324
6608
|
|
|
6325
6609
|
<footer>
|
|
6326
6610
|
<span id="statusLine"><span id="statusSpinner" aria-hidden="true"> </span><span id="status">Booting studio…</span></span>
|
|
6327
|
-
<span id="footerMeta" class="footer-meta"><span id="footerMetaText" class="footer-meta-text"
|
|
6611
|
+
<span id="footerMeta" class="footer-meta"><span id="footerMetaText" class="footer-meta-text"><span id="footerMetaModel" class="footer-meta-part footer-meta-model">${initialModel}</span><span class="footer-meta-sep">·</span><span id="footerMetaTerminal" class="footer-meta-part footer-meta-terminal">${initialTerminal}</span><span class="footer-meta-sep">·</span><span id="footerMetaContext" class="footer-meta-part footer-meta-context">unknown</span></span><button id="compactBtn" class="footer-compact-btn" type="button" title="Trigger pi context compaction now.">Compact</button></span>
|
|
6328
6612
|
<span class="shortcut-hint">Focus pane: F10 (or Cmd/Ctrl+Esc) to toggle · Save editor: Cmd/Ctrl+S · Run / queue steering: Cmd/Ctrl+Enter · Stop request: Esc</span>
|
|
6329
6613
|
</footer>
|
|
6330
6614
|
|
|
@@ -6383,6 +6667,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6383
6667
|
let currentModel: { provider?: string; id?: string } | undefined;
|
|
6384
6668
|
let currentModelLabel = "none";
|
|
6385
6669
|
let terminalSessionLabel = buildTerminalSessionLabel(studioCwd);
|
|
6670
|
+
let terminalSessionDetail = buildTerminalSessionDetail(studioCwd);
|
|
6386
6671
|
let studioResponseHistory: StudioResponseHistoryItem[] = [];
|
|
6387
6672
|
let latestSessionUserPrompt: string | null = null;
|
|
6388
6673
|
let pendingTurnPrompt: string | null = null;
|
|
@@ -6467,6 +6752,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
6467
6752
|
const baseModelLabel = formatModelLabel(currentModel);
|
|
6468
6753
|
currentModelLabel = formatModelLabelWithThinking(baseModelLabel, getThinkingLevelSafe());
|
|
6469
6754
|
terminalSessionLabel = buildTerminalSessionLabel(studioCwd, getSessionNameSafe());
|
|
6755
|
+
terminalSessionDetail = buildTerminalSessionDetail(studioCwd, getSessionNameSafe());
|
|
6470
6756
|
};
|
|
6471
6757
|
|
|
6472
6758
|
const notifyStudio = (message: string, level: "info" | "warning" | "error" = "info") => {
|
|
@@ -7040,6 +7326,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7040
7326
|
|
|
7041
7327
|
const broadcastState = () => {
|
|
7042
7328
|
terminalSessionLabel = buildTerminalSessionLabel(studioCwd, getSessionNameSafe());
|
|
7329
|
+
terminalSessionDetail = buildTerminalSessionDetail(studioCwd, getSessionNameSafe());
|
|
7043
7330
|
currentModelLabel = formatModelLabelWithThinking(formatModelLabel(currentModel), getThinkingLevelSafe());
|
|
7044
7331
|
refreshContextUsage();
|
|
7045
7332
|
broadcast({
|
|
@@ -7051,6 +7338,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7051
7338
|
terminalActivityLabel,
|
|
7052
7339
|
modelLabel: currentModelLabel,
|
|
7053
7340
|
terminalSessionLabel,
|
|
7341
|
+
terminalSessionDetail,
|
|
7054
7342
|
contextTokens: contextUsageSnapshot.tokens,
|
|
7055
7343
|
contextWindow: contextUsageSnapshot.contextWindow,
|
|
7056
7344
|
contextPercent: contextUsageSnapshot.percent,
|
|
@@ -7228,6 +7516,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
7228
7516
|
promptTriggerText: descriptor.promptTriggerText,
|
|
7229
7517
|
};
|
|
7230
7518
|
queuedStudioDirectRequests.push(queuedRequest);
|
|
7519
|
+
|
|
7520
|
+
// Steering is delivered into the currently running Studio turn rather than
|
|
7521
|
+
// becoming a fully separate visible response request. Keep the active direct
|
|
7522
|
+
// request metadata aligned with the effective prompt chain so persisted
|
|
7523
|
+
// prompt metadata, response history, and "Load effective prompt" all refer
|
|
7524
|
+
// to the original run plus queued steering, not just the original run.
|
|
7525
|
+
if (activeRequest && activeRequest.kind === "direct") {
|
|
7526
|
+
activeRequest.prompt = descriptor.prompt;
|
|
7527
|
+
activeRequest.promptMode = descriptor.promptMode;
|
|
7528
|
+
activeRequest.promptTriggerKind = descriptor.promptTriggerKind;
|
|
7529
|
+
activeRequest.promptSteeringCount = descriptor.promptSteeringCount;
|
|
7530
|
+
activeRequest.promptTriggerText = descriptor.promptTriggerText;
|
|
7531
|
+
}
|
|
7532
|
+
|
|
7231
7533
|
return queuedRequest;
|
|
7232
7534
|
};
|
|
7233
7535
|
|
|
@@ -7331,6 +7633,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
7331
7633
|
terminalActivityLabel,
|
|
7332
7634
|
modelLabel: currentModelLabel,
|
|
7333
7635
|
terminalSessionLabel,
|
|
7636
|
+
terminalSessionDetail,
|
|
7334
7637
|
contextTokens: contextUsageSnapshot.tokens,
|
|
7335
7638
|
contextWindow: contextUsageSnapshot.contextWindow,
|
|
7336
7639
|
contextPercent: contextUsageSnapshot.percent,
|
|
@@ -8456,7 +8759,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
8456
8759
|
refreshContextUsage();
|
|
8457
8760
|
const studioMode = normalizeStudioUiMode(requestUrl.searchParams.get("mode"));
|
|
8458
8761
|
const requestInitialDocument = resolveRequestedStudioDocumentFromUrl(requestUrl, initialStudioDocument, studioCwd, lastStudioResponse);
|
|
8459
|
-
res.end(buildStudioHtml(requestInitialDocument, serverState.token, lastCommandCtx?.ui.theme, currentModelLabel, terminalSessionLabel, contextUsageSnapshot, studioMode));
|
|
8762
|
+
res.end(buildStudioHtml(requestInitialDocument, serverState.token, lastCommandCtx?.ui.theme, currentModelLabel, terminalSessionLabel, terminalSessionDetail, contextUsageSnapshot, studioMode));
|
|
8460
8763
|
};
|
|
8461
8764
|
|
|
8462
8765
|
const ensureServer = async (): Promise<StudioServerState> => {
|