feat: comprehensive documentation and demo updates
- Update READMEs and docs across multiple languages - Enhance interactive demos for Agent, LLM, VLM, Audio, Image Gen, Terminal, and Web Basics - Add new appendix sections for Database and IDE intros - Update VitePress config, theme, and utility scripts - Clean up unused assets and components
This commit is contained in:
@@ -6,8 +6,8 @@
|
||||
</div>
|
||||
|
||||
<div class="scenario-tabs">
|
||||
<button
|
||||
v-for="s in scenarios"
|
||||
<button
|
||||
v-for="s in scenarios"
|
||||
:key="s.id"
|
||||
class="tab-btn"
|
||||
:class="{ active: currentScenario === s.id }"
|
||||
@@ -20,39 +20,59 @@
|
||||
<div class="demo-container">
|
||||
<!-- Image Area -->
|
||||
<div class="image-area">
|
||||
<div class="image-placeholder" :class="{ loaded: hasImage, 'receipt-bg': currentScenario === 'ocr' }">
|
||||
<div
|
||||
class="image-placeholder"
|
||||
:class="{ loaded: hasImage, 'receipt-bg': currentScenario === 'ocr' }"
|
||||
>
|
||||
<div v-if="!hasImage" class="upload-prompt">
|
||||
<div class="icon">🖼️</div>
|
||||
<button class="upload-btn" @click="loadImage">
|
||||
上传图片 (模拟)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else class="image-content">
|
||||
<!-- Chat: Landscape -->
|
||||
<div v-if="currentScenario === 'chat'" class="real-image-container landscape">
|
||||
<div
|
||||
v-if="currentScenario === 'chat'"
|
||||
class="real-image-container landscape"
|
||||
>
|
||||
<div class="real-image">🏔️</div>
|
||||
<div class="sun">☀️</div>
|
||||
<div class="tree">🌲</div>
|
||||
</div>
|
||||
|
||||
<!-- Detection: Fruits -->
|
||||
<div v-else-if="currentScenario === 'detection'" class="real-image-container fruits">
|
||||
<div
|
||||
v-else-if="currentScenario === 'detection'"
|
||||
class="real-image-container fruits"
|
||||
>
|
||||
<div class="real-image">
|
||||
<span class="fruit apple">🍎</span>
|
||||
<span class="fruit banana">🍌</span>
|
||||
<span class="fruit grape">🍇</span>
|
||||
</div>
|
||||
<div v-if="showBoundingBox" class="bounding-box apple-box" title="Apple">
|
||||
<div
|
||||
v-if="showBoundingBox"
|
||||
class="bounding-box apple-box"
|
||||
title="Apple"
|
||||
>
|
||||
<span class="box-label">apple: 0.98</span>
|
||||
</div>
|
||||
<div v-if="showBoundingBox" class="bounding-box banana-box" title="Banana">
|
||||
<div
|
||||
v-if="showBoundingBox"
|
||||
class="bounding-box banana-box"
|
||||
title="Banana"
|
||||
>
|
||||
<span class="box-label">banana: 0.95</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Analysis: Factory Safety -->
|
||||
<div v-else-if="currentScenario === 'analysis'" class="factory-image">
|
||||
<div
|
||||
v-else-if="currentScenario === 'analysis'"
|
||||
class="factory-image"
|
||||
>
|
||||
<div class="safety-sign">⚠️ 安全生产</div>
|
||||
<div class="worker-container">
|
||||
<span class="worker">👷</span>
|
||||
@@ -67,7 +87,9 @@
|
||||
<div class="receipt-body">
|
||||
<div class="line"><span>Coffee</span><span>$4.50</span></div>
|
||||
<div class="line"><span>Bagel</span><span>$3.00</span></div>
|
||||
<div class="line total"><span>TOTAL</span><span>$7.50</span></div>
|
||||
<div class="line total">
|
||||
<span>TOTAL</span><span>$7.50</span>
|
||||
</div>
|
||||
<div class="line date"><span>2023-10-24</span></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,29 +107,45 @@
|
||||
<div v-if="messages.length === 0" class="empty-text">
|
||||
{{ hasImage ? '图片已就绪,请选择指令' : '请先上传图片' }}
|
||||
</div>
|
||||
<div v-for="(msg, index) in messages" :key="index" class="message" :class="msg.role">
|
||||
<div
|
||||
v-for="(msg, index) in messages"
|
||||
:key="index"
|
||||
class="message"
|
||||
:class="msg.role"
|
||||
>
|
||||
<div class="content">
|
||||
<div v-if="msg.isJson" class="json-content">
|
||||
<pre>{{ msg.content }}</pre>
|
||||
</div>
|
||||
<span v-else>{{ msg.content }}</span>
|
||||
<span v-if="msg.role === 'assistant' && isGenerating && index === messages.length - 1" class="cursor">|</span>
|
||||
<span
|
||||
v-if="
|
||||
msg.role === 'assistant' &&
|
||||
isGenerating &&
|
||||
index === messages.length - 1
|
||||
"
|
||||
class="cursor"
|
||||
>|</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<div class="quick-actions" v-if="hasImage && !isGenerating">
|
||||
<button v-for="q in currentQuestions" :key="q" @click="ask(q)" class="action-btn">
|
||||
<button
|
||||
v-for="q in currentQuestions"
|
||||
:key="q"
|
||||
@click="ask(q)"
|
||||
class="action-btn"
|
||||
>
|
||||
{{ q }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="status-text" v-else-if="isGenerating">
|
||||
AI 正在观察图片并思考...
|
||||
</div>
|
||||
<div class="status-text" v-else>
|
||||
等待图片上传...
|
||||
</div>
|
||||
<div class="status-text" v-else>等待图片上传...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -132,75 +170,80 @@ const messages = ref([])
|
||||
const messagesRef = ref(null)
|
||||
|
||||
const questionsMap = {
|
||||
chat: [
|
||||
"这里是哪里?",
|
||||
"描述一下天气",
|
||||
"写首关于这座山的诗"
|
||||
],
|
||||
detection: [
|
||||
"检测图中的水果",
|
||||
"数数有几个苹果",
|
||||
"输出检测框坐标"
|
||||
],
|
||||
ocr: [
|
||||
"提取所有文字",
|
||||
"总金额是多少?",
|
||||
"消费日期是哪天?"
|
||||
],
|
||||
analysis: [
|
||||
"工人是否佩戴安全帽?",
|
||||
"检测现场安全隐患",
|
||||
"输出风险评估报告"
|
||||
]
|
||||
chat: ['这里是哪里?', '描述一下天气', '写首关于这座山的诗'],
|
||||
detection: ['检测图中的水果', '数数有几个苹果', '输出检测框坐标'],
|
||||
ocr: ['提取所有文字', '总金额是多少?', '消费日期是哪天?'],
|
||||
analysis: ['工人是否佩戴安全帽?', '检测现场安全隐患', '输出风险评估报告']
|
||||
}
|
||||
|
||||
const answersMap = {
|
||||
chat: {
|
||||
"这里是哪里?": "这是一张高山风景照。远处是覆盖着皑皑白雪的山峰,可能是阿尔卑斯山或喜马拉雅山脉。山脚下有郁郁葱葱的松树林。",
|
||||
"描述一下天气": "天气看起来非常晴朗,阳光明媚(☀️),能见度很高。蓝天白云,是一个适合登山或滑雪的好天气。",
|
||||
"写首关于这座山的诗": "🏔️ 雪岭插云天,\n🌲 松涛响翠烟。\n☀️ 金阳融冷色,\n🏞️ 壮丽入心田。"
|
||||
'这里是哪里?':
|
||||
'这是一张高山风景照。远处是覆盖着皑皑白雪的山峰,可能是阿尔卑斯山或喜马拉雅山脉。山脚下有郁郁葱葱的松树林。',
|
||||
描述一下天气:
|
||||
'天气看起来非常晴朗,阳光明媚(☀️),能见度很高。蓝天白云,是一个适合登山或滑雪的好天气。',
|
||||
写首关于这座山的诗:
|
||||
'🏔️ 雪岭插云天,\n🌲 松涛响翠烟。\n☀️ 金阳融冷色,\n🏞️ 壮丽入心田。'
|
||||
},
|
||||
detection: {
|
||||
"检测图中的水果": {
|
||||
type: 'json',
|
||||
text: JSON.stringify({ objects: ['apple', 'banana', 'grape'], count: 3 }, null, 2),
|
||||
检测图中的水果: {
|
||||
type: 'json',
|
||||
text: JSON.stringify(
|
||||
{ objects: ['apple', 'banana', 'grape'], count: 3 },
|
||||
null,
|
||||
2
|
||||
),
|
||||
action: 'showBox'
|
||||
},
|
||||
"数数有几个苹果": "图中检测到 1 个苹果(🍎)。",
|
||||
"输出检测框坐标": {
|
||||
数数有几个苹果: '图中检测到 1 个苹果(🍎)。',
|
||||
输出检测框坐标: {
|
||||
type: 'json',
|
||||
text: JSON.stringify({
|
||||
objects: [
|
||||
{ label: 'apple', box: [15, 15, 85, 85] },
|
||||
{ label: 'banana', box: [95, 15, 165, 85] }
|
||||
]
|
||||
}, null, 2),
|
||||
text: JSON.stringify(
|
||||
{
|
||||
objects: [
|
||||
{ label: 'apple', box: [15, 15, 85, 85] },
|
||||
{ label: 'banana', box: [95, 15, 165, 85] }
|
||||
]
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
action: 'showBox'
|
||||
}
|
||||
},
|
||||
ocr: {
|
||||
"提取所有文字": {
|
||||
提取所有文字: {
|
||||
type: 'json',
|
||||
text: JSON.stringify({
|
||||
lines: [
|
||||
"RECEIPT",
|
||||
"Coffee $4.50",
|
||||
"Bagel $3.00",
|
||||
"TOTAL $7.50",
|
||||
"2023-10-24"
|
||||
]
|
||||
}, null, 2)
|
||||
text: JSON.stringify(
|
||||
{
|
||||
lines: [
|
||||
'RECEIPT',
|
||||
'Coffee $4.50',
|
||||
'Bagel $3.00',
|
||||
'TOTAL $7.50',
|
||||
'2023-10-24'
|
||||
]
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
},
|
||||
"总金额是多少?": "这张小票的总金额是 $7.50。",
|
||||
"消费日期是哪天?": "消费日期是 2023年10月24日。"
|
||||
'总金额是多少?': '这张小票的总金额是 $7.50。',
|
||||
'消费日期是哪天?': '消费日期是 2023年10月24日。'
|
||||
},
|
||||
analysis: {
|
||||
"工人是否佩戴安全帽?": "检测到画面中有一名工人(👷),已正确佩戴红色安全帽(⛑️)。",
|
||||
"检测现场安全隐患": {
|
||||
'工人是否佩戴安全帽?':
|
||||
'检测到画面中有一名工人(👷),已正确佩戴红色安全帽(⛑️)。',
|
||||
检测现场安全隐患: {
|
||||
type: 'json',
|
||||
text: JSON.stringify({ hazards: [], safety_score: 100, status: "SAFE" }, null, 2)
|
||||
text: JSON.stringify(
|
||||
{ hazards: [], safety_score: 100, status: 'SAFE' },
|
||||
null,
|
||||
2
|
||||
)
|
||||
},
|
||||
"输出风险评估报告": "✅ **安全合规**\n- 人员:1人\n- 防护装备:齐全\n- 机械设备:正常运行中\n- 风险等级:低"
|
||||
输出风险评估报告:
|
||||
'✅ **安全合规**\n- 人员:1人\n- 防护装备:齐全\n- 机械设备:正常运行中\n- 风险等级:低'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +257,9 @@ const getImageLabel = () => {
|
||||
return map[currentScenario.value]
|
||||
}
|
||||
|
||||
const currentQuestions = computed(() => questionsMap[currentScenario.value] || [])
|
||||
const currentQuestions = computed(
|
||||
() => questionsMap[currentScenario.value] || []
|
||||
)
|
||||
|
||||
const switchScenario = (id) => {
|
||||
currentScenario.value = id
|
||||
@@ -232,16 +277,16 @@ const loadImage = () => {
|
||||
const ask = async (question) => {
|
||||
messages.value.push({ role: 'user', content: question })
|
||||
isGenerating.value = true
|
||||
|
||||
|
||||
await wait(800) // Simulate vision encoding time
|
||||
|
||||
|
||||
const scenarioAnswers = answersMap[currentScenario.value]
|
||||
const rawAnswer = scenarioAnswers[question] || "我还在学习这个任务..."
|
||||
|
||||
const rawAnswer = scenarioAnswers[question] || '我还在学习这个任务...'
|
||||
|
||||
let content = ''
|
||||
let isJson = false
|
||||
let action = null
|
||||
|
||||
|
||||
if (typeof rawAnswer === 'object') {
|
||||
content = rawAnswer.text
|
||||
isJson = rawAnswer.type === 'json'
|
||||
@@ -249,10 +294,10 @@ const ask = async (question) => {
|
||||
} else {
|
||||
content = rawAnswer
|
||||
}
|
||||
|
||||
|
||||
messages.value.push({ role: 'assistant', content: '', isJson })
|
||||
const answerIdx = messages.value.length - 1
|
||||
|
||||
|
||||
// Streaming effect
|
||||
const stepSize = isJson ? 5 : 1 // JSON types faster
|
||||
for (let i = 0; i < content.length; i += stepSize) {
|
||||
@@ -260,15 +305,15 @@ const ask = async (question) => {
|
||||
scrollToBottom()
|
||||
await wait(20)
|
||||
}
|
||||
|
||||
|
||||
if (action === 'showBox') {
|
||||
showBoundingBox.value = true
|
||||
}
|
||||
|
||||
|
||||
isGenerating.value = false
|
||||
}
|
||||
|
||||
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
const scrollToBottom = () => {
|
||||
nextTick(() => {
|
||||
@@ -286,7 +331,8 @@ const scrollToBottom = () => {
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
.header {
|
||||
@@ -413,7 +459,7 @@ const scrollToBottom = () => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(to bottom, #87CEEB 50%, #e0e0e0 50%);
|
||||
background: linear-gradient(to bottom, #87ceeb 50%, #e0e0e0 50%);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
@@ -563,7 +609,7 @@ const scrollToBottom = () => {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
width: 160px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 11px;
|
||||
text-align: left;
|
||||
@@ -602,7 +648,7 @@ const scrollToBottom = () => {
|
||||
.image-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
background: rgba(255,255,255,0.8);
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
@@ -711,28 +757,53 @@ const scrollToBottom = () => {
|
||||
}
|
||||
|
||||
@keyframes popIn {
|
||||
from { transform: scale(0); opacity: 0; }
|
||||
to { transform: scale(1); opacity: 1; }
|
||||
from {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(20px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
from {
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@@ -755,4 +826,4 @@ const scrollToBottom = () => {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user