1150 lines
28 KiB
Vue
1150 lines
28 KiB
Vue
<!--
|
||
filepath: h:\blews\akmon\uni_modules\ak-ai-news\components\AINewsDemo.vue
|
||
AI News System Demo Component
|
||
-->
|
||
<template>
|
||
<view class="ai-news-demo">
|
||
<view class="demo-header">
|
||
<text class="demo-title">🤖 AI新闻系统演示</text>
|
||
<text class="demo-subtitle">多语言AI驱动的新闻处理平台</text>
|
||
</view>
|
||
|
||
<!-- 功能选择标签 -->
|
||
<view class="tab-container">
|
||
<view
|
||
v-for="(tab, index) in tabs"
|
||
:key="tab.key"
|
||
class="tab-item"
|
||
:class="{ active: activeTab === tab.key }"
|
||
@click="switchTab(tab.key)"
|
||
>
|
||
<text class="tab-text">{{ tab.icon }} {{ tab.label }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 翻译服务演示 -->
|
||
<view v-if="activeTab === 'translation'" class="demo-section">
|
||
<view class="section-header">
|
||
<text class="section-title">🔄 翻译服务</text>
|
||
</view>
|
||
|
||
<view class="input-group">
|
||
<textarea
|
||
v-model="translationInput"
|
||
placeholder="请输入要翻译的文本..."
|
||
class="text-input"
|
||
:maxlength="500"
|
||
/>
|
||
</view>
|
||
|
||
<view class="language-selector">
|
||
<picker
|
||
:value="sourceLanguageIndex"
|
||
:range="languages"
|
||
range-key="label"
|
||
@change="onSourceLanguageChange"
|
||
>
|
||
<view class="picker-text">源语言: {{ languages[sourceLanguageIndex].label }}</view>
|
||
</picker>
|
||
|
||
<text class="arrow">→</text>
|
||
|
||
<picker
|
||
:value="targetLanguageIndex"
|
||
:range="languages"
|
||
range-key="label"
|
||
@change="onTargetLanguageChange"
|
||
>
|
||
<view class="picker-text">目标语言: {{ languages[targetLanguageIndex].label }}</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<button
|
||
class="action-button"
|
||
:disabled="!translationInput || isTranslating"
|
||
@click="performTranslation"
|
||
>
|
||
{{ isTranslating ? '翻译中...' : '开始翻译' }}
|
||
</button>
|
||
|
||
<view v-if="translationResult" class="result-box">
|
||
<text class="result-label">翻译结果:</text>
|
||
<text class="result-text">{{ translationResult.translatedText }}</text>
|
||
<view class="result-meta">
|
||
<text class="meta-item">质量分数: {{ translationResult.qualityScore.toFixed(2) }}</text>
|
||
<text class="meta-item">提供商: {{ translationResult.provider }}</text>
|
||
<text class="meta-item">成本: ${{ translationResult.costUSD.toFixed(4) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 内容分析演示 -->
|
||
<view v-if="activeTab === 'analysis'" class="demo-section">
|
||
<view class="section-header">
|
||
<text class="section-title">🔍 内容分析</text>
|
||
</view>
|
||
|
||
<view class="input-group">
|
||
<textarea
|
||
v-model="analysisInput"
|
||
placeholder="请输入要分析的新闻内容..."
|
||
class="text-input large"
|
||
:maxlength="1000"
|
||
/>
|
||
</view>
|
||
|
||
<button
|
||
class="action-button"
|
||
:disabled="!analysisInput || isAnalyzing"
|
||
@click="performAnalysis"
|
||
>
|
||
{{ isAnalyzing ? '分析中...' : '开始分析' }}
|
||
</button>
|
||
|
||
<view v-if="analysisResult" class="analysis-results">
|
||
<view class="analysis-item">
|
||
<text class="analysis-label">📊 情感分析:</text>
|
||
<text class="analysis-value" :class="getSentimentClass(analysisResult.sentimentLabel)">
|
||
{{ getSentimentText(analysisResult.sentimentLabel) }} ({{ analysisResult.sentimentScore.toFixed(2) }})
|
||
</text>
|
||
</view>
|
||
|
||
<view class="analysis-item">
|
||
<text class="analysis-label">📚 可读性:</text>
|
||
<progress class="progress-bar" :percent="analysisResult.readabilityScore * 100" />
|
||
<text class="progress-text">{{ (analysisResult.readabilityScore * 100).toFixed(1) }}%</text>
|
||
</view>
|
||
|
||
<view class="analysis-item">
|
||
<text class="analysis-label">🛡️ 可信度:</text>
|
||
<progress class="progress-bar" :percent="analysisResult.credibilityScore * 100" />
|
||
<text class="progress-text">{{ (analysisResult.credibilityScore * 100).toFixed(1) }}%</text>
|
||
</view>
|
||
|
||
<view class="analysis-item">
|
||
<text class="analysis-label">🏷️ 关键词:</text>
|
||
<view class="keyword-tags">
|
||
<text v-for="keyword in analysisResult.keywords.slice(0, 5)" :key="keyword" class="keyword-tag">
|
||
{{ keyword }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="analysisResult.categories.length > 0" class="analysis-item">
|
||
<text class="analysis-label">📂 分类:</text>
|
||
<view class="category-list">
|
||
<view v-for="category in analysisResult.categories" :key="category.categoryId" class="category-item">
|
||
<text class="category-name">{{ category.categoryName }}</text>
|
||
<text class="category-confidence">{{ (category.confidence * 100).toFixed(1) }}%</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="analysisResult.summary" class="analysis-item">
|
||
<text class="analysis-label">📝 摘要:</text>
|
||
<text class="summary-text">{{ analysisResult.summary }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 智能对话演示 -->
|
||
<view v-if="activeTab === 'chat'" class="demo-section">
|
||
<view class="section-header">
|
||
<text class="section-title">💬 智能对话</text>
|
||
</view>
|
||
|
||
<view class="chat-container">
|
||
<scroll-view
|
||
class="chat-messages"
|
||
scroll-y="true"
|
||
:scroll-top="scrollTop"
|
||
scroll-with-animation="true"
|
||
>
|
||
<view v-for="(message, index) in chatMessages" :key="index" class="message-item">
|
||
<view class="message" :class="message.type">
|
||
<text class="message-sender">{{ getMessageSender(message.type) }}</text>
|
||
<text class="message-content">{{ message.content }}</text>
|
||
<text class="message-time">{{ formatTime(message.timestamp) }}</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view class="chat-input-container">
|
||
<input
|
||
v-model="chatInput"
|
||
placeholder="问问AI关于新闻的任何问题..."
|
||
class="chat-input"
|
||
@confirm="sendChatMessage"
|
||
/>
|
||
<button
|
||
class="send-button"
|
||
:disabled="!chatInput || isChatting"
|
||
@click="sendChatMessage"
|
||
>
|
||
发送
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 推荐系统演示 -->
|
||
<view v-if="activeTab === 'recommendation'" class="demo-section">
|
||
<view class="section-header">
|
||
<text class="section-title">🎯 智能推荐</text>
|
||
</view>
|
||
|
||
<view class="recommendation-controls">
|
||
<button class="action-button" @click="generateRecommendations">
|
||
{{ isRecommending ? '生成中...' : '生成推荐' }}
|
||
</button>
|
||
|
||
<button class="action-button secondary" @click="getTrendingNews">
|
||
获取热门新闻
|
||
</button>
|
||
</view>
|
||
|
||
<view v-if="recommendations.length > 0" class="recommendation-list">
|
||
<view v-for="(rec, index) in recommendations" :key="rec.contentId" class="recommendation-item">
|
||
<view class="rec-header">
|
||
<text class="rec-title">{{ getMockNewsTitle(rec.contentId) }}</text>
|
||
<text class="rec-score">{{ (rec.score * 100).toFixed(0) }}%</text>
|
||
</view>
|
||
<text class="rec-reason">{{ rec.reason }}</text>
|
||
<view class="rec-meta">
|
||
<text class="rec-algorithm">{{ rec.algorithm }}</text>
|
||
<text class="rec-type">{{ getRecommendationTypeText(rec.recommendationType) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 系统状态 -->
|
||
<view class="system-status">
|
||
<text class="status-title">📊 系统状态</text>
|
||
<view class="status-grid">
|
||
<view class="status-item">
|
||
<text class="status-label">总请求数</text>
|
||
<text class="status-value">{{ systemStats.totalRequests }}</text>
|
||
</view>
|
||
<view class="status-item">
|
||
<text class="status-label">成功率</text>
|
||
<text class="status-value">{{ getSuccessRate() }}%</text>
|
||
</view>
|
||
<view class="status-item">
|
||
<text class="status-label">总成本</text>
|
||
<text class="status-value">${{ systemStats.totalCost.toFixed(4) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, onMounted, nextTick } from 'vue'
|
||
import {
|
||
AIServiceManager,
|
||
type AIServiceConfig,
|
||
type TranslationResult,
|
||
type ContentAnalysisResult,
|
||
type ChatMessage,
|
||
type RecommendationResult
|
||
} from '../index.uts'
|
||
|
||
// 响应式数据
|
||
const activeTab = ref('translation')
|
||
const isTranslating = ref(false)
|
||
const isAnalyzing = ref(false)
|
||
const isChatting = ref(false)
|
||
const isRecommending = ref(false)
|
||
|
||
// 翻译相关
|
||
const translationInput = ref('')
|
||
const translationResult = ref<TranslationResult | null>(null)
|
||
const sourceLanguageIndex = ref(0)
|
||
const targetLanguageIndex = ref(1)
|
||
|
||
// 分析相关
|
||
const analysisInput = ref('')
|
||
const analysisResult = ref<ContentAnalysisResult | null>(null)
|
||
|
||
// 聊天相关
|
||
const chatInput = ref('')
|
||
const chatMessages = ref<Array<ChatMessage & { type: string }>>([])
|
||
const scrollTop = ref(0)
|
||
const currentSessionId = ref('')
|
||
|
||
// 推荐相关
|
||
const recommendations = ref<RecommendationResult[]>([])
|
||
|
||
// 系统统计
|
||
const systemStats = reactive({
|
||
totalRequests: 0,
|
||
successfulRequests: 0,
|
||
totalCost: 0
|
||
})
|
||
|
||
// AI服务管理器
|
||
let serviceManager: AIServiceManager | null = null
|
||
|
||
// 常量定义
|
||
const tabs = [
|
||
{ key: 'translation', label: '翻译', icon: '🔄' },
|
||
{ key: 'analysis', label: '分析', icon: '🔍' },
|
||
{ key: 'chat', label: '对话', icon: '💬' },
|
||
{ key: 'recommendation', label: '推荐', icon: '🎯' }
|
||
]
|
||
|
||
const languages = [
|
||
{ code: 'zh-CN', label: '中文' },
|
||
{ code: 'en', label: 'English' },
|
||
{ code: 'ja', label: '日本語' },
|
||
{ code: 'ko', label: '한국어' },
|
||
{ code: 'es', label: 'Español' },
|
||
{ code: 'fr', label: 'Français' }
|
||
]
|
||
|
||
// 组件挂载时初始化
|
||
onMounted(async () => {
|
||
await initializeAIServices()
|
||
await initializeChat()
|
||
})
|
||
|
||
// 初始化AI服务
|
||
async function initializeAIServices() {
|
||
try {
|
||
const config: AIServiceConfig = {
|
||
openai: {
|
||
apiKey: 'demo-key', // 在实际使用中应该从配置文件读取
|
||
model: 'gpt-3.5-turbo',
|
||
maxTokens: 2000,
|
||
temperature: 0.7
|
||
},
|
||
costLimits: {
|
||
dailyUSD: 50,
|
||
monthlyUSD: 500,
|
||
perRequestUSD: 2
|
||
}
|
||
}
|
||
|
||
serviceManager = new AIServiceManager(config)
|
||
await serviceManager.initialize()
|
||
|
||
console.log('AI服务初始化成功')
|
||
} catch (error) {
|
||
console.error('AI服务初始化失败:', error)
|
||
uni.showToast({
|
||
title: '服务初始化失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
}
|
||
|
||
// 初始化聊天会话
|
||
async function initializeChat() {
|
||
if (!serviceManager) return
|
||
|
||
try {
|
||
const chatService = serviceManager.getChatService()
|
||
const sessionResponse = await chatService.createChatSession('demo-user', 'zh-CN')
|
||
|
||
if (sessionResponse.success && sessionResponse.data) {
|
||
currentSessionId.value = sessionResponse.data.id
|
||
|
||
// 添加欢迎消息
|
||
chatMessages.value.push({
|
||
id: 'welcome',
|
||
sessionId: sessionResponse.data.id,
|
||
type: 'assistant',
|
||
content: '您好!我是AI新闻助手,可以帮您了解最新新闻、分析新闻内容、翻译文本等。请问有什么可以帮您的吗?',
|
||
language: 'zh-CN',
|
||
timestamp: Date.now()
|
||
})
|
||
}
|
||
} catch (error) {
|
||
console.error('聊天初始化失败:', error)
|
||
}
|
||
}
|
||
|
||
// 切换标签
|
||
function switchTab(tabKey: string) {
|
||
activeTab.value = tabKey
|
||
}
|
||
|
||
// 语言选择处理
|
||
function onSourceLanguageChange(e: any) {
|
||
sourceLanguageIndex.value = e.detail.value
|
||
}
|
||
|
||
function onTargetLanguageChange(e: any) {
|
||
targetLanguageIndex.value = e.detail.value
|
||
}
|
||
|
||
// 执行翻译
|
||
async function performTranslation() {
|
||
if (!serviceManager || !translationInput.value) return
|
||
|
||
isTranslating.value = true
|
||
translationResult.value = null
|
||
|
||
try {
|
||
const translationService = serviceManager.getTranslationService()
|
||
const sourceLanguage = languages[sourceLanguageIndex.value].code
|
||
const targetLanguage = languages[targetLanguageIndex.value].code
|
||
|
||
const result = await translationService.translateText(
|
||
translationInput.value,
|
||
targetLanguage,
|
||
sourceLanguage,
|
||
{
|
||
provider: 'openai',
|
||
culturalAdaptation: true
|
||
}
|
||
)
|
||
|
||
if (result.success && result.data) {
|
||
translationResult.value = result.data
|
||
updateSystemStats(true, result.costUSD || 0)
|
||
|
||
uni.showToast({
|
||
title: '翻译完成',
|
||
icon: 'success'
|
||
})
|
||
} else {
|
||
throw new Error(result.error || '翻译失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('翻译失败:', error)
|
||
updateSystemStats(false, 0)
|
||
|
||
uni.showToast({
|
||
title: '翻译失败',
|
||
icon: 'error'
|
||
})
|
||
} finally {
|
||
isTranslating.value = false
|
||
}
|
||
}
|
||
|
||
// 执行内容分析
|
||
async function performAnalysis() {
|
||
if (!serviceManager || !analysisInput.value) return
|
||
|
||
isAnalyzing.value = true
|
||
analysisResult.value = null
|
||
|
||
try {
|
||
const analysisService = serviceManager.getAnalysisService()
|
||
|
||
const result = await analysisService.analyzeContent(analysisInput.value, {
|
||
types: ['sentiment', 'entities', 'topics', 'categories', 'readability', 'credibility', 'summary', 'keywords'],
|
||
language: 'zh-CN',
|
||
includeScores: true
|
||
})
|
||
|
||
if (result.success && result.data) {
|
||
analysisResult.value = result.data
|
||
updateSystemStats(true, result.costUSD || 0)
|
||
|
||
uni.showToast({
|
||
title: '分析完成',
|
||
icon: 'success'
|
||
})
|
||
} else {
|
||
throw new Error(result.error || '分析失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('分析失败:', error)
|
||
updateSystemStats(false, 0)
|
||
|
||
uni.showToast({
|
||
title: '分析失败',
|
||
icon: 'error'
|
||
})
|
||
} finally {
|
||
isAnalyzing.value = false
|
||
}
|
||
}
|
||
|
||
// 发送聊天消息
|
||
async function sendChatMessage() {
|
||
if (!serviceManager || !chatInput.value || !currentSessionId.value) return
|
||
|
||
const userMessage = chatInput.value
|
||
chatInput.value = ''
|
||
isChatting.value = true
|
||
|
||
// 添加用户消息
|
||
chatMessages.value.push({
|
||
id: `user-${Date.now()}`,
|
||
sessionId: currentSessionId.value,
|
||
type: 'user',
|
||
content: userMessage,
|
||
language: 'zh-CN',
|
||
timestamp: Date.now()
|
||
})
|
||
|
||
scrollToBottom()
|
||
|
||
try {
|
||
const chatService = serviceManager.getChatService()
|
||
const response = await chatService.sendMessage(currentSessionId.value, userMessage)
|
||
|
||
if (response.success && response.data) {
|
||
chatMessages.value.push(response.data)
|
||
updateSystemStats(true, response.costUSD || 0)
|
||
scrollToBottom()
|
||
} else {
|
||
throw new Error(response.error || '发送失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('聊天失败:', error)
|
||
updateSystemStats(false, 0)
|
||
|
||
// 添加错误消息
|
||
chatMessages.value.push({
|
||
id: `error-${Date.now()}`,
|
||
sessionId: currentSessionId.value,
|
||
type: 'error',
|
||
content: '抱歉,我现在无法回复您的消息,请稍后再试。',
|
||
language: 'zh-CN',
|
||
timestamp: Date.now()
|
||
})
|
||
|
||
scrollToBottom()
|
||
} finally {
|
||
isChatting.value = false
|
||
}
|
||
}
|
||
|
||
// 生成推荐
|
||
async function generateRecommendations() {
|
||
if (!serviceManager) return
|
||
|
||
isRecommending.value = true
|
||
recommendations.value = []
|
||
|
||
try {
|
||
const recommendationService = serviceManager.getRecommendationService()
|
||
|
||
// 模拟可用新闻内容
|
||
const mockNews = [
|
||
{ id: 'news1', title: 'AI技术新突破', categoryId: 'technology' },
|
||
{ id: 'news2', title: '经济政策分析', categoryId: 'economy' },
|
||
{ id: 'news3', title: '环保新举措', categoryId: 'environment' },
|
||
{ id: 'news4', title: '体育赛事报道', categoryId: 'sports' },
|
||
{ id: 'news5', title: '文化艺术活动', categoryId: 'culture' }
|
||
]
|
||
|
||
const result = await recommendationService.getPersonalizedRecommendations(
|
||
'demo-user',
|
||
mockNews as any,
|
||
{
|
||
algorithm: 'hybrid',
|
||
maxResults: 5,
|
||
diversityWeight: 0.3,
|
||
freshnessWeight: 0.4,
|
||
personalizedWeight: 0.3
|
||
}
|
||
)
|
||
|
||
if (result.success && result.data) {
|
||
recommendations.value = result.data
|
||
updateSystemStats(true, 0)
|
||
|
||
uni.showToast({
|
||
title: '推荐生成完成',
|
||
icon: 'success'
|
||
})
|
||
} else {
|
||
throw new Error(result.error || '推荐生成失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('推荐失败:', error)
|
||
updateSystemStats(false, 0)
|
||
|
||
uni.showToast({
|
||
title: '推荐失败',
|
||
icon: 'error'
|
||
})
|
||
} finally {
|
||
isRecommending.value = false
|
||
}
|
||
}
|
||
|
||
// 获取热门新闻
|
||
async function getTrendingNews() {
|
||
if (!serviceManager) return
|
||
|
||
isRecommending.value = true
|
||
|
||
try {
|
||
const recommendationService = serviceManager.getRecommendationService()
|
||
|
||
// 模拟新闻数据
|
||
const mockNews = [
|
||
{ id: 'trend1', title: '今日热点:科技创新', viewCount: 5000, likeCount: 300 },
|
||
{ id: 'trend2', title: '经济动态:市场分析', viewCount: 3500, likeCount: 200 },
|
||
{ id: 'trend3', title: '社会新闻:民生关注', viewCount: 4200, likeCount: 250 }
|
||
]
|
||
|
||
const result = await recommendationService.getTrendingRecommendations(
|
||
mockNews as any,
|
||
24,
|
||
5
|
||
)
|
||
|
||
if (result.success && result.data) {
|
||
recommendations.value = result.data
|
||
updateSystemStats(true, 0)
|
||
|
||
uni.showToast({
|
||
title: '热门新闻获取成功',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
} catch (error) {
|
||
console.error('获取热门新闻失败:', error)
|
||
updateSystemStats(false, 0)
|
||
} finally {
|
||
isRecommending.value = false
|
||
}
|
||
}
|
||
|
||
// 工具函数
|
||
function getSentimentClass(sentiment: string): string {
|
||
switch (sentiment) {
|
||
case 'positive': return 'sentiment-positive'
|
||
case 'negative': return 'sentiment-negative'
|
||
default: return 'sentiment-neutral'
|
||
}
|
||
}
|
||
|
||
function getSentimentText(sentiment: string): string {
|
||
switch (sentiment) {
|
||
case 'positive': return '积极'
|
||
case 'negative': return '消极'
|
||
default: return '中性'
|
||
}
|
||
}
|
||
|
||
function getMessageSender(type: string): string {
|
||
switch (type) {
|
||
case 'user': return '我'
|
||
case 'assistant': return 'AI助手'
|
||
case 'system': return '系统'
|
||
default: return '错误'
|
||
}
|
||
}
|
||
|
||
function formatTime(timestamp: number): string {
|
||
const date = new Date(timestamp)
|
||
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||
}
|
||
|
||
function getMockNewsTitle(contentId: string): string {
|
||
const titles: Record<string, string> = {
|
||
'news1': 'AI技术突破:新一代智能系统发布',
|
||
'news2': '经济分析:数字化转型推动增长',
|
||
'news3': '环保新政:绿色发展战略实施',
|
||
'news4': '体育快报:国际赛事精彩回顾',
|
||
'news5': '文化动态:传统艺术现代传承',
|
||
'trend1': '今日热点:科技创新引领未来',
|
||
'trend2': '经济动态:全球市场深度分析',
|
||
'trend3': '社会新闻:民生议题广泛关注'
|
||
}
|
||
return titles[contentId] || `新闻标题 ${contentId}`
|
||
}
|
||
|
||
function getRecommendationTypeText(type: string): string {
|
||
switch (type) {
|
||
case 'personalized': return '个性化'
|
||
case 'trending': return '热门'
|
||
case 'similar': return '相似'
|
||
case 'latest': return '最新'
|
||
default: return type
|
||
}
|
||
}
|
||
|
||
function getSuccessRate(): string {
|
||
if (systemStats.totalRequests === 0) return '0'
|
||
return ((systemStats.successfulRequests / systemStats.totalRequests) * 100).toFixed(1)
|
||
}
|
||
|
||
function updateSystemStats(success: boolean, cost: number) {
|
||
systemStats.totalRequests++
|
||
if (success) {
|
||
systemStats.successfulRequests++
|
||
}
|
||
systemStats.totalCost += cost
|
||
}
|
||
|
||
async function scrollToBottom() {
|
||
await nextTick()
|
||
scrollTop.value = 999999
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.ai-news-demo {
|
||
padding: 20rpx;
|
||
background-color: #f8f9fa;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.demo-header {
|
||
text-align: center;
|
||
margin-bottom: 30rpx;
|
||
|
||
.demo-title {
|
||
font-size: 48rpx;
|
||
font-weight: bold;
|
||
color: #2c3e50;
|
||
display: block;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.demo-subtitle {
|
||
font-size: 28rpx;
|
||
color: #7f8c8d;
|
||
display: block;
|
||
}
|
||
}
|
||
|
||
.tab-container {
|
||
display: flex;
|
||
background-color: white;
|
||
border-radius: 16rpx;
|
||
padding: 8rpx;
|
||
margin-bottom: 30rpx;
|
||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.tab-item {
|
||
flex: 1;
|
||
text-align: center;
|
||
padding: 20rpx;
|
||
border-radius: 12rpx;
|
||
transition: all 0.3s ease;
|
||
|
||
&.active {
|
||
background-color: #3498db;
|
||
|
||
.tab-text {
|
||
color: white;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
|
||
.tab-text {
|
||
font-size: 26rpx;
|
||
color: #7f8c8d;
|
||
}
|
||
}
|
||
|
||
.demo-section {
|
||
background-color: white;
|
||
border-radius: 16rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 30rpx;
|
||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.section-header {
|
||
margin-bottom: 30rpx;
|
||
|
||
.section-title {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #2c3e50;
|
||
}
|
||
}
|
||
|
||
.input-group {
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.text-input {
|
||
width: 100%;
|
||
min-height: 120rpx;
|
||
padding: 20rpx;
|
||
border: 2rpx solid #e0e6ed;
|
||
border-radius: 12rpx;
|
||
font-size: 28rpx;
|
||
background-color: #f8f9fa;
|
||
|
||
&.large {
|
||
min-height: 200rpx;
|
||
}
|
||
|
||
&:focus {
|
||
border-color: #3498db;
|
||
background-color: white;
|
||
}
|
||
}
|
||
|
||
.language-selector {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 30rpx;
|
||
|
||
.picker-text {
|
||
padding: 20rpx;
|
||
background-color: #f8f9fa;
|
||
border-radius: 12rpx;
|
||
font-size: 26rpx;
|
||
color: #2c3e50;
|
||
min-width: 200rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.arrow {
|
||
font-size: 32rpx;
|
||
color: #3498db;
|
||
margin: 0 20rpx;
|
||
}
|
||
}
|
||
|
||
.action-button {
|
||
width: 100%;
|
||
padding: 24rpx;
|
||
background-color: #3498db;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 12rpx;
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
margin-bottom: 20rpx;
|
||
|
||
&:disabled {
|
||
background-color: #bdc3c7;
|
||
}
|
||
|
||
&.secondary {
|
||
background-color: #95a5a6;
|
||
}
|
||
}
|
||
|
||
.result-box {
|
||
padding: 24rpx;
|
||
background-color: #f8f9fa;
|
||
border-radius: 12rpx;
|
||
border-left: 8rpx solid #3498db;
|
||
|
||
.result-label {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #2c3e50;
|
||
display: block;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.result-text {
|
||
font-size: 30rpx;
|
||
color: #2c3e50;
|
||
line-height: 1.6;
|
||
display: block;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.result-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 20rpx;
|
||
|
||
.meta-item {
|
||
font-size: 24rpx;
|
||
color: #7f8c8d;
|
||
background-color: white;
|
||
padding: 8rpx 16rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.analysis-results {
|
||
.analysis-item {
|
||
margin-bottom: 30rpx;
|
||
padding-bottom: 20rpx;
|
||
border-bottom: 2rpx solid #ecf0f1;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
|
||
.analysis-label {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #2c3e50;
|
||
display: block;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.analysis-value {
|
||
font-size: 30rpx;
|
||
font-weight: bold;
|
||
|
||
&.sentiment-positive {
|
||
color: #27ae60;
|
||
}
|
||
|
||
&.sentiment-negative {
|
||
color: #e74c3c;
|
||
}
|
||
|
||
&.sentiment-neutral {
|
||
color: #f39c12;
|
||
}
|
||
}
|
||
|
||
.progress-bar {
|
||
margin: 10rpx 0;
|
||
}
|
||
|
||
.progress-text {
|
||
font-size: 24rpx;
|
||
color: #7f8c8d;
|
||
margin-left: 10rpx;
|
||
}
|
||
}
|
||
|
||
.keyword-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12rpx;
|
||
|
||
.keyword-tag {
|
||
background-color: #3498db;
|
||
color: white;
|
||
padding: 8rpx 16rpx;
|
||
border-radius: 20rpx;
|
||
font-size: 24rpx;
|
||
}
|
||
}
|
||
|
||
.category-list {
|
||
.category-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 12rpx 0;
|
||
|
||
.category-name {
|
||
font-size: 26rpx;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.category-confidence {
|
||
font-size: 24rpx;
|
||
color: #7f8c8d;
|
||
}
|
||
}
|
||
}
|
||
|
||
.summary-text {
|
||
font-size: 26rpx;
|
||
color: #2c3e50;
|
||
line-height: 1.6;
|
||
background-color: #f8f9fa;
|
||
padding: 20rpx;
|
||
border-radius: 8rpx;
|
||
display: block;
|
||
}
|
||
|
||
.chat-container {
|
||
height: 600rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.chat-messages {
|
||
flex: 1;
|
||
padding: 20rpx;
|
||
background-color: #f8f9fa;
|
||
border-radius: 12rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.message-item {
|
||
margin-bottom: 20rpx;
|
||
|
||
.message {
|
||
max-width: 80%;
|
||
padding: 16rpx 20rpx;
|
||
border-radius: 20rpx;
|
||
|
||
&.user {
|
||
background-color: #3498db;
|
||
color: white;
|
||
margin-left: auto;
|
||
text-align: right;
|
||
}
|
||
|
||
&.assistant {
|
||
background-color: white;
|
||
color: #2c3e50;
|
||
border: 2rpx solid #ecf0f1;
|
||
}
|
||
|
||
&.error {
|
||
background-color: #e74c3c;
|
||
color: white;
|
||
}
|
||
|
||
.message-sender {
|
||
font-size: 20rpx;
|
||
opacity: 0.8;
|
||
display: block;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.message-content {
|
||
font-size: 26rpx;
|
||
line-height: 1.5;
|
||
display: block;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.message-time {
|
||
font-size: 20rpx;
|
||
opacity: 0.6;
|
||
}
|
||
}
|
||
}
|
||
|
||
.chat-input-container {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
|
||
.chat-input {
|
||
flex: 1;
|
||
padding: 20rpx;
|
||
border: 2rpx solid #e0e6ed;
|
||
border-radius: 12rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.send-button {
|
||
padding: 20rpx 30rpx;
|
||
background-color: #3498db;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 12rpx;
|
||
font-size: 26rpx;
|
||
|
||
&:disabled {
|
||
background-color: #bdc3c7;
|
||
}
|
||
}
|
||
}
|
||
|
||
.recommendation-controls {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
margin-bottom: 30rpx;
|
||
|
||
.action-button {
|
||
flex: 1;
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
|
||
.recommendation-list {
|
||
.recommendation-item {
|
||
background-color: #f8f9fa;
|
||
padding: 24rpx;
|
||
border-radius: 12rpx;
|
||
margin-bottom: 20rpx;
|
||
border-left: 8rpx solid #3498db;
|
||
|
||
.rec-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12rpx;
|
||
|
||
.rec-title {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #2c3e50;
|
||
flex: 1;
|
||
}
|
||
|
||
.rec-score {
|
||
font-size: 24rpx;
|
||
font-weight: bold;
|
||
color: #3498db;
|
||
background-color: white;
|
||
padding: 4rpx 12rpx;
|
||
border-radius: 12rpx;
|
||
}
|
||
}
|
||
|
||
.rec-reason {
|
||
font-size: 24rpx;
|
||
color: #7f8c8d;
|
||
margin-bottom: 12rpx;
|
||
display: block;
|
||
}
|
||
|
||
.rec-meta {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
|
||
.rec-algorithm,
|
||
.rec-type {
|
||
font-size: 20rpx;
|
||
color: #95a5a6;
|
||
background-color: white;
|
||
padding: 4rpx 8rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.system-status {
|
||
background-color: white;
|
||
border-radius: 16rpx;
|
||
padding: 30rpx;
|
||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
|
||
|
||
.status-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #2c3e50;
|
||
margin-bottom: 24rpx;
|
||
display: block;
|
||
}
|
||
|
||
.status-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 20rpx;
|
||
|
||
.status-item {
|
||
text-align: center;
|
||
padding: 20rpx;
|
||
background-color: #f8f9fa;
|
||
border-radius: 12rpx;
|
||
|
||
.status-label {
|
||
font-size: 24rpx;
|
||
color: #7f8c8d;
|
||
display: block;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.status-value {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #2c3e50;
|
||
display: block;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|