feat: 更新多个组件和API,优化用户体验和错误处理

This commit is contained in:
Wenjie Zhang 2026-01-27 21:46:10 +08:00
parent 52f7abf50e
commit 20268d6400
8 changed files with 140 additions and 76 deletions

View File

@ -17,7 +17,6 @@
- 系统层面添加 apikey在智能体、知识库调用中支持 apikey 以支持外部调用
- 支持更多类型的文档源的导入功能(支持后端配置的白名单的 URL 导入)
- 部分场景应该使用默认模型作为默认值而不是空值
- CommonRAG 添加更多检索类型,比如 BM25关键词grep 等
- 文件上传解析后,如何提示用户需要入库
- 检索测试中,添加问答
- 丰富当前智能体的 Prompt最好支持从 markdown 解析

View File

@ -101,7 +101,7 @@ export async function apiRequest(url, options = {}, requiresAuth = true, respons
} else if (response.status === 403) {
throw new Error('没有权限执行此操作')
} else if (response.status === 500) {
throw new Error('Server 500 Error, please check the log use `docker logs api-dev`')
throw new Error('服务器内部错误,请使用 docker logs api-dev 查看详细日志')
}
throw new Error(errorMessage)

View File

@ -1278,7 +1278,7 @@ watch(
max-width: 800px;
margin: 0 auto;
flex-grow: 1;
padding: 1rem;
padding: 1rem 1.25rem;
display: flex;
flex-direction: column;
}

View File

@ -1,5 +1,6 @@
<template>
<a-drawer :open="isOpen" :width="620" title="任务中心" placement="right" @close="handleClose">
<p>提醒任务执行成功只代表任务已执行完成但是任务内部可能有问题已捕获请注意观察日志</p>
<div class="task-center">
<div class="task-toolbar">
<div class="task-filter-group">

View File

@ -8,14 +8,15 @@ export const useInfoStore = defineStore('info', () => {
const isLoading = ref(false)
const isLoaded = ref(false)
const debugMode = ref(false)
const error = ref(null) // 错误信息
// 计算属性 - 组织信息
const organization = computed(
() =>
infoConfig.value.organization || {
name: '江南语析',
logo: '/favicon.svg',
avatar: '/avatar.jpg'
name: '',
logo: '',
avatar: ''
}
)
@ -23,75 +24,22 @@ export const useInfoStore = defineStore('info', () => {
const branding = computed(
() =>
infoConfig.value.branding || {
name: 'Yuxi-Know',
title: 'Yuxi-Know',
subtitle: '大模型驱动的知识库管理工具',
description: '结合知识库与知识图谱,提供更准确、更全面的回答'
name: '',
title: '',
subtitle: ''
}
)
// 计算属性 - 功能特性
const features = computed(
() =>
infoConfig.value.features || [
{
label: 'GitHub Stars',
value: '3200+',
description: '开发者社区的认可与支持',
icon: 'stars'
},
{
label: '已解决 Issues',
value: '250+',
description: '持续改进和问题解决能力',
icon: 'issues'
},
{
label: '累计 Commits',
value: '1200+',
description: '活跃的开发迭代和功能更新',
icon: 'commits'
},
{
label: '开源协议',
value: 'MIT 协议',
description: '完全免费,支持商业使用',
icon: 'license'
}
]
)
const features = computed(() => infoConfig.value.features || [])
const actions = computed(
() =>
infoConfig.value.actions || [
{
name: '演示视频',
icon: 'video',
url: 'https://www.bilibili.com/video/BV1DF14BTETq'
},
{
name: '文档中心',
icon: 'docs',
url: 'https://xerrors.github.io/Yuxi-Know/'
},
{
name: '提交 Issue',
icon: 'issue',
url: 'https://github.com/xerrors/Yuxi-Know/issues/new/choose'
},
{
name: '开发路线图',
icon: 'roadmap',
url: 'https://github.com/xerrors/Yuxi-Know#roadmap'
}
]
)
const actions = computed(() => infoConfig.value.actions || [])
// 计算属性 - 页脚信息
const footer = computed(
() =>
infoConfig.value.footer || {
copyright: '© 江南语析 2025 [WIP] v0.12.138'
copyright: ''
}
)

View File

@ -630,7 +630,6 @@ const goToDatabasePage = () => {
.db-selector {
display: flex;
align-items: center;
margin-right: 20px;
.label {
font-size: 14px;
@ -767,7 +766,7 @@ const goToDatabasePage = () => {
}
:deep(.ant-input) {
padding: 2px 10px;
padding: 2px 0px;
}
button {

View File

@ -1,6 +1,27 @@
<template>
<div class="home-container">
<div class="hero-section">
<!-- 加载中状态 -->
<div v-if="isLoading" class="loading-container">
<a-spin size="large" />
<p class="loading-text">正在连接服务...</p>
</div>
<!-- 错误状态 -->
<div v-else-if="error" class="error-container">
<a-result
status="error"
:title="error.title"
:sub-title="error.message"
>
<template #extra>
<a-button type="primary" @click="retryLoad">重试</a-button>
</template>
</a-result>
</div>
<!-- 正常内容 -->
<template v-else>
<div class="hero-section">
<div class="glass-header">
<div class="logo">
<img
@ -106,16 +127,19 @@
<p class="copyright">{{ infoStore.footer?.copyright || '© 2025 All rights reserved' }}</p>
</div>
</footer>
</template>
</div>
</template>
<script setup>
import { computed, onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { useInfoStore } from '@/stores/info'
import { useAgentStore } from '@/stores/agent'
import { useThemeStore } from '@/stores/theme'
import { healthApi } from '@/apis/system_api'
import { Result, Button } from 'ant-design-vue'
import UserInfoComponent from '@/components/UserInfoComponent.vue'
import ProjectOverview from '@/components/ProjectOverview.vue'
import {
@ -130,12 +154,54 @@ import {
ShieldCheck
} from 'lucide-vue-next'
const AResult = Result
const AButton = Button
const router = useRouter()
const userStore = useUserStore()
const infoStore = useInfoStore()
const agentStore = useAgentStore()
const themeStore = useThemeStore()
//
const isLoading = ref(true)
const error = ref(null)
const checkHealth = async () => {
try {
const response = await healthApi.checkHealth()
if (response.status !== 'ok') {
throw new Error('服务不可用')
}
} catch (e) {
error.value = {
title: '服务连接失败',
message: '后端服务无法响应,请检查服务是否正常运行'
}
throw e
}
}
const loadData = async () => {
isLoading.value = true
error.value = null
try {
//
await checkHealth()
//
await infoStore.loadInfoConfig()
} catch (e) {
console.error('加载失败:', e)
} finally {
isLoading.value = false
}
}
const retryLoad = () => {
loadData()
}
const goToChat = async () => {
//
if (!userStore.isLoggedIn) {
@ -167,9 +233,9 @@ const goToChat = async () => {
}
}
onMounted(async () => {
//
await infoStore.loadInfoConfig()
onMounted(() => {
//
loadData()
})
const iconKey = (value) => (typeof value === 'string' ? value.toLowerCase() : '')
@ -252,6 +318,30 @@ const actionLinks = computed(() => {
position: relative;
overflow-x: hidden;
}
//
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
gap: 1rem;
.loading-text {
color: var(--gray-600);
font-size: 0.95rem;
}
}
//
.error-container {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 2rem;
}
.glass-header {
display: flex;
justify-content: space-between;

View File

@ -18,7 +18,11 @@
<nav class="login-navbar">
<div class="navbar-content">
<div class="brand-container">
<h1 class="brand-text">{{ brandName }}</h1>
<h1 class="brand-text">
<span v-if="brandOrgName" class="brand-org">{{ brandOrgName }}</span>
<span v-if="brandOrgName && brandName !== brandOrgName" class="brand-separator"></span>
<span class="brand-main">{{ brandName }}</span>
</h1>
</div>
<div class="login-top-action">
<a-button type="text" size="small" class="back-home-btn" @click="goHome">
@ -255,12 +259,15 @@ const agentStore = useAgentStore()
const loginBgImage = computed(() => {
return infoStore.organization?.login_bg || '/login-bg.jpg'
})
const brandOrgName = computed(() => {
return infoStore.organization?.name?.trim() || ''
})
const brandName = computed(() => {
const orgName = infoStore.organization?.name?.trim()
const orgName = brandOrgName.value
const brandNameRaw = infoStore.branding?.name?.trim() || 'Yuxi-Know'
if (orgName && brandNameRaw && orgName !== brandNameRaw) {
return `${orgName} | ${brandNameRaw}`
return brandNameRaw
}
return orgName || brandNameRaw
@ -584,8 +591,28 @@ onUnmounted(() => {
margin: 0;
font-size: 20px;
font-weight: 600;
color: var(--main-color);
line-height: 1;
display: flex;
align-items: center;
gap: 12px;
.brand-org {
color: var(--gray-700);
font-weight: 600;
}
.brand-separator {
width: 4px;
height: 4px;
background-color: var(--gray-400);
border-radius: 50%;
font-weight: 600;
}
.brand-main {
color: var(--main-color);
font-weight: 600;
}
}
.back-home-btn {