2026-04-30 21:28:00 +08:00
|
|
|
<template>
|
|
|
|
|
<aside class="workspace-sidebar">
|
|
|
|
|
<section class="sidebar-section">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
class="workspace-nav-item"
|
2026-05-01 21:00:58 +08:00
|
|
|
:class="{ active: activeKey === 'personal' && !isQuickAccessPath(currentPath) }"
|
2026-04-30 21:28:00 +08:00
|
|
|
@click="$emit('select-personal')"
|
|
|
|
|
>
|
|
|
|
|
<FolderKanban :size="16" />
|
|
|
|
|
<span>个人工作区</span>
|
|
|
|
|
</button>
|
|
|
|
|
</section>
|
|
|
|
|
|
2026-04-30 22:29:22 +08:00
|
|
|
<section class="sidebar-section">
|
|
|
|
|
<div class="section-title">快速访问</div>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
class="workspace-nav-item secondary"
|
2026-05-01 22:17:27 +08:00
|
|
|
:class="{
|
|
|
|
|
active: activeKey === 'personal' && isSameOrChildPath(currentPath, savedArtifactsPath)
|
|
|
|
|
}"
|
2026-04-30 22:29:22 +08:00
|
|
|
@click="$emit('select-path', savedArtifactsPath)"
|
|
|
|
|
>
|
|
|
|
|
<Archive :size="15" />
|
|
|
|
|
<span>Saved Artifacts</span>
|
|
|
|
|
</button>
|
2026-05-01 21:00:58 +08:00
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
class="workspace-nav-item secondary"
|
|
|
|
|
:class="{ active: activeKey === 'personal' && isSameOrChildPath(currentPath, agentsPath) }"
|
|
|
|
|
@click="$emit('select-path', agentsPath)"
|
|
|
|
|
>
|
|
|
|
|
<Bot :size="15" />
|
|
|
|
|
<span>Agents</span>
|
|
|
|
|
</button>
|
2026-04-30 22:29:22 +08:00
|
|
|
</section>
|
|
|
|
|
|
2026-05-18 09:41:12 +08:00
|
|
|
<section v-if="myDatabases.length" class="sidebar-section">
|
|
|
|
|
<div class="section-title">我的知识库</div>
|
|
|
|
|
<button
|
|
|
|
|
v-for="database in myDatabases"
|
2026-05-21 19:33:00 +08:00
|
|
|
:key="database.kb_id || database.id || database.name"
|
2026-05-18 09:41:12 +08:00
|
|
|
type="button"
|
|
|
|
|
class="workspace-nav-item secondary"
|
2026-05-21 19:33:00 +08:00
|
|
|
:class="{ active: activeKey === `database:${database.kb_id}` }"
|
2026-05-18 09:41:12 +08:00
|
|
|
@click="$emit('select-database', database)"
|
|
|
|
|
>
|
|
|
|
|
<LibraryBig :size="15" />
|
|
|
|
|
<span>{{ database.name }}</span>
|
|
|
|
|
</button>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section v-if="sharedDatabases.length" class="sidebar-section">
|
|
|
|
|
<div class="section-title">共享知识库</div>
|
2026-04-30 21:28:00 +08:00
|
|
|
<button
|
2026-05-18 09:41:12 +08:00
|
|
|
v-for="database in sharedDatabases"
|
2026-05-21 19:33:00 +08:00
|
|
|
:key="database.kb_id || database.id || database.name"
|
2026-04-30 21:28:00 +08:00
|
|
|
type="button"
|
|
|
|
|
class="workspace-nav-item secondary"
|
2026-05-21 19:33:00 +08:00
|
|
|
:class="{ active: activeKey === `database:${database.kb_id}` }"
|
2026-04-30 21:28:00 +08:00
|
|
|
@click="$emit('select-database', database)"
|
|
|
|
|
>
|
|
|
|
|
<LibraryBig :size="15" />
|
|
|
|
|
<span>{{ database.name }}</span>
|
|
|
|
|
</button>
|
2026-05-18 09:41:12 +08:00
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section v-if="loadingDatabases" class="sidebar-section">
|
|
|
|
|
<div class="sidebar-muted">正在加载知识库...</div>
|
|
|
|
|
</section>
|
|
|
|
|
<section v-else-if="!databases.length" class="sidebar-section">
|
|
|
|
|
<div class="sidebar-muted">暂无可访问知识库</div>
|
2026-04-30 21:28:00 +08:00
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section class="sidebar-section">
|
|
|
|
|
<div class="section-title">共享空间</div>
|
|
|
|
|
<button type="button" class="workspace-nav-item secondary disabled" disabled>
|
|
|
|
|
<UsersRound :size="15" />
|
|
|
|
|
<span>团队工作区</span>
|
|
|
|
|
<span class="soon-tag">即将支持</span>
|
|
|
|
|
</button>
|
|
|
|
|
</section>
|
|
|
|
|
</aside>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-05-18 09:41:12 +08:00
|
|
|
import { computed } from 'vue'
|
2026-05-01 21:00:58 +08:00
|
|
|
import { Archive, Bot, FolderKanban, LibraryBig, UsersRound } from 'lucide-vue-next'
|
2026-04-30 22:29:22 +08:00
|
|
|
|
|
|
|
|
const savedArtifactsPath = '/saved_artifacts'
|
2026-05-01 21:00:58 +08:00
|
|
|
const agentsPath = '/agents/'
|
|
|
|
|
const quickAccessPaths = [savedArtifactsPath, agentsPath]
|
|
|
|
|
|
|
|
|
|
const normalizePath = (path) => String(path || '/').replace(/\/$/, '') || '/'
|
|
|
|
|
const isSameOrChildPath = (path, targetPath) => {
|
|
|
|
|
const current = normalizePath(path)
|
|
|
|
|
const target = normalizePath(targetPath)
|
|
|
|
|
return current === target || current.startsWith(`${target}/`)
|
|
|
|
|
}
|
2026-05-01 22:17:27 +08:00
|
|
|
const isQuickAccessPath = (path) =>
|
|
|
|
|
quickAccessPaths.some((targetPath) => isSameOrChildPath(path, targetPath))
|
2026-04-30 21:28:00 +08:00
|
|
|
|
2026-05-18 09:41:12 +08:00
|
|
|
const props = defineProps({
|
2026-04-30 21:28:00 +08:00
|
|
|
activeKey: { type: String, default: 'personal' },
|
2026-04-30 22:29:22 +08:00
|
|
|
currentPath: { type: String, default: '/' },
|
2026-04-30 21:28:00 +08:00
|
|
|
databases: { type: Array, default: () => [] },
|
2026-05-18 09:41:12 +08:00
|
|
|
loadingDatabases: { type: Boolean, default: false },
|
|
|
|
|
currentUid: { type: String, default: '' }
|
2026-04-30 21:28:00 +08:00
|
|
|
})
|
|
|
|
|
|
2026-04-30 22:29:22 +08:00
|
|
|
defineEmits(['select-personal', 'select-database', 'select-path'])
|
2026-05-18 09:41:12 +08:00
|
|
|
|
|
|
|
|
const myDatabases = computed(() =>
|
|
|
|
|
props.databases.filter((db) => db.created_by === props.currentUid)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const sharedDatabases = computed(() =>
|
|
|
|
|
props.databases.filter((db) => db.created_by !== props.currentUid)
|
|
|
|
|
)
|
2026-04-30 21:28:00 +08:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
|
|
.workspace-sidebar {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
2026-05-18 09:41:12 +08:00
|
|
|
gap: 12px;
|
2026-04-30 21:28:00 +08:00
|
|
|
min-width: 0;
|
2026-04-30 22:07:48 +08:00
|
|
|
padding: 14px 10px;
|
2026-04-30 21:28:00 +08:00
|
|
|
border-right: 1px solid var(--gray-100);
|
2026-05-01 21:39:14 +08:00
|
|
|
background: var(--gray-0);
|
2026-04-30 21:28:00 +08:00
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-section {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
2026-05-18 09:41:12 +08:00
|
|
|
// gap: 6px;
|
2026-04-30 21:28:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.section-title {
|
2026-05-18 09:41:12 +08:00
|
|
|
padding: 4px 8px;
|
2026-04-30 21:28:00 +08:00
|
|
|
color: var(--gray-500);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
line-height: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workspace-nav-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
min-height: 36px;
|
|
|
|
|
padding: 0 10px;
|
|
|
|
|
border: 1px solid transparent;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
background: transparent;
|
2026-04-30 22:40:55 +08:00
|
|
|
color: var(--gray-600);
|
2026-04-30 21:28:00 +08:00
|
|
|
font-size: 14px;
|
2026-04-30 22:40:55 +08:00
|
|
|
font-weight: 600;
|
2026-04-30 21:28:00 +08:00
|
|
|
text-align: left;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition:
|
|
|
|
|
background-color 0.2s ease,
|
|
|
|
|
color 0.2s ease,
|
|
|
|
|
border-color 0.2s ease;
|
|
|
|
|
|
|
|
|
|
span:first-of-type {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
2026-04-30 22:40:55 +08:00
|
|
|
font-weight: 500;
|
2026-04-30 21:28:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&:hover:not(:disabled),
|
|
|
|
|
&.active {
|
|
|
|
|
border-color: transparent;
|
2026-04-30 22:40:55 +08:00
|
|
|
color: var(--main-600);
|
2026-04-30 21:28:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.secondary {
|
|
|
|
|
min-height: 32px;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.disabled {
|
|
|
|
|
color: var(--gray-400);
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.soon-tag {
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
color: var(--gray-400);
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-muted {
|
|
|
|
|
padding: 6px 8px;
|
|
|
|
|
color: var(--gray-500);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
}
|
2026-05-31 13:40:15 +08:00
|
|
|
</style>
|