模块介绍 / 今日规划 / 下班日报 / 团队摘要 生成成功后没有上报,也几乎不写「AI」日志。
流结束时的 usage 用了非阻塞发送,通道一满就被丢掉;很多服务商默认还不在流里带 usage。 现在每次真正打到模型的调用都会: 在本地日志(分类 AI,消息 AI 调用完成)写下 scene / prompt / completion / cached 登录后同步上报到后台日汇总
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { LayoutDashboard, ScrollText, Settings, X, Database, SlidersHorizontal, Home, ListTodo, TicketCheck, CalendarDays, CalendarCheck2, Sparkles, UserRound, ClipboardList, Wrench, Rocket, ChevronDown, Bell, StickyNote, ListFilter, Palette, Bot, Users, NotebookPen, CloudUpload, Power, Image, Activity, Package, Shield } from 'lucide-vue-next'
|
||||
import { LayoutDashboard, ScrollText, Settings, X, Database, SlidersHorizontal, Home, ListTodo, TicketCheck, CalendarDays, CalendarCheck2, Sparkles, UserRound, ClipboardList, Wrench, Rocket, ChevronDown, Bell, StickyNote, ListFilter, Palette, Bot, Users, NotebookPen, CloudUpload, Power, Image, Activity, Package, Shield, Plus, Pin, PinOff } from 'lucide-vue-next'
|
||||
import { useAppStore } from './store'
|
||||
import DatabaseSetup from './components/DatabaseSetup.vue'
|
||||
import BrowserBlocked from './components/BrowserBlocked.vue'
|
||||
@@ -107,14 +107,74 @@ const navGroups = [
|
||||
{ to: '/settings?tab=database', icon: Database, label: 'tabDatabase', match: r => r.path === '/settings' && settingsTab(r) === 'database' }
|
||||
] }
|
||||
]
|
||||
|
||||
// ---- 概览快捷入口:把其它分区菜单钉到「概览」二级导航 ----
|
||||
const SHORTCUT_KEY = 'cc-nav-shortcuts'
|
||||
const loadShortcuts = () => {
|
||||
try {
|
||||
const raw = JSON.parse(localStorage.getItem(SHORTCUT_KEY) || '[]')
|
||||
return Array.isArray(raw) ? raw.filter(x => typeof x === 'string') : []
|
||||
} catch { return [] }
|
||||
}
|
||||
const navShortcuts = ref(loadShortcuts())
|
||||
const shortcutPicker = ref(false)
|
||||
function saveShortcuts() {
|
||||
localStorage.setItem(SHORTCUT_KEY, JSON.stringify(navShortcuts.value))
|
||||
}
|
||||
const catalogByTo = computed(() => {
|
||||
const map = new Map()
|
||||
for (const g of navGroups) {
|
||||
for (const it of g.items) map.set(it.to, { ...it, groupId: g.id, groupLabel: g.label })
|
||||
}
|
||||
return map
|
||||
})
|
||||
// 可钉到概览的项:非概览内置、且当前用户可见
|
||||
const pinnableItems = computed(() => {
|
||||
const overviewTos = new Set(navGroups[0].items.map(it => it.to))
|
||||
const out = []
|
||||
for (const g of navGroups) {
|
||||
if (g.id === 'overview') continue
|
||||
for (const it of g.items) {
|
||||
if (overviewTos.has(it.to)) continue
|
||||
if (it.adminOnly && store.syncStatus.userId !== 1) continue
|
||||
out.push({ ...it, groupId: g.id, groupLabel: g.label })
|
||||
}
|
||||
}
|
||||
return out
|
||||
})
|
||||
function addShortcut(to) {
|
||||
if (navShortcuts.value.includes(to)) return
|
||||
navShortcuts.value = [...navShortcuts.value, to]
|
||||
saveShortcuts()
|
||||
shortcutPicker.value = false
|
||||
}
|
||||
function removeShortcut(to) {
|
||||
navShortcuts.value = navShortcuts.value.filter(x => x !== to)
|
||||
saveShortcuts()
|
||||
}
|
||||
function toggleShortcut(to) {
|
||||
if (navShortcuts.value.includes(to)) removeShortcut(to)
|
||||
else addShortcut(to)
|
||||
}
|
||||
|
||||
// adminOnly 项只对云端管理员(id=1)展示
|
||||
const visibleItems = g => g.items.filter(it => !it.adminOnly || store.syncStatus.userId === 1)
|
||||
const visibleItems = g => {
|
||||
let items = g.items.filter(it => !it.adminOnly || store.syncStatus.userId === 1)
|
||||
if (g.id === 'overview') {
|
||||
const extras = navShortcuts.value
|
||||
.map(to => catalogByTo.value.get(to))
|
||||
.filter(it => it && (!it.adminOnly || store.syncStatus.userId === 1))
|
||||
.map(it => ({ ...it, shortcut: true }))
|
||||
items = [...items, ...extras]
|
||||
}
|
||||
return items
|
||||
}
|
||||
const itemActive = it => it.match ? it.match(route) : route.path === it.to.split('?')[0]
|
||||
// 导航徽标:badge 显示数字,dot 只显示小圆点;一级 rail 在组内任一非零时亮点
|
||||
const badgeVal = it => it.badge ? (store.badges[it.badge] || 0) : 0
|
||||
const badgeText = it => { const n = badgeVal(it); return n > 99 ? '99+' : String(n) }
|
||||
const dotVal = it => it.dot ? !!store.badges[it.dot] : false
|
||||
const groupDot = g => g.items.some(it => badgeVal(it) > 0 || dotVal(it))
|
||||
const groupDot = g => visibleItems(g).some(it => badgeVal(it) > 0 || dotVal(it))
|
||||
const childActive = c => route.path === '/settings' && String(route.query.tab || '') === c.tab
|
||||
const groupOfRoute = () => navGroups.find(g => g.items.some(it => itemActive(it)))?.id
|
||||
const activeGroupId = ref(groupOfRoute() || 'overview')
|
||||
@@ -126,6 +186,7 @@ watch(() => route.fullPath, () => {
|
||||
const g = groupOfRoute()
|
||||
if (g) activeGroupId.value = g
|
||||
railFlyout.value = null
|
||||
if (g !== 'overview') shortcutPicker.value = false
|
||||
for (const grp of navGroups) {
|
||||
for (const it of grp.items) if (it.children && itemActive(it)) expandedParents.value[it.label] = true
|
||||
}
|
||||
@@ -263,16 +324,29 @@ watch(activeTask, task => {
|
||||
<div class="sub-title">{{ t(activeGroup.label) }}</div>
|
||||
<nav class="sub-nav" :aria-label="t(activeGroup.label)">
|
||||
<template v-for="it in visibleItems(activeGroup)" :key="it.to">
|
||||
<RouterLink :to="it.to" :class="{ active: itemActive(it) }">
|
||||
<RouterLink :to="it.to" :class="{ active: itemActive(it), shortcut: it.shortcut }">
|
||||
<component :is="it.icon" /><span>{{ t(it.label) }}</span>
|
||||
<em v-if="badgeVal(it)" class="nav-badge">{{ badgeText(it) }}</em>
|
||||
<i v-else-if="dotVal(it)" class="nav-dot" aria-hidden="true" />
|
||||
<button
|
||||
v-if="it.shortcut"
|
||||
type="button"
|
||||
class="nav-unpin"
|
||||
:title="t('navUnpinShortcut')"
|
||||
@click.prevent.stop="removeShortcut(it.to)"
|
||||
><PinOff /></button>
|
||||
<button v-if="it.children" type="button" class="sub-caret" :class="{ open: expandedParents[it.label] }" :aria-label="t(it.label)" @click="toggleParent(it, $event)"><ChevronDown /></button>
|
||||
</RouterLink>
|
||||
<div v-if="it.children && expandedParents[it.label]" class="sub-children">
|
||||
<RouterLink v-for="c in it.children" :key="c.to" :to="c.to" :class="{ active: childActive(c) }">{{ t(c.label) }}</RouterLink>
|
||||
</div>
|
||||
</template>
|
||||
<button
|
||||
v-if="activeGroup.id === 'overview'"
|
||||
type="button"
|
||||
class="nav-add-shortcut"
|
||||
@click="shortcutPicker = !shortcutPicker"
|
||||
><Plus />{{ t('navAddShortcut') }}</button>
|
||||
</nav>
|
||||
<div class="sidebar-bottom">
|
||||
<span class="version"><i />v{{ appVersion }}</span>
|
||||
@@ -348,6 +422,33 @@ watch(activeTask, task => {
|
||||
</section>
|
||||
</div>
|
||||
</Teleport>
|
||||
<Teleport to="body">
|
||||
<div v-if="shortcutPicker" class="overlay" @click.self="shortcutPicker = false">
|
||||
<section class="modal nav-shortcut-modal" @click.stop>
|
||||
<header class="modal-head">
|
||||
<h2><Pin />{{ t('navShortcutPicker') }}</h2>
|
||||
<button type="button" class="nm-close" :title="t('close')" @click="shortcutPicker = false"><X /></button>
|
||||
</header>
|
||||
<p class="nav-shortcut-hint">{{ t('navShortcutHint') }}</p>
|
||||
<div class="nav-shortcut-list">
|
||||
<button
|
||||
v-for="it in pinnableItems"
|
||||
:key="it.to"
|
||||
type="button"
|
||||
class="nav-shortcut-item"
|
||||
:class="{ on: navShortcuts.includes(it.to) }"
|
||||
@click="toggleShortcut(it.to)"
|
||||
>
|
||||
<component :is="it.icon" class="nav-shortcut-ico" />
|
||||
<span class="nav-shortcut-label">{{ t(it.label) }}</span>
|
||||
<small class="nav-shortcut-group">{{ t(it.groupLabel) }}</small>
|
||||
<Pin v-if="navShortcuts.includes(it.to)" class="nav-shortcut-mark on" />
|
||||
<Plus v-else class="nav-shortcut-mark" />
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Teleport>
|
||||
<main><RouterView /></main>
|
||||
<div v-if="visibleTask && !useFullscreenLoading" class="taskbar">
|
||||
<div><b>{{ activeTaskProject || visibleTask.stage }}</b><span>{{ t(visibleTask.messageKey || 'task.start', visibleTask.params || {}) }}</span></div>
|
||||
|
||||
Reference in New Issue
Block a user