前言
上一篇学了React,这一篇我们学习Vue 3 + 端侧AI!
1. 创建Vue Composables
javascript
// ai.js
import { ref, computed } from 'vue';
export function useAI() {
const isReady = ref(false);
const isLoading = ref(false);
const error = ref(null);
const session = ref(null);
async function initAI(options = {}) {
try {
isLoading.value = true;
error.value = null;
if (!('ai' in window)) {
throw new Error('浏览器不支持Chrome AI');
}
session.value = await window.ai.assistant.create({
systemPrompt: options.systemPrompt || '你是一个AI助手。',
temperature: options.temperature || 0.7
});
isReady.value = true;
} catch (err) {
error.value = err.message;
} finally {
isLoading.value = false;
}
}
async function sendMessage(prompt) {
if (!session.value) {
throw new Error('AI未初始化');
}
return await session.value.prompt(prompt);
}
async function* sendMessageStream(prompt) {
if (!session.value) {
throw new Error('AI未初始化');
}
const stream = session.value.promptStreaming(prompt);
for await (const chunk of stream) {
yield chunk;
}
}
function destroy() {
if (session.value) {
session.value.destroy();
session.value = null;
isReady.value = false;
}
}
return {
isReady,
isLoading,
error,
initAI,
sendMessage,
sendMessageStream,
destroy
};
}
2. Vue AI聊天组件
vue
<template>
<div class="max-w-3xl mx-auto p-6 h-screen flex flex-col">
<h1 class="text-2xl font-bold mb-6">🤖 AI聊天 (Vue版)</h1>
<div class="flex-1 overflow-y-auto space-y-4 mb-6">
<div v-if="messages.length === 0" class="text-center py-12 text-gray-500">
开始和AI聊天吧!
</div>
<div
v-for="(msg, index) in messages"
:key="msg.id || index"
:class="[
'p-4 rounded-lg max-w-[80%]',
msg.role === 'user' ? 'ml-auto bg-blue-600 text-white' : 'bg-gray-100'
]"
>
<p>{{ msg.content }}</p>
</div>
<div v-if="isGenerating" class="p-4 bg-gray-100 rounded-lg max-w-[80%]">
<div class="flex gap-2">
<span class="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></span>
<span class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.1s"></span>
<span class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.2s"></span>
</div>
</div>
<div ref="messagesEndRef"></div>
</div>
<div class="flex gap-2">
<input
v-model="input"
@keydown.enter="handleSend"
:disabled="!isReady || isGenerating"
placeholder="输入消息..."
class="flex-1 p-3 border rounded-lg disabled:opacity-50"
/>
<button
@click="handleSend"
:disabled="!input.trim() || !isReady || isGenerating"
class="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
发送
</button>
</div>
</div>
</template>
<script setup>
import { ref, watch, onMounted, nextTick } from 'vue';
import { useAI } from './ai';
const { isReady, initAI, sendMessageStream } = useAI();
const messages = ref([]);
const input = ref('');
const isGenerating = ref(false);
const messagesEndRef = ref(null);
onMounted(async () => {
if (!isReady.value) {
await initAI({
systemPrompt: '你是一个友好的AI助手。用中文回答。'
});
}
});
watch(messages, async () => {
await nextTick();
messagesEndRef.value?.scrollIntoView({ behavior: 'smooth' });
});
async function handleSend() {
if (!input.value.trim() || isGenerating.value || !isReady.value) return;
// 添加用户消息
const userMessage = { role: 'user', content: input.value };
messages.value.push(userMessage);
input.value = '';
isGenerating.value = true;
try {
// 添加AI回复
const aiMessageId = Date.now();
messages.value.push({ role: 'assistant', content: '', id: aiMessageId });
// 流式生成
let fullContent = '';
for await (const chunk of sendMessageStream(input.value)) {
fullContent += chunk;
const lastIndex = messages.value.length - 1;
if (messages.value[lastIndex].id === aiMessageId) {
messages.value[lastIndex].content = fullContent;
}
}
} catch (error) {
console.error('生成失败:', error);
} finally {
isGenerating.value = false;
}
}
</script>
3. 今日小结
这一篇我们学习了:
- Vue Composables封装AI
- Vue AI聊天组件
4. 下篇预告
下一篇,我们学习 端侧AI性能优化!