uniapp接入阿里云百炼大模型并实现流式输出

安装marked
npm install marked --save

<template>
    <view class="container">
        <!-- 对话列表 -->
        <scroll-view class="chat-list" scroll-y="true" :scroll-into-view="scrollToId">

            <view v-for="(item, index) in messages" :key="index" :id="'msg' + index" class="message-wrap"
                :class="[item.role === 'assistant' ? 'left' : 'right']">
                <view class="message-box">
                    <rich-text class="message-content" :nodes="parseMarkdown(item.content)"
                        v-if="item.role === 'assistant'"></rich-text>
                    <text v-else class="message-content">{{ item.content }}</text>
                </view>
            </view>
        </scroll-view>

        <!-- 输入框 -->
        <!-- <view class="input-area">
      <input 
        class="input" 
        v-model="inputText" 
        placeholder="请输入问题"
        @confirm="sendQuestion"
      />
      <button class="send-btn" @click="sendQuestion">发送</button>
    </view> -->
    </view>
</template>

<script>
    import marked from 'marked';
    export default {
        data() {
            return {
                messages: [], // 消息列表
                // inputText: '请根据填写信息帮我制定一份个人专属营养品补充方案', // 输入内容
                inputText: uni.getStorageSync('AIContent'),
                scrollToId: '', // 自动滚动锚点
                isResponding: false, // 是否正在响应中
                ai_storage: '',
            };
        },
        onLoad() {
            const {
                keys
            } = uni.getStorageInfoSync()
            if (!keys.includes('AIContent')) {
                console.log("不存在")
                uni.redirectTo({
                    url: '/activity/wenjuan/wenjuan',
                    fail: function(err) { // 失败回调函数
                        console.error('Failed to redirect:', err); // 在控制台输出错误信息
                    },
                    success() {
                        console.log("success")
                    }
                });
                return
            }
            this.sendQuestion()
            this.ai()
        },
        methods: {
            ai() {

                const aiContent = uni.getStorageSync('AIContent');
                if (aiContent !== null | aiContent !== undefined) {
                    uni.redirectTo({
                        url: 'activity/wenjuan/wenjuan'
                    })
                }

                // 若没有配置环境变量,请用百炼API Key将下行替换为:const apiKey = "sk-xxx";
                const apiKey = "sk-111111111111111111111";
                // 设置请求头
                let that = this;
                that.requestTask = uni.request({
                    url: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
                    method: 'POST',
                    header: {
                        'Authorization': `Bearer ${apiKey}`,
                        'Content-Type': 'application/json',
                        'Accept': 'text/event-stream',
                    },
                    enableChunked: true, // 开启流传输
                    data: {
                        // 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
                        "model": "qwen-plus", //deepseek-r1  qwen-plus  qwen-max  qwen-turbo  deepseek-v3
                        "messages": [{
                            "role": "user",
                            "content": uni.getStorageSync('AIContent')
                        }],
                        "stream": true,
                        // "stream_options": [{
                        //      "include_usage": true
                        //  }]
                    },
                    success: (res) => {
                        // console.log(res.data.choices[0].message.content);
                        console.log('success Response:', res);
                        // console.log("结束")
                    },
                    fail: (err) => {
                        console.error('Request failed:', err);
                    }
                })
                that.requestTask.onChunkReceived((res) => {
                    const mockAnswer = this.decode(res);
                    const answerIndex = this.messages.length - 1;
                    for (let i = 0; i < mockAnswer.length; i++) {
                        new Promise(resolve => setTimeout(resolve, 30));
                        this.messages[answerIndex].content += mockAnswer[i];
                        if (i % 5 === 0) this.scrollToBottom();
                    }
                });
            },
            decode(res) {
                const text = this.decodeUTF8(res.data);
                const lines = text.split('\n');
                let result = '';
                for (let line of lines) {
                    if (line.startsWith('data: ')) {
                        const jsonData = line.slice(6).trim();
                        if (jsonData === '[DONE]') return result;
                        const cleanedData = jsonData.replace(/[\u0000-\u001F\u007F-\u009F]/g, '');
                        try {
                            const parsedData = JSON.parse(cleanedData);
                            result += parsedData.choices[0].delta.content || '';
                        } catch (e) {
                            console.error('解析失败:', e);
                        }
                    }
                }
                return result;
            },
            decodeUTF8(data) {
                const uint8Array = new Uint8Array(data);
                let string = '';
                for (let i = 0; i < uint8Array.length; i++) {
                    string += String.fromCharCode(uint8Array[i]);
                }
                return decodeURIComponent(escape(string));
            },


            // 发送问题
            async sendQuestion() {
                if (!this.inputText.trim() || this.isResponding) return;

                // 添加用户消息
                this.messages.push({
                    role: 'user',
                    content: this.inputText.trim()
                });

                const question = this.inputText;
                this.inputText = '';
                this.scrollToBottom();

                // 添加助手消息
                this.messages.push({
                    role: 'assistant',
                    content: '',
                    isStreaming: true
                });

                // 模拟流式响应
                this.isResponding = true;
                // await this.mockStreamResponse(question);
                this.isResponding = false;
            },


            // 模拟流式响应
            async mockStreamResponse(ai_answer) {
                const mockAnswer = ai_answer
                const answerIndex = this.messages.length - 1;
                for (let i = 0; i < mockAnswer.length; i++) {
                    await new Promise(resolve => setTimeout(resolve, 30));
                    this.messages[answerIndex].content += mockAnswer[i];
                    if (i % 5 === 0) this.scrollToBottom();
                }
            },

            // 解析Markdown
            parseMarkdown(content) {
                return marked.parse(content)
                    .replace(/<a href/g, '<a style="color: #007AFF; text-decoration: none" href')
                    .replace(/<code>/g, '<code style="background: #f5f5f5; padding: 2px 4px; border-radius: 4px">')
                    .replace(/<pre>/g,
                        '<pre style="background: #f5f5f5; padding: 10px; border-radius: 8px; overflow: auto">');
            },

            // 滚动到底部
            scrollToBottom() {
                const lastIndex = this.messages.length - 1;
                this.scrollToId = 'msg' + lastIndex;
            }
        }
    };
</script>

<style scoped>
    .container {
        flex: 1;
        height: 100vh;
        display: flex;
        flex-direction: column;
        background-color: #f5f5f5;
    }

    .chat-list {
        flex: 1;
        padding: 20rpx;
    }

    .message-wrap {
        margin: 20rpx 0;
        display: flex;
    }

    .message-wrap.left {
        justify-content: flex-start;
    }

    .message-wrap.right {
        justify-content: flex-end;
    }

    .message-box {
        max-width: 70%;
        padding: 20rpx;
        border-radius: 12rpx;
        background-color: #fff;
        box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
    }

    .message-wrap.right .message-box {
        background-color: #007AFF;
        margin-right: 30rpx;
        padding-right: 30rpx;
    }

    .message-content {
        font-size: 28rpx;
        line-height: 1.5;
        color: #333;
    }

    .message-wrap.right .message-content {
        color: #fff;
    }

    .input-area {
        padding: 20rpx;
        background-color: #fff;
        display: flex;
        align-items: center;
        border-top: 1rpx solid #eee;
    }

    .input {
        flex: 1;
        height: 80rpx;
        padding: 0 20rpx;
        background-color: #f5f5f5;
        border-radius: 40rpx;
        margin-right: 20rpx;
    }

    .send-btn {
        width: 140rpx;
        height: 80rpx;
        line-height: 80rpx;
        font-size: 28rpx;
        background-color: #007AFF;
        color: #fff;
        border-radius: 40rpx;
    }
</style>
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容