feat(web): 字體系統升級 + NemoClaw SVG 還原 + Knowledge Base 設計文件
- 字體:Syne (標題) + DM Mono (內文) + VT323 (品牌點陣),替換 Inter - Tailwind: fontFamily 更新 + 5 層文字色彩 token (primary→disabled) - Sidebar: NemoClaw 白瓷龍蝦爪 SVG + AWOOOI 用 VT323 放大 - OpenClaw Panel: 還原 NemoClaw 3D 白瓷龍蝦爪 (替換 NemoNodeAnimation) - Knowledge Base 設計文件 (B分離/A K8s Job/Phase1跳過向量搜尋) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { Inter, JetBrains_Mono, VT323 } from 'next/font/google'
|
||||
import { Syne, DM_Mono, JetBrains_Mono, VT323 } from 'next/font/google'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { NextIntlClientProvider } from 'next-intl'
|
||||
import { getMessages } from 'next-intl/server'
|
||||
@@ -8,17 +8,26 @@ import '../globals.css'
|
||||
import { Providers } from '../providers'
|
||||
import { AutoHealingErrorBoundary } from '@/components/shared/auto-healing-error-boundary'
|
||||
|
||||
const inter = Inter({
|
||||
// Syne - 標題 / Display(接近 Nothing Ndot 顯示感)
|
||||
const syne = Syne({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-inter',
|
||||
variable: '--font-heading',
|
||||
})
|
||||
|
||||
// DM Mono - 內文 / 標籤 / UI 文字(乾淨等寬)
|
||||
const dmMono = DM_Mono({
|
||||
weight: ['300', '400', '500'],
|
||||
subsets: ['latin'],
|
||||
variable: '--font-body',
|
||||
})
|
||||
|
||||
// JetBrains Mono - 程式碼 / Terminal 專用
|
||||
const jetbrainsMono = JetBrains_Mono({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-mono',
|
||||
})
|
||||
|
||||
// VT323 點陣字體 - 品牌與 AI 狀態專用
|
||||
// VT323 - 點陣像素字體,品牌 AWOOOI 專用
|
||||
const vt323 = VT323({
|
||||
weight: '400',
|
||||
subsets: ['latin'],
|
||||
@@ -61,7 +70,7 @@ export default async function LocaleLayout({
|
||||
return (
|
||||
<html lang={locale}>
|
||||
<body
|
||||
className={`${inter.variable} ${jetbrainsMono.variable} ${vt323.variable} font-body bg-nothing-gray-50 text-nothing-black antialiased`}
|
||||
className={`${syne.variable} ${dmMono.variable} ${jetbrainsMono.variable} ${vt323.variable} font-body bg-nothing-gray-50 text-nothing-black antialiased`}
|
||||
>
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
<AutoHealingErrorBoundary fallbackMessage="Critical Application Error">
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Sparkles } from 'lucide-react'
|
||||
import { NemoNodeAnimation } from './nemo-node-animation'
|
||||
|
||||
// =============================================================================
|
||||
// Types
|
||||
@@ -37,6 +35,87 @@ export interface OpenClawPanelProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// NemoClaw 3D Ceramic SVG Component (Lab-White Style)
|
||||
// =============================================================================
|
||||
|
||||
function NemoClaw({ isActive, isPulsing }: { isActive: boolean; isPulsing: boolean }) {
|
||||
return (
|
||||
<svg viewBox="0 0 140 140" className="w-full h-full drop-shadow-lg" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="nc-ceramic3d" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stopColor="#FFFFFF" />
|
||||
<stop offset="40%" stopColor="#F8F8F8" />
|
||||
<stop offset="70%" stopColor="#E8E8E8" />
|
||||
<stop offset="100%" stopColor="#D8D8D8" />
|
||||
</linearGradient>
|
||||
<filter id="nc-coreGlow" x="-100%" y="-100%" width="300%" height="300%">
|
||||
<feGaussianBlur stdDeviation="6" result="blur" />
|
||||
<feComposite in="SourceGraphic" in2="blur" operator="over" />
|
||||
</filter>
|
||||
<filter id="nc-pulseGlow" x="-100%" y="-100%" width="300%" height="300%">
|
||||
<feGaussianBlur stdDeviation="8" result="blur">
|
||||
<animate attributeName="stdDeviation" values="6;10;6" dur="1.5s" repeatCount="indefinite" />
|
||||
</feGaussianBlur>
|
||||
<feComposite in="SourceGraphic" in2="blur" operator="over" />
|
||||
</filter>
|
||||
<filter id="nc-shadow3d" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="2" dy="4" stdDeviation="3" floodOpacity="0.15" />
|
||||
</filter>
|
||||
</defs>
|
||||
{/* Base shadow */}
|
||||
<ellipse cx="70" cy="125" rx="35" ry="8" fill="rgba(0,0,0,0.08)" />
|
||||
{/* Main body - 3D ceramic sphere */}
|
||||
<circle cx="70" cy="70" r="32" fill="url(#nc-ceramic3d)" filter="url(#nc-shadow3d)" stroke="#E0E0E0" strokeWidth="1" />
|
||||
{/* Inner ring */}
|
||||
<circle cx="70" cy="70" r="26" fill="none" stroke="#D0D0D0" strokeWidth="1" opacity="0.5" />
|
||||
{/* Core LED */}
|
||||
<circle cx="70" cy="70" r="16" fill={isActive ? '#4A90D9' : '#B0B0B0'} filter={isPulsing ? 'url(#nc-pulseGlow)' : 'url(#nc-coreGlow)'} className="transition-all duration-500">
|
||||
{isPulsing && <animate attributeName="r" values="14;17;14" dur="1s" repeatCount="indefinite" />}
|
||||
</circle>
|
||||
<circle cx="70" cy="70" r="8" fill="white" opacity={isActive ? 0.9 : 0.4} className="transition-opacity duration-300" />
|
||||
{/* Top arm */}
|
||||
<g filter="url(#nc-shadow3d)">
|
||||
<path d="M 70 38 L 70 18 L 58 6 M 70 18 L 82 6" stroke="url(#nc-ceramic3d)" strokeWidth="6" strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
<path d="M 70 38 L 70 18 L 58 6 M 70 18 L 82 6" stroke={isActive ? '#4A90D9' : '#C0C0C0'} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" fill="none" opacity="0.6" />
|
||||
<circle cx="58" cy="6" r="4" fill="url(#nc-ceramic3d)" stroke={isActive ? '#4A90D9' : '#D0D0D0'} strokeWidth="1.5" />
|
||||
<circle cx="82" cy="6" r="4" fill="url(#nc-ceramic3d)" stroke={isActive ? '#4A90D9' : '#D0D0D0'} strokeWidth="1.5" />
|
||||
</g>
|
||||
{/* Left arm */}
|
||||
<g filter="url(#nc-shadow3d)">
|
||||
<path d="M 38 70 L 18 70 L 6 58 M 18 70 L 6 82" stroke="url(#nc-ceramic3d)" strokeWidth="6" strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
<path d="M 38 70 L 18 70 L 6 58 M 18 70 L 6 82" stroke={isActive ? '#4A90D9' : '#C0C0C0'} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" fill="none" opacity="0.6" />
|
||||
<circle cx="6" cy="58" r="4" fill="url(#nc-ceramic3d)" stroke={isActive ? '#4A90D9' : '#D0D0D0'} strokeWidth="1.5" />
|
||||
<circle cx="6" cy="82" r="4" fill="url(#nc-ceramic3d)" stroke={isActive ? '#4A90D9' : '#D0D0D0'} strokeWidth="1.5" />
|
||||
</g>
|
||||
{/* Right arm */}
|
||||
<g filter="url(#nc-shadow3d)">
|
||||
<path d="M 102 70 L 122 70 L 134 58 M 122 70 L 134 82" stroke="url(#nc-ceramic3d)" strokeWidth="6" strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
<path d="M 102 70 L 122 70 L 134 58 M 122 70 L 134 82" stroke={isActive ? '#4A90D9' : '#C0C0C0'} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" fill="none" opacity="0.6" />
|
||||
<circle cx="134" cy="58" r="4" fill="url(#nc-ceramic3d)" stroke={isActive ? '#4A90D9' : '#D0D0D0'} strokeWidth="1.5" />
|
||||
<circle cx="134" cy="82" r="4" fill="url(#nc-ceramic3d)" stroke={isActive ? '#4A90D9' : '#D0D0D0'} strokeWidth="1.5" />
|
||||
</g>
|
||||
{/* Bottom left arm */}
|
||||
<g filter="url(#nc-shadow3d)">
|
||||
<path d="M 48 92 L 28 112 L 16 116" stroke="url(#nc-ceramic3d)" strokeWidth="6" strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
<path d="M 48 92 L 28 112 L 16 116" stroke={isActive ? '#4A90D9' : '#C0C0C0'} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" fill="none" opacity="0.6" />
|
||||
<circle cx="16" cy="116" r="4" fill="url(#nc-ceramic3d)" stroke={isActive ? '#4A90D9' : '#D0D0D0'} strokeWidth="1.5" />
|
||||
</g>
|
||||
{/* Bottom right arm */}
|
||||
<g filter="url(#nc-shadow3d)">
|
||||
<path d="M 92 92 L 112 112 L 124 116" stroke="url(#nc-ceramic3d)" strokeWidth="6" strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||
<path d="M 92 92 L 112 112 L 124 116" stroke={isActive ? '#4A90D9' : '#C0C0C0'} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" fill="none" opacity="0.6" />
|
||||
<circle cx="124" cy="116" r="4" fill="url(#nc-ceramic3d)" stroke={isActive ? '#4A90D9' : '#D0D0D0'} strokeWidth="1.5" />
|
||||
</g>
|
||||
{/* Orbit ring when active */}
|
||||
{isActive && (
|
||||
<circle cx="70" cy="70" r="42" fill="none" stroke="#4A90D9" strokeWidth="1" strokeDasharray="6 6" opacity="0.4"
|
||||
className="animate-spin" style={{ animationDuration: '8s', transformOrigin: '70px 70px' }} />
|
||||
)}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Status Messages (Dot Matrix Style)
|
||||
// =============================================================================
|
||||
@@ -146,9 +225,11 @@ export function OpenClawPanel({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* NemoClaw 截圖風格動畫 */}
|
||||
{/* NemoClaw 3D Ceramic Visualization */}
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, padding: '10px 12px' }}>
|
||||
<NemoNodeAnimation size={72} />
|
||||
<div className="relative w-20 h-20 flex-shrink-0">
|
||||
<NemoClaw isActive={isActive} isPulsing={isPulsing} />
|
||||
</div>
|
||||
|
||||
{/* 右側文字 */}
|
||||
<div style={{ flex: 1 }}>
|
||||
|
||||
@@ -168,18 +168,54 @@ export function Sidebar({
|
||||
)}
|
||||
style={{ zIndex: Z_INDEX.SIDEBAR }}
|
||||
>
|
||||
{/* Logo 區 - 極簡化 */}
|
||||
{/* Logo 區 - NemoClaw + 品牌名稱 (VT323 點陣字體) */}
|
||||
<div className={cn(
|
||||
'flex items-center border-b-[0.5px] border-neutral-200',
|
||||
'transition-all duration-300',
|
||||
collapsed ? 'h-16 justify-center px-2' : 'h-16 px-4'
|
||||
collapsed ? 'h-16 justify-center px-2' : 'h-16 px-4 gap-3'
|
||||
)}>
|
||||
{collapsed ? (
|
||||
<span className="font-mono text-lg font-bold text-neutral-900 tracking-tighter">
|
||||
A
|
||||
</span>
|
||||
) : (
|
||||
<span className="font-mono text-sm font-bold text-neutral-900 tracking-widest uppercase">
|
||||
{/* NemoClaw Mini SVG */}
|
||||
<div style={{ width: 36, height: 36, flexShrink: 0 }}>
|
||||
<svg width="36" height="36" viewBox="0 0 140 140" className="drop-shadow-sm" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="sb-ceramic" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stopColor="#FFFFFF" />
|
||||
<stop offset="40%" stopColor="#F8F8F8" />
|
||||
<stop offset="70%" stopColor="#E8E8E8" />
|
||||
<stop offset="100%" stopColor="#D8D8D8" />
|
||||
</linearGradient>
|
||||
<radialGradient id="sb-led" cx="40%" cy="35%" r="60%">
|
||||
<stop offset="0%" stopColor="#7AB8F5" />
|
||||
<stop offset="100%" stopColor="#2B6CB0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
{/* Body */}
|
||||
<circle cx="70" cy="70" r="32" fill="url(#sb-ceramic)" stroke="#E0E0E0" strokeWidth="1" />
|
||||
<circle cx="70" cy="70" r="16" fill="url(#sb-led)">
|
||||
<animate attributeName="r" values="14;17;14" dur="2s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="70" cy="70" r="8" fill="white" opacity="0.8" />
|
||||
{/* Top arm */}
|
||||
<path d="M 70 38 L 70 18 L 58 6 M 70 18 L 82 6" stroke="url(#sb-ceramic)" strokeWidth="6" strokeLinecap="round" fill="none" />
|
||||
<path d="M 70 38 L 70 18 L 58 6 M 70 18 L 82 6" stroke="#4A90D9" strokeWidth="3" strokeLinecap="round" fill="none" opacity="0.5" />
|
||||
{/* Left arm */}
|
||||
<path d="M 38 70 L 18 70 L 6 58 M 18 70 L 6 82" stroke="url(#sb-ceramic)" strokeWidth="6" strokeLinecap="round" fill="none" />
|
||||
<path d="M 38 70 L 18 70 L 6 58 M 18 70 L 6 82" stroke="#4A90D9" strokeWidth="3" strokeLinecap="round" fill="none" opacity="0.5" />
|
||||
{/* Right arm */}
|
||||
<path d="M 102 70 L 122 70 L 134 58 M 122 70 L 134 82" stroke="url(#sb-ceramic)" strokeWidth="6" strokeLinecap="round" fill="none" />
|
||||
<path d="M 102 70 L 122 70 L 134 58 M 122 70 L 134 82" stroke="#4A90D9" strokeWidth="3" strokeLinecap="round" fill="none" opacity="0.5" />
|
||||
{/* Bottom arms */}
|
||||
<path d="M 48 92 L 28 112 L 16 116" stroke="url(#sb-ceramic)" strokeWidth="6" strokeLinecap="round" fill="none" />
|
||||
<path d="M 92 92 L 112 112 L 124 116" stroke="url(#sb-ceramic)" strokeWidth="6" strokeLinecap="round" fill="none" />
|
||||
{/* Orbit ring */}
|
||||
<circle cx="70" cy="70" r="42" fill="none" stroke="#4A90D9" strokeWidth="1" strokeDasharray="6 6" opacity="0.3">
|
||||
<animateTransform attributeName="transform" type="rotate" from="0 70 70" to="360 70 70" dur="8s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
</svg>
|
||||
</div>
|
||||
{/* Brand name - VT323 點陣字體 (hidden when collapsed) */}
|
||||
{!collapsed && (
|
||||
<span className="font-dot-matrix text-xl text-neutral-900 tracking-widest uppercase">
|
||||
{tBrand('name')}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -59,18 +59,24 @@ const config: Config = {
|
||||
|
||||
// ==================== Typography ====================
|
||||
fontFamily: {
|
||||
display: ['var(--font-mono)', 'JetBrains Mono', 'monospace'],
|
||||
heading: ['Inter', 'system-ui', 'sans-serif'],
|
||||
body: ['Inter', 'system-ui', 'sans-serif'],
|
||||
mono: ['var(--font-mono)', 'JetBrains Mono', 'Fira Code', 'monospace'],
|
||||
heading: ['var(--font-heading)', 'Syne', 'system-ui', 'sans-serif'],
|
||||
body: ['var(--font-body)', 'DM Mono', 'monospace'],
|
||||
display: ['var(--font-heading)', 'Syne', 'system-ui', 'sans-serif'],
|
||||
mono: ['var(--font-mono)', 'JetBrains Mono', 'monospace'],
|
||||
// DSEG7 點陣字體 - 用於核心指標數字顯示
|
||||
'dot-matrix': ['DSEG7-Classic', 'var(--font-mono)', 'monospace'],
|
||||
},
|
||||
|
||||
// Nothing.tech 高對比墨水色
|
||||
// Nothing.tech 文字層級系統
|
||||
textColor: {
|
||||
ink: '#111111',
|
||||
'ink-secondary': '#525252',
|
||||
// 5層文字層級
|
||||
primary: '#111111', // 主標題、最重要資訊
|
||||
secondary: '#333333', // 大/中標題、正文
|
||||
tertiary: '#525252', // 小標題、說明文字
|
||||
muted: '#737373', // 輔助標籤、時間戳
|
||||
disabled: '#A3A3A3', // 停用狀態
|
||||
},
|
||||
|
||||
fontSize: {
|
||||
|
||||
185
docs/superpowers/specs/2026-04-02-knowledge-base-design.md
Normal file
185
docs/superpowers/specs/2026-04-02-knowledge-base-design.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# Knowledge Base 設計文件
|
||||
|
||||
> **版本**: v1.0
|
||||
> **日期**: 2026-04-02
|
||||
> **作者**: ogt + Claude Code
|
||||
> **狀態**: 已批准,待實作
|
||||
|
||||
---
|
||||
|
||||
## 背景與目標
|
||||
|
||||
打造 AWOOOI 知識庫系統,讓工程師與 AI 管理人員可查閱 SRE 知識,同時作為 OpenClaw 決策的 Context 來源。
|
||||
|
||||
**已確認決策**:
|
||||
- 使用者:工程師 + AI 管理人員
|
||||
- 知識來源:AI 自動萃取為主,人工審核補充
|
||||
- 用途:人工查閱 + AI 自動引用(兩者兼備)
|
||||
- 資料結構:兩層架構(底層知識條目 + Playbook 為特殊關聯)
|
||||
- UI:左側分類導航 + 頂部搜尋 + 右側詳情
|
||||
|
||||
---
|
||||
|
||||
## 架構決策
|
||||
|
||||
| 項目 | 決策 | 理由 |
|
||||
|------|------|------|
|
||||
| Playbook 邊界 | **分離**,用 `related_playbook_id` 關聯 | 執行語意 ≠ 查閱知識,職責不同 |
|
||||
| Migration | **K8s Job**(沿用現有方式)| 4 個 table,Alembic 是過度工程 |
|
||||
| Vector Search | **Phase 1 跳過**,關鍵字 + tag | Phase 2 再評估 pgvector |
|
||||
|
||||
---
|
||||
|
||||
## 資料模型
|
||||
|
||||
### KnowledgeEntry(新 PostgreSQL table)
|
||||
|
||||
```sql
|
||||
CREATE TABLE knowledge_entries (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
title VARCHAR(255) NOT NULL,
|
||||
content TEXT NOT NULL, -- Markdown
|
||||
entry_type VARCHAR(50) NOT NULL, -- 'incident_case' | 'runbook' | 'best_practice' | 'postmortem'
|
||||
category VARCHAR(100) NOT NULL, -- 對應左側分類樹
|
||||
tags JSONB DEFAULT '[]', -- string[]
|
||||
source VARCHAR(20) NOT NULL, -- 'ai_extracted' | 'human'
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'draft', -- 'draft' | 'review' | 'approved'
|
||||
related_incident_id UUID REFERENCES incidents(id) ON DELETE SET NULL,
|
||||
related_playbook_id VARCHAR(255), -- Playbook Redis key(非 FK)
|
||||
view_count INTEGER DEFAULT 0,
|
||||
created_by VARCHAR(100),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_knowledge_entries_type ON knowledge_entries(entry_type);
|
||||
CREATE INDEX idx_knowledge_entries_category ON knowledge_entries(category);
|
||||
CREATE INDEX idx_knowledge_entries_status ON knowledge_entries(status);
|
||||
```
|
||||
|
||||
### entry_type 分類
|
||||
|
||||
| type | 說明 |
|
||||
|------|------|
|
||||
| `incident_case` | AI 從 Incident 萃取的案例分析 |
|
||||
| `runbook` | 手動建立的操作手冊 |
|
||||
| `best_practice` | 最佳實踐文章 |
|
||||
| `postmortem` | 事後分析報告 |
|
||||
|
||||
### category 分類樹(左側導航)
|
||||
|
||||
```
|
||||
全部
|
||||
├── 基礎設施
|
||||
│ ├── K8s / K3s
|
||||
│ ├── 資料庫
|
||||
│ └── 網路
|
||||
├── 應用層
|
||||
│ ├── API 服務
|
||||
│ └── 前端
|
||||
├── AI 系統
|
||||
│ ├── OpenClaw
|
||||
│ └── Playbook
|
||||
└── 安全 / 合規
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API 設計
|
||||
|
||||
Base path: `/api/v1/knowledge`
|
||||
|
||||
| Method | Path | 說明 |
|
||||
|--------|------|------|
|
||||
| GET | `/` | 列表(支援 `category`, `type`, `status`, `q` 搜尋, `tags` 篩選)|
|
||||
| GET | `/{id}` | 單筆(同時 view_count +1)|
|
||||
| POST | `/` | 建立新條目 |
|
||||
| PATCH | `/{id}` | 更新(含狀態流轉)|
|
||||
| POST | `/{id}/approve` | 審核通過(status: draft/review → approved)|
|
||||
| DELETE | `/{id}` | 軟刪除(status → archived)|
|
||||
| GET | `/categories` | 取得分類樹(含各類數量)|
|
||||
| GET | `/search` | 關鍵字搜尋(title + content + tags)|
|
||||
|
||||
---
|
||||
|
||||
## 後端分層架構
|
||||
|
||||
遵循 leWOOOgo 積木化原則:
|
||||
|
||||
```
|
||||
apps/api/src/
|
||||
├── db/models.py # 新增 KnowledgeEntry ORM class
|
||||
├── models/knowledge.py # Pydantic Schema (Request/Response)
|
||||
├── repositories/
|
||||
│ ├── interfaces.py # 新增 IKnowledgeRepository Protocol
|
||||
│ └── knowledge_repository.py # PostgreSQL CRUD 實作
|
||||
├── services/
|
||||
│ └── knowledge_service.py # 業務邏輯(搜尋、狀態流轉、view_count)
|
||||
└── api/v1/
|
||||
└── knowledge.py # Router,prefix /knowledge
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 前端設計
|
||||
|
||||
### 頁面結構
|
||||
|
||||
```
|
||||
/knowledge-base
|
||||
├── 左側分類導航 (w-52, 固定)
|
||||
│ ├── 全部 (N)
|
||||
│ ├── 基礎設施 (N)
|
||||
│ ├── 應用層 (N)
|
||||
│ ├── AI 系統 (N)
|
||||
│ └── 安全/合規 (N)
|
||||
├── 主區域
|
||||
│ ├── 頂部搜尋欄 + 篩選 (type / source / status)
|
||||
│ ├── 結果列表
|
||||
│ │ └── KnowledgeCard (title, type badge, tags, view_count, 相關 Playbook)
|
||||
│ └── 空態
|
||||
└── 右側詳情 Panel (抽屜式,點擊條目開啟)
|
||||
├── 標題 + meta
|
||||
├── Markdown 渲染
|
||||
├── 相關 Playbook 連結
|
||||
└── 審核操作(approve / 回 review)
|
||||
```
|
||||
|
||||
### KnowledgeCard
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ [type badge] 標題文字 👁 12 │
|
||||
│ category · source · 2026-04-01 │
|
||||
│ [tag] [tag] [tag] │
|
||||
│ 🔗 相關 Playbook: restart-api-server │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 狀態標示
|
||||
|
||||
| status | 顯示 | 顏色 |
|
||||
|--------|------|------|
|
||||
| `draft` | 草稿 | gray |
|
||||
| `review` | 審核中 | warning(黃)|
|
||||
| `approved` | 已批准 | healthy(綠)|
|
||||
|
||||
---
|
||||
|
||||
## Migration K8s Job
|
||||
|
||||
參考 `k8s/jobs/migrate-phase18-audit-logs.yaml`,建立:
|
||||
|
||||
```
|
||||
k8s/jobs/migrate-knowledge-entries.yaml
|
||||
```
|
||||
|
||||
執行 `CREATE TABLE knowledge_entries ...` + index。
|
||||
|
||||
---
|
||||
|
||||
## Phase 2(未來)
|
||||
|
||||
- pgvector 語意搜尋
|
||||
- OpenClaw 在評估 Incident 時自動 RAG 查詢 Knowledge Base
|
||||
- Embedding 自動化(建立 entry 時觸發)
|
||||
Reference in New Issue
Block a user