2025-03-01 vue2 deepssk对话 流数据渲染 小demo

示例图:

image.png
<template>
    <div class="chat-container">
        <!-- 消息区 -->
        <div ref="messageBox" class="messages">
            <div v-for="(msg, index) in messages" :key="index" :class="['message', msg.role]">
                <div class="bubble">
                    <div v-if="msg.role === 'assistan' && msg.isStreaming">
                        {{msg.content}}<span class="cursor">...</span>
                    </div>
                    <markdown-it-vue v-else :content="msg.content"></markdown-it-vue>
                </div>
            </div>
        </div>

        <!-- 输入区 -->
        <div class="input-area">
            <textarea v-model="input" @keydown.enter.exact.prevent="submit" placeholder="向DeepSeek提问..."></textarea>
            <button @click="submit" :disabled="isLoading">
                {{ isLoading ? '生成中...' : '发送' }}
            </button>
        </div>
    </div>
</template>

<script>
    import axios from 'axios'
    import MarkdownItVue from 'markdown-it-vue';


    export default {
        components: {
            MarkdownItVue
        },
        data() {
            return {
                DEEPSEEK_API_KEY: process.env.VUE_APP_API_KEY,
                messages: [],
                content: '',
                input: '',
                isLoading: false,
                messageBox: null
            }
        },
        mounted() {
            this.messageBox = this.$refs.messageBox
        },
        methods: {
            async submit() {
                if (!this.input.trim() || this.isLoading) return
                // 思考中 对话框
                const assistantMsg = {
                    role: 'assistan',
                    content: '正在思考',
                    isStreaming: true
                }
                
                try {
                    this.isLoading = true
                    
                    // 输入消息框
                    const userMsg = {
                        role: 'user',
                        content: this.input
                    }
                    this.messages.push(userMsg)
                    // 思考中 对话写入
                    this.messages.push(assistantMsg)


                    this.input = ''
                    this.$nextTick() // 确保DOM更

                    // let url = '/test.json'
                    let url = 'https://api.deepseek.com/chat/completions'
                    const response = await fetch(url, {
                        method: 'POST', // 或者 'PUT'
                        model: "deepseek-reasoner",
                        headers: {
                            'Authorization': `Bearer ${this.DEEPSEEK_API_KEY}`,
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify({
                            model: "deepseek-reasoner",
                            temperature: 0.7,
                            messages: [{
                                role: "user",
                                content: userMsg.content
                            }],
                            stream: true
                        }) // 将数据对象转换为JSON字符串
                    });

                    if (!response.ok) {
                        throw new Error('Network response was not ok');
                    }

                    const reader = response.body.getReader()
                    const decoder = new TextDecoder()
                    const contentChunks = []
                    
                    // 对话框数据写入
                    const contentMsg = {
                        role: 'assistant',
                        content: '|'
                    }
                    this.messages.push(contentMsg)
                    this.$nextTick()
                    
                    

                    while (true) {
                        const {done,value} = await reader.read()
                        if (done) break

                        const chunk = decoder.decode(value)
                        const lines = chunk.split('\n').filter(line => line.trim())

                        // 返回的数据  循环读取
                        for (const line of lines) {
                            if (line === ': keep-alive') { // 数据开始读取判断
                                continue;
                            }
                            try {
                                let newStr = line.replace('data: ', '')
                                if (newStr === '[DONE]') break // 数据到 DONE 结束循环
                                let dataL = JSON.parse(newStr);
                                if (dataL.choices && dataL.choices[0] && dataL.choices[0].delta && dataL.choices[0].delta.content) {
                                    //数据写入
                                    contentChunks.push(dataL.choices[0].delta.content)
                                    console.log('contentChunksArr', contentChunks)
                                    const string = contentChunks.join('');
                                    console.log('string', string)
                                    this.$nextTick(() => {
                                        contentMsg.content = string
                                        // 自动滚动到底部
                                        this.messageBox.scrollTop = this.messageBox.scrollHeight
                                    })

                                }
                            } catch (parseError) {
                                console.warn('Non-JSON response:', parseError)
                            }
                        }
                    }





                } catch (error) {
                    console.error('API Error:', error)
                    this.messages.push({
                        role: 'system',
                        content: '请求失败,请检查网络连接或API配置'
                    })
                } finally {
                    console.log('finally')
                    this.isLoading = false
                    assistantMsg.isStreaming = false
                    assistantMsg.content = '思考完成'
                }


            }
        }
    }
</script>

<style scoped>
    .chat-container {
        max-width: 800px;
        margin: 0 auto;
        height: 100vh;
        display: flex;
        flex-direction: column;
    }

    .messages {
        flex: 1;
        overflow-y: auto;
        padding: 20px;
    }

    .message {
        margin: 10px 0;
    }

    .message.assistant {
        text-align: left;
    }

    .message.user {
        text-align: right;
    }

    .bubble {
        display: inline-block;
        padding: 12px 18px;
        border-radius: 15px;
        max-width: 80%;
        background: #f0f2f5;
        /* 助手消息背景 */
    }

    .user .bubble {
        background: #1890ff;
        /* 用户消息背景 */
        color: white;
    }

    .cursor {
        animation: blink 1s infinite;
    }

    @keyframes blink {
        50% {
            opacity: 0
        }
    }

    .input-area {
        padding: 20px;
        border-top: 1px solid #eee;
        display: flex;
        gap: 10px;
    }

    textarea {
        flex: 1;
        padding: 12px;
        border: 1px solid #ddd;
        border-radius: 8px;
        resize: none;
        height: 80px;
    }

    button {
        padding: 0 24px;
        background: #1890ff;
        color: white;
        border: none;
        border-radius: 8px;
        cursor: pointer;
    }
</style>

.env 配置


image.png
VUE_APP_API_KEY = your api-key
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

友情链接更多精彩内容