可以直接复制到uniapp项目中, 替换你的key就可以了, 免费key获取见文末...
<template>
<view class="chat-container">
<!-- 顶部导航栏 -->
<view class="chat-header">
<view class="header-left">
<view class="user-info">
<image
src="https://img1.baidu.com/it/u=2041764812,3088740490&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500"
class="avatar" mode="aspectFill"></image>
<view class="user-details">
<text class="username">AI助手</text>
<text class="status online">在线</text>
</view>
</view>
</view>
</view>
<!-- 聊天内容区域 -->
<scroll-view scroll-y class="chat-content" :scroll-top="scrollTop" @scrolltoupper="loadMoreMessages">
<view class="chat-messages">
<!-- 时间分隔 -->
<view class="time-divider" v-if="showDateDivider">
<text>{{ today }}</text>
</view>
<!-- 消息列表 -->
<view v-for="(message, index) in messages" :key="message.id"
:class="['message-item', message.sender === 'me' ? 'my-message' : 'other-message']">
<!-- 对方消息 -->
<template v-if="message.sender !== 'me'">
<image
src="https://img1.baidu.com/it/u=2041764812,3088740490&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500"
class="message-avatar" mode="aspectFill"></image>
<view class="message-content">
<view class="message-bubble">
<text>{{ displayContent(message) }}</text>
<view class="typing-indicator" v-if="message.typing">
<view class="dot"></view>
<view class="dot"></view>
<view class="dot"></view>
</view>
</view>
<view class="message-time">
{{ formatTime(message.time) }}
</view>
</view>
</template>
<!-- 我的消息 -->
<template v-else>
<view class="message-content my-content">
<view class="message-bubble my-bubble">
<text>{{ message.content }}</text>
</view>
<view class="message-time my-time">
{{ formatTime(message.time) }}
</view>
</view>
<image
src="https://img2.baidu.com/it/u=4022596008,3077835772&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500"
class="message-avatar my-avatar" mode="aspectFill"></image>
</template>
</view>
</view>
</scroll-view>
<!-- 输入区域 -->
<view class="input-area">
<view class="input-container">
<!-- 表情按钮 -->
<view class="emoji-btn" @click="toggleEmojiPanel">
<text>😃</text>
</view>
<!-- 输入框 -->
<input type="text" v-model="inputMessage" placeholder="输入消息..." class="message-input"
@confirm="sendMessage" @focus="hideEmojiPanel" placeholder-class="placeholder-style" />
<!-- 发送按钮 -->
<button class="send-btn" :class="{ active: inputMessage.trim() }" @click="sendMessage"
:disabled="isSending">
{{ isSending ? '发送中...' : '发送' }}
</button>
</view>
<!-- 表情面板 -->
<view class="emoji-panel" v-if="showEmojiPanel">
<scroll-view scroll-y class="emoji-list">
<view v-for="(emoji, index) in emojis" :key="index" class="emoji-item" @click="insertEmoji(emoji)">
<text>{{ emoji }}</text>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
inputMessage: '',
showEmojiPanel: false,
scrollTop: 0,
loading: false,
isSending: false,
typingMessages: {}, // 存储正在打字的消息 {id: {content: '', index: 0}}
typingSpeed: 30, // 打字速度(毫秒/字符)
messages: [{
id: 'msg_001',
sender: 'other',
content: '你好!我是AI助手,有什么可以帮您的吗?',
time: new Date().getTime() - 3600000 * 2
}],
emojis: [
'😀', '😃', '😄', '😁', '😆', '😅', '😂', '🤣', '😊', '😇',
'🙂', '🙃', '😉', '😌', '😍', '🥰', '😘', '😗', '😙', '😚',
'😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🤩',
'🥳', '😏', '😒', '😞', '😔', '😟', '😕', '🙁', '☹️', '😣',
'😖', '😫', '😩', '🥺', '😢', '😭', '😤', '😠', '😡', '🤬'
]
}
},
computed: {
today() {
const date = new Date()
return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`
},
showDateDivider() {
return this.messages.some(msg => {
const msgDate = new Date(msg.time)
const today = new Date()
return (
msgDate.getDate() === today.getDate() &&
msgDate.getMonth() === today.getMonth() &&
msgDate.getFullYear() === today.getFullYear()
)
})
}
},
methods: {
// 显示内容(实现打字机效果)
displayContent(message) {
if (message.sender === 'me' || !message.id) {
return message.content
}
// 如果是AI消息且有打字效果
if (this.typingMessages[message.id]) {
const typingMsg = this.typingMessages[message.id]
return typingMsg.content.substring(0, typingMsg.index)
}
return message.content
},
// 发送消息
sendMessage() {
if (!this.inputMessage.trim()) {
return uni.showToast({
title: '请输入消息',
duration: 1000,
icon: 'none'
})
}
this.isSending = true
const newMessage = {
id: 'msg_' + Date.now(),
sender: 'me',
content: this.inputMessage,
time: new Date().getTime()
}
this.messages.push(newMessage)
const userMessage = this.inputMessage
this.inputMessage = ''
this.showEmojiPanel = false
this.$nextTick(() => {
this.scrollToBottom(true)
})
// 添加AI正在输入中的状态
const typingMessageId = 'msg_typing_' + Date.now()
const typingMessage = {
id: typingMessageId,
sender: 'other',
content: '',
typing: true,
time: new Date().getTime()
}
this.messages.push(typingMessage)
this.scrollToBottom(true)
// 调用AI接口
this.getAIResponse(userMessage).then(aiResponse => {
// 移除正在输入状态,用打字机效果显示回复
this.receiveReply(aiResponse, typingMessageId.replace('typing_', ''))
}).catch(err => {
console.error('AI请求失败:', err)
// 移除正在输入状态的消息
this.messages = this.messages.filter(msg => !msg.typing)
// 添加错误提示(直接显示,不加打字效果)
this.receiveReply('抱歉,我暂时无法处理您的请求,请替换你的key')
}).finally(() => {
this.isSending = false
})
},
// 获取AI回复
getAIResponse(userMessage) {
return new Promise((resolve, reject) => {
uni.request({
url: 'https://api.siliconflow.cn/v1/chat/completions',
method: 'POST',
header: {
'Authorization': 'Bearer 你的key',
'Content-Type': 'application/json'
},
data: {
"model": "Qwen/QwQ-32B",
"messages": [{
"role": "user",
"content": userMessage
}],
"stream": false,
"max_tokens": 2512, // 根据模型调整 输出的token数
"stop": null,
"temperature": 0.7,
"top_p": 0.7,
"top_k": 50,
"frequency_penalty": 0.5,
"n": 1,
"response_format": {
"type": "text"
}
},
success: (res) => {
if (res.data && res.data.choices && res.data.choices[0] && res.data
.choices[0].message) {
// 去掉回复中的换行符
const replyContent = res.data.choices[0].message.content.replace(/\n/g, '')
if (replyContent) {
resolve(replyContent)
} else {
reject(new Error('AI回复为空'))
}
} else {
reject(new Error('无效的API响应'))
}
},
fail: (err) => {
reject(err)
}
})
})
},
// 接收回复(修改为打字机效果)
receiveReply(content, messageId) {
// 移除正在输入状态的消息
this.messages = this.messages.filter(msg => !msg.typing)
const replyMessage = {
id: messageId || ('msg_' + Date.now()),
sender: 'other',
content: content,
time: new Date().getTime()
}
this.messages.push(replyMessage)
this.scrollToBottom(true)
// 如果是AI回复且内容不为空,启动打字机效果
if (content && content.length > 0) {
this.startTypingEffect(replyMessage.id, content)
}
},
// 开始打字机效果
startTypingEffect(messageId, fullContent) {
// 初始化打字状态
this.$set(this.typingMessages, messageId, {
content: fullContent,
index: 0,
timer: null
})
// 开始打字
const typeNextChar = () => {
const typingMsg = this.typingMessages[messageId]
if (!typingMsg) return
if (typingMsg.index < typingMsg.content.length) {
typingMsg.index++
this.$forceUpdate() // 强制更新视图
this.scrollToBottom(true)
typingMsg.timer = setTimeout(typeNextChar, this.typingSpeed)
} else {
// 打字完成,清除状态
this.$delete(this.typingMessages, messageId)
}
}
// 延迟一点开始打字,让消息先显示出来
setTimeout(typeNextChar, 300)
},
// 滚动到底部
scrollToBottom(animate = false) {
setTimeout(() => {
const query = uni.createSelectorQuery().in(this)
query.select('.chat-content').boundingClientRect()
query.select('.chat-messages').boundingClientRect()
query.exec(res => {
if (res[0] && res[1]) {
this.scrollTop = res[1].height - res[0].height
if (animate) {
// 添加平滑滚动效果
this.$forceUpdate()
}
}
})
}, 50)
},
// 加载更多消息
loadMoreMessages() {
if (this.loading) return
this.loading = true
uni.showLoading({
title: '加载中...'
})
// 模拟加载更多消息
setTimeout(() => {
const oldMessages = [...this.messages]
const newMessages = [{
id: 'msg_' + (Date.now() - 86400000),
sender: Math.random() > 0.5 ? 'me' : 'other',
content: '这是更早的聊天记录',
time: Date.now() - 86400000
},
{
id: 'msg_' + (Date.now() - 86400000 * 2),
sender: Math.random() > 0.5 ? 'me' : 'other',
content: '昨天聊的内容',
time: Date.now() - 86400000 * 2
}
]
this.messages = [...newMessages, ...oldMessages]
this.loading = false
uni.hideLoading()
}, 800)
},
// 切换表情面板
toggleEmojiPanel() {
this.showEmojiPanel = !this.showEmojiPanel
if (this.showEmojiPanel) {
this.scrollToBottom()
}
},
hideEmojiPanel() {
this.showEmojiPanel = false
},
// 插入表情
insertEmoji(emoji) {
this.inputMessage += emoji
},
// 格式化时间
formatTime(timestamp) {
const date = new Date(timestamp)
const hours = date.getHours().toString().padStart(2, '0')
const minutes = date.getMinutes().toString().padStart(2, '0')
return `${hours}:${minutes}`
}
},
onLoad() {
this.scrollToBottom()
},
beforeDestroy() {
// 组件销毁前清除所有打字计时器
Object.values(this.typingMessages).forEach(msg => {
if (msg.timer) {
clearTimeout(msg.timer)
}
})
}
}
</script>
<style lang="scss" scoped>
.chat-container {
height: 100vh;
display: flex;
flex-direction: column;
background-color: #f5f5f5;
}
/* 顶部导航栏 */
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15rpx 30rpx;
background: linear-gradient(135deg, #ff6b6b 0%, #ff8e8e 100%);
height: 100rpx;
box-sizing: border-box;
box-shadow: 0 2rpx 10rpx rgba(255, 107, 107, 0.3);
position: relative;
z-index: 10;
.header-left {
display: flex;
align-items: center;
.user-info {
display: flex;
align-items: center;
margin-left: 20rpx;
.avatar {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
border: 2rpx solid rgba(255, 255, 255, 0.3);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.user-details {
display: flex;
flex-direction: column;
margin-left: 20rpx;
.username {
font-size: 32rpx;
font-weight: bold;
color: #fff;
}
.status {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.8);
&.online {
&::before {
content: '';
display: inline-block;
width: 12rpx;
height: 12rpx;
background-color: #7cfc00;
border-radius: 50%;
margin-right: 8rpx;
}
}
}
}
}
}
}
/* 聊天内容区域 */
.chat-content {
flex: 1;
padding: 20rpx;
overflow: hidden;
background-color: #f5f5f5;
background-image: radial-gradient(circle at 10% 20%, rgba(255, 218, 185, 0.1) 0%, transparent 20%),
radial-gradient(circle at 90% 80%, rgba(255, 192, 203, 0.1) 0%, transparent 20%);
position: relative;
&::-webkit-scrollbar {
width: 4rpx;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(255, 107, 107, 0.5);
border-radius: 4rpx;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
}
.chat-messages {
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.time-divider {
display: flex;
justify-content: center;
margin: 30rpx 0;
text {
background-color: rgba(0, 0, 0, 0.1);
color: #666;
font-size: 24rpx;
padding: 5rpx 20rpx;
border-radius: 20rpx;
backdrop-filter: blur(10rpx);
}
}
.message-item {
display: flex;
margin-bottom: 40rpx;
transition: all 0.3s ease;
transform-origin: center bottom;
&.my-message {
justify-content: flex-end;
animation: slideInRight 0.3s ease;
}
&.other-message {
justify-content: flex-start;
animation: slideInLeft 0.3s ease;
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(50rpx);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-50rpx);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.message-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
align-self: flex-end;
margin: 0 20rpx;
border: 2rpx solid rgba(255, 255, 255, 0.5);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
flex-shrink: 0;
&.my-avatar {
order: 1;
}
}
.message-content {
max-width: 65%;
display: flex;
flex-direction: column;
&.my-content {
align-items: flex-end;
}
}
.message-bubble {
padding: 15rpx 20rpx;
border-radius: 18rpx;
position: relative;
font-size: 30rpx;
line-height: 1.4;
word-break: break-word;
animation: bubbleIn 0.2s ease;
&.my-bubble {
background-color: #95ec69;
border-top-right-radius: 0;
box-shadow: 0 2rpx 8rpx rgba(149, 236, 105, 0.3);
}
&:not(.my-bubble) {
background-color: #fff;
border-top-left-radius: 0;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
}
/* 打字光标效果 */
&:after {
content: '';
position: absolute;
right: -8rpx;
bottom: 2rpx;
width: 4rpx;
height: 30rpx;
background-color: #333;
opacity: 0;
}
}
@keyframes bubbleIn {
from {
transform: scale(0.9);
opacity: 0.8;
}
to {
transform: scale(1);
opacity: 1;
}
}
@keyframes blinkCursor {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
.message-time {
font-size: 22rpx;
color: #999;
margin-top: 8rpx;
&.my-time {
text-align: right;
}
}
/* 输入区域 */
.input-area {
background-color: #fff;
border-top: 1rpx solid #eee;
padding-bottom: env(safe-area-inset-bottom);
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
position: relative;
z-index: 10;
}
.input-container {
display: flex;
align-items: center;
padding: 20rpx;
}
.emoji-btn {
padding: 10rpx;
margin-right: 20rpx;
transition: all 0.3s ease;
&:active {
transform: scale(0.9);
}
}
.message-input {
flex: 1;
background-color: #f5f5f5;
border-radius: 40rpx;
padding: 20rpx 30rpx;
font-size: 28rpx;
height: 80rpx;
box-sizing: border-box;
transition: all 0.3s ease;
&:focus {
background-color: #fff;
box-shadow: 0 0 0 2rpx rgba(255, 107, 107, 0.3);
}
}
.placeholder-style {
color: #bbb;
font-size: 28rpx;
}
.send-btn {
margin-left: 20rpx;
background-color: #f5f5f5;
color: #999;
border-radius: 40rpx;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
padding: 0 30rpx;
border: none;
transition: all 0.3s ease;
&.active {
background-color: #ff6b6b;
color: white;
box-shadow: 0 2rpx 10rpx rgba(255, 107, 107, 0.3);
}
&[disabled] {
opacity: 0.7;
}
}
/* 表情面板 */
.emoji-panel {
height: 400rpx;
border-top: 1rpx solid #eee;
background-color: #f9f9f9;
animation: slideUp 0.3s ease;
}
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
.emoji-list {
height: 100%;
display: flex;
flex-wrap: wrap;
padding: 20rpx;
box-sizing: border-box;
}
.emoji-item {
height: 80rpx;
display: inline-block;
font-size: 40rpx;
transition: all 0.2s ease;
&:active {
background-color: #eee;
border-radius: 10rpx;
transform: scale(1.2);
}
}
.typing-indicator {
display: flex;
padding: 10rpx 0 0 0;
.dot {
width: 10rpx;
height: 10rpx;
background-color: #999;
border-radius: 50%;
margin-right: 8rpx;
animation: typingAnimation 1.4s infinite ease-in-out;
&:nth-child(1) {
animation-delay: 0s;
}
&:nth-child(2) {
animation-delay: 0.2s;
}
&:nth-child(3) {
animation-delay: 0.4s;
margin-right: 0;
}
}
}
@keyframes typingAnimation {
0%,
60%,
100% {
transform: translateY(0);
opacity: 0.6;
}
30% {
transform: translateY(-10rpx);
opacity: 1;
}
}
</style>
key获取:
使用硅基流动的免费key额度,访问下面网站
https://cloud.siliconflow.cn/models
**硅基流动 AI 免费接口**(Silicon-Based Flow AI Free API)是一种基于硅基计算架构的开放人工智能接口,旨在为开发者、研究者和企业提供高效、低成本的 AI 计算服务。该技术依托硅基光子芯片、神经形态计算或分布式 AI 加速架构,通过云端或边缘设备提供免费访问,支持自然语言处理、计算机视觉、数据分析等 AI 任务。
与传统的 GPU/CPU 计算不同,硅基流动 AI 接口利用光计算或类脑芯片优化数据流,降低能耗并提升推理速度,适用于轻量化 AI 应用。免费接口通常提供基础模型调用、API 接入和开发工具,适用于学术研究、初创企业或个人开发者进行低成本 AI 实验。
目前,部分科技公司和研究机构正探索开放硅基 AI 接口,以推动生态发展。未来,随着硅基计算技术的成熟,免费 AI 接口可能成为普惠人工智能的重要推动力,降低行业技术门槛。
1- 登录
2- 点击api密钥--点击新建api密钥
3- 复制你的密钥到代码中,替换掉 '你的key' ,注意key前面有一个空格的
到此,就可以ai对话了。