avatar

Charlie的博客

学习进步 学习进步 学习进步

  • 首页
  • 后端
  • 前端
  • 移动端
  • 操作系统
Home 原生JS实现ChatGPT API流式输出
文章

原生JS实现ChatGPT API流式输出

Posted 2024-03-27 Updated 2024-11- 18
By Administrator
15~19 min read

B站链接:https://www.bilibili.com/video/BV1u1421D723
无需库依赖!通过原生JS的接口来调用ChatGPT的API,并实现流式输出AI的回复内容。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>ChatGPT with Element UI</title>
    <script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</head>
<body>
<div id="app">
    <div class="response-area" id="response">
        <div style="padding: 10px; overflow: auto">
            <div class="messages" v-for="(item,index) in messages" v-if="index > 0">
                <div class="message" :style="item.role === 'user' ? {float: 'right'} : {float: 'left'}">
                    <div>
                        <div style="margin-bottom: 10px; font-weight: bold">{{ item.role === 'user' ? '你:' : 'AI:' }}</div>
                    </div>
                    <div style="color: gray" v-if="item.role === 'assistant'" v-html="formatContent(item.content)"></div>
                    <div style="color: gray" v-else>{{ item.content }}<p></p></div>
                </div>
            </div>
        </div>
    </div>

    <div class="request-area">
        <el-input type="textarea" @keyup.enter.native="askChatGPT" :autofocus="true"
                  :rows="5" v-model="userInput" placeholder="输入你的问题"></el-input>
        <div style="position: absolute; right: 10px; bottom: 15px">
            <el-button @click="askChatGPT" type="primary" v-loading="loading">发送</el-button>
        </div>
    </div>
</div>

<script>
    new Vue({
        el: '#app',
        data() {
            return {
                loading: false,
                userInput: '',
                apiKey: 'apiKey', // 替换为你的API密钥
                messages: [
                    {
                        "role": "system",
                        "content": "You are a helpful assistant."
                    }
                ],
                currentIndex: 0
            }
        },
        methods: {
            formatContent(content) {
                return marked.parse(content);
            },
            scrollToBottom() {
                var scrollTarget = document.getElementById("response");
                scrollTarget.scrollTop=scrollTarget.scrollHeight;
            },
            async askChatGPT() {
                var _this = this
                _this.loading = true
                _this.messages.push({
                    "role": "user",
                    "content": this.userInput
                })
                _this.currentIndex += 1
                _this.userInput = ''
                const response = await fetch('https://api.openai.com/v1/chat/completions', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': `Bearer ${this.apiKey}`
                    },
                    body: JSON.stringify({
                        model: 'gpt-3.5-turbo',
                        messages: this.messages,
                        max_tokens: 1000,
                        stream: true
                    })
                });
                if (response.ok) {
                    _this.scrollToBottom()
                    _this.loading = false
                    const reader = response.body.getReader();
                    const stream = new ReadableStream({
                        async start(controller) {
                            function fetchData() {
                                reader.read().then(({done, value}) => {
                                    if (done) {
                                        controller.close();
                                        return;
                                    }
                                    const chunkText = new TextDecoder().decode(value);
                                    chunkText.split('\n').forEach(line => {
                                        if (line) {
                                            if (line === 'data: [DONE]') {
                                                return
                                            }
                                            line = line.replaceAll('data: ', '');
                                            const data = JSON.parse(line);
                                            if (data.choices[0].finish_reason && data.choices[0].finish_reason === 'stop') {
                                                return;
                                            }
                                            if (data.choices[0].delta.content) {
                                                const text = data.choices[0].delta.content;
                                                _this.messages[_this.currentIndex].content += text;
                                                _this.scrollToBottom()
                                            }
                                        }
                                    });
                                    fetchData();
                                });
                            }
                            _this.messages.push({
                                "role": "assistant",
                                "content": ""
                            })
                            _this.currentIndex += 1
                            fetchData();
                        }
                    });
                    await new Response(stream).text();
                } else {
                    this.$message.error('发生错误')
                }
            }
        }
    });
</script>
<style>
    #app {
        width: 800px;
        margin: 0 auto;
    }

    .messages {
        overflow: auto;
    }

    .message {
        min-width: 100px;
        max-width: 80%;
        padding: 10px 10px 0 10px;
        background: #FFF;
        border-radius: 10px;
        overflow: auto;
        margin-bottom: 20px;
    }

    .request-area {
        height: 120px;
        width: 800px;
        position: fixed;
        bottom: 0;
    }

    .response-area {
        height: calc(100vh - 140px);
        width: 800px;
        background: rgba(0, 0, 0, 0.05);
        position: fixed;
        top: 10px;
        border-radius: 5px;
        color: #333;
        overflow: auto;
    }
</style>
</body>
</html>

前端
JS HTML
License:  CC BY 4.0
Share

Further Reading

Mar 27, 2024

原生JS实现ChatGPT API流式输出

使用原生JS对接ChatGPT API,实现流式输出。

Oct 14, 2022

原生HTML+CSS适配深色模式/暗色模式

原生HTML+CSS适配深色模式,原生HTML+CSS适配暗色模式,原生HTML+CSS适配夜间模式

OLDER

Linux禁用IPv6

NEWER

SwiftUI仿照Apple Music制作全屏过渡动效

Recently Updated

  • iptables规则持久化
  • Win11的OOBE阶段启用Administrator账户并跳过账户创建步骤
  • 复刻iOS圆环时间选择器
  • 打造可滑动切换的顶部TabBar
  • Java Lambda表达式:让你的代码像喝了红牛一样飞起来!

Trending Tags

iOS HTML macOS Redis Java JS Swift Windows Linux JDK

Contents

©2025 Charlie的博客. Some rights reserved.

Using the Halo theme Chirpy