{status.map((s) => (
diff --git a/src/styles/datascreen.css b/src/styles/datascreen.css
index d64e018..9ea1e66 100644
--- a/src/styles/datascreen.css
+++ b/src/styles/datascreen.css
@@ -108,6 +108,8 @@
letter-spacing: 0.03em;
color: var(--bd-ink);
white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.bd-header-title-accent {
color: #fc8318;
@@ -2696,10 +2698,13 @@
color: #9fb0c6;
}
-/* 页眉开发署名(跟在左侧标题后,上下居中) */
+/* 页眉开发署名(© 图标 + 公司名 + 开发;空间不足自动折叠为两行) */
.bd-credit {
flex: none;
- font-size: 15px;
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 13px;
color: var(--bd-sub, #7d8fa9);
white-space: nowrap;
letter-spacing: 0.02em;
@@ -2707,7 +2712,47 @@
padding: 0 6px;
border-left: 1px solid rgba(47, 107, 255, 0.18);
margin-left: 4px;
- line-height: 1;
+ line-height: 1.2;
+}
+.bd-credit .appica-ico {
+ flex-shrink: 0;
+ color: var(--bd-blue);
+ opacity: 0.7;
+}
+.bd-credit-text {
+ display: inline-flex;
+ align-items: baseline;
+ gap: 4px;
+ min-width: 0;
+}
+.bd-credit-name {
+ white-space: nowrap;
+}
+.bd-credit-dev {
+ color: #9fb0c6;
+ white-space: nowrap;
+}
+/* 空间不足(窄屏):折叠为两行 —— 公司名 / 开发 */
+@media (max-width: 1560px) {
+ .bd-credit {
+ font-size: 12px;
+ gap: 4px;
+ }
+ .bd-credit-text {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 1px;
+ line-height: 1.15;
+ }
+ .bd-credit-dev {
+ font-size: 11px;
+ }
+}
+/* 更窄:仅保留公司名,隐藏「开发」 */
+@media (max-width: 1180px) {
+ .bd-credit-dev {
+ display: none;
+ }
}
/* ============ 真实数据面板补充样式 ============ */
diff --git a/src/voice/markdown.jsx b/src/voice/markdown.jsx
new file mode 100644
index 0000000..4829b4e
--- /dev/null
+++ b/src/voice/markdown.jsx
@@ -0,0 +1,156 @@
+/* =========================================================
+ 技能:Markdown 格式化 / 可视化渲染器(问问小园回复专用)
+ —— 将 LLM 返回的 md 文本解析为美观的富文本结构
+ ---------------------------------------------------------
+ 支持:标题 · 无序/有序列表 · 表格 · 加粗 · 行内代码
+ 引用 · 分隔线 · 代码块 · 段落
+ 返回 React 节点(美观 UI 结构)
+ ========================================================= */
+
+/* 行内解析:加粗、行内代码 */
+export function inlineMd(text) {
+ const out = [];
+ // 先保行内代码,再处理加粗
+ const codeRe = /`([^`]+)`/g;
+ const boldRe = /\*\*([^*]+)\*\*/g;
+ const chunks = [];
+ let last = 0;
+ let coll = '';
+ // 拆分行内代码与加粗
+ let segs = [];
+ const parts = text.split(/(`[^`]+`|\*\*[^*]+\*\*)/g);
+ for (const p of parts) {
+ if (!p) continue;
+ if (p.startsWith('`') && p.endsWith('`')) {
+ segs.push({ type: 'code', text: p.slice(1, -1) });
+ } else if (p.startsWith('**') && p.endsWith('**')) {
+ segs.push({ type: 'bold', text: p.slice(2, -2) });
+ } else {
+ segs.push({ type: 'text', text: p });
+ }
+ }
+ return segs.map((s, i) => {
+ if (s.type === 'code') return
{s.text};
+ if (s.type === 'bold') return
{s.text};
+ return
{s.text};
+ });
+}
+
+/* 表格解析:文本行数组 → 表头/行 */
+function parseTable(rows) {
+ const cells = rows.map((r) => r.split('|').map((c) => c.trim()).filter((c, i, a) => !(i === 0 && c === '' && a.length > 2) && !(i === a.length - 1 && c === '')));
+ // 去掉全空/分隔线行
+ return cells.filter((r) => !(r.length === 1 && /^[-:]+$/.test(r[0])));
+}
+
+/* 将整段 md 解析为结构行(AST 风格数组) */
+export function parseMarkdown(mdText) {
+ const lines = String(mdText || '').split('\n');
+ const nodes = [];
+ let i = 0;
+ let inCode = false;
+ let codeBuf = [];
+ let tableBuf = [];
+
+ const flushCode = () => {
+ if (codeBuf.length) { nodes.push({ type: 'codeblock', text: codeBuf.join('\n') }); codeBuf = []; }
+ };
+ const flushTable = () => {
+ if (tableBuf.length >= 2) {
+ const tbl = parseTable(tableBuf);
+ if (tbl.length >= 2) nodes.push({ type: 'table', rows: tbl });
+ }
+ tableBuf = [];
+ };
+
+ for (; i < lines.length; i++) {
+ const line = lines[i];
+ const t = line.trim();
+
+ // 代码块
+ if (t.startsWith('```')) {
+ if (inCode) { inCode = false; flushCode(); }
+ else { inCode = true; codeBuf = []; }
+ continue;
+ }
+ if (inCode) { codeBuf.push(line); continue; }
+
+ // 表格(连续 | 开头的行)
+ if (t.startsWith('|')) { tableBuf.push(t); continue; }
+ flushTable();
+
+ // 空行
+ if (!t) continue;
+
+ // 标题
+ const hm = t.match(/^(#{1,4})\s+(.*)$/);
+ if (hm) { nodes.push({ type: 'heading', level: hm[1].length, text: hm[2] }); continue; }
+
+ // 分隔线
+ if (/^(-{3,}|\*{3,}|_{3,})$/.test(t)) { nodes.push({ type: 'hr' }); continue; }
+
+ // 引用
+ if (t.startsWith('>')) { nodes.push({ type: 'quote', text: t.replace(/^>\s?/, '') }); continue; }
+
+ // 无序列表
+ const um = t.match(/^[-*+]\s+(.*)$/);
+ if (um) { nodes.push({ type: 'bullet', text: um[1] }); continue; }
+
+ // 有序列表
+ const om = t.match(/^\d+[.)]\s+(.*)$/);
+ if (om) { nodes.push({ type: 'ordered', text: om[1] }); continue; }
+
+ // 普通段落
+ nodes.push({ type: 'paragraph', text: line });
+ }
+ flushCode();
+ flushTable();
+ return nodes;
+}
+
+/* 渲染为 React 节点(美观 UI) */
+export function renderMarkdown(mdText) {
+ const nodes = parseMarkdown(mdText);
+ const key = { current: 0 };
+ const nk = () => key.current++;
+ return (
+
+ {nodes.map((n) => {
+ const k = nk();
+ switch (n.type) {
+ case 'heading':
+ if (n.level === 1) return
{inlineMd(n.text)}
;
+ if (n.level === 2) return
{inlineMd(n.text)}
;
+ return
{inlineMd(n.text)}
;
+ case 'paragraph':
+ return
{inlineMd(n.text)}
;
+ case 'bullet':
+ return
{inlineMd(n.text)}
;
+ case 'ordered':
+ return
{inlineMd(n.text)}
;
+ case 'quote':
+ return
{inlineMd(n.text)}
;
+ case 'hr':
+ return
;
+ case 'codeblock':
+ return
{n.text}
;
+ case 'table':
+ return (
+
+
+ {n.rows[0].map((c, ci) => | {inlineMd(c)} | )}
+
+
+ {n.rows.slice(1).map((r, ri) => (
+ {r.map((c, ci) => | {inlineMd(c)} | )}
+ ))}
+
+
+ );
+ default:
+ return null;
+ }
+ })}
+
+ );
+}