ai-chat-ui/src/components/chat/WelcomeScreen.vue

394 lines
7.5 KiB
Vue

<template>
<div class="welcome-screen">
<!-- Logo 和标题 -->
<div class="welcome-header">
<h1 class="title">教研聊天助手</h1>
</div>
<!-- 功能卡片 -->
<div class="feature-cards">
<div
v-for="feature in features"
:key="feature.title"
class="feature-card"
>
<div class="feature-icon" :style="{ background: feature.gradient }">
<component :is="feature.icon" :size="22" />
</div>
<h3>{{ feature.title }}</h3>
<p>{{ feature.description }}</p>
</div>
</div>
<!-- 快速开始建议 -->
<div class="quick-start">
<h4>试试这些问题</h4>
<div class="suggestions-grid">
<button
v-for="suggestion in suggestions"
:key="suggestion.text"
class="suggestion-card"
@click="$emit('select', { id: suggestion.id, text: suggestion.text, systemPrompt: suggestion.systemPrompt })"
>
<component :is="suggestion.iconComponent" :size="18" class="suggestion-icon" />
<span>{{ suggestion.text }}</span>
<ChevronRight :size="16" class="arrow-icon" />
</button>
</div>
</div>
<!-- 底部提示 -->
<div class="welcome-footer">
<div class="tip">
<Keyboard :size="14" />
<span>按 <kbd>Ctrl</kbd> + <kbd>/</kbd> 聚焦输入框</span>
</div>
<div class="tip">
<Zap :size="14" />
<span>支持 Markdown、代码高亮、LaTeX 公式</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from "vue";
import {
MessageSquare,
Code,
Image,
FileText,
ChevronRight,
Keyboard,
Zap,
Globe,
Lightbulb,
PenTool,
} from "@/components/icons";
import promptData from "@/assets/prompt.json";
import type { Suggestion } from "@/types/chat";
defineEmits<{
select: [suggestion: Suggestion];
}>();
const features = computed(() => [
{
icon: MessageSquare,
title: "智能对话",
description: "自然流畅的对话体验,理解上下文",
gradient: "linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)",
},
{
icon: Code,
title: "代码助手",
description: "编写、解释、优化各种编程语言代码",
gradient: "linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%)",
},
{
icon: Image,
title: "图像理解",
description: "分析图片内容,提取关键信息",
gradient: "linear-gradient(135deg, #ec4899 0%, #d946ef 100%)",
},
{
icon: FileText,
title: "文档处理",
description: "阅读、总结、翻译各类文档",
gradient: "linear-gradient(135deg, #f59e0b 0%, #f97316 100%)",
},
]);
// 图标映射,根据文本内容选择合适的图标
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const iconMap: Record<string, any> = {
学习: Lightbulb,
调试: Code,
写作: PenTool,
语言: Globe,
职业: Globe,
学术: FileText,
};
const suggestions = computed(() => {
// 内部类型,包含 icon 组件用于渲染
type SuggestionWithIcon = Suggestion & { iconComponent: typeof Code };
const allSuggestions: SuggestionWithIcon[] = [];
// 遍历 prompt.json 的分类和条目
for (const category of Object.values(promptData)) {
for (const [text, systemPrompt] of Object.entries(category)) {
// 根据文本关键词选择图标
let iconComponent = Code; // 默认图标
for (const [keyword, icon] of Object.entries(iconMap)) {
if (text.includes(keyword)) {
iconComponent = icon;
break;
}
}
allSuggestions.push({
id: text,
text,
systemPrompt,
iconComponent,
});
}
}
return allSuggestions;
});
</script>
<style lang="scss" scoped>
.welcome-screen {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100%;
padding: 40px 24px;
animation: fadeIn 0.5s ease;
}
.welcome-header {
text-align: center;
margin-bottom: 48px;
}
.logo-wrapper {
position: relative;
display: inline-flex;
margin-bottom: 20px;
}
.logo-icon {
display: flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
border-radius: 24px;
color: white;
box-shadow: 0 20px 40px -12px rgba(59, 130, 246, 0.35);
}
.logo-glow {
position: absolute;
inset: -20px;
background: radial-gradient(
circle,
rgba(59, 130, 246, 0.2) 0%,
transparent 70%
);
pointer-events: none;
}
.title {
margin: 0 0 12px;
font-size: 32px;
font-weight: 700;
color: #1f2937;
.dark & {
color: #f3f4f6;
}
}
.subtitle {
margin: 0;
font-size: 16px;
color: #6b7280;
.dark & {
color: #9ca3af;
}
}
.feature-cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
max-width: 900px;
width: 100%;
margin-bottom: 48px;
@media (max-width: 900px) {
grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 500px) {
grid-template-columns: 1fr;
}
}
.feature-card {
padding: 24px;
background: white;
border: 1px solid #e2e8f0;
border-radius: 16px;
text-align: center;
transition: all 0.3s ease;
.dark & {
background: #1e1e2e;
border-color: #2d2d3d;
}
&:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px -8px rgba(0, 0, 0, 0.1);
.dark & {
box-shadow: 0 12px 24px -8px rgba(0, 0, 0, 0.4);
}
}
.feature-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 14px;
color: white;
margin-bottom: 16px;
}
h3 {
margin: 0 0 8px;
font-size: 16px;
font-weight: 600;
color: #1f2937;
.dark & {
color: #f3f4f6;
}
}
p {
margin: 0;
font-size: 13px;
color: #6b7280;
line-height: 1.5;
.dark & {
color: #9ca3af;
}
}
}
.quick-start {
max-width: 710px;
width: 100%;
margin-bottom: 40px;
h4 {
margin: 0 0 16px;
font-size: 14px;
font-weight: 600;
color: #6b7280;
text-align: center;
.dark & {
color: #9ca3af;
}
}
}
.suggestions-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
.suggestion-card {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 20px;
background: white;
border: 1px solid #e2e8f0;
border-radius: 14px;
color: #374151;
font-size: 14px;
text-align: left;
cursor: pointer;
transition: all 0.2s ease;
.dark & {
background: #1e1e2e;
border-color: #2d2d3d;
color: #e5e7eb;
}
&:hover {
border-color: #3b82f6;
background: rgba(59, 130, 246, 0.05);
.arrow-icon {
transform: translateX(4px);
color: #3b82f6;
}
}
.suggestion-icon {
flex-shrink: 0;
color: #3b82f6;
}
span {
flex: 1;
}
.arrow-icon {
flex-shrink: 0;
color: #9ca3af;
transition: all 0.2s ease;
}
}
.welcome-footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 24px;
}
.tip {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: #9ca3af;
kbd {
padding: 2px 8px;
background: #f3f4f6;
border-radius: 4px;
font-size: 12px;
.dark & {
background: #374151;
}
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>