Files
Arcrun/landing/app/mira/wiki/[pageName]/page.tsx
T
Leo 519423cb0d feat(arcrun): mira wiki page with tag filter + accumulated WIP
- landing/app/mira/wiki: tag=mira-wiki list now shows all wiki paragraphs
  (depends on KBDB tag filter exposed in matrix/kbdb commit, separate repo)
- landing: app/mira hub + feed split + various WIP from prior sessions
- registry/components: claude_api / kbdb_create_block / kbdb_get / km_writer /
  platform_crypto / auth_oauth2 contracts + main.go (accumulated)
- .component-builds: pkg-lock updates + index.ts adjustments (WIP)
- .agents/specs/arcrun/frontend-redesign: design notes
- docs/test_credentials, docs/user_requirements/arcrun-landing-page: WIP docs
- cypher-executor: auth-dispatcher / wasi-shim adjustments (WIP)

Includes accumulated work from prior sessions plus the wiki UI tag-filter
update that surfaces the AI-generated wiki paragraphs at /mira/wiki.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 16:52:01 +08:00

166 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
export const runtime = 'edge';
// Mira Wiki 單篇頁
// SDD: polaris/mira/.agents/specs/mira-app/design.md §5.2 + §3.5.7
// 對應 task: 7C.2
// 路由:/mira/wiki/[pageName]
// 顯示單一 wiki block + 它的 childrenwiki-paragraph
import { useEffect, useState, use } from 'react';
import Link from 'next/link';
import { MarkdownView } from '../../_shared/markdown';
import '../../mira.css';
const KBDB_BASE = 'https://kbdb.finally.click';
const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? 'https://cypher.arcrun.dev';
type Block = {
id: string;
page_name: string;
content: string;
type: string;
parent_id: string | null;
tags_json: string | null;
created_at: number;
updated_at: number;
};
export default function WikiPagePage({
params,
}: {
params: Promise<{ pageName: string }>;
}) {
const { pageName } = use(params);
const decodedName = decodeURIComponent(pageName);
const [block, setBlock] = useState<Block | null>(null);
const [siblings, setSiblings] = useState<Block[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
async function load() {
try {
const meRes = await fetch(`${API_BASE}/me`, { credentials: 'include' });
if (!meRes.ok) throw new Error('未登入');
const me = (await meRes.json()) as { api_key: string };
const headers = { Authorization: `Bearer ${me.api_key}` };
const res = await fetch(
`${KBDB_BASE}/blocks?page_name=${encodeURIComponent(decodedName)}&limit=1`,
{ headers },
);
if (!res.ok) throw new Error(`KBDB ${res.status}`);
const data = await res.json();
const found: Block | undefined = data.blocks?.[0];
if (cancelled) return;
if (!found) {
setError(`找不到 wiki page${decodedName}`);
return;
}
setBlock(found);
// 若這是個 child block,撈 parent 下其他 siblings 給導航用
if (found.parent_id) {
// KBDB 沒 children endpoint,用 page_name 找不到 siblings;先略過
setSiblings([]);
}
} catch (e: any) {
if (!cancelled) setError(e?.message ?? 'load failed');
} finally {
if (!cancelled) setLoading(false);
}
}
load();
return () => {
cancelled = true;
};
}, [decodedName]);
const tags = parseTags(block?.tags_json);
const subtype = tags
.find((t) => t.startsWith('subtype:'))
?.replace('subtype:', '');
return (
<main className="mira-app">
<div className="mira-content">
<header style={{ padding: '24px 0 16px', borderBottom: '1px solid #2a2a2a' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 4 }}>
<Link
href="/mira/wiki"
style={{ color: '#888', fontSize: 14, textDecoration: 'none' }}
>
Wiki 索引
</Link>
</div>
<h1 style={{ fontSize: 24, fontWeight: 700, color: '#fff', margin: 0 }}>
{decodedName}
</h1>
{subtype && (
<div style={{ marginTop: 6 }}>
<span
style={{
display: 'inline-block',
padding: '2px 8px',
fontSize: 11,
background: '#2a2a3a',
color: '#aab',
borderRadius: 3,
}}
>
subtype: {subtype}
</span>
</div>
)}
</header>
{loading && <div style={{ padding: 24, color: '#666' }}>載入中⋯</div>}
{error && (
<div style={{ padding: 24, color: '#e66' }}>{error}</div>
)}
{block && !loading && !error && (
<>
<article style={{ padding: '20px 0' }}>
<MarkdownView text={block.content} />
</article>
{/* 7C.3 contribution log placeholder(此頁是 schema/index/log infra 而非 wiki-page,先不顯示) */}
<footer
style={{
padding: '20px 0',
borderTop: '1px solid #1f1f1f',
color: '#555',
fontSize: 12,
}}
>
<div>id: <span style={{ fontFamily: 'monospace' }}>{block.id}</span></div>
<div>type: {block.type}</div>
{block.parent_id && (
<div>
parent: <span style={{ fontFamily: 'monospace' }}>{block.parent_id}</span>
</div>
)}
{tags.length > 0 && <div>tags: {tags.join(', ')}</div>}
<div>updated: {new Date(block.updated_at * 1000).toLocaleString('zh-TW')}</div>
</footer>
</>
)}
</div>
</main>
);
}
function parseTags(tags_json: string | null | undefined): string[] {
if (!tags_json) return [];
try {
return JSON.parse(tags_json) as string[];
} catch {
return [];
}
}