Repository navigation
page-chat 0.1.4: model brand icons, inline images, gateway port from magpie config - #534
Conversation
…magpie config - Model brand icons (OpenAI / Claude / Gemini / DeepSeek / Qwen / Kimi / GLM / MiniMax / MiMo / ...) in the model picker, next to each reply and on the composer's model button - Inline images in replies:  and base64 data URIs render in place and open full size - Streaming: split the render at the last blank line outside code fences, so code blocks with blank lines no longer jump; a reply that arrives in one chunk still renders - Gateway: read the real port from magpie's settings.json (and XDG_CONFIG_HOME); report the address actually probed instead of recomputing the candidate list - MCP: stdio servers run in <config>/magpie-chat/workspace, and the tools hint says so - New "use system prompt" switch (ticked instructions and skills still apply) - Launcher: after 5s, show a "service is not running" message with a Retry button (WebKit hangs silently) - Remove the dead port.json write - Fix: runChat never received options, so resolveGateway(options) threw
4198831 to
e3a3906
Compare
yetone
left a comment
There was a problem hiding this comment.
Thanks. I read the whole diff at e3a3906. Most of it is good:
- The system-prompt switch is applied on both sides:
composeSystemon the server andsend()on the page. - The stdio MCP servers now get their own workspace, so a tool's relative writes no longer land next to magpie's
settings.json. lastSafeBreakkeeps code fences together while a reply streams.- Reading the port from
settings.jsonreads a key magpie really writes (Port int json:"port").
The suite passes (see the end), but three things need changing before this merges.
1. A reply can send data off the machine with no click. Inline images accept any https:// URL (safeImgSrc, chat/app.js), and the chat UI is served with no CSP. A model's reply that contains  therefore makes the page fetch that URL as soon as the reply is drawn.
This page also runs the user's MCP tools: a web fetch, a GitHub issue, a file. Text a tool returns can tell the model to write such an image with the conversation, a file's contents or a token in the query. This is the usual markdown-image exfiltration path, and a page with tools is exactly where it matters. Please don't load remote images by default. Either:
- render data: URIs only, and show an
https://image as a link or a "click to load" placeholder; or - serve the UI with a CSP whose
img-srcis'self' data: blob:.
2. A reply that arrives all at once comes out empty again. finish() used to render whenever the message had content:
else if (refs.body && refs.msg.content) renderMarkdown(refs.body, refs.msg.content);Its comment said why: when the whole reply comes in one frame, pumpTick never runs, so dirty is false and there is no .md-stable. 0.1.4 changes the condition to refs.body.querySelector(".md-stable"), which is false in exactly that case. pumpFlush moves the text into msg.content without drawing it, so the body stays empty. This undoes the fix from 0.1.3 (#498). Please keep the content check, and render once when either condition holds.
3. chat/style.css is now CRLF. The file was LF, so the diff shows all 2,729 lines changed. With -w --ignore-cr-at-eol it is +72/-25. Please convert it back to LF so the real change can be reviewed and blamed.
Smaller things:
toolsHintnow puts the absolute workspace path,/Users/<name>/.config/magpie/magpie-chat/workspace, into the system prompt, which goes to the vendor along with the user's account name. A relative description ("the chat's own workspace folder") would do. Alternatively, leave it out: the server's cwd already makes relative paths land there.- The brand icons are from
@lobehub/icons. Please name the source and its license (MIT) in the README, or in a NOTICE besidechat/icons/models/. - Taking out the empty state's suggestion buttons and the 「模型」 label is fine since this is your package. Please mention both in the PR body, though, since the README doesn't.
What I ran (Mac mini, Bun 1.4.2, through mote-run, sandbox HOME), at e3a3906:
HOME=$(mktemp -d) bun test: 3869 pass, 0 fail, including the three new chat tests.bun scripts/check.mjs: exit 0.
Items 1 and 2 come from reading the code. Neither has a test yet, and a test for each would help: a reply with an https:// image loads nothing, and a reply delivered in one frame renders.
- Never auto-load remote images: safeImgSrc now only accepts base64 data URIs, so a reply containing  can no longer make the page fetch that URL (it could carry conversation text, MCP tool output or the page token). Remote images become a click-through link instead. - finish(): render whenever the message has content. Checking only for .md-stable made a reply that arrived in one chunk render empty, because that structure never exists in that case. - chat/style.css back to LF (it was CRLF, showing a 2729-line diff for a 72-line change). - toolsHint: drop the absolute workspace path from the system prompt (it carried the local user name to the model vendor); describe it as the chat's own workspace instead. - Add chat/icons/models/NOTICE with the @lobehub/icons source and MIT license; mention the icons in the README. - Two new regression tests: remote images are not auto-loaded, and a one-chunk reply renders.
|
@yetone 三条都改了,小问题也一起处理了。 1. 远程图片不再自动加载
if (!imgSrc) {
// 远程图片不自动加载,给一个用户主动点的链接
var imgLink = remoteImgLink(m[9], m[8]);
if (imgLink) parent.appendChild(imgLink);
else appendText(parent, m[0]);
}验证:渲染 2. 一帧到达的回复又空了
else if (refs.body && refs.msg.content) renderMarkdown(refs.body, refs.msg.content);验证:构造一个没有 3. 转成 LF 了;同一文件里 小问题
新测试 两个回归: |
yetone
left a comment
There was a problem hiding this comment.
Thanks, I read 3e3233f line by line. All three blockers are fixed in the code, and so are the smaller items. One thing still stops the merge: the new remote-image test doesn't fail when the protection it's named for is taken out.
Checked against the code, and in a real browser
I ran the real service (startServer) against a fake gateway. The gateway sends the whole reply in one SSE chunk, the page is driven through Playwright, and every request that leaves 127.0.0.1 is aborted and recorded. The reply contained:
,http://, protocol-relative//, andblob:images;<img src>,<img srcset>,<picture><source srcset>,<div style="background:url(…)">, and a<style>block;- a link wrapped around an image, a reference-style image, and a bare
https://….png; - KaTeX
\includegraphics{https://…}and\href; - a
data:image/svg+xml;base64image whose SVG has a<script>,onload, an<image href=https://…>and CSSurl(https://…). I also clicked it to open the lightbox.
Result: no request leaves the machine. The https and http images show as a.md-img-link, raw HTML stays text, and the data-URI PNG and SVG render as <img>, so their scripts don't run. The one-chunk reply renders in full. chat/style.css and every other changed file are LF. Reading the port is read-only: configuredGateways only calls readFileSync/existsSync, every candidate is 127.0.0.1, and an explicit gateway or MAGPIE_ADDR still comes first. Taking out the port.json write is fine too, since nothing read it.
Broken by hand
- B,
finish()back to.md-stableonly: the probe shows an empty body, andfinish() renders a reply that arrived in one chunkgoes red. Good. - A,
if (/^https:\/\//i.test(url)) return url;added tosafeImgSrc: the probe recordshttps://attacker.example/md?q=secret, butthe chat UI never auto-loads a remote imagestill passes (25/25). The test only looks for the literal texthttps?:in the function body, so any other way of writing the check gets past it.
Blocker: make the remote-image test check what the function does. A test named for a guarantee has to go red when that guarantee is broken. Take safeImgSrc out of app.js and call it. For example, take the source from function safeImgSrc to function remoteImgLink, then new Function(src + "; return safeImgSrc")(). Assert that https://x/a.png, HTTPS://x, http://x, //x/a.png, blob:https://x/1 and data:text/html;base64,… all give "", and that data:image/png;base64,… comes back unchanged. With change A above, that test fails.
Optional: the finish() test is also a regex over the source, so a harmless reformat would break it. Calling finish on a stub turn would be sturdier. I won't hold the merge for it.
What I ran (Mac mini, Bun 1.4.2, mote-run, sandbox HOME), at 3e3233f:
bun test: 3871 pass, 0 fail, 9 skip on three runs. A first run under load (another session's GUI suite was on the box) had 1 fail. I lost its name by piping throughtail, and it didn't come back.bun scripts/check.mjs: exit 0.- The Playwright probe above, Chromium: no external requests, one-chunk reply rendered. Breaks A and B as described. WebKit didn't launch on the box while it was under load (a Playwright launch timeout), so the probe hasn't run in WebKit yet. I'll run it there on the next round.
…e text The old test only looked for the literal "https?" in safeImgSrc's body, so rewriting the check in any other way would still pass while the page started fetching remote images. It now extracts safeImgSrc and calls it: data URIs must come back unchanged, and https://, HTTPS://, http://, //host, blob:https:// and data:text/html must all return "". Verified the test fails when safeImgSrc is changed to accept https:// again, and passes when reverted.
|
Blocker 修了,
const safeImgSrc = new Function(app.slice(start, end) + "\nreturn safeImgSrc")()
const png = "data:image/png;base64,iVBORw0KGgo="
expect(safeImgSrc(png)).toBe(png)
const svg = "data:image/svg+xml;base64,PHN2Zz48L3N2Zz4="
expect(safeImgSrc(svg)).toBe(svg)
for (const bad of [
"https://a.png", "HTTPS://a.png", "http://a.png", "//a.png",
"blob:https://a.png", "data:text/html;base64,PHNjcmlwdD4=",
]) {
expect(safeImgSrc(bad)).toBe("")
}按你说的反向验证过了:
你那边的 Playwright 探针(只放行 127.0.0.1、记录所有外部请求)比我本地的手测更全面——特别是 |
|
已合并为 20a3898,page-chat 0.1.4 已在 dist 分支上(index.json latest 0.1.4,sha512 对得上)。谢谢! 一个小的后续:README 的「What the service reads and sends」还写着网关按 |
|
谢谢!小 PR 开了:#542 改了两处:
只改 README 和版本号(0.1.5),没有代码改动。 |
page-chat 0.1.5 documents the gateway port it now reads from magpie's settings.json (#534's follow-up). The README order now matches chat's code at 20a3898: MAGPIE_CHAT_GATEWAY, then MAGPIE_ADDR, then settings.json's port, then 127.0.0.1:3425. settings.json is listed as read for fonts and the port. Run: read the diff against packages/chat's gateway lookup at 20a3898. It is docs plus the version bump; no code changed, so no tests were run. Co-authored-by: Magiczerowxy <Magiczerowxy@users.noreply.github.com>
page-chat 0.1.4
从 0.1.3 升到 0.1.4。
新增
模型品牌图标
chat/icons/models/NOTICE),放在chat/icons/models/mask上色,颜色跟随主题;按模型 id / provider 关键词匹配,匹配不到就退回原来的供应商图标回复里的图片
系统提示词开关
界面调整
修复
settings.json/XDG_CONFIG_HOME里的实际端口,bootstrap 返回真正探测成功的地址<config>/magpie-chat/workspace作为工作目录,并设PWDrunChat补上options参数(之前resolveGateway(options)会抛 ReferenceError)port.json写入测试
新增 5 个:systemEnabled 开关、MCP workspace、gateway 候选端口、远程图片不自动加载、一帧到达的回复能渲染。
保留
magpie.page.service/magpie.options.port声明与server钩子启动方式