feat(frontend): update chat page composables and sidebar plan implementation
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, toRef } from 'vue'
|
||||
import {
|
||||
Database,
|
||||
ChevronRight,
|
||||
Send,
|
||||
Sparkles,
|
||||
@@ -14,6 +15,7 @@ import FileMessage from '@/components/chat/FileMessage.vue'
|
||||
import KnowledgeHudPanel from '@/components/chat/KnowledgeHudPanel.vue'
|
||||
import KnowledgeSlidePanel from '@/components/chat/KnowledgeSlidePanel.vue'
|
||||
import KnowledgeHUDPreview from '@/components/chat/KnowledgeHUDPreview.vue'
|
||||
import KnowledgeRAGPanel from '@/components/chat/KnowledgeRAGPanel.vue'
|
||||
import OrchestrationPanel from '@/components/chat/OrchestrationPanel.vue'
|
||||
import KanbanPanel from '@/components/chat/KanbanPanel.vue'
|
||||
import KanbanDetail from '@/components/chat/KanbanDetail.vue'
|
||||
@@ -21,11 +23,14 @@ import TelemetrySparkline from '@/components/chat/TelemetrySparkline.vue'
|
||||
import NavShortcutRow from '@/components/navigation/NavShortcutRow.vue'
|
||||
import DailyDigestCard from '@/components/memory/DailyDigestCard.vue'
|
||||
import ReminderToast from '@/components/memory/ReminderToast.vue'
|
||||
import type { TaskQuadrant } from '@/api/task'
|
||||
import { documentApi } from '@/api/document'
|
||||
import { useChatView } from '@/pages/chat/composables/useChatView'
|
||||
import { useKnowledgeView } from '@/pages/knowledge/composables/useKnowledgeView'
|
||||
import { useDailyDigest } from '@/pages/chat/composables/useDailyDigest'
|
||||
import { useClientTime, formatNetworkRate } from '@/pages/chat/composables/useClientTime'
|
||||
import { useSidebarPlan, formatDateKey } from '@/pages/chat/composables/useSidebarPlan'
|
||||
import TempleModal from '@/pages/temple/index.vue'
|
||||
|
||||
// --- Chat view (core messaging logic) ---
|
||||
const {
|
||||
@@ -37,10 +42,9 @@ const {
|
||||
isTyping,
|
||||
fileInputRef,
|
||||
showEmojiPicker,
|
||||
chatModels,
|
||||
selectedModelName,
|
||||
selectedModel,
|
||||
isLoadingModels,
|
||||
selectedRuntime,
|
||||
orchestrationStatus,
|
||||
orchestrationInsight,
|
||||
activeAgent,
|
||||
@@ -67,18 +71,18 @@ const {
|
||||
} = useKnowledgeView()
|
||||
|
||||
// --- Client time & weather ---
|
||||
const { clientTime, city, weatherIcon, weatherSummary, formatClientDate, formatClientClock } = useClientTime()
|
||||
const { clientTime, city, weatherIcon, weatherSummary } = useClientTime()
|
||||
|
||||
// --- Daily digest & reminders ---
|
||||
const { dailyDigest, digestLoading, activeReminder, reminderVisible, loadDailyDigest, handleSnooze, handleDismiss } = useDailyDigest()
|
||||
|
||||
// --- Sidebar plan (calendar, focus, review) ---
|
||||
const {
|
||||
calendarCells, calendarYear, calendarMonth, todayPlanCounters,
|
||||
calendarCells, issueStatusCounters,
|
||||
sidebarWeekLabels, sidebarStatusHeadline, sidebarStatusBreakdown,
|
||||
sidebarFocusItems, sidebarReviewAchievements, sidebarReviewReflections,
|
||||
sidebarFocusItems, issueStatusQuadrants, issueCommanderSummary, sidebarReviewAchievements, sidebarReviewReflections,
|
||||
sidebarFeedItems, topbarFeedItems, sidebarCollapsedModules,
|
||||
selectedDate, selectCalendarDate
|
||||
selectCalendarDate, loadSidebarPlanSnapshot
|
||||
} = useSidebarPlan(clientTime, loadDailyDigest, toRef(store, 'conversations'))
|
||||
|
||||
// --- Local UI state ---
|
||||
@@ -86,32 +90,93 @@ const sidebarCollapsed = ref(false)
|
||||
const orchestrationDrawerOpen = ref(false)
|
||||
const kanbanDrawerOpen = ref(false)
|
||||
const kanbanDetailOpen = ref(false)
|
||||
const kanbanDetailQuadrant = ref<{ id: string; title: string; color: string } | null>(null)
|
||||
const kanbanDetailState = ref<{ mode: 'create' | 'edit'; taskId?: string | null; quadrant?: TaskQuadrant | null } | null>(null)
|
||||
const knowledgeHudOpen = ref(false)
|
||||
const knowledgeRAGOpen = ref(false)
|
||||
const ragPanelRef = ref<any>(null)
|
||||
const selectedFolder = ref<any>(null)
|
||||
const previewDoc = ref<any>(null)
|
||||
const templeVisible = ref(false)
|
||||
|
||||
function openOrchestrationDrawer() { orchestrationDrawerOpen.value = true }
|
||||
function closeOrchestrationDrawer() { orchestrationDrawerOpen.value = false }
|
||||
function openKanbanDrawer() { kanbanDrawerOpen.value = true }
|
||||
function closeKanbanDrawer() { kanbanDrawerOpen.value = false }
|
||||
function openKanbanDetail(quadrantId: string) {
|
||||
const quadrantMap: Record<string, { title: string; color: string }> = {
|
||||
'urgent-important': { title: '重要且紧急', color: '#f56565' },
|
||||
'not-urgent-important': { title: '重要不紧急', color: '#ecc94b' },
|
||||
'urgent-not-important': { title: '紧急不重要', color: '#42b9f5' },
|
||||
'not-urgent-not-important': { title: '不重要不紧急', color: '#97c950' },
|
||||
}
|
||||
kanbanDetailQuadrant.value = { id: quadrantId, ...quadrantMap[quadrantId] }
|
||||
function openKanbanCreate(quadrantId: string) {
|
||||
kanbanDetailState.value = { mode: 'create', quadrant: quadrantId as TaskQuadrant }
|
||||
kanbanDetailOpen.value = true
|
||||
}
|
||||
function closeKanbanDetail() { kanbanDetailOpen.value = false }
|
||||
function openKanbanTask(taskId: string) {
|
||||
kanbanDetailState.value = { mode: 'edit', taskId }
|
||||
kanbanDetailOpen.value = true
|
||||
}
|
||||
function closeKanbanDetail() {
|
||||
kanbanDetailOpen.value = false
|
||||
kanbanDetailState.value = null
|
||||
refreshTodayStatus()
|
||||
}
|
||||
function refreshTodayStatus() {
|
||||
void loadSidebarPlanSnapshot(clientTime.value)
|
||||
}
|
||||
async function handleKanbanSaved() {
|
||||
await loadSidebarPlanSnapshot(clientTime.value)
|
||||
}
|
||||
async function handleKanbanDeleted() {
|
||||
await loadSidebarPlanSnapshot(clientTime.value)
|
||||
closeKanbanDetail()
|
||||
}
|
||||
function openKnowledgeHud() {
|
||||
selectedFolder.value = null
|
||||
previewDoc.value = null
|
||||
knowledgeHudOpen.value = true
|
||||
}
|
||||
function closeKnowledgeHud() { knowledgeHudOpen.value = false }
|
||||
function openKnowledgeRAG() { knowledgeRAGOpen.value = true }
|
||||
function closeKnowledgeRAG() {
|
||||
knowledgeRAGOpen.value = false
|
||||
}
|
||||
|
||||
// RAG chat mode - send message to knowledge base instead of chat
|
||||
async function sendRAGMessage() {
|
||||
if (!inputMessage.value.trim() || isSending.value) return
|
||||
|
||||
const userQuery = inputMessage.value.trim()
|
||||
inputMessage.value = ''
|
||||
isSending.value = true
|
||||
|
||||
try {
|
||||
// Add user message to RAG panel
|
||||
ragPanelRef.value?.addMessage({
|
||||
id: Date.now().toString(),
|
||||
role: 'user',
|
||||
content: userQuery,
|
||||
})
|
||||
|
||||
// Call RAG API
|
||||
const response = await documentApi.ragChat({
|
||||
query: userQuery,
|
||||
top_k: 5,
|
||||
})
|
||||
|
||||
// Add assistant response to RAG panel
|
||||
ragPanelRef.value?.addMessage({
|
||||
id: (Date.now() + 1).toString(),
|
||||
role: 'assistant',
|
||||
content: response.data.answer,
|
||||
sources: response.data.sources || [],
|
||||
})
|
||||
} catch (error) {
|
||||
// Fallback response
|
||||
ragPanelRef.value?.addMessage({
|
||||
id: (Date.now() + 1).toString(),
|
||||
role: 'assistant',
|
||||
content: '抱歉,搜索知识库时出现问题。请稍后重试。',
|
||||
sources: [],
|
||||
})
|
||||
} finally {
|
||||
isSending.value = false
|
||||
}
|
||||
}
|
||||
function handleSelectFolder(folder: any) { selectedFolder.value = folder }
|
||||
function handleOpenPreview(doc: any) { previewDoc.value = doc }
|
||||
|
||||
@@ -291,7 +356,7 @@ function renderMarkdown(content: string) {
|
||||
v-for="cell in calendarCells"
|
||||
:key="cell.key"
|
||||
class="calendar-day"
|
||||
:class="{ active: cell.active, busy: cell.busy, muted: cell.value === null, selected: cell.selected, clickable: cell.active || cell.hasConversation }"
|
||||
:class="{ active: cell.active, muted: cell.value === null, selected: cell.selected, clickable: cell.active || cell.hasConversation }"
|
||||
@click="(cell.active || cell.hasConversation) && handleCalendarDateSelect(cell.key)"
|
||||
>
|
||||
{{ cell.value ?? '' }}
|
||||
@@ -303,11 +368,11 @@ function renderMarkdown(content: string) {
|
||||
</div>
|
||||
|
||||
<div class="jarvis-panel jarvis-plan-panel" @click="openKanbanDrawer" style="cursor: pointer;">
|
||||
<div class="jarvis-section-title">TODAY'S STATUS</div>
|
||||
<div class="jarvis-section-title">ISSUE STATUS</div>
|
||||
<div class="jarvis-status-shell">
|
||||
<div class="jarvis-progress-ring" :style="{ '--completion': `${todayPlanCounters.completion}%` }">
|
||||
<div class="jarvis-progress-ring" :style="{ '--completion': `${issueStatusCounters.completion}%` }">
|
||||
<div class="jarvis-progress-core">
|
||||
<strong>{{ todayPlanCounters.completion }}%</strong>
|
||||
<strong>{{ issueStatusCounters.completion }}%</strong>
|
||||
<span>COMPLETION</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -386,6 +451,16 @@ function renderMarkdown(content: string) {
|
||||
<span>{{ selectedModel?.model || selectedModelName || 'Default' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-model-panel">
|
||||
<label class="chat-model-label" for="chat-runtime-select">
|
||||
<Sparkles :size="12" />
|
||||
<span>RUNTIME</span>
|
||||
</label>
|
||||
<select id="chat-runtime-select" v-model="selectedRuntime" class="chat-runtime-select">
|
||||
<option value="jarvis">Jarvis</option>
|
||||
<option value="hermes">Hermes</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-intel-strip" v-if="sidebarFeedItems.length > 0">
|
||||
@@ -513,10 +588,10 @@ function renderMarkdown(content: string) {
|
||||
|
||||
<!-- Top buttons above input -->
|
||||
<div class="top-buttons-row">
|
||||
<button class="top-action-btn" @click="$router.push('/temple')" title="Temple">
|
||||
<button class="top-action-btn" @click="templeVisible = true" title="Temple">
|
||||
<span class="btn-icon temple-icon">◈</span>
|
||||
</button>
|
||||
<button class="top-action-btn" @click="$router.push('/knowledge')" title="Knowledge">
|
||||
<button class="top-action-btn" @click="openKnowledgeRAG()" title="Knowledge">
|
||||
<span class="btn-icon knowledge-icon">◉</span>
|
||||
</button>
|
||||
<button class="top-action-btn" @click="$router.push('/war-room')" title="War Room">
|
||||
@@ -534,10 +609,10 @@ function renderMarkdown(content: string) {
|
||||
<textarea
|
||||
ref="inputRef"
|
||||
v-model="inputMessage"
|
||||
placeholder="输入指令,按 Enter 发送..."
|
||||
:placeholder="knowledgeRAGOpen ? '输入问题搜索知识库...' : '输入指令,按 Enter 发送...'"
|
||||
:disabled="isSending"
|
||||
rows="1"
|
||||
@keydown.enter.exact.prevent="sendMessage"
|
||||
@keydown.enter.exact.prevent="knowledgeRAGOpen ? sendRAGMessage() : sendMessage()"
|
||||
@input="autoResize"
|
||||
></textarea>
|
||||
<input
|
||||
@@ -570,16 +645,21 @@ function renderMarkdown(content: string) {
|
||||
class="send-btn"
|
||||
:class="{ active: inputMessage.trim() }"
|
||||
:disabled="!inputMessage.trim() || isSending"
|
||||
@click="sendMessage"
|
||||
@click="knowledgeRAGOpen ? sendRAGMessage() : sendMessage()"
|
||||
>
|
||||
<Send :size="15" />
|
||||
<CornerDownLeft :size="12" class="enter-hint" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-hints">
|
||||
<span class="hint-item">ENTER 发送</span>
|
||||
<span class="hint-sep">|</span>
|
||||
<span class="hint-item">SHIFT+ENTER 换行</span>
|
||||
<template v-if="knowledgeRAGOpen">
|
||||
<span class="hint-item rag-hint">知识库搜索模式</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="hint-item">ENTER 发送</span>
|
||||
<span class="hint-sep">|</span>
|
||||
<span class="hint-item">SHIFT+ENTER 换行</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -719,6 +799,15 @@ function renderMarkdown(content: string) {
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Knowledge RAG Panel (Slide-up from bottom) -->
|
||||
<KnowledgeRAGPanel
|
||||
v-if="knowledgeRAGOpen"
|
||||
ref="ragPanelRef"
|
||||
:is-chat-loading="isSending"
|
||||
@close="closeKnowledgeRAG"
|
||||
@send="sendRAGMessage"
|
||||
/>
|
||||
|
||||
<div class="agent-drawer-shell" :class="{ open: orchestrationDrawerOpen }">
|
||||
<button
|
||||
v-if="orchestrationDrawerOpen"
|
||||
@@ -757,8 +846,11 @@ function renderMarkdown(content: string) {
|
||||
<aside class="kanban-drawer" :class="{ open: kanbanDrawerOpen }">
|
||||
<KanbanPanel
|
||||
:visible="kanbanDrawerOpen"
|
||||
:quadrants="issueStatusQuadrants"
|
||||
:commander-summary="issueCommanderSummary"
|
||||
@close="closeKanbanDrawer"
|
||||
@open-detail="openKanbanDetail"
|
||||
@create-task="openKanbanCreate"
|
||||
@open-task="openKanbanTask"
|
||||
/>
|
||||
</aside>
|
||||
</div>
|
||||
@@ -766,12 +858,14 @@ function renderMarkdown(content: string) {
|
||||
<!-- Kanban Detail Modal (Teleported to body to avoid blur) -->
|
||||
<Teleport to="body">
|
||||
<KanbanDetail
|
||||
v-if="kanbanDetailQuadrant"
|
||||
v-if="kanbanDetailState"
|
||||
:visible="kanbanDetailOpen"
|
||||
:quadrant-id="kanbanDetailQuadrant.id"
|
||||
:quadrant-title="kanbanDetailQuadrant.title"
|
||||
:quadrant-color="kanbanDetailQuadrant.color"
|
||||
:mode="kanbanDetailState.mode"
|
||||
:task-id="kanbanDetailState.taskId"
|
||||
:default-quadrant="kanbanDetailState.quadrant"
|
||||
@close="closeKanbanDetail"
|
||||
@saved="handleKanbanSaved"
|
||||
@deleted="handleKanbanDeleted"
|
||||
/>
|
||||
</Teleport>
|
||||
|
||||
@@ -807,6 +901,12 @@ function renderMarkdown(content: string) {
|
||||
@dismiss="handleDismiss"
|
||||
/>
|
||||
|
||||
<!-- Temple Modal (智慧神殿) -->
|
||||
<TempleModal
|
||||
:visible="templeVisible"
|
||||
@close="templeVisible = false"
|
||||
/>
|
||||
|
||||
<div v-if="showNewFolderDialog" class="knowledge-hud-preview" @click.self="showNewFolderDialog = false">
|
||||
<div class="hud-dialog-jarvis">
|
||||
<div class="dialog-header-tech">
|
||||
|
||||
Reference in New Issue
Block a user