Browse the knowledge base from a single side panel menu
Add a read-only view of backend/knowledge_base/**.md so the files that get injected into the system prompt can be inspected from the GUI: - GET /api/kb/files lists the Markdown files (title, preview, size, mtime and whether the file is already embedded), GET /api/kb/file returns one in full. Paths are resolved against the knowledge base directory and rejected unless they stay inside it and end in .md. - KnowledgeBasePanel.vue groups the files by folder (collapsible, so web_search can be folded away), filters by title/path/preview and renders the selected file with MarkdownContent. The header had grown to four side panel toggles, so collapse them into one menu: panels.ts now holds the list of panels along with the component each one renders, App.vue picks it with <component :is>, and SidePanelMenu.vue shows the active panel on its button. Re-picking the open panel (or "パネルを閉じる") closes it; a click outside or Esc closes just the menu. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+15
-31
@@ -1,27 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import SessionList from './components/SessionList.vue'
|
||||
import ChatWindow from './components/ChatWindow.vue'
|
||||
import MessageInput from './components/MessageInput.vue'
|
||||
import ModelSelector from './components/ModelSelector.vue'
|
||||
import LogViewer from './components/LogViewer.vue'
|
||||
import SettingsPanel from './components/SettingsPanel.vue'
|
||||
import NotePanel from './components/NotePanel.vue'
|
||||
import SidePanelMenu from './components/SidePanelMenu.vue'
|
||||
import ToastList from './components/ToastList.vue'
|
||||
import MemoryMonitor from './components/MemoryMonitor.vue'
|
||||
import Clock from './components/Clock.vue'
|
||||
import WordPool from './components/WordPool.vue'
|
||||
import LoginGate from './components/LoginGate.vue'
|
||||
import { SIDE_PANELS, type PanelId } from './panels'
|
||||
import { useAuthStore } from './stores/auth'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
|
||||
type SidePanel = 'none' | 'logs' | 'settings' | 'notes'
|
||||
const activePanel = ref<SidePanel>('none')
|
||||
|
||||
function togglePanel(panel: SidePanel) {
|
||||
activePanel.value = activePanel.value === panel ? 'none' : panel
|
||||
}
|
||||
/** 右サイドに開いているパネル。null はどれも開いていない状態。 */
|
||||
const activePanel = ref<PanelId | null>(null)
|
||||
const activePanelDef = computed(() => SIDE_PANELS.find((p) => p.id === activePanel.value) ?? null)
|
||||
|
||||
function handleUnauthorized() {
|
||||
authStore.markUnauthenticated()
|
||||
@@ -51,32 +47,20 @@ onUnmounted(() => {
|
||||
<span class="title">PlayChat</span>
|
||||
<ModelSelector />
|
||||
<Clock />
|
||||
<button class="log-toggle" :class="{ active: activePanel === 'notes' }" @click="togglePanel('notes')">
|
||||
ノート
|
||||
</button>
|
||||
<button class="log-toggle" :class="{ active: activePanel === 'settings' }" @click="togglePanel('settings')">
|
||||
設定
|
||||
</button>
|
||||
<button class="log-toggle" :class="{ active: activePanel === 'logs' }" @click="togglePanel('logs')">
|
||||
ログ
|
||||
</button>
|
||||
<button v-if="authStore.required" class="log-toggle" @click="authStore.logout()">
|
||||
ログアウト
|
||||
</button>
|
||||
<div class="header-actions">
|
||||
<SidePanelMenu :active="activePanel" @select="activePanel = $event" />
|
||||
<button v-if="authStore.required" class="log-toggle" @click="authStore.logout()">
|
||||
ログアウト
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="body">
|
||||
<div class="chat-pane">
|
||||
<ChatWindow />
|
||||
<MessageInput />
|
||||
</div>
|
||||
<div v-if="activePanel === 'logs'" class="log-pane">
|
||||
<LogViewer />
|
||||
</div>
|
||||
<div v-else-if="activePanel === 'settings'" class="log-pane">
|
||||
<SettingsPanel />
|
||||
</div>
|
||||
<div v-else-if="activePanel === 'notes'" class="log-pane">
|
||||
<NotePanel />
|
||||
<div v-if="activePanelDef" class="log-pane">
|
||||
<component :is="activePanelDef.component" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -101,11 +85,11 @@ header {
|
||||
border-bottom: 1px solid #333; background: #1a1a1a; flex-shrink: 0;
|
||||
}
|
||||
.title { font-size: 16px; font-weight: 600; color: #fff; }
|
||||
.header-actions { display: flex; align-items: center; gap: 8px; }
|
||||
.log-toggle {
|
||||
background: #2a2a2a; color: #aaa; border: 1px solid #444;
|
||||
border-radius: 6px; padding: 4px 14px; font-size: 13px; cursor: pointer;
|
||||
}
|
||||
.log-toggle.active { background: #1d3557; color: #93c5fd; border-color: #2563eb; }
|
||||
.body { flex: 1; display: flex; overflow: hidden; }
|
||||
.chat-pane { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.log-pane { width: 400px; min-width: 300px; border-left: 1px solid #2a2a2a; overflow: hidden; }
|
||||
|
||||
Reference in New Issue
Block a user