fix(llm): stop CSS-injection clash on chat UI; style natively in HTI-5 palette
All checks were successful
CI / lint (push) Successful in 37s
CI / notebooks-smoke (push) Successful in 1m30s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / llm (push) Has been skipped
Deploy / mc (push) Has been skipped
Infra CI / notebooks (push) Successful in 47s
Infra CI / zotero (push) Successful in 13s
Infra CI / docs (push) Successful in 16s
Infra CI / api (push) Successful in 1m3s
Infra CI / llm (push) Successful in 48s
Infra CI / mc (push) Successful in 12s
Deploy / report (push) Successful in 16s
CI / test (push) Successful in 14m45s
Harden / build-scan-report (push) Successful in 27m32s
Notebooks Integration / notebooks-integration (push) Successful in 8m1s
Zotero Sync / zotero-sync (push) Successful in 58s
Package Supply Chain / pkg-supply-chain (push) Successful in 59s
All checks were successful
CI / lint (push) Successful in 37s
CI / notebooks-smoke (push) Successful in 1m30s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / llm (push) Has been skipped
Deploy / mc (push) Has been skipped
Infra CI / notebooks (push) Successful in 47s
Infra CI / zotero (push) Successful in 13s
Infra CI / docs (push) Successful in 16s
Infra CI / api (push) Successful in 1m3s
Infra CI / llm (push) Successful in 48s
Infra CI / mc (push) Successful in 12s
Deploy / report (push) Successful in 16s
CI / test (push) Successful in 14m45s
Harden / build-scan-report (push) Successful in 27m32s
Notebooks Integration / notebooks-integration (push) Successful in 8m1s
Zotero Sync / zotero-sync (push) Successful in 58s
Package Supply Chain / pkg-supply-chain (push) Successful in 59s
theme:true injected the generic fhirworx.css (light editorial, !important rules) over the chat page's own dark theme, wrecking the colors. Set theme:false and restyle the page natively in the platform palette (cream/navy, Playfair/Source Serif), with cited [ID] tokens as mono chips.
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
"zotero" (dict "port" "8080" "theme" true "mw" "git-sso,secure-headers")
|
"zotero" (dict "port" "8080" "theme" true "mw" "git-sso,secure-headers")
|
||||||
"webdav" (dict "port" "8080" "theme" false "mw" "secure-headers")
|
"webdav" (dict "port" "8080" "theme" false "mw" "secure-headers")
|
||||||
"api" (dict "port" "8000" "theme" false "mw" "secure-headers")
|
"api" (dict "port" "8000" "theme" false "mw" "secure-headers")
|
||||||
"llm" (dict "port" "8000" "theme" true "mw" "git-sso,secure-headers")
|
"llm" (dict "port" "8000" "theme" false "mw" "git-sso,secure-headers")
|
||||||
"nessie" (dict "port" "19120" "theme" false "mw" "git-sso,infra-headers")
|
"nessie" (dict "port" "19120" "theme" false "mw" "git-sso,infra-headers")
|
||||||
"trino" (dict "port" "8080" "theme" true "mw" "git-sso,infra-headers")
|
"trino" (dict "port" "8080" "theme" true "mw" "git-sso,infra-headers")
|
||||||
"polaris" (dict "port" "8181" "theme" false "mw" "git-sso,infra-headers")
|
"polaris" (dict "port" "8181" "theme" false "mw" "git-sso,infra-headers")
|
||||||
|
|||||||
@@ -4,63 +4,121 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Comment Chat — fhirworx</title>
|
<title>Comment Chat — fhirworx</title>
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="//dashboard.fhirworx.io/fav32.png">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700&family=Source+Serif+4:ital,wght@0,400;0,600;1,400&family=JetBrains+Mono:wght@500&display=swap">
|
||||||
<style>
|
<style>
|
||||||
|
/* HTI-5 editorial palette — matches the fhirworx platform theme. */
|
||||||
:root {
|
:root {
|
||||||
--bg: #0f1115; --panel: #171a21; --line: #262b36; --fg: #e6e9ef;
|
--background: #F7F5F0; --foreground: #1A1A18; --card: #FAFAF7;
|
||||||
--muted: #8b93a7; --accent: #6ea8fe; --user: #1f6feb22; --bot: #171a21;
|
--primary: #1C2B3A; --primary-fg: #F7F5F0; --secondary: #EDEBE6;
|
||||||
|
--muted-fg: #6B6B68; --border: #D4D0C8; --accent: #2E3D8F;
|
||||||
|
--destructive: #C0392B;
|
||||||
|
--font-display: "Playfair Display", Georgia, serif;
|
||||||
|
--font-body: "Source Serif 4", Georgia, serif;
|
||||||
|
--font-mono: "JetBrains Mono", "Fira Code", monospace;
|
||||||
}
|
}
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
body {
|
body {
|
||||||
margin: 0; font: 15px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif;
|
margin: 0; height: 100vh; display: flex; flex-direction: column;
|
||||||
color: var(--fg); background: var(--bg); height: 100vh; display: flex;
|
background: var(--background); color: var(--foreground);
|
||||||
flex-direction: column;
|
font-family: var(--font-body); font-size: 16px; line-height: 1.55;
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
padding: 12px 18px; border-bottom: 1px solid var(--line);
|
flex: 0 0 auto; background: var(--primary); color: var(--primary-fg);
|
||||||
display: flex; align-items: baseline; gap: 12px; flex: 0 0 auto;
|
border-bottom: 3px solid var(--foreground);
|
||||||
|
padding: 14px 22px; display: flex; align-items: baseline; gap: 14px;
|
||||||
|
}
|
||||||
|
header h1 {
|
||||||
|
font-family: var(--font-display); font-weight: 700; font-size: 20px; margin: 0;
|
||||||
|
}
|
||||||
|
header .sub { font-size: 13px; opacity: .7; }
|
||||||
|
header .who {
|
||||||
|
margin-left: auto; font-family: var(--font-mono); font-size: 11px;
|
||||||
|
text-transform: uppercase; letter-spacing: .08em; opacity: .8;
|
||||||
}
|
}
|
||||||
header h1 { font-size: 15px; margin: 0; font-weight: 600; }
|
|
||||||
header .who { color: var(--muted); font-size: 13px; margin-left: auto; }
|
|
||||||
#log {
|
#log {
|
||||||
flex: 1 1 auto; overflow-y: auto; padding: 20px; display: flex;
|
flex: 1 1 auto; overflow-y: auto; padding: 26px 22px;
|
||||||
flex-direction: column; gap: 16px; max-width: 820px; width: 100%;
|
display: flex; flex-direction: column; gap: 22px;
|
||||||
margin: 0 auto;
|
max-width: 860px; width: 100%; margin: 0 auto;
|
||||||
}
|
}
|
||||||
.msg { display: flex; flex-direction: column; gap: 6px; }
|
.msg { display: flex; flex-direction: column; gap: 7px; }
|
||||||
.msg .role { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
|
.msg .role {
|
||||||
.bubble { padding: 12px 14px; border-radius: 10px; white-space: pre-wrap; word-wrap: break-word; }
|
font-family: var(--font-mono); font-size: 10px; letter-spacing: .12em;
|
||||||
.user .bubble { background: var(--user); align-self: flex-end; max-width: 80%; }
|
text-transform: uppercase; color: var(--muted-fg);
|
||||||
.bot .bubble { background: var(--bot); border: 1px solid var(--line); }
|
}
|
||||||
.sources { margin-top: 4px; font-size: 13px; }
|
.bubble {
|
||||||
.sources summary { cursor: pointer; color: var(--muted); }
|
padding: 14px 16px; white-space: pre-wrap; word-wrap: break-word;
|
||||||
.src { border-left: 2px solid var(--line); padding: 4px 10px; margin: 8px 0; color: var(--muted); }
|
border: 1px solid var(--border); border-radius: 3px;
|
||||||
.src b { color: var(--accent); font-weight: 600; }
|
}
|
||||||
.err { color: #ff7b72; }
|
.user { align-items: flex-end; }
|
||||||
|
.user .role { text-align: right; }
|
||||||
|
.user .bubble {
|
||||||
|
background: var(--primary); color: var(--primary-fg);
|
||||||
|
border-color: var(--primary); max-width: 82%;
|
||||||
|
}
|
||||||
|
.bot .bubble { background: var(--card); }
|
||||||
|
/* Model-cited [ID] tokens rendered as editorial small-caps mono. */
|
||||||
|
.bot .bubble .cite {
|
||||||
|
font-family: var(--font-mono); font-size: .82em; color: var(--accent);
|
||||||
|
background: var(--secondary); padding: 1px 5px; border-radius: 2px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
details.sources { margin-top: 2px; font-size: 14px; }
|
||||||
|
details.sources > summary {
|
||||||
|
cursor: pointer; color: var(--muted-fg); font-family: var(--font-mono);
|
||||||
|
font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
details.sources > summary::-webkit-details-marker { display: none; }
|
||||||
|
details.sources > summary::before { content: "▸ "; }
|
||||||
|
details.sources[open] > summary::before { content: "▾ "; }
|
||||||
|
.src {
|
||||||
|
border-left: 3px solid var(--border); padding: 6px 12px; margin: 10px 0;
|
||||||
|
color: var(--muted-fg);
|
||||||
|
}
|
||||||
|
.src b { color: var(--accent); font-family: var(--font-mono); font-size: .8em; font-weight: 500; }
|
||||||
|
.err { color: var(--destructive); border-color: var(--destructive); }
|
||||||
form {
|
form {
|
||||||
flex: 0 0 auto; border-top: 1px solid var(--line); padding: 14px 18px;
|
flex: 0 0 auto; border-top: 3px solid var(--foreground); background: var(--secondary);
|
||||||
display: flex; gap: 10px; max-width: 820px; width: 100%; margin: 0 auto;
|
padding: 16px 22px; display: flex; gap: 12px;
|
||||||
|
max-width: 860px; width: 100%; margin: 0 auto;
|
||||||
}
|
}
|
||||||
textarea {
|
textarea {
|
||||||
flex: 1; resize: none; height: 44px; padding: 11px 12px; border-radius: 8px;
|
flex: 1; resize: none; height: 46px; padding: 12px 13px;
|
||||||
border: 1px solid var(--line); background: var(--panel); color: var(--fg);
|
font-family: var(--font-body); font-size: 16px;
|
||||||
font: inherit;
|
background: var(--card); color: var(--foreground);
|
||||||
|
border: 1px solid var(--border); border-radius: 3px;
|
||||||
|
}
|
||||||
|
textarea:focus {
|
||||||
|
outline: none; border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 22%, transparent);
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
padding: 0 18px; border: 0; border-radius: 8px; background: var(--accent);
|
padding: 0 22px; border: 1px solid var(--primary); border-radius: 3px;
|
||||||
color: #06090f; font-weight: 600; cursor: pointer;
|
background: var(--primary); color: var(--primary-fg);
|
||||||
|
font-family: var(--font-body); font-size: 15px; font-weight: 600; cursor: pointer;
|
||||||
|
transition: background .15s ease;
|
||||||
|
}
|
||||||
|
button:hover:not(:disabled) { background: #253748; }
|
||||||
|
button:disabled { opacity: .45; cursor: default; }
|
||||||
|
.hint {
|
||||||
|
margin: auto; max-width: 500px; text-align: center; color: var(--muted-fg);
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
button:disabled { opacity: .5; cursor: default; }
|
|
||||||
.hint { color: var(--muted); text-align: center; margin: auto; max-width: 460px; }
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1>Comment Chat</h1>
|
<h1>Comment Chat</h1>
|
||||||
|
<span class="sub">grounded in indexed CMS rulemaking comments</span>
|
||||||
<span class="who" id="who"></span>
|
<span class="who" id="who"></span>
|
||||||
</header>
|
</header>
|
||||||
<div id="log">
|
<div id="log">
|
||||||
<div class="hint">Ask a question about the indexed CMS rulemaking comments.
|
<div class="hint">Ask a question about the indexed CMS rulemaking comments.
|
||||||
Answers are grounded in the comments and cite their ids.</div>
|
Answers are drawn only from the comments and cite their ids.</div>
|
||||||
</div>
|
</div>
|
||||||
<form id="form">
|
<form id="form">
|
||||||
<textarea id="q" placeholder="e.g. What do commenters say about telehealth originating sites?" autofocus></textarea>
|
<textarea id="q" placeholder="e.g. What do commenters say about telehealth originating sites?" autofocus></textarea>
|
||||||
@@ -74,7 +132,7 @@
|
|||||||
const send = document.getElementById('send');
|
const send = document.getElementById('send');
|
||||||
|
|
||||||
fetch('whoami').then(r => r.json()).then(d => {
|
fetch('whoami').then(r => r.json()).then(d => {
|
||||||
if (d.user) document.getElementById('who').textContent = 'signed in as ' + d.user;
|
if (d.user) document.getElementById('who').textContent = d.user;
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
|
||||||
function bubble(role) {
|
function bubble(role) {
|
||||||
@@ -91,6 +149,21 @@
|
|||||||
return { wrap, b };
|
return { wrap, b };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render answer text, wrapping [CMS-...] citations in styled spans.
|
||||||
|
function paint(el, text) {
|
||||||
|
el.innerHTML = '';
|
||||||
|
const re = /\[([A-Z0-9-]+)\]/g;
|
||||||
|
let last = 0, m;
|
||||||
|
while ((m = re.exec(text)) !== null) {
|
||||||
|
if (m.index > last) el.appendChild(document.createTextNode(text.slice(last, m.index)));
|
||||||
|
const c = document.createElement('span');
|
||||||
|
c.className = 'cite'; c.textContent = m[0];
|
||||||
|
el.appendChild(c);
|
||||||
|
last = m.index + m[0].length;
|
||||||
|
}
|
||||||
|
if (last < text.length) el.appendChild(document.createTextNode(text.slice(last)));
|
||||||
|
}
|
||||||
|
|
||||||
function renderSources(wrap, sources) {
|
function renderSources(wrap, sources) {
|
||||||
if (!sources || !sources.length) return;
|
if (!sources || !sources.length) return;
|
||||||
const d = document.createElement('details');
|
const d = document.createElement('details');
|
||||||
@@ -116,6 +189,7 @@
|
|||||||
if (hint) hint.remove();
|
if (hint) hint.remove();
|
||||||
bubble('user').b.textContent = question;
|
bubble('user').b.textContent = question;
|
||||||
const { wrap, b } = bubble('bot');
|
const { wrap, b } = bubble('bot');
|
||||||
|
let answer = '';
|
||||||
send.disabled = true;
|
send.disabled = true;
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('chat', {
|
const resp = await fetch('chat', {
|
||||||
@@ -137,12 +211,12 @@
|
|||||||
const line = part.replace(/^data: /, '').trim();
|
const line = part.replace(/^data: /, '').trim();
|
||||||
if (!line) continue;
|
if (!line) continue;
|
||||||
const ev = JSON.parse(line);
|
const ev = JSON.parse(line);
|
||||||
if (ev.type === 'token') { b.textContent += ev.text; log.scrollTop = log.scrollHeight; }
|
if (ev.type === 'token') { answer += ev.text; paint(b, answer); log.scrollTop = log.scrollHeight; }
|
||||||
else if (ev.type === 'sources') renderSources(wrap, ev.sources);
|
else if (ev.type === 'sources') renderSources(wrap, ev.sources);
|
||||||
else if (ev.type === 'error') { b.className = 'bubble err'; b.textContent = 'Error: ' + ev.message; }
|
else if (ev.type === 'error') { b.className = 'bubble err'; b.textContent = 'Error: ' + ev.message; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!b.textContent) b.textContent = '(no answer)';
|
if (!answer && b.className === 'bubble') b.textContent = '(no answer)';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
b.className = 'bubble err';
|
b.className = 'bubble err';
|
||||||
b.textContent = 'Error: ' + e.message;
|
b.textContent = 'Error: ' + e.message;
|
||||||
|
|||||||
Reference in New Issue
Block a user