如何搭建个人的GPT网页服务

news2024/11/23 0:41:42

写在前面

在创建个人的 GPT网页之前,我登录了 Git 并尝试了一些开源项目,但是没有找到满足我个性化需求的设计。虽然许多收费的 GPT网页提供了一些免费额度,足够我使用,但是公司的安全策略会屏蔽这些网页。因此,我决定自己创建一个既可以从公司访问又能满足个性化需求的 聊天机器人网页服务,以提高我的工作效率。

经过一段时间的摸索,我成功搭建了自己的 GPT网页服务,并且这个服务整体来说非常稳定。在使用了几个月的时间里,只出现了一次网页崩溃的情况,而且只需重启服务就可以继续使用。

接下来,我将介绍我是如何搭建我的 GPT网页服务的。

在这里插入图片描述
在这里插入图片描述

需求分析

我的GPT网页服务主要是为个人使用,目的是为了提高工作效率,所以整体设计比较简单,主要包含

  1. 登录页面,防止其他人访问网页,网页被攻击。
  2. 聊天机器人页面,类似于聊天机器人官网的页面,向聊天机器人提交问题。
    在这里插入图片描述
  3. English Helper个性化页面,因为工作中每天都要接触到英语,所以创建该页面加强自己对英文词汇的理解。
    在这里插入图片描述

技术实现

整体使用的是最基础的页面架构,前端用的是HTML+CSS+JS,以及Bootstrap,后端用的是Flask。这样的好处是代码编写快捷,简单,只需要一个Pycharm编辑器就可以了。网页服务是搭建在Gunicorn上的,整体配置简单,方便。

就代码而言,整体结构也比较简单。主要分为一个Template包,里面包含的是一些前端的页面。后端服务的代码都写在了app.py里面。另外因为这里的所有需求都需要和聊天机器人进行交互,所以将和聊天机器人进行交互的逻辑单独写在了gpt_untils里面,用户配额和用户权限管理也是相对独立的一部分,将用户的相关逻辑写在了userconfig.py里面,因为这个网页只提供给少数人使用,用户的信息保存在userconfig.json文件里(而不是数据库里面)。
在这里插入图片描述

服务器选择的是阿里云服务器,因为阿里云服务器价格较为优惠,而且遇到问题有较多的参考文档。因为服务器是国内的,所以调用ChatGpt接口的时候需要使用代理,我使用过的代理有https://api.openai-proxy.com/v1和https://api2d.com,使用openai-proxy还需要自己去open-ai官网申请gpt账号,申请账号所花的成本大概为15块钱,使用https://api2d.com可以自己在上面充值,价格如下,充值一次有6个月的有效期,根据自己的需要选择即可。
在这里插入图片描述

代码详细解释

登录页面和首页面

登录页面代码如下,采用常用的登录页面的结构,失败后会给消息提示,页面的结构设计继承自base.html.

{% extends "base.html" %}

{% block content %}

<h3>用户登录</h3>
<form action="/login" method="post">
    <div class="row">
        <div class="col-8">
            请输入用户名:<input type="text" class="form-control" name="username">
            <br />
            请输入密码:<input type="password" class="form-control" name="password">
            <br />
            <div id="result">{{message}}</div>
            <br />
            <button type="submit" id="submitbtn" class="btn btn-primary">登录</button>
        </div>
    </div>
</form>
{% endblock %}

以下为base.html的页面,主要定义了整个网页的框架结构。需要注意页面中的JS部分show_quota_info和gpt_show_stream_info方法,一个是显示用户配额信息,一个是显示GPT返回的结果。

show_quota_info函数使用jQuery库中的ajax方法发送GET请求到"/show_quotas"路径。请求成功后,将返回的数据使用$(“#quota_info”).html(data)方法插入到id为quota_info的HTML元素中。

gpt_show_stream_info函数首先禁用id为submitbtn的HTML元素(按钮),然后将id为result的HTML元素的内容设置为"ChatGPT正在请求中,加载中…"。接下来,创建一个EventSource实例,其中的参数是传入的url。通过该实例,可以与服务器建立一个持久化的连接,以接收来自服务器的事件。

在EventSource的onmessage事件处理程序中,首先检查变量begin_output,如果为false,表示第一次收到消息,则将begin_output设置为true,并清空id为result的HTML元素的内容。

然后通过检查接收到的消息的内容,如果内容为"[DONE]",表示流式输出结束,关闭EventSource连接,启用id为submitbtn的HTML元素(按钮),并调用show_quota_info函数刷新配额信息。

如果接收到的消息不是"[DONE]",则将该消息追加到id为result的HTML元素的内容中。

<!doctype html>
<html lang="zh-CN">
<head>
    <!-- 必须的 meta 标签 -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap 的 CSS 文件 -->
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.css.map" rel="stylesheet">


    <title>DaPengGPT</title>

    <style>
        #topnav {
            background-color:#000;
            color: #fff;
            padding-top: 10px;
            padding-bottom: 10px;
            margin-bottom: 20px;
        }

        #topnav2{
            background-color:#000;
            color: #fff;
            padding-top: 10px;
            padding-bottom: 10px;
            margin-bottom: 20px;
        }
        #topnav2 a{
            color: #fff;
            font-size: 18px;
        }
    </style>
</head>
<body>

<div id="topnav" class="container-fluid">
    <div class="row">
        <div class="col-7">
            <h2>DaPengChatGPT</h2>
        </div>
        <div class="col-5 d-flex justify-content-end align-items-center">
            <div class="ml-auto pr-3">
                <h4>
                    <span>{{session["username"]}}</span>
                    {% if session["username"] is defined %}
                    &nbsp;&nbsp;|&nbsp;&nbsp;
                    <a href="/logout">logout</a>
                    {% endif %}
                </h4>
            </div>
        </div>
    </div>
    </br>
    <h5><a href="/">MyChatGPT</a>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <a href="/english_helper">English Helper</a>
    </h5>


</div>


<div class="container-fluid">
    <div id="content">{% block content %}{% endblock %}</div>
</div>

<!-- JavaScript 文件是可选的。从以下两种建议中选择一个即可! -->

<!-- 选项 1:jQuery 和 Bootstrap 集成包(集成了 Popper) -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.5.0/js/bootstrap.bundle.min.js"></script>

<script>
  // 展示最新的配额信息
  function show_quota_info(){
    $.ajax({
      url:"/show_quotas",
      type:"GET",
      success:function(data){
          $("#quota_info").html(data)
      }
    })
  }

  // 使用流式的方法,展示gpt的结果
  function gpt_show_stream_info(url){
    $("#submitbtn").prop('disabled', true)
    $("#result").html("ChatGPT正在请求中,加载中...")
    var source = new EventSource(url)
    var begin_output = false
    source.onmessage = function(event){
        if (begin_output === false){
            begin_output = true
            $("#result").html("")
        }
        if(event.data == "[DONE]"){
            source.close()
            $("#submitbtn").prop('disabled', false)
            show_quota_info()
        } else {
            $("#result").html($("#result").html() + event.data)
        }
    }
  }
</script>

{% block myjavascript %}{% endblock %}

<!-- 选项 2:Popper 和 Bootstrap 的 JS 插件各自独立 -->
<!--
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script>
-->
</body>
</html>
```html
如上代码中的show_quota_info和gpt_show_stream_info被主页面使用到,主页面的代码如下:

```html
{% extends "base.html" %}

{% block content %}
<style>
    #result{
        padding: 10px;
        background-color: #eee;
        margin-top: 10px;
        margin-bottom: 100px;
        word-wrap: break-word;
    }
    #rendered-content {
  display: none;
}




</style>
<form action="/chatgpt-clone" method="post">
    <div class="alert alert-success" id="quota_info"></div>
    <div>
        <textarea class="form-control" rows="2" cols="20" name="question" id="question"></textarea>
    </div>


    <div style="margin-top: 10px;" class="text-right">
        <button type="button" id="submitbtn" class="btn btn-primary">提交问题</button>
    </div>
    <div id="rendered-content"></div>
    <div id="result">返回的结果</div>
</form>
{% endblock %}

{% block myjavascript %}

<script src="https://cdn.bootcdn.net/ajax/libs/marked/3.0.2/marked.min.js"></script>

<script>
  var questionTextarea = document.getElementById('question'); // 获取 textarea 元素
  var renderedContent = document.getElementById('rendered-content'); // 获取渲染后内容的目标元素

  questionTextarea.addEventListener('input', function() {
    var inputValue = questionTextarea.value; // 获取 textarea 的输入值
    var renderedHTML = marked(inputValue);  // 使用 marked 库解析输入的 HTML 代码
    renderedContent.innerHTML = renderedHTML; // 将解析后的 HTML 渲染到目标元素中
  });

</script>


<script type="text/javascript">


    $(function(){
        show_quota_info()

        $("#submitbtn").click(function(){
           var questionText = $("#question").val(); // 获取 textarea 的值
           var formattedQuestion = questionText.replace(/\n/g, '<br>'); // 将换行符转换为 <br> 标签
           var url = "/chatgpt-clone?question=" + encodeURIComponent(formattedQuestion);
          gpt_show_stream_info(url)
        })
    })

</script>

{% endblock %}

代码中有两个JS,其中第一个JS处理提交问题的表单数据,防止前端页面的代码不被识别,第二个JS用于处理表单传过来的值,传给chatgpt-clone函数。

如上前端页面代码中对应的登录代码的逻辑如下,获取前端提交过来的用户名和密码,使用userconfig.check_user()方法,和配置文件中存取的用户名和密码进行对比。

@app.route("/login", methods=["GET", "POST"])
def login():
    message = ""
    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")
        if userconfig.check_user(username, password):
            session["username"] = username
            return redirect("/")
        else:
            message = "用户名或者密码错误"
    return render_template("login.html", message=message)

对应页面MyChatGPT 后端代码处理逻辑如下:

@app.route("/chatgpt-clone", methods=["POST", "GET"])
def chatgpt_clone():
    if "username" not in session:
        return redirect("/login")

    username = session["username"]
    question = request.args.get("question", "")
    question = str(question).strip()
    if question:
        new_question = question
        if 'J:' in question[:2]:
            new_question = f"我们店的名字叫做茶园路-重庆鸡公煲,现在我们店新出了菜品{question[2:]},请帮我生成不少于100字的广告语,包含我们店的名字,强调菜品好吃,实惠,量大,特别有满足感,吃了还想吃"
        elif 'C:' in question[:2]:
            new_question = f"请帮我指出以下代码的问题,并优化代码{question[2:]}"
        return flask.Response(
            gpt_utils.gpt_stream(username, new_question),
            mimetype="text/event-stream")
    return "没有内容"

代码中除了单纯将前端提交的问题转给聊天机器人,另外当提交的问题有J:,C:前缀的时候,代码中会对前端提交的问题进行二次处理,然后转给聊天机器人。

类似于MyChatGPT 页面的后端代码,english_helper的后端代码如下:

@app.route("/english_helper", methods=["POST", "GET"])
def english_helper():
    if "username" not in session:
        return redirect("/login")
    # 获取用户上传过的文件列表
    username = session["username"]
    if request.args.get("word", ""):
        word = request.args.get("word", "")
        my_understanding = request.args.get("my_understanding", "")
        if 'O:' in word:
            new_word = word.replace('O:', '')
            sentence = f"""I want to use the following expressions in my email and meetings, but I am not sure if they are appropriate. Please help me optimize the expressions using declarative sentences and techniques from the book "Nonviolent Communication”.{new_word}"""
        else:
            sentence = f"""I'm an IT guy works in english environment, i want to know the meaning of '{word}' used in meeting or email,
        and how to pronounce it"""
        if my_understanding != '':
            sentence = sentence + f"""per my understanding it means {my_understanding}, is that correct?"""
        print(sentence)
        return flask.Response(
            gpt_utils.gpt_stream(username, sentence),
            mimetype="text/event-stream")
    return render_template("english_helper.html")

这段代码是将MyChatGPT 的表单分成两个输入框,根据数据框中输入的内容进行不同的处理,然后传给GPT接口。
现在市面上有很多GPT应用其实都是换汤不换药,仅仅是改了前端页面和业务范围,后端处理的逻辑与上方后端代码处理类似。

整体项目中有一段核心代码如下所示:

def gpt_stream(username, question):
    try:
        userconfig.check_quota(username)
    except Exception as e:
        yield "data: %s\n\n" % str(e)
        yield "data: [DONE]\n\n"
        return

    openai.api_key = os.getenv("OPENAI_API_KEY")
    openai.api_base = "https://api.openai-proxy.com/v1"

    result = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "user", "content": question}
        ],
        stream=True,
        user=username
    )
    for chunk in result:
        if chunk["choices"][0]["finish_reason"] is not None:
            data = "[DONE]"
        else:
            data = chunk["choices"][0]["delta"].get("content", "")
        yield "data: %s\n\n" % data.replace("\n", "<br/>").replace(" ", "&nbsp;")

这段代码从open-ai官网获取并调试的,是个人项目和open-ai接口交互的部分,向接口传入提问的问题并按照一定格式返回GPT的答案。

最后在terminal敲入便可以运行项目。
在这里插入图片描述
在这里插入图片描述

写在后面

尽管自己搭建的chatgpt相比其他网站还存在一些不足之处,但它能够满足个性化需求,并且可以在公司内网访问,显著提高了工作效率。在开发chatGpt个人网页的过程中,我进一步巩固了自己的前后端知识,并为将来快速开发增强了信心。在开发过程中遇到了一些问题,但通过chatGpt工具,我得到了很多解答,进而提高了开发效率。同时,我还能够推测出市面上其他AI工具背后的逻辑。以下是项目的代码,请自行获取:

链接:https://pan.baidu.com/s/1UREj4rPbZn18lqe1cGaSlg?pwd=8hzg
提取码:8hzg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/841999.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【开源项目--稻草】Day06

【开源项目--稻草】Day06 1. 学生提问与解答功能2. 显示create.html2.1 HomeController中代码2.2 复用网页的标签导航条 3. 创建问题发布界面3.1 富文本编辑器 4.多选下列框5.动态加载所有标签和老师6. 发布问题的业务处理 1. 学生提问与解答功能 学生提问: 提问时指定标签和回…

LeetCode-Java(02)

5. 最长回文子串 首先用start和end记录开始和结尾位置&#xff0c;遍历每一个字符&#xff0c;对于每一个字符有两种情况&#xff0c;第一种情况&#xff0c;从一个字符中心扩展&#xff0c;得到len1&#xff0c;第二种情况&#xff0c;从两个字符中心扩展&#xff0c;得到len2…

MFC第二十七天 通过动态链表实现游戏角色动态增加、WM_ERASEBKGND背景刷新的原理、RegisterClass注册窗口与框架程序开发

文章目录 通过动态链表实现游戏角色动态增加CMemoryDC.hCFlashDlg.hCFlashDlg.cpp WM_ERASEBKGND背景刷新的原理RegisterClass注册窗口与框架程序开发CFrameRegister 通过动态链表实现游戏角色动态增加 CMemoryDC.h #pragma once#include "resource.h"/*内存DC类简介…

【Shell】基础语法(二)

文章目录 一、Shell基本语法文件名代换命令代换算术代换转义字符引号 二、Shell脚本语法条件测试分支结构循环 三、总结 一、Shell基本语法 文件名代换 用于匹配的字符称为通配符&#xff08;Wildcard&#xff09;&#xff0c;如&#xff1a;* ? [ ] 具体如下&#xff1a; *…

【Datawhale AI 夏令营第二期】AI 量化模型预测挑战赛

文章目录 赛题分析赛题背景赛事任务赛题数据集评价指标 Baseline实践导入模块EDA特征工程模型训练与验证结果输出 改进 赛题分析 赛题背景 量化金融在国外已经有数十年的历程&#xff0c;而在国内兴起还不到十年。这是一个极具挑战的领域。量化金融结合了数理统计、金融理论、…

DataWhale 机器学习夏令营第二期——AI量化模型预测挑战赛 学习记录

DataWhale 机器学习夏令营第二期 学习记录一 (2023.08.06)1. 问题建模1.1 赛事数据数据集情况数据中缺失值类别和数值特征的基本分布 1.2 评价指标中间价的计算方式价格移动方向说明 1.3 线下验证 DataWhale 机器学习夏令营第二期 ——AI量化模型预测挑战赛 已跑通baseline&…

排查吞吐量和 SNR 方面的 Wi-Fi 问题

服务交付对于客户在选择品牌时要考虑很重要&#xff0c;组织依靠其网络向全球客户无缝提供服务&#xff0c;强大的网络连接对于更好的最终用户体验至关重要&#xff0c;而高质量访问的关键是两个关键指标&#xff1a; 吞吐量信噪比 &#xff08;SNR&#xff09; 为了获得更好…

解决word打字卡顿问题的方法

❤ 2023.8.5 ❤ 最近整理论文&#xff0c;本来我是wps死忠粉&#xff0c;奈何wps不支持latex公式。。。 无奈用起了word&#xff0c;但是谁想字数稍微多了一点&#xff0c;word就卡得欲仙欲死&#xff0c;打个字过去2s才显示出来&#xff0c;删除的时候都不知道自己删了几个字…

基于STM32CUBEMX驱动低压步进器电机驱动器STSPIN220(1)----套件概述

基于STM32CUBEMX驱动低压步进器电机驱动器STSPIN220----1.套件概述 套件概述样品申请特征系统控制和生态系统访问功能示意图系统框图跳线设置开发板原理图 套件概述 STM32C011F4Px_STSPIN220 是一款基于 STM32C011F4Px 的低压步进电机驱动套件。其中&#xff0c;STSPIN220 是一…

离散化的两种实现方式【sort或者map】

离散化 定义 把无限空间中有限的个体映射到有限的空间中去&#xff0c;以此提高算法的时空效率。通俗的说&#xff0c;离散化是在不改变数据相对大小的条件下&#xff0c;对数据进行相应的缩小。 适用范围&#xff1a;数组中元素值域很大&#xff0c;但个数不是很多。 比如将…

Navicat远程连接Linux的MySQL

打开Linux终端&#xff0c;进入root权限&#xff0c;用vim打开MySQL的配置文件 vim /etc/mysql/mysql.conf.d/mysqld.cnf将bind-address的值改为0.0.0.0 进入MySQL mysql -u root -p 将root用户改为允许远程登录 update user set host % where user root; 创建用户 CRE…

码出高效_第二章 | 面向对象_上

目录 一. OOP理念1. 概念辨析2. 四大特性1. 抽象2. 封装3. 继承4. 多态 二. 初识Java1. JDKJDK 5-11的重要类、特性及重大改变 2. JRE关于JVM 三. 类1. 概述2. 接口和抽象类1. 概念及相同点2. 不同点3. 总结 3. 内部类4. 访问权限控制1. 由来2. public/private/无/private3. 推…

无涯教程-Perl - endgrent函数

描述 此功能告诉系统您不再希望使用getgrent从groups文件中读取条目。 语法 以下是此函数的简单语法- endgrent返回值 此函数不返回任何值。 Perl 中的 endgrent函数 - 无涯教程网无涯教程网提供描述此功能告诉系统您不再希望使用getgrent从groups文件中读取条目。 语法以…

开源项目-私人牙医管理系统

哈喽,大家好,今天给大家带来一个开源项目-私人牙医管理系统,项目使用springboot+mysql技术实现 私人牙医管理系统的主要功能包括客户管理,医生管理,药品管理,文章管理模块 登录 客户管理 客户管理主要有客户数据,客户列表,添加客户功能 客户数据 客户列表 添加…

VIOOVI的精益生产探析:深入了解精益生产的本质

精益生产它是利用杜绝浪费和稳定、连续生产的作业流程&#xff0c;是通过系统性的结构管理、生产人员组织以及市场端的供求现状等方面的因素做对应的调整、变革。具备有一定战斗力的生产管理体系&#xff0c;可以很快的根据市场端需求做出对应的调整&#xff0c;而且实现生产过…

黑马大数据学习笔记5-案例

目录 需求分析背景介绍目标需求数据内容DBeaver连接到Hive建库建表加载数据 ETL数据清洗数据问题需求实现查看结果扩展 指标计算需求需求指标统计 可视化展示BIFineBI的介绍及安装FineBI配置数据源及数据准备 可视化展示 P73~77 https://www.bilibili.com/video/BV1WY4y197g7?…

如何使用win10专业版系统自带远程桌面公司内网电脑,从而实现居家办公?

使用win10专业版自带远程桌面公司内网电脑 文章目录 使用win10专业版自带远程桌面公司内网电脑 在现代社会中&#xff0c;各类电子硬件已经遍布我们身边&#xff0c;除了应用在个人娱乐场景的消费类电子产品外&#xff0c;各项工作也离不开电脑的帮助&#xff0c;特别是涉及到数…

router和route的区别

简单理解为&#xff0c;route是用来获取路由信息的&#xff0c;router是用来操作路由的。 一、router router是VueRouter的实例&#xff0c;通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象&#xff0c;这个对象中是一个全局的对象&#xff0c;他包含了所…

vue结合three.js加载3D模型报404错误

使用vue结合three.js加载3D模型时报404的错误&#xff0c;加载字体库也会报404错误&#xff0c;同样的方法。 vue项目虽然使用npm install three安装了three&#xff0c;但是有些静态资源时读取不到的&#xff0c;当出现异常的404错误时&#xff0c;比如加载3D模型资源时&…

第5章 运算符、表达式和语句

本章介绍以下内容&#xff1a; 关键字&#xff1a;while、typedef 运算符&#xff1a;、-、*、/、%、、--、(类型名) C语言的各种运算符&#xff0c;包括用于普通数学运算的运算符 运算符优先级以及语句、表达式的含义 while循环 复合语句、自动类型转换和强制类型转换 如何编写…