pi-studio 0.1.8 → 0.1.9
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 +2 -0
- package/README.md +3 -0
- package/index.ts +115 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -25,6 +25,7 @@ All notable changes to `pi-studio` are documented here.
|
|
|
25
25
|
- Response-side markdown highlighting toggle (`Highlight markdown: Off|On`) in `Response: Markdown` view, with local preference persistence.
|
|
26
26
|
- Markdown highlighter now applies lightweight fenced-code token colors for common languages (`js/ts`, `python`, `bash/sh`, `json`).
|
|
27
27
|
- Obsidian wiki-image syntax normalization (`![[path]]`, `![[path|alt]]`) before pandoc preview rendering.
|
|
28
|
+
- Client-side Mermaid rendering for fenced `mermaid` code blocks in both Preview panes.
|
|
28
29
|
|
|
29
30
|
### Changed
|
|
30
31
|
- Removed Annotate/Critique tabs and related mode state.
|
|
@@ -47,6 +48,7 @@ All notable changes to `pi-studio` are documented here.
|
|
|
47
48
|
- `respondText` now includes `X-Content-Type-Options: nosniff` for consistency with JSON responses.
|
|
48
49
|
- If `dompurify` is unavailable, preview now falls back to escaped plain markdown instead of injecting unsanitized HTML.
|
|
49
50
|
- Preview sanitization now preserves MathML profile and strips MathML annotation tags to avoid duplicate raw TeX text beside rendered equations.
|
|
51
|
+
- Preview now shows an inline warning when Mermaid is unavailable or diagram rendering fails, instead of failing silently.
|
|
50
52
|
|
|
51
53
|
### Changed
|
|
52
54
|
- Added npm metadata fields (`repository`, `homepage`, `bugs`) so npm package page links to GitHub.
|
package/README.md
CHANGED
|
@@ -33,6 +33,7 @@ Status: experimental alpha.
|
|
|
33
33
|
- critique: **Load critique (notes)** / **Load critique (full)**
|
|
34
34
|
- File actions: **Save As…**, **Save file**, **Load file in editor**
|
|
35
35
|
- View toggles: `Editor: Markdown|Preview`, `Response: Markdown|Preview`
|
|
36
|
+
- Preview mode supports MathML equations and Mermaid fenced diagrams
|
|
36
37
|
- Optional markdown highlighting toggles for editor and response markdown views (including fenced-code token colors for common languages)
|
|
37
38
|
- Theme-aware browser UI based on current pi theme
|
|
38
39
|
|
|
@@ -75,6 +76,8 @@ pi -e https://github.com/omaclaren/pi-studio
|
|
|
75
76
|
- Pi Studio is currently optimized for markdown workflows (model responses, plans, and notes), including fenced code blocks. Pure code files are supported, but highlighting is tuned for markdown and fenced blocks rather than full-file language mode.
|
|
76
77
|
- Studio URLs include a token query parameter; avoid sharing full Studio URLs.
|
|
77
78
|
- Preview panes render markdown via `pandoc` (`gfm+tex_math_dollars` → HTML5 + MathML), including pandoc code syntax highlighting, sanitized in-browser with `dompurify`.
|
|
79
|
+
- Mermaid fenced `mermaid` code blocks are rendered client-side in preview mode (Mermaid v11 loaded from jsDelivr).
|
|
80
|
+
- If Mermaid cannot load or a diagram fails to render, preview shows an inline warning and keeps source text visible.
|
|
78
81
|
- Preview rendering normalizes Obsidian wiki-image syntax (`![[path]]`, `![[path|alt]]`) into standard markdown images.
|
|
79
82
|
- Install pandoc for full preview rendering (`brew install pandoc` on macOS).
|
|
80
83
|
- If `pandoc` is unavailable, preview falls back to plain markdown text with an inline warning.
|
package/index.ts
CHANGED
|
@@ -1424,6 +1424,17 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
1424
1424
|
overflow-y: hidden;
|
|
1425
1425
|
}
|
|
1426
1426
|
|
|
1427
|
+
.rendered-markdown .mermaid-container {
|
|
1428
|
+
text-align: center;
|
|
1429
|
+
margin: 1em 0;
|
|
1430
|
+
overflow-x: auto;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
.rendered-markdown .mermaid-container svg {
|
|
1434
|
+
max-width: 100%;
|
|
1435
|
+
height: auto;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1427
1438
|
.plain-markdown {
|
|
1428
1439
|
margin: 0;
|
|
1429
1440
|
white-space: pre-wrap;
|
|
@@ -1453,6 +1464,13 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
1453
1464
|
font-size: 12px;
|
|
1454
1465
|
}
|
|
1455
1466
|
|
|
1467
|
+
.preview-warning {
|
|
1468
|
+
color: var(--warn);
|
|
1469
|
+
margin-top: 0.75em;
|
|
1470
|
+
font-size: 12px;
|
|
1471
|
+
font-style: italic;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1456
1474
|
.marker {
|
|
1457
1475
|
display: inline-block;
|
|
1458
1476
|
padding: 0 4px;
|
|
@@ -1710,6 +1728,12 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
1710
1728
|
let editorHighlightEnabled = false;
|
|
1711
1729
|
let responseHighlightEnabled = false;
|
|
1712
1730
|
let editorHighlightRenderRaf = null;
|
|
1731
|
+
const MERMAID_CDN_URL = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
|
1732
|
+
const MERMAID_THEME = "${style.mode === "dark" ? "dark" : "default"}";
|
|
1733
|
+
const MERMAID_UNAVAILABLE_MESSAGE = "Mermaid renderer unavailable. Showing mermaid blocks as code.";
|
|
1734
|
+
const MERMAID_RENDER_FAIL_MESSAGE = "Mermaid render failed. Showing diagram source text.";
|
|
1735
|
+
let mermaidModulePromise = null;
|
|
1736
|
+
let mermaidInitialized = false;
|
|
1713
1737
|
|
|
1714
1738
|
function getIdleStatus() {
|
|
1715
1739
|
return "Ready. Edit text, then run or critique (insert annotation header if needed).";
|
|
@@ -1906,6 +1930,96 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
1906
1930
|
return buildPreviewErrorHtml("Preview sanitizer unavailable. Showing plain markdown.", markdown);
|
|
1907
1931
|
}
|
|
1908
1932
|
|
|
1933
|
+
function appendMermaidNotice(targetEl, message) {
|
|
1934
|
+
if (!targetEl || typeof targetEl.querySelector !== "function" || typeof targetEl.appendChild !== "function") {
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
if (targetEl.querySelector(".preview-mermaid-warning")) {
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
const warningEl = document.createElement("div");
|
|
1943
|
+
warningEl.className = "preview-warning preview-mermaid-warning";
|
|
1944
|
+
warningEl.textContent = String(message || MERMAID_RENDER_FAIL_MESSAGE);
|
|
1945
|
+
targetEl.appendChild(warningEl);
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
async function getMermaidApi() {
|
|
1949
|
+
if (mermaidModulePromise) {
|
|
1950
|
+
return mermaidModulePromise;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
mermaidModulePromise = import(MERMAID_CDN_URL)
|
|
1954
|
+
.then((module) => {
|
|
1955
|
+
const mermaidApi = module && module.default ? module.default : null;
|
|
1956
|
+
if (!mermaidApi) {
|
|
1957
|
+
throw new Error("Mermaid module did not expose a default export.");
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
if (!mermaidInitialized) {
|
|
1961
|
+
mermaidApi.initialize({
|
|
1962
|
+
startOnLoad: false,
|
|
1963
|
+
theme: MERMAID_THEME,
|
|
1964
|
+
});
|
|
1965
|
+
mermaidInitialized = true;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
return mermaidApi;
|
|
1969
|
+
})
|
|
1970
|
+
.catch((error) => {
|
|
1971
|
+
mermaidModulePromise = null;
|
|
1972
|
+
throw error;
|
|
1973
|
+
});
|
|
1974
|
+
|
|
1975
|
+
return mermaidModulePromise;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
async function renderMermaidInElement(targetEl) {
|
|
1979
|
+
if (!targetEl || typeof targetEl.querySelectorAll !== "function") return;
|
|
1980
|
+
|
|
1981
|
+
const mermaidBlocks = targetEl.querySelectorAll("pre.mermaid");
|
|
1982
|
+
if (!mermaidBlocks || mermaidBlocks.length === 0) return;
|
|
1983
|
+
|
|
1984
|
+
let mermaidApi;
|
|
1985
|
+
try {
|
|
1986
|
+
mermaidApi = await getMermaidApi();
|
|
1987
|
+
} catch (error) {
|
|
1988
|
+
console.error("Mermaid module load failed:", error);
|
|
1989
|
+
appendMermaidNotice(targetEl, MERMAID_UNAVAILABLE_MESSAGE);
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
mermaidBlocks.forEach((preEl) => {
|
|
1994
|
+
const codeEl = preEl.querySelector("code");
|
|
1995
|
+
const source = codeEl ? codeEl.textContent : preEl.textContent;
|
|
1996
|
+
|
|
1997
|
+
const wrapper = document.createElement("div");
|
|
1998
|
+
wrapper.className = "mermaid-container";
|
|
1999
|
+
|
|
2000
|
+
const diagramEl = document.createElement("div");
|
|
2001
|
+
diagramEl.className = "mermaid";
|
|
2002
|
+
diagramEl.textContent = source || "";
|
|
2003
|
+
|
|
2004
|
+
wrapper.appendChild(diagramEl);
|
|
2005
|
+
preEl.replaceWith(wrapper);
|
|
2006
|
+
});
|
|
2007
|
+
|
|
2008
|
+
const diagramNodes = Array.from(targetEl.querySelectorAll(".mermaid"));
|
|
2009
|
+
if (diagramNodes.length === 0) return;
|
|
2010
|
+
|
|
2011
|
+
try {
|
|
2012
|
+
await mermaidApi.run({ nodes: diagramNodes });
|
|
2013
|
+
} catch (error) {
|
|
2014
|
+
try {
|
|
2015
|
+
await mermaidApi.run();
|
|
2016
|
+
} catch (fallbackError) {
|
|
2017
|
+
console.error("Mermaid render failed:", fallbackError || error);
|
|
2018
|
+
appendMermaidNotice(targetEl, MERMAID_RENDER_FAIL_MESSAGE);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
|
|
1909
2023
|
async function renderMarkdownWithPandoc(markdown) {
|
|
1910
2024
|
const token = getToken();
|
|
1911
2025
|
if (!token) {
|
|
@@ -1976,6 +2090,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
1976
2090
|
}
|
|
1977
2091
|
|
|
1978
2092
|
targetEl.innerHTML = sanitizeRenderedHtml(renderedHtml, markdown);
|
|
2093
|
+
await renderMermaidInElement(targetEl);
|
|
1979
2094
|
} catch (error) {
|
|
1980
2095
|
if (pane === "source") {
|
|
1981
2096
|
if (nonce !== sourcePreviewRenderNonce || editorView !== "preview") return;
|