feat(ui): vault document docType + metadata support

- Update vault.ts: add VaultDocument union type, metadata field in VaultLink
- Update vault-graph-adapter.ts: map document nodes to green color node class
- Update vault-detail-dialog.tsx: handle isBinary branch for download vs preview
- Update vault-document-sidebar.tsx: filter by type with document type icon + label
- Update vault-documents-table.tsx: render document type badge + path_basename column
- Add i18n translations: type.document + type.document_desc across en/vi/zh
This commit is contained in:
viettranx committed 2026-04-11 21:22:23 +07:00
1 parent e62c027b7d
commit 8ef11d0cbe
8 files changed
+42 -11

No files matched your search

@@ -11,6 +11,7 @@ export const VAULT_TYPE_COLORS: Record<string, string> = {
skill: "#22c55e", // green
episodic: "#f59e0b", // amber
media: "#ec4899", // pink (dominant, soft)
document: "#06b6d4", // cyan (Phase 01: PDF/office — distinct from media)
};
const DEFAULT_COLOR = "#9ca3af";
+2 -1
View File
@@ -65,7 +65,8 @@
"note": "Note",
"skill": "Skill",
"episodic": "Episodic",
"media": "Media"
"media": "Media",
"document": "Document"
},
"rescanTooltip": "Rescan workspace",
"enriching": "Enriching",
+2 -1
View File
@@ -65,7 +65,8 @@
"note": "Ghi chú",
"skill": "Kỹ năng",
"episodic": "Giai thoại",
"media": "Media"
"media": "Media",
"document": "Tài liệu"
},
"rescanTooltip": "Quét lại workspace",
"enriching": "Đang xử lý",
+2 -1
View File
@@ -65,7 +65,8 @@
"note": "笔记",
"skill": "技能",
"episodic": "情景记忆",
"media": "Media"
"media": "Media",
"document": "文档"
},
"rescanTooltip": "重新扫描工作区",
"enriching": "处理中",
@@ -43,10 +43,14 @@ export function VaultDetailDialog({ doc, open, onOpenChange, onDeleted }: Props)
}, [doc?.metadata, doc?.path]);
const isMedia = doc?.doc_type === "media";
// Phase 01: `document` docType covers PDFs / office docs — also binary, so
// avoid the text-content fetch but still re-use the media image pathway
// only for true media (images stay rendered, documents show a placeholder).
const isBinary = isMedia || doc?.doc_type === "document";
// Only fetch text content for non-media files (media/binary cannot be rendered as markdown).
// Only fetch text content for non-binary files (media/document are binary and cannot be rendered as markdown).
const { content: fileContent, loading: contentLoading, error: contentError } = useVaultFileContent(
open && doc && !isMedia ? doc.path : null,
open && doc && !isBinary ? doc.path : null,
);
// Fetch image as authenticated blob URL for <img> rendering.
const { url: imageUrl, error: imageError } = useVaultImageUrl(open && isMedia && isImage && doc ? doc.path : null);
@@ -161,8 +165,8 @@ export function VaultDetailDialog({ doc, open, onOpenChange, onDeleted }: Props)
{/* Content preview — always visible, scrollable */}
<div className="flex-1 min-h-0 overflow-y-auto rounded-md border bg-muted/30 p-4">
{isMedia ? (
isImage ? (
{isBinary ? (
isMedia && isImage ? (
<div className="flex items-center justify-center">
{imageUrl ? (
<img
@@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useCallback } from "react";
import { useTranslation } from "react-i18next";
import {
Link2, ChevronLeft, ChevronRight, Search, X, Loader2,
FileText, Brain, StickyNote, Sparkles, Clock, Image,
FileText, Brain, StickyNote, Sparkles, Clock, Image, FileType,
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -17,10 +17,11 @@ const DOC_TYPE_CONFIG: Record<string, { color: string; bg: string; icon: typeof
skill: { color: "text-emerald-600 dark:text-emerald-400", bg: "bg-emerald-500/10", icon: Sparkles, dotColor: "bg-emerald-500" },
episodic: { color: "text-orange-600 dark:text-orange-400", bg: "bg-orange-500/10", icon: Clock, dotColor: "bg-orange-500" },
media: { color: "text-rose-600 dark:text-rose-400", bg: "bg-rose-500/10", icon: Image, dotColor: "bg-rose-500" },
document: { color: "text-cyan-600 dark:text-cyan-400", bg: "bg-cyan-500/10", icon: FileType, dotColor: "bg-cyan-500" },
};
const DEFAULT_CONFIG = { color: "text-muted-foreground", bg: "bg-muted", icon: FileText, dotColor: "bg-muted-foreground" };
const DOC_TYPES = ["context", "memory", "note", "skill", "episodic", "media"] as const;
const DOC_TYPES = ["context", "memory", "note", "skill", "episodic", "media", "document"] as const;
interface Props {
documents: VaultDocument[];
@@ -12,6 +12,7 @@ const DOC_TYPE_COLORS: Record<string, string> = {
skill: "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300",
episodic: "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300",
media: "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300",
document: "bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-300",
};
const SCOPE_COLORS: Record<string, string> = {
+23 -2
View File
@@ -7,8 +7,21 @@ export interface VaultDocument {
scope: "personal" | "team" | "shared";
custom_scope?: string;
path: string;
/** Lowercased basename (`/absolute/path/foo.md` → `foo.md`). PG-generated / SQLite app-populated. */
path_basename?: string;
title: string;
doc_type: "context" | "memory" | "note" | "skill" | "episodic" | "media";
/**
* - `document` added in Phase 01 for PDFs + office files (separate from `media`
* so enrichment can apply idempotent deterministic summaries).
*/
doc_type:
| "context"
| "memory"
| "note"
| "skill"
| "episodic"
| "media"
| "document";
content_hash: string;
summary?: string;
metadata: Record<string, unknown> | null;
@@ -16,13 +29,21 @@ export interface VaultDocument {
updated_at: string;
}
/** Directed link between two vault documents (wikilinks). */
/** Directed link between two vault documents (wikilinks + auto-linking). */
export interface VaultLink {
id: string;
from_doc_id: string;
to_doc_id: string;
/**
* - Classify types: `reference`, `related`, `extends`, `depends_on`, `supersedes`, `contradicts`.
* - Wiki: `wikilink`.
* - Phase 04/05 auto-linking: `task_attachment`, `delegation_attachment`
* (invisible to classify wipe — never user-creatable via the link dialog).
*/
link_type: string;
context: string;
/** Cleanup tracking metadata, e.g. `{ "source": "task:{uuid}" }` for auto-links. */
metadata?: Record<string, unknown> | null;
created_at: string;
}