Expand the frontend with brain, graph, and chat workspace updates so the new backend orchestration and memory features have matching screens. These changes also wire the new APIs into routing and add focused view and routing tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1657 lines
38 KiB
Vue
1657 lines
38 KiB
Vue
<script setup lang="ts">
|
||
import {
|
||
ArrowLeft,
|
||
Database,
|
||
FileText,
|
||
Folder,
|
||
FolderPlus,
|
||
Loader,
|
||
Trash2,
|
||
Upload,
|
||
X,
|
||
} from 'lucide-vue-next'
|
||
import { useKnowledgeView } from '@/pages/knowledge/composables/useKnowledgeView'
|
||
|
||
const {
|
||
documents,
|
||
currentFolderId,
|
||
uploadError,
|
||
uploadSuccess,
|
||
highlightedDocumentId,
|
||
uploadInput,
|
||
showNewFolderDialog,
|
||
newFolderName,
|
||
newFolderParentId,
|
||
showRenameDialog,
|
||
renameFolderName,
|
||
showDeleteDialog,
|
||
deletingFolder,
|
||
showDocumentDialog,
|
||
activeDocument,
|
||
activeDocumentContent,
|
||
isLoadingDocumentContent,
|
||
activeDocumentChunks,
|
||
isLoadingDocumentChunks,
|
||
chunkDrafts,
|
||
chunkEditing,
|
||
chunkSaving,
|
||
chunkEditError,
|
||
isRoot,
|
||
visibleFolders,
|
||
breadcrumbs,
|
||
explorerTitle,
|
||
enterFolder,
|
||
goToFolder,
|
||
goBack,
|
||
triggerUpload,
|
||
handleUpload,
|
||
handleDeleteDocument,
|
||
openNewFolderDialog,
|
||
createFolder,
|
||
openRenameDialog,
|
||
renameFolder,
|
||
openDeleteDialog,
|
||
deleteFolder,
|
||
openDocument,
|
||
closeDocumentDialog,
|
||
startChunkEdit,
|
||
cancelChunkEdit,
|
||
saveChunkEdit,
|
||
getFileTypeColor,
|
||
formatFileSize,
|
||
formatDate,
|
||
getStatusLabel,
|
||
} = useKnowledgeView()
|
||
</script>
|
||
|
||
<template>
|
||
<div class="knowledge-view grid-bg scanlines">
|
||
<div class="page-header">
|
||
<div class="header-left">
|
||
<div class="header-icon"><Database :size="20" /></div>
|
||
<div class="header-text">
|
||
<h1>KNOWLEDGE BASE</h1>
|
||
<span class="header-sub">{{ explorerTitle }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="header-actions">
|
||
<button class="btn" @click="openNewFolderDialog(currentFolderId)">
|
||
<FolderPlus :size="14" />
|
||
{{ isRoot ? '新建文件夹' : '新建子文件夹' }}
|
||
</button>
|
||
<button v-if="!isRoot" class="btn primary" @click="triggerUpload">
|
||
<Upload :size="14" /> 上传文件
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<input
|
||
ref="uploadInput"
|
||
type="file"
|
||
class="hidden-upload"
|
||
accept=".pdf,.md,.txt,.doc,.docx,.csv,.xlsx"
|
||
@change="handleUpload"
|
||
/>
|
||
|
||
<div class="explorer-shell holo-card">
|
||
<div class="toolbar">
|
||
<div class="toolbar-left">
|
||
<button class="nav-btn" :disabled="isRoot" @click="goBack">
|
||
<ArrowLeft :size="14" />
|
||
</button>
|
||
<div class="breadcrumbs">
|
||
<button
|
||
v-for="(crumb, index) in breadcrumbs"
|
||
:key="`${crumb.id ?? 'root'}-${index}`"
|
||
class="breadcrumb-item"
|
||
:class="{ active: crumb.id === currentFolderId || (crumb.id === null && isRoot) }"
|
||
@click="goToFolder(crumb.id)"
|
||
>
|
||
{{ crumb.name }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="toolbar-right">
|
||
<span class="location-tag">{{ isRoot ? 'ROOT' : 'FOLDER' }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="uploadError" class="upload-error">
|
||
{{ uploadError }}
|
||
</div>
|
||
<div v-if="uploadSuccess" class="upload-success">
|
||
{{ uploadSuccess }}
|
||
</div>
|
||
|
||
<div class="explorer-grid">
|
||
<template v-if="visibleFolders.length || documents.length">
|
||
<button
|
||
v-for="folder in visibleFolders"
|
||
:key="folder.id"
|
||
class="explorer-tile folder-tile"
|
||
@click="enterFolder(folder)"
|
||
>
|
||
<div class="folder-activate-flash"></div>
|
||
<div class="tile-frame"></div>
|
||
<div class="folder-tech-corners">
|
||
<span class="corner corner-tl"></span>
|
||
<span class="corner corner-tr"></span>
|
||
<span class="corner corner-bl"></span>
|
||
<span class="corner corner-br"></span>
|
||
</div>
|
||
<div class="folder-scan"></div>
|
||
<div class="folder-grid-lines"></div>
|
||
<div class="tile-actions" @click.stop>
|
||
<button class="icon-btn" title="重命名文件夹" @click="openRenameDialog(folder)">
|
||
<FileText :size="12" />
|
||
</button>
|
||
<button class="icon-btn danger" title="删除文件夹" @click="openDeleteDialog(folder)">
|
||
<Trash2 :size="12" />
|
||
</button>
|
||
</div>
|
||
<div class="folder-glyph">
|
||
<div class="folder-pulse-ring"></div>
|
||
<div class="folder-core">
|
||
<Folder :size="42" />
|
||
</div>
|
||
<span class="folder-beam beam-a"></span>
|
||
<span class="folder-beam beam-b"></span>
|
||
</div>
|
||
<div class="folder-label-bar">
|
||
<div class="tile-name folder-title-name">{{ folder.name }}</div>
|
||
</div>
|
||
<div class="tile-meta folder-meta">{{ folder.children?.length ?? 0 }} 个子文件夹</div>
|
||
</button>
|
||
|
||
<button
|
||
v-for="doc in documents"
|
||
:key="doc.id"
|
||
class="explorer-tile file-tile"
|
||
:class="{ 'upload-highlight': highlightedDocumentId === doc.id }"
|
||
@click="openDocument(doc)"
|
||
>
|
||
<div class="tile-frame"></div>
|
||
<div class="tile-actions" @click.stop>
|
||
<button class="icon-btn danger" title="删除文件" @click="handleDeleteDocument(doc.id)">
|
||
<Trash2 :size="12" />
|
||
</button>
|
||
</div>
|
||
<div class="file-badge" :style="{ color: getFileTypeColor(doc.file_type), borderColor: `${getFileTypeColor(doc.file_type)}55` }">
|
||
{{ doc.file_type.toUpperCase() }}
|
||
</div>
|
||
<div class="file-icon" :style="{ color: getFileTypeColor(doc.file_type) }">
|
||
<FileText :size="38" />
|
||
</div>
|
||
<div class="tile-name">{{ doc.title }}</div>
|
||
<div class="tile-meta">{{ formatFileSize(doc.file_size) }} · {{ formatDate(doc.created_at) }}</div>
|
||
<div class="tile-status-row">
|
||
<div class="tile-status" :class="(doc.ingestion_status ?? (doc.is_indexed ? 'ready' : 'uploaded')).toLowerCase()">
|
||
{{ getStatusLabel(doc.ingestion_status, doc.is_indexed) }}
|
||
</div>
|
||
<Loader
|
||
v-if="['uploaded', 'parsing', 'indexing'].includes(doc.ingestion_status ?? (doc.is_indexed ? 'ready' : 'uploaded'))"
|
||
:size="12"
|
||
class="spin tile-inline-loader"
|
||
/>
|
||
</div>
|
||
<div v-if="doc.ingestion_error" class="tile-warning">{{ doc.ingestion_error }}</div>
|
||
</button>
|
||
</template>
|
||
|
||
<div v-else class="empty-state">
|
||
<div class="empty-core-shell">
|
||
<div class="empty-orbit orbit-outer"></div>
|
||
<div class="empty-orbit orbit-inner"></div>
|
||
<div class="empty-scanline"></div>
|
||
<div class="empty-core-glow"></div>
|
||
<div class="empty-folder-chamber">
|
||
<div class="empty-folder-plate"></div>
|
||
<div class="empty-folder-pulse"></div>
|
||
<div class="empty-folder-icon-wrap">
|
||
<Folder :size="54" />
|
||
</div>
|
||
<span class="empty-beam beam-left"></span>
|
||
<span class="empty-beam beam-right"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="empty-copy">
|
||
<span class="empty-kicker">{{ isRoot ? 'ARCHIVE CORE ONLINE' : 'FOLDER CHAMBER STANDBY' }}</span>
|
||
<span class="empty-title">{{ isRoot ? 'ROOT DIRECTORY EMPTY' : 'FOLDER IS READY FOR DEPLOYMENT' }}</span>
|
||
<span class="empty-sub">
|
||
{{ isRoot ? '当前知识库尚未初始化目录结构,请先创建第一个文件夹。' : '当前目录为空,可在此新建子文件夹或上传文件。' }}
|
||
</span>
|
||
</div>
|
||
|
||
<div class="empty-actions">
|
||
<button class="btn primary empty-cta" @click="openNewFolderDialog(currentFolderId)">
|
||
<FolderPlus :size="14" />
|
||
{{ isRoot ? '初始化知识仓库' : '新建子文件夹' }}
|
||
</button>
|
||
<button v-if="!isRoot" class="btn" @click="triggerUpload">
|
||
<Upload :size="14" /> 上传文件
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="showNewFolderDialog" class="dialog-overlay" @click.self="showNewFolderDialog = false">
|
||
<div class="dialog">
|
||
<div class="dialog-header">
|
||
<h3>{{ isRoot && newFolderParentId === null ? '新建文件夹' : '新建子文件夹' }}</h3>
|
||
<button class="close-btn" @click="showNewFolderDialog = false">
|
||
<X :size="16" />
|
||
</button>
|
||
</div>
|
||
<input
|
||
v-model="newFolderName"
|
||
class="dialog-input"
|
||
placeholder="文件夹名称"
|
||
@keyup.enter="createFolder"
|
||
autofocus
|
||
/>
|
||
<div class="dialog-actions">
|
||
<button class="btn" @click="showNewFolderDialog = false">取消</button>
|
||
<button class="btn primary" @click="createFolder">创建</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="showRenameDialog" class="dialog-overlay" @click.self="showRenameDialog = false">
|
||
<div class="dialog">
|
||
<div class="dialog-header">
|
||
<h3>重命名文件夹</h3>
|
||
<button class="close-btn" @click="showRenameDialog = false">
|
||
<X :size="16" />
|
||
</button>
|
||
</div>
|
||
<input
|
||
v-model="renameFolderName"
|
||
class="dialog-input"
|
||
placeholder="文件夹名称"
|
||
@keyup.enter="renameFolder"
|
||
autofocus
|
||
/>
|
||
<div class="dialog-actions">
|
||
<button class="btn" @click="showRenameDialog = false">取消</button>
|
||
<button class="btn primary" @click="renameFolder">重命名</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="showDeleteDialog" class="dialog-overlay" @click.self="showDeleteDialog = false">
|
||
<div class="dialog">
|
||
<div class="dialog-header">
|
||
<h3>删除文件夹</h3>
|
||
<button class="close-btn" @click="showDeleteDialog = false">
|
||
<X :size="16" />
|
||
</button>
|
||
</div>
|
||
<p class="dialog-body">
|
||
确定要删除文件夹「{{ deletingFolder?.name }}」吗?该操作不可恢复。
|
||
</p>
|
||
<div class="dialog-actions">
|
||
<button class="btn" @click="showDeleteDialog = false">取消</button>
|
||
<button class="btn danger" @click="deleteFolder">删除</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="showDocumentDialog && activeDocument" class="dialog-overlay" @click.self="closeDocumentDialog()">
|
||
<div class="dialog document-dialog">
|
||
<div class="dialog-header">
|
||
<h3>{{ activeDocument.title }}</h3>
|
||
<button class="close-btn" @click="closeDocumentDialog()">
|
||
<X :size="16" />
|
||
</button>
|
||
</div>
|
||
|
||
<div class="document-info-row">
|
||
<span class="file-badge" :style="{ color: getFileTypeColor(activeDocument.file_type), borderColor: `${getFileTypeColor(activeDocument.file_type)}55` }">
|
||
{{ activeDocument.file_type.toUpperCase() }}
|
||
</span>
|
||
<span>{{ formatFileSize(activeDocument.file_size) }}</span>
|
||
<span class="doc-chunk-count">{{ activeDocument.chunk_count }} 个知识切片</span>
|
||
<span class="doc-status-pill">{{ getStatusLabel(activeDocument.ingestion_status, activeDocument.is_indexed) }}</span>
|
||
</div>
|
||
<div v-if="activeDocument.ingestion_error" class="upload-error">
|
||
{{ activeDocument.ingestion_error }}
|
||
</div>
|
||
|
||
<div class="document-content-grid">
|
||
<div class="document-preview">
|
||
<div v-if="isLoadingDocumentContent" class="preview-loading">
|
||
<Loader :size="16" class="spin" />
|
||
<span>加载文档内容中...</span>
|
||
</div>
|
||
<pre v-else>{{ activeDocumentContent || '暂无可预览内容。' }}</pre>
|
||
</div>
|
||
|
||
<div class="chunk-panel">
|
||
<div class="chunk-panel-header">
|
||
<div>
|
||
<div class="chunk-panel-title">知识切片</div>
|
||
<div class="chunk-panel-subtitle">当前已加载 {{ activeDocumentChunks.length }} / {{ activeDocument.chunk_count }} 个切片</div>
|
||
</div>
|
||
<span class="chunk-count-badge">{{ activeDocumentChunks.length }}</span>
|
||
</div>
|
||
|
||
<div v-if="isLoadingDocumentChunks" class="chunk-loading-state">
|
||
<div class="chunk-loading-bar"></div>
|
||
<span>正在读取切片内容...</span>
|
||
</div>
|
||
|
||
<div v-else-if="activeDocumentChunks.length" class="chunk-list">
|
||
<div
|
||
v-for="chunk in activeDocumentChunks"
|
||
:key="chunk.id"
|
||
class="chunk-card"
|
||
:class="{ editing: chunkEditing[chunk.id] }"
|
||
>
|
||
<div class="chunk-card-header">
|
||
<div class="chunk-card-meta">
|
||
<span class="chunk-index">切片 #{{ chunk.chunk_index + 1 }}</span>
|
||
<span class="chunk-size">{{ chunk.content.length }} 字符</span>
|
||
<span v-if="chunk.metadata_" class="chunk-meta-raw">{{ chunk.metadata_ }}</span>
|
||
</div>
|
||
<button
|
||
v-if="!chunkEditing[chunk.id]"
|
||
class="btn"
|
||
@click="startChunkEdit(chunk)"
|
||
>
|
||
编辑
|
||
</button>
|
||
</div>
|
||
|
||
<pre v-if="!chunkEditing[chunk.id]" class="chunk-content">{{ chunk.content }}</pre>
|
||
|
||
<div v-else class="chunk-edit-form">
|
||
<textarea
|
||
v-model="chunkDrafts[chunk.id]"
|
||
class="chunk-textarea"
|
||
rows="7"
|
||
></textarea>
|
||
<div v-if="chunkEditError[chunk.id]" class="upload-error chunk-error">
|
||
{{ chunkEditError[chunk.id] }}
|
||
</div>
|
||
<div class="chunk-actions">
|
||
<button class="btn" :disabled="chunkSaving[chunk.id]" @click="cancelChunkEdit(chunk.id)">
|
||
取消
|
||
</button>
|
||
<button class="btn primary" :disabled="chunkSaving[chunk.id]" @click="saveChunkEdit(chunk.id)">
|
||
<Loader v-if="chunkSaving[chunk.id]" :size="12" class="spin" />
|
||
{{ chunkSaving[chunk.id] ? '保存中' : '保存' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else class="chunk-empty">暂无切片数据。</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.knowledge-view {
|
||
height: 100%;
|
||
overflow-y: auto;
|
||
padding: 24px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
}
|
||
|
||
.page-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
}
|
||
|
||
.header-icon {
|
||
color: var(--accent-cyan);
|
||
filter: drop-shadow(0 0 8px var(--accent-cyan));
|
||
}
|
||
|
||
h1 {
|
||
font-family: var(--font-display);
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.15em;
|
||
color: var(--text-primary);
|
||
margin: 0;
|
||
}
|
||
|
||
.header-sub {
|
||
font-family: var(--font-mono);
|
||
font-size: 10px;
|
||
color: var(--text-dim);
|
||
letter-spacing: 0.1em;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.hidden-upload {
|
||
display: none;
|
||
}
|
||
|
||
.explorer-shell {
|
||
min-height: 620px;
|
||
padding: 18px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 18px;
|
||
}
|
||
|
||
.toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
padding: 14px 16px;
|
||
background: linear-gradient(180deg, rgba(0, 245, 212, 0.06), rgba(0, 245, 212, 0.02));
|
||
border: 1px solid var(--border-dim);
|
||
border-radius: var(--radius-lg);
|
||
}
|
||
|
||
.toolbar-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
|
||
.nav-btn {
|
||
width: 34px;
|
||
height: 34px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(0, 245, 212, 0.08);
|
||
border: 1px solid var(--border-mid);
|
||
border-radius: var(--radius-md);
|
||
color: var(--accent-cyan);
|
||
}
|
||
|
||
.breadcrumbs {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.breadcrumb-item {
|
||
padding: 6px 10px;
|
||
border-radius: 999px;
|
||
background: transparent;
|
||
border: 1px solid transparent;
|
||
color: var(--text-secondary);
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.breadcrumb-item:hover,
|
||
.breadcrumb-item.active {
|
||
color: var(--accent-cyan);
|
||
border-color: var(--border-mid);
|
||
background: rgba(0, 245, 212, 0.08);
|
||
}
|
||
|
||
.location-tag {
|
||
padding: 4px 10px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--border-mid);
|
||
color: var(--accent-cyan);
|
||
background: rgba(0, 245, 212, 0.08);
|
||
font-family: var(--font-display);
|
||
font-size: 10px;
|
||
letter-spacing: 0.12em;
|
||
}
|
||
|
||
.upload-error,
|
||
.upload-success {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 10px 14px;
|
||
border-radius: var(--radius-md);
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.upload-error {
|
||
color: var(--accent-red);
|
||
background: rgba(255, 71, 87, 0.08);
|
||
border: 1px solid rgba(255, 71, 87, 0.2);
|
||
}
|
||
|
||
.upload-success {
|
||
color: var(--accent-green);
|
||
background: rgba(52, 211, 153, 0.08);
|
||
border: 1px solid rgba(52, 211, 153, 0.24);
|
||
}
|
||
|
||
.explorer-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
|
||
gap: 18px;
|
||
align-content: start;
|
||
}
|
||
|
||
.explorer-tile {
|
||
position: relative;
|
||
min-height: 220px;
|
||
padding: 18px 16px 16px;
|
||
border-radius: 20px;
|
||
border: 1px solid var(--border-dim);
|
||
background:
|
||
radial-gradient(circle at top, rgba(0, 245, 212, 0.08), transparent 45%),
|
||
linear-gradient(180deg, rgba(10, 15, 26, 0.92), rgba(13, 21, 37, 0.98));
|
||
color: var(--text-primary);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 10px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.explorer-tile:hover {
|
||
border-color: var(--border-bright);
|
||
box-shadow: var(--glow-cyan);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.upload-highlight {
|
||
border-color: rgba(52, 211, 153, 0.8);
|
||
box-shadow: 0 0 0 1px rgba(52, 211, 153, 0.35), 0 0 28px rgba(52, 211, 153, 0.25);
|
||
}
|
||
|
||
.tile-frame {
|
||
position: absolute;
|
||
inset: 10px;
|
||
border: 1px solid rgba(0, 245, 212, 0.08);
|
||
border-radius: 16px;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.tile-actions {
|
||
position: absolute;
|
||
top: 12px;
|
||
right: 12px;
|
||
display: flex;
|
||
gap: 6px;
|
||
opacity: 0;
|
||
transition: opacity var(--transition-fast);
|
||
}
|
||
|
||
.explorer-tile:hover .tile-actions {
|
||
opacity: 1;
|
||
}
|
||
|
||
.icon-btn {
|
||
width: 26px;
|
||
height: 26px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 8px;
|
||
background: rgba(0, 9, 19, 0.78);
|
||
border: 1px solid var(--border-dim);
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.icon-btn:hover {
|
||
color: var(--accent-cyan);
|
||
border-color: var(--border-mid);
|
||
background: rgba(0, 245, 212, 0.1);
|
||
}
|
||
|
||
.icon-btn.danger:hover {
|
||
color: var(--accent-red);
|
||
border-color: rgba(255, 71, 87, 0.3);
|
||
background: rgba(255, 71, 87, 0.12);
|
||
}
|
||
|
||
.folder-tile {
|
||
text-align: center;
|
||
}
|
||
|
||
.folder-activate-flash,
|
||
.folder-tech-corners,
|
||
.folder-scan,
|
||
.folder-grid-lines {
|
||
position: absolute;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.folder-activate-flash {
|
||
opacity: 0;
|
||
background: radial-gradient(circle at center, rgba(124, 230, 255, 0.34), rgba(0, 245, 212, 0.14) 35%, transparent 68%);
|
||
}
|
||
|
||
.folder-tech-corners {
|
||
z-index: 1;
|
||
}
|
||
|
||
.folder-tech-corners .corner {
|
||
position: absolute;
|
||
width: 26px;
|
||
height: 26px;
|
||
border-color: rgba(0, 245, 212, 0.22);
|
||
border-style: solid;
|
||
opacity: 0.75;
|
||
transition: all var(--transition-mid);
|
||
}
|
||
|
||
.folder-tech-corners .corner-tl {
|
||
top: 9px;
|
||
left: 9px;
|
||
border-width: 2px 0 0 2px;
|
||
border-top-left-radius: 10px;
|
||
}
|
||
|
||
.folder-tech-corners .corner-tr {
|
||
top: 9px;
|
||
right: 9px;
|
||
border-width: 2px 2px 0 0;
|
||
border-top-right-radius: 10px;
|
||
}
|
||
|
||
.folder-tech-corners .corner-bl {
|
||
bottom: 9px;
|
||
left: 9px;
|
||
border-width: 0 0 2px 2px;
|
||
border-bottom-left-radius: 10px;
|
||
}
|
||
|
||
.folder-tech-corners .corner-br {
|
||
bottom: 9px;
|
||
right: 9px;
|
||
border-width: 0 2px 2px 0;
|
||
border-bottom-right-radius: 10px;
|
||
}
|
||
|
||
.folder-scan,
|
||
.folder-grid-lines {
|
||
opacity: 0;
|
||
transition: opacity var(--transition-mid);
|
||
}
|
||
|
||
.folder-scan {
|
||
background: linear-gradient(
|
||
115deg,
|
||
transparent 24%,
|
||
rgba(0, 245, 212, 0.04) 40%,
|
||
rgba(124, 230, 255, 0.18) 50%,
|
||
rgba(0, 245, 212, 0.04) 60%,
|
||
transparent 76%
|
||
);
|
||
transform: translateX(-130%);
|
||
}
|
||
|
||
.folder-grid-lines {
|
||
background:
|
||
linear-gradient(rgba(0, 245, 212, 0.05) 1px, transparent 1px),
|
||
linear-gradient(90deg, rgba(0, 245, 212, 0.05) 1px, transparent 1px);
|
||
background-size: 18px 18px;
|
||
mix-blend-mode: screen;
|
||
}
|
||
|
||
.folder-tile:hover .folder-scan,
|
||
.folder-tile:hover .folder-grid-lines {
|
||
opacity: 1;
|
||
}
|
||
|
||
.folder-tile:hover .folder-tech-corners .corner {
|
||
border-color: rgba(124, 230, 255, 0.7);
|
||
box-shadow: 0 0 10px rgba(0, 245, 212, 0.18);
|
||
}
|
||
|
||
.folder-tile:hover .corner-tl,
|
||
.folder-tile:hover .corner-br {
|
||
transform: translate(1px, 1px);
|
||
}
|
||
|
||
.folder-tile:hover .corner-tr,
|
||
.folder-tile:hover .corner-bl {
|
||
transform: translate(-1px, -1px);
|
||
}
|
||
|
||
.folder-tile:active .folder-activate-flash {
|
||
animation: activate-flash 320ms ease-out;
|
||
}
|
||
|
||
.folder-tile:hover .folder-scan {
|
||
animation: hologram-scan 1.4s ease-out forwards;
|
||
}
|
||
|
||
.folder-glyph {
|
||
position: relative;
|
||
margin-top: 22px;
|
||
width: 100px;
|
||
height: 94px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
animation: folder-float 4.8s ease-in-out infinite;
|
||
}
|
||
|
||
.folder-pulse-ring {
|
||
position: absolute;
|
||
width: 108px;
|
||
height: 108px;
|
||
border-radius: 50%;
|
||
border: 1px solid rgba(0, 245, 212, 0.16);
|
||
box-shadow: 0 0 18px rgba(0, 245, 212, 0.08);
|
||
animation: pulse-ring 3.2s ease-in-out infinite;
|
||
}
|
||
|
||
.folder-core {
|
||
width: 92px;
|
||
height: 78px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 22px;
|
||
color: #79d4ff;
|
||
background: linear-gradient(180deg, rgba(0, 245, 212, 0.16), rgba(123, 44, 191, 0.12));
|
||
border: 1px solid rgba(0, 245, 212, 0.22);
|
||
box-shadow:
|
||
inset 0 0 18px rgba(0, 245, 212, 0.08),
|
||
0 0 18px rgba(0, 245, 212, 0.12);
|
||
animation: core-breathe 3.4s ease-in-out infinite;
|
||
}
|
||
|
||
.folder-core svg {
|
||
filter: drop-shadow(0 0 6px rgba(124, 230, 255, 0.35));
|
||
}
|
||
|
||
.folder-beam {
|
||
position: absolute;
|
||
display: block;
|
||
width: 70px;
|
||
height: 1px;
|
||
background: linear-gradient(90deg, transparent, rgba(0, 245, 212, 0.85), transparent);
|
||
animation: beam-flicker 2.6s ease-in-out infinite;
|
||
}
|
||
|
||
.beam-a {
|
||
top: 18px;
|
||
left: -8px;
|
||
}
|
||
|
||
.beam-b {
|
||
right: -8px;
|
||
bottom: 20px;
|
||
}
|
||
|
||
.file-tile {
|
||
text-align: center;
|
||
}
|
||
|
||
.folder-label-bar {
|
||
width: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
min-height: 34px;
|
||
padding: 8px 2px 0;
|
||
border-top: 1px solid rgba(0, 245, 212, 0.1);
|
||
position: relative;
|
||
}
|
||
|
||
.folder-label-bar::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: -1px;
|
||
left: 2px;
|
||
width: 42px;
|
||
height: 1px;
|
||
background: linear-gradient(90deg, rgba(0, 245, 212, 0.85), transparent);
|
||
}
|
||
|
||
.folder-title-name {
|
||
text-align: left;
|
||
}
|
||
|
||
.folder-meta {
|
||
color: #84c7d6;
|
||
}
|
||
|
||
.folder-tile:hover .folder-label-bar {
|
||
border-top-color: rgba(124, 230, 255, 0.24);
|
||
}
|
||
|
||
.folder-tile:hover .folder-label-bar::before {
|
||
width: 64px;
|
||
background: linear-gradient(90deg, rgba(124, 230, 255, 0.95), transparent);
|
||
}
|
||
|
||
.folder-tile:hover .folder-title-name {
|
||
color: #f2fbff;
|
||
}
|
||
|
||
.file-badge {
|
||
margin-top: 8px;
|
||
padding: 3px 8px;
|
||
border: 1px solid;
|
||
border-radius: 999px;
|
||
font-family: var(--font-display);
|
||
font-size: 9px;
|
||
letter-spacing: 0.12em;
|
||
}
|
||
|
||
.file-icon {
|
||
margin-top: 22px;
|
||
min-height: 88px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
filter: drop-shadow(0 0 10px currentColor);
|
||
}
|
||
|
||
.tile-name {
|
||
width: 100%;
|
||
font-size: 13px;
|
||
line-height: 1.45;
|
||
color: var(--text-primary);
|
||
word-break: break-word;
|
||
}
|
||
|
||
.tile-meta {
|
||
width: 100%;
|
||
min-height: 30px;
|
||
color: var(--text-dim);
|
||
font-family: var(--font-mono);
|
||
font-size: 10px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.tile-status-row {
|
||
margin-top: auto;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.tile-status {
|
||
font-family: var(--font-display);
|
||
font-size: 9px;
|
||
letter-spacing: 0.12em;
|
||
}
|
||
|
||
.tile-inline-loader {
|
||
color: var(--accent-cyan);
|
||
}
|
||
|
||
.ready {
|
||
color: var(--accent-green);
|
||
}
|
||
|
||
.uploaded,
|
||
.parsing,
|
||
.indexing {
|
||
color: var(--accent-amber);
|
||
}
|
||
|
||
.warning {
|
||
color: var(--accent-purple);
|
||
}
|
||
|
||
.failed {
|
||
color: var(--accent-red);
|
||
}
|
||
|
||
.tile-warning {
|
||
width: 100%;
|
||
color: var(--accent-red);
|
||
font-family: var(--font-mono);
|
||
font-size: 10px;
|
||
line-height: 1.4;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.doc-chunk-count {
|
||
color: var(--accent-cyan);
|
||
font-family: var(--font-display);
|
||
font-size: 10px;
|
||
letter-spacing: 0.08em;
|
||
}
|
||
|
||
.doc-status-pill {
|
||
padding: 4px 10px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--border-mid);
|
||
color: var(--accent-cyan);
|
||
background: rgba(0, 245, 212, 0.08);
|
||
font-family: var(--font-display);
|
||
font-size: 10px;
|
||
letter-spacing: 0.12em;
|
||
}
|
||
|
||
.empty-state {
|
||
grid-column: 1 / -1;
|
||
min-height: 440px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 20px;
|
||
padding: 40px 24px;
|
||
border: 1px solid rgba(0, 245, 212, 0.12);
|
||
border-radius: 24px;
|
||
background:
|
||
radial-gradient(circle at center, rgba(0, 245, 212, 0.08), transparent 42%),
|
||
linear-gradient(180deg, rgba(8, 13, 24, 0.96), rgba(4, 8, 16, 0.98));
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.empty-state::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background:
|
||
linear-gradient(90deg, transparent, rgba(0, 245, 212, 0.05), transparent),
|
||
radial-gradient(circle at top, rgba(123, 44, 191, 0.14), transparent 32%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.empty-core-shell {
|
||
position: relative;
|
||
width: 250px;
|
||
height: 250px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.empty-orbit {
|
||
position: absolute;
|
||
border-radius: 50%;
|
||
border: 1px solid rgba(0, 245, 212, 0.12);
|
||
box-shadow: inset 0 0 18px rgba(0, 245, 212, 0.04);
|
||
}
|
||
|
||
.orbit-outer {
|
||
width: 250px;
|
||
height: 250px;
|
||
}
|
||
|
||
.orbit-inner {
|
||
width: 184px;
|
||
height: 184px;
|
||
border-color: rgba(0, 245, 212, 0.18);
|
||
}
|
||
|
||
.empty-scanline {
|
||
position: absolute;
|
||
width: 180px;
|
||
height: 1px;
|
||
background: linear-gradient(90deg, transparent, rgba(0, 245, 212, 0.9), transparent);
|
||
box-shadow: 0 0 12px rgba(0, 245, 212, 0.5);
|
||
animation: float 3s ease-in-out infinite;
|
||
}
|
||
|
||
.empty-core-glow {
|
||
position: absolute;
|
||
width: 126px;
|
||
height: 126px;
|
||
border-radius: 50%;
|
||
background: radial-gradient(circle, rgba(0, 245, 212, 0.16), rgba(123, 44, 191, 0.08), transparent 70%);
|
||
filter: blur(6px);
|
||
}
|
||
|
||
.empty-folder-chamber {
|
||
position: relative;
|
||
width: 124px;
|
||
height: 124px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
animation: folder-float 5.4s ease-in-out infinite;
|
||
}
|
||
|
||
.empty-folder-plate {
|
||
position: absolute;
|
||
inset: 14px;
|
||
border-radius: 28px;
|
||
border: 1px solid rgba(0, 245, 212, 0.22);
|
||
background: linear-gradient(180deg, rgba(0, 245, 212, 0.14), rgba(123, 44, 191, 0.14));
|
||
box-shadow:
|
||
inset 0 0 24px rgba(0, 245, 212, 0.08),
|
||
0 0 32px rgba(0, 245, 212, 0.12);
|
||
}
|
||
|
||
.empty-folder-pulse {
|
||
position: absolute;
|
||
width: 138px;
|
||
height: 138px;
|
||
border-radius: 50%;
|
||
border: 1px solid rgba(0, 245, 212, 0.14);
|
||
box-shadow: 0 0 22px rgba(0, 245, 212, 0.08);
|
||
animation: pulse-ring 3.6s ease-in-out infinite;
|
||
}
|
||
|
||
.empty-folder-icon-wrap {
|
||
position: relative;
|
||
z-index: 1;
|
||
width: 92px;
|
||
height: 92px;
|
||
border-radius: 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #7ce6ff;
|
||
background: rgba(3, 10, 18, 0.72);
|
||
border: 1px solid rgba(0, 245, 212, 0.16);
|
||
box-shadow: 0 0 24px rgba(0, 245, 212, 0.16);
|
||
animation: core-breathe 3.8s ease-in-out infinite;
|
||
}
|
||
|
||
.empty-folder-icon-wrap svg {
|
||
filter: drop-shadow(0 0 8px rgba(124, 230, 255, 0.45));
|
||
}
|
||
|
||
.empty-beam {
|
||
position: absolute;
|
||
width: 78px;
|
||
height: 1px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
background: linear-gradient(90deg, transparent, rgba(0, 245, 212, 0.95), transparent);
|
||
animation: beam-flicker 2.4s ease-in-out infinite;
|
||
}
|
||
|
||
.beam-left {
|
||
left: -34px;
|
||
}
|
||
|
||
.beam-right {
|
||
right: -34px;
|
||
}
|
||
|
||
.empty-copy {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8px;
|
||
text-align: center;
|
||
max-width: 560px;
|
||
}
|
||
|
||
.empty-kicker {
|
||
color: var(--accent-cyan);
|
||
font-family: var(--font-display);
|
||
font-size: 10px;
|
||
letter-spacing: 0.24em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.empty-title {
|
||
color: var(--text-primary);
|
||
font-family: var(--font-display);
|
||
font-size: 22px;
|
||
letter-spacing: 0.16em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.empty-sub {
|
||
color: var(--text-secondary);
|
||
font-size: 12px;
|
||
line-height: 1.8;
|
||
}
|
||
|
||
.empty-actions {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
}
|
||
|
||
.empty-cta {
|
||
min-width: 188px;
|
||
justify-content: center;
|
||
}
|
||
|
||
@keyframes folder-float {
|
||
0%, 100% { transform: translateY(0px); }
|
||
50% { transform: translateY(-5px); }
|
||
}
|
||
|
||
@keyframes core-breathe {
|
||
0%, 100% {
|
||
transform: scale(1);
|
||
opacity: 0.92;
|
||
}
|
||
50% {
|
||
transform: scale(1.04);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
@keyframes pulse-ring {
|
||
0%, 100% {
|
||
transform: scale(0.92);
|
||
opacity: 0.35;
|
||
}
|
||
50% {
|
||
transform: scale(1.06);
|
||
opacity: 0.78;
|
||
}
|
||
}
|
||
|
||
@keyframes beam-flicker {
|
||
0%, 100% {
|
||
opacity: 0.35;
|
||
transform: translateY(-50%) scaleX(0.92);
|
||
}
|
||
50% {
|
||
opacity: 1;
|
||
transform: translateY(-50%) scaleX(1.06);
|
||
}
|
||
}
|
||
|
||
@keyframes hologram-scan {
|
||
0% {
|
||
transform: translateX(-130%);
|
||
opacity: 0;
|
||
}
|
||
20% {
|
||
opacity: 0.55;
|
||
}
|
||
100% {
|
||
transform: translateX(130%);
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
@keyframes activate-flash {
|
||
0% {
|
||
opacity: 0;
|
||
transform: scale(0.92);
|
||
}
|
||
35% {
|
||
opacity: 0.95;
|
||
transform: scale(1.03);
|
||
}
|
||
100% {
|
||
opacity: 0;
|
||
transform: scale(1.08);
|
||
}
|
||
}
|
||
|
||
@keyframes shimmer {
|
||
0% {
|
||
background-position: 200% 0;
|
||
}
|
||
100% {
|
||
background-position: -200% 0;
|
||
}
|
||
}
|
||
|
||
.folder-tile:hover .folder-glyph,
|
||
.folder-tile:hover .empty-folder-chamber {
|
||
animation-duration: 2.3s;
|
||
}
|
||
|
||
.folder-tile:hover .folder-core,
|
||
.folder-tile:hover .folder-pulse-ring,
|
||
.folder-tile:hover .empty-folder-icon-wrap,
|
||
.folder-tile:hover .empty-folder-pulse {
|
||
animation-duration: 2s;
|
||
}
|
||
|
||
.folder-tile:hover .folder-beam,
|
||
.folder-tile:hover .empty-beam {
|
||
animation-duration: 1.2s;
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.folder-glyph,
|
||
.folder-pulse-ring,
|
||
.folder-core,
|
||
.folder-beam,
|
||
.folder-scan,
|
||
.folder-tech-corners .corner,
|
||
.empty-folder-chamber,
|
||
.empty-folder-pulse,
|
||
.empty-folder-icon-wrap,
|
||
.empty-beam {
|
||
animation: none !important;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 700px) {
|
||
.explorer-grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
|
||
gap: 14px;
|
||
}
|
||
|
||
.explorer-tile {
|
||
min-height: 204px;
|
||
padding: 16px 14px 14px;
|
||
}
|
||
|
||
.folder-glyph {
|
||
width: 90px;
|
||
height: 84px;
|
||
margin-top: 18px;
|
||
}
|
||
|
||
.folder-pulse-ring {
|
||
width: 96px;
|
||
height: 96px;
|
||
}
|
||
|
||
.folder-core {
|
||
width: 84px;
|
||
height: 72px;
|
||
}
|
||
|
||
.empty-core-shell {
|
||
width: 210px;
|
||
height: 210px;
|
||
}
|
||
|
||
.orbit-outer {
|
||
width: 210px;
|
||
height: 210px;
|
||
}
|
||
|
||
.orbit-inner {
|
||
width: 156px;
|
||
height: 156px;
|
||
}
|
||
|
||
.empty-title {
|
||
font-size: 18px;
|
||
letter-spacing: 0.12em;
|
||
}
|
||
}
|
||
|
||
.spin {
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
.dialog-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.dialog {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border-mid);
|
||
border-radius: var(--radius-lg);
|
||
padding: 24px;
|
||
width: 400px;
|
||
max-width: 90vw;
|
||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||
}
|
||
|
||
.document-dialog {
|
||
width: min(860px, 92vw);
|
||
}
|
||
|
||
.dialog-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 16px;
|
||
gap: 12px;
|
||
}
|
||
|
||
.dialog-header h3 {
|
||
font-family: var(--font-display);
|
||
font-size: 14px;
|
||
letter-spacing: 0.1em;
|
||
color: var(--text-primary);
|
||
margin: 0;
|
||
}
|
||
|
||
.close-btn {
|
||
background: none;
|
||
border: none;
|
||
color: var(--text-dim);
|
||
cursor: pointer;
|
||
padding: 4px;
|
||
border-radius: 3px;
|
||
display: flex;
|
||
align-items: center;
|
||
transition: all var(--transition-fast);
|
||
}
|
||
|
||
.close-btn:hover {
|
||
color: var(--text-primary);
|
||
background: var(--bg-panel);
|
||
}
|
||
|
||
.dialog-input {
|
||
width: 100%;
|
||
padding: 10px 14px;
|
||
background: var(--bg-panel);
|
||
border: 1px solid var(--border-dim);
|
||
border-radius: var(--radius-md);
|
||
color: var(--text-primary);
|
||
font-size: 13px;
|
||
margin-bottom: 16px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.dialog-input:focus {
|
||
border-color: var(--accent-cyan);
|
||
outline: none;
|
||
}
|
||
|
||
.dialog-body {
|
||
font-size: 13px;
|
||
color: var(--text-secondary);
|
||
margin: 0 0 16px 0;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.dialog-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.document-info-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
color: var(--text-secondary);
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
margin-bottom: 14px;
|
||
}
|
||
|
||
.document-content-grid {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.1fr) minmax(320px, 0.9fr);
|
||
gap: 16px;
|
||
}
|
||
|
||
.document-preview,
|
||
.chunk-panel {
|
||
min-height: 360px;
|
||
max-height: 62vh;
|
||
overflow: auto;
|
||
background: rgba(3, 5, 10, 0.7);
|
||
border: 1px solid var(--border-dim);
|
||
border-radius: 14px;
|
||
padding: 16px;
|
||
}
|
||
|
||
.document-preview pre {
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
color: var(--text-secondary);
|
||
font-family: var(--font-mono);
|
||
font-size: 12px;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.chunk-panel-header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.chunk-panel-title {
|
||
color: var(--accent-cyan);
|
||
font-family: var(--font-display);
|
||
font-size: 11px;
|
||
letter-spacing: 0.14em;
|
||
}
|
||
|
||
.chunk-panel-subtitle {
|
||
margin-top: 4px;
|
||
color: var(--text-dim);
|
||
font-family: var(--font-mono);
|
||
font-size: 10px;
|
||
}
|
||
|
||
.chunk-count-badge {
|
||
min-width: 32px;
|
||
height: 32px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 999px;
|
||
border: 1px solid rgba(0, 245, 212, 0.2);
|
||
background: rgba(0, 245, 212, 0.08);
|
||
color: var(--accent-cyan);
|
||
font-family: var(--font-display);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.chunk-loading-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
padding: 12px 0;
|
||
color: var(--text-dim);
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.chunk-loading-bar {
|
||
width: 100%;
|
||
height: 6px;
|
||
border-radius: 999px;
|
||
background: linear-gradient(90deg, rgba(0, 245, 212, 0.15), rgba(124, 230, 255, 0.45), rgba(0, 245, 212, 0.15));
|
||
background-size: 200% 100%;
|
||
animation: shimmer 1.4s linear infinite;
|
||
}
|
||
|
||
.chunk-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.chunk-card {
|
||
border: 1px solid var(--border-dim);
|
||
border-radius: 12px;
|
||
background: rgba(8, 15, 24, 0.82);
|
||
padding: 12px;
|
||
}
|
||
|
||
.chunk-card.editing {
|
||
border-color: rgba(0, 245, 212, 0.4);
|
||
box-shadow: inset 0 0 0 1px rgba(0, 245, 212, 0.08);
|
||
}
|
||
|
||
.chunk-card-header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.chunk-card-meta {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.chunk-index {
|
||
color: var(--accent-cyan);
|
||
font-family: var(--font-display);
|
||
font-size: 10px;
|
||
letter-spacing: 0.12em;
|
||
}
|
||
|
||
.chunk-size {
|
||
color: var(--text-dim);
|
||
font-family: var(--font-mono);
|
||
font-size: 10px;
|
||
}
|
||
|
||
.chunk-meta-raw {
|
||
color: var(--text-dim);
|
||
font-family: var(--font-mono);
|
||
font-size: 10px;
|
||
line-height: 1.5;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.chunk-content {
|
||
margin: 0;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
color: var(--text-secondary);
|
||
font-family: var(--font-mono);
|
||
font-size: 12px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.chunk-edit-form {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.chunk-textarea {
|
||
width: 100%;
|
||
min-height: 150px;
|
||
resize: vertical;
|
||
background: rgba(0, 9, 19, 0.9);
|
||
border: 1px solid var(--border-dim);
|
||
border-radius: 10px;
|
||
color: var(--text-primary);
|
||
padding: 12px;
|
||
font-family: var(--font-mono);
|
||
font-size: 12px;
|
||
line-height: 1.6;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.chunk-textarea:focus {
|
||
border-color: var(--accent-cyan);
|
||
outline: none;
|
||
}
|
||
|
||
.chunk-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
}
|
||
|
||
.chunk-error {
|
||
width: 100%;
|
||
}
|
||
|
||
.chunk-empty {
|
||
color: var(--text-dim);
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.preview-loading {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: var(--accent-cyan);
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 8px 16px;
|
||
background: var(--bg-panel);
|
||
border: 1px solid var(--border-mid);
|
||
border-radius: var(--radius-md);
|
||
color: var(--text-secondary);
|
||
font-family: var(--font-display);
|
||
font-size: 10px;
|
||
letter-spacing: 0.1em;
|
||
cursor: pointer;
|
||
transition: all var(--transition-fast);
|
||
}
|
||
|
||
.btn:hover {
|
||
border-color: var(--border-mid);
|
||
background: var(--bg-card-hover);
|
||
}
|
||
|
||
.btn.primary {
|
||
background: var(--accent-cyan-dim);
|
||
border-color: rgba(0, 245, 212, 0.3);
|
||
color: var(--accent-cyan);
|
||
}
|
||
|
||
.btn.primary:hover {
|
||
background: rgba(0, 245, 212, 0.2);
|
||
box-shadow: var(--glow-cyan);
|
||
}
|
||
|
||
.btn.danger {
|
||
background: rgba(255, 71, 87, 0.1);
|
||
border-color: rgba(255, 71, 87, 0.3);
|
||
color: var(--accent-red);
|
||
}
|
||
|
||
.btn.danger:hover {
|
||
background: rgba(255, 71, 87, 0.2);
|
||
box-shadow: 0 0 12px rgba(255, 71, 87, 0.2);
|
||
}
|
||
|
||
@media (max-width: 900px) {
|
||
.toolbar {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.toolbar-left,
|
||
.toolbar-right {
|
||
width: 100%;
|
||
}
|
||
|
||
.toolbar-right {
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
.document-content-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style>
|