微信小程序| AIGC之动手实现ChatGPT法律顾问小程序

news/2024/7/20 1:02:46 标签: 小程序, 微信小程序, chatgpt

在这里插入图片描述

一、需求背景

在资本退去后,现如今的互联网行情很差劲,很多创新业务都不得不砍除。再加上国内互联网时代进入到了一个增量犹显疲态,增量杀红了眼!阶段,各大互联网公司均有一种断臂求生的态势!各位互联网同行,大力发展第二职业已然变成了必要的生产力!

我们既要辩证的看待事物,也要符合规定去看待!作为法律门外汉的我们,从零去学习庞大的法律知识已然是不切实际!单独去聘专业的法律专家或许又流程繁琐!所以,就让我们拿起ChatGPT,做一个AI版的法律质询顾问,解答你的全部法律问题!


二、项目原理及架构

2.1 实现原理

CahtGPT的角色设置为专业法律顾问,让他根据我们所提供的实际案例来产生相应的专业法律内容的回复,所以核心的一点就是我们需要写好相应的Prompt提示词内容,根据Prompt提示词的设计原则:在OpenAI接口所允许的Tokens数量限制下,为AI模型提供尽可能丰富的内容引导,也就是使用Few-Shot机制完成AI模型角色的预设!具体使用如下:

你是一个研究劳动法的专业律师,从现在开始你需要根据我的问题从劳动法的角度来分析我的案件,并且给出我相应的专业建议。我的问题是:我被裁员了该怎么办?


在这里插入图片描述


2.2 技术架构

本程序的核心在于构建优值的Prompt提示词+优化Token的消费接口,剩余的都是我们的一些接口的增删改查以及前端流式数据的输出与展示。
在这里插入图片描述


2.3 技术栈

模块语言及框架涉及的技术要点
小程序前端基于VUE 2.0语法+Uni-app跨平台开发框架Http接口通信、Flex布局方式、uView样式库的使用、JSON数据解析、定时器的使用
小程序接口服务端Python + Flask WEB框架rest接口的开发、 ChatGPT API接口的数据对接 、 前后端websocket实时通信

2.4 数据交互原理

选择操作模式
数据交互
构造问题数据发送
通过API接口返回回答数据
用户
小程序后端服务
ChatGPT

三、项目功能的实现

3.1 ChatGPT API的接入

要接入ChatGPT API,需要按照以下步骤进行操作:

  1. 注册一个账号并登录到OpenAI的官网:https://openai.com/
  2. 在Dashboard页面上,创建一个API密钥。在“API Keys”选项卡下,点击“Generate New Key”按钮。将生成的密钥保存好,以备后续使用。
  3. 选择所需的API服务,例如“Completion” API,以使用OpenAI的文本生成功能。

在这里插入图片描述
使用Python调用ChatGPT API实现代码如下:

  • 方法一:使用request
import requests
import json

# 构建API请求
url = "https://api.openai.com/v1/engines/davinci-codex/completions"
headers = {"Content-Type": "application/json",
           "Authorization": "Bearer YOUR_API_KEY"}
data = {
    "prompt": "Hello, my name is",
    "max_tokens": 5
}

# 发送API请求
response = requests.post(url, headers=headers, data=json.dumps(data))

# 解析API响应
response_data = json.loads(response.text)
generated_text = response_data["choices"][0]["text"]

print(generated_text)


  • 方式二:使用openAI库
from flask import Flask, request
import openai

app = Flask(__name__)

openai.api_key = "YOUR_API_KEY_HERE"

@app.route("/")
def home():
    return "Hello, World!"

@app.route("/chat", methods=["POST"])
def chat():
    data = request.json
    response = openai.Completion.create(
        engine="davinci",
        prompt=data["message"],
        max_tokens=60
    )
    return response.choices[0].text

if __name__ == "__main__":
    app.run()


3.2 小程序端设计与实现

法律引导界面法律质询界面
在这里插入图片描述在这里插入图片描述
咨询历史付费质询记录
在这里插入图片描述在这里插入图片描述

3.3 小程序前端流式输出设计与实现

通过查阅ChatGPT官网文档我们可以知道:·chatGPT是支持流式数据的返回·,而我们作为一个API的调用方,我们起到的只是一个接口数据的获取和处理的作用,ChatGPT所提供的流式数据返回也只是对于后端调用接口服务时有效,而对于我们的小程序前端就需要在数据返回接口中使用到数据流式响应的技术:正是由于Http是无状态的协议,前端在发送完请求之后,后端无法继续唤起上一次的调用方,也就无法保持流式数据的响应。所以我们这里使用WebSocket长链接技术以实现数据的流式响应!

  • CahtGPT法律咨询完整流式连接示例代码:
<template>
  <view class="content">
    <view class="body">
      <view v-for="(item, index) in arr" :key="index" :id="'message' + item.message_id">
        <view class="body_l" v-if="item.role == 'assistant'">
          <view class="body_lpic a">
            <image :src="now_model.icon" mode=""></image>
          </view>
          <view class="body_box">
            <view class="body_jt">
              <u-icon name="play-left-fill" color="#F6F7FB" size="20"></u-icon>
            </view>
            <!-- <view class="body_lcon pdg" v-html="item.content"></view> -->
            <view class="body_rcon pdg">
              <zero-markdown-view :markdown="item.content"></zero-markdown-view>
            </view>
            <view style="position: absolute;right: 0;bottom: 10rpx;">
              <u-icon @click="copy(item)" name="file-text-fill" size="30"></u-icon>
            </view>
          </view>
        </view>
        <view class="body_r" v-if="item.role == 'user'">
          <view class="body_box">
            <view class="body_rjt">
              <u-icon name="play-right-fill" color="#F6F7FB" size="20"></u-icon>
            </view>
            <!-- <view class="body_rcon pdg" v-html="item.content"> -->
            <view class="body_rcon pdg">
              <zero-markdown-view :markdown="item.content"></zero-markdown-view>
            </view>
          </view>
          <view class="body_rpic a">
            <!-- <image v-if="token != ''" :src="vals.avatar" mode="aspectFill"></image> -->
            <image :src="userInfo.avatar ? userInfo.avatar : '../../static/head.png'" mode="aspectFill">
            </image>
          </view>
        </view>
      </view>
    </view>
    <view class="body_l" v-if="loading" style="margin-left: 30rpx;" id="loading">
      <view class="body_lpic a">
        <image :src="now_model.icon" mode=""></image>
      </view>
      <view class="body_box ">
        <view class="body_lcon pdg" style="display: flex;overflow-x: hidden;">
          <u-loading-icon :show="loading"></u-loading-icon>
          <view v-if="!typeingText" style="padding: 30rpx;">{{ now_model.title }}思考中...</view>
          <!-- <zero-markdown-view v-if="!typeingText" style="padding: 0 20rpx;" :markdown="now_model.title">思考中...</zero-markdown-view> -->
          <!-- <view v-else style="padding: 30rpx;">{{ typeingText }}...</view> -->
          <zero-markdown-view v-else style="padding: 0 20rpx;" :markdown="typeingText"></zero-markdown-view>
        </view>
      </view>
    </view>
    <view class="flooer">
      <view class="flooer_l">
        <u--textarea v-model="prompt" placeholder="请输入内容,开始体验与人工智能交流吧" border="none" autoHeight
          style="background-color: #F6F7FB;" :maxlength="256" holdKeyboard :showConfirmBar="false"
          :cursorSpacing="28"></u--textarea>
        <!-- <u--input placeholder="请输入内容,开始体验与人工智能交流吧" border="none" clearable v-model="prompt"
					placeholderStyle="color: #7F8084"></u--input> -->
      </view>
      <view class="flooer_r pdg" @click="sendout">
        <view class="flooer_rpic a">
          <image src="../../static/fasong.png" mode=""></image>
        </view>
      </view>
    </view>
    <view style="height: 108rpx;"></view>
  </view>
</template>

<script>
import setting from "@/common/config";
//markdown相关插件
// import markdownFunc from '@/uni_modules/jo-markdown/components/jo-markdown/index.js';
// #ifdef H5
import axios from 'axios'
// #endif
import {
  mapState
} from "vuex"
var that;
export default {
  data() {
    return {
      prompt: '',
      arr: [],
      timer: null,
      loading: false,
      chat_id: '',
      typeingText: "",
      typeingId: "",
      now_model: {},
    }
  },
  computed: {
    ...mapState(['config', 'num', 'userInfo'])
  },
  onLoad(op) {
    if (op.model) {
      this.now_model = JSON.parse(op.model)
    } else {
      this.now_model = this.$store.state.main_model
    }
    uni.setNavigationBarTitle({
      title: this.now_model.title
    })
    this.arr.push({
      role: 'assistant',
      content: this.now_model.first_message,
      message_id: 'aa'
    })
    let that = this;
    if (op.chat_id) {
      this.chat_id = op.chat_id;
      this.getMessages()
    }
    let pong = '';
    let userInfo = uni.getStorageSync('userInfo')
    uni.connectSocket({
      url: setting.ws
    });
    uni.onSocketOpen(function (res) {
      console.log('chat页WebSocket连接已打开!');
      // 绑定UID
      sendMsg({
        type: 'login',
        uid: userInfo.member_id
        // uid: 3
      })
      // 设置心跳包
      pong = setInterval(() => {
        sendMsg({
          type: 'pong'
        })
      }, 20 * 1000);

    });
    uni.onSocketError(function (res) {
      console.log('WebSocket连接打开失败,请检查!');
    });
    uni.onSocketMessage(function (res) {
      // console.log('收到服务器内容:' + res.data);
      let json = JSON.parse(res.data)
      // console.log(json)
      if (json.hasOwnProperty('id')) {
        that.typeingId = json.id
        if (!json.choices[0].delta.finish_reason) {
          if (json.choices[0].delta.hasOwnProperty('content')) {
            that.typeingText += json.choices[0].delta.content
            // 此处自行斟酌是否替换
            // that.typeingText = that.typeingText.replace(/[cogptn]+/ig, "ChatGLM-6B")
          }
        }
      }

    });
    // socket断开时通知后端改变登录状态
    uni.onSocketClose(function (res) {
      console.log('WebSocket 已关闭!');
    });
    //上文中的sendMsg原型
    const sendMsg = function (data) {
      uni.sendSocketMessage({
        data: JSON.stringify(data)
      })
    }
  },
  onUnload() {
    uni.closeSocket()
  },
  onShow() {
    that = this;
    uni.getStorage({
      key: 'token',
      success: function (res) {
        that.token = res.data;

      },
      fail() {
        that.token = '';
      }
    });
  },
  onBackPress() {
    uni.hideLoading()
    clearInterval(this.timer);
  },
  onHide() {
    uni.hideLoading()
    clearInterval(this.timer);
  },
  methods: {
    getMessages() {
      uni.showLoading({

      })
      this.$request("/api/Message/index", "POST", {
        chat_id: this.chat_id,
        limit: 200,
        sort: 'message_id',
        order: 'asc',
      }).then((res) => {
        console.log(res);
        if (res.status == 200) {
          this.arr = this.arr.concat(res.data.data)
          this.$nextTick(() => {
            uni.pageScrollTo({
              selector: "#message" + this.arr[this.arr.length - 1].message_id,
              duration: 100
            });
          });
          uni.hideLoading()
        } else {
          this.$failToast('服务器繁忙,请稍后再试');
        }

      });
    },
    sendout() {
      if (!this.prompt) {
        this.$failToast("请输入内容");
        return;
      }

      this.loading = true;
      this.arr.push({
        role: 'user',
        content: this.prompt,
        message_id: this.arr.length + 1
      });
      let message = this.prompt;
      this.prompt = ""
      this.$nextTick(() => {
        uni.pageScrollTo({
          selector: "#loading",
          duration: 100
        });
      });
      setTimeout(() => {
        this.sub(message)
      }, 200);
    },
    sub(message) {
      // 条件编译处理,h5端uni.request与socket会互相阻塞
      // #ifndef H5
      this.$request("/api/Message/sendV2", "POST", {
        message: message,
        chat_id: this.chat_id,
        model_id: this.now_model.assistant_id
        // member_id: 3 // 调试之用,线上环境为token解码
      }).then((res) => {
        console.log(res);
        if (res.status == 200) {
          this.loading = false;
          this.typeingText = "";
          this.typeingId = "";
          this.arr.push({
            role: res.data.role,
            // 此处自行斟酌是否替换
            content: res.data.content,
            // content: res.data.content.replace(/[cogptn]+/ig, "ChatGLM-6B")
          });
          if (!this.chat_id) {
            this.chat_id = res.data.chat_id
          }
          this.$store.commit('useNum', 1);
          this.$nextTick(() => {
            uni.pageScrollTo({
              scrollTop: 20000,
              duration: 100
            });
          });
        } else if (res.status == 901) {
          this.loading = false;
          this.$failToast('服务器繁忙,请稍后再试');
        } else if (res.status == 411) {
          this.loading = false;
          uni.showModal({
            content: res.msg
          })
        } else {
          this.loading = false;
          this.$failToast(res.msg);
        }
      }).catch(err => {
        console.log(err)
        this.loading = false;
        this.$failToast(err.errMsg);
      })
      // #endif
      // #ifdef H5
      axios({
        method: 'post',
        url: setting.base_url + "/api/Message/sendV2",
        data: {
          message: message,
          chat_id: this.chat_id,
          model_id: this.now_model.assistant_id
        },
        headers: {
          'content-type': 'application/x-www-form-urlencoded;charset=utf-8',
          'Authorization': this.$store.state.token
        }
      }).then(response => {
        console.log(response)
        let res = response.data
        if (res.status == 200) {
          this.loading = false;
          this.typeingText = "";
          this.typeingId = "";
          this.arr.push({
            role: res.data.role,
            content: res.data.content
          });
          if (!this.chat_id) {
            this.chat_id = res.data.chat_id
          }
          this.$store.commit('useNum', 1);
          this.$nextTick(() => {
            uni.pageScrollTo({
              scrollTop: 20000,
              duration: 100
            });
          });
        } else if (res.status == 901) {
          this.loading = false;
          this.$failToast('服务器繁忙,请稍后再试');
        } else if (res.status == 411) {
          this.loading = false;
          uni.showModal({
            content: res.msg
          })
        } else {
          this.loading = false;
          this.$failToast(res.msg);
        }
      }).catch(error => {
        // reject(error);
      });
      // #endif

    },
    copy(item) {
      uni.setClipboardData({
        data: item.content,
        success() {
          uni.showModal({
            title: '提示',
            content: '内容已复制到系统剪贴板'
          })
        }
      })
    }
  }
}
</script>

<style>
@import '../../components/css/index.css';
</style>


3.4 法律小程序界面实现

<template>
	<view class="content">
		<!-- 图片区域 -->
		<view style="margin:0;padding:0;padding-top:-10rpx">
			<image src="/static/images/banner.png" mode="widthFix" style="width:100%;display: block;"></image>
		</view>
		<!-- 咨询区域 -->
		<u-row :gutter="0">
			<u-col :span="6">
				<u-card :show-head="false" :border-radius="18" box-shadow="7rpx 8rpx 20rpx #ddd">
					<view slot="body" @click="toChat">
						<view>
							<text class="consult">AI咨询</text>
						</view>
						<view>
							<u-row :gutter="0">
								<u-col :span="9" style="margin:0;padding:0;margin-bottom:20rpx;">
									<text class="consult-sub">CahtGPT智能AI咨询</text>
								</u-col>
								<u-col :span="2" style="margin:0;padding:0">
									<view style="position:absolute;right:5rpx;bottom:5rpx">
										<image src="/static/images/chat_consult.png" mode="widthFix"
											style="width:110rpx;"></image>
									</view>
								</u-col>
							</u-row>

						</view>
					</view>

				</u-card>
			</u-col>
			<u-col :span="6">
				<u-card :show-head="false" :border-radius="18" box-shadow="7rpx 8rpx 20rpx #ddd">
					<view slot="body">
						<view>
							<text class="consult">真人咨询</text>
						</view>
						<view>
							<u-row :gutter="0">
								<u-col :span="9" style="margin:0;padding:0;margin-bottom:20rpx">
									<text class="consult-sub">专业律师在线服务</text>
								</u-col>
								<u-col :span="2" style="margin:0;padding:0;">
									<view style="position:absolute;right:5rpx;bottom:5rpx">
										<image src="/static/images/phone_consult.png" mode="widthFix"
											style="width:110rpx;"></image>
									</view>
								</u-col>
							</u-row>

						</view>
					</view>

				</u-card>
			</u-col>
		</u-row>


		<!-- 按类型咨询版块 -->
		<u-row style="margin-top:15rpx">
			<u-col :span="12">
				<u-section font-size="35" lineColor="#5b80f6" title="按类型咨询" :right="false"></u-section>
			</u-col>
		</u-row>
		<view style="margin-left:2%;margin-right:2%;">
			<u-row gutter="0" style="margin-top:5rpx;">
				<u-col :span="3" v-for="(caseType,index) in caseTypeList" :key="index">
					<view class="case-type">
						<view style="margin-bottom:10rpx;">
							<image :src="caseType.caseTypeIcon" mode="widthFix" style="width:55rpx"></image>
						</view>
						<view>
							<text>{{caseType.caseTypeName}}</text>
						</view>
					</view>
				</u-col>
			</u-row>
		</view>


		<!-- 免费咨询 -->
		<view style="margin-top:30rpx;">
			<u-row>
				<u-col :span="12">
					<u-section font-size="35" lineColor="#5b80f6" title="免费咨询" sub-title="我要提问"></u-section>
				</u-col>
			</u-row>
		</view>
		<view style="margin-left:2%;margin-right:2%;margin-top:20rpx;">
			<u-cell-group>
				<u-cell-item title="法律问题"></u-cell-item>
				<u-cell-item title="法律问题"></u-cell-item>
			</u-cell-group>
		</view>

		<!-- 法律知识 -->
		<view style="margin-top:30rpx;">
			<u-row>
				<u-col :span="12">
					<u-section font-size="35" lineColor="#5b80f6" title="法律知识" sub-title="查看更多"></u-section>
				</u-col>
			</u-row>
		</view>
		<view style="margin-left:2%;margin-right:2%;margin-top:20rpx;">
			<u-cell-group>
				<u-cell-item title="法律知识"></u-cell-item>
				<u-cell-item title="法律知识"></u-cell-item>
			</u-cell-group>
		</view>

	</view>
</template>

<script>
	export default {
		data() {
			return {
				title: 'XX法律咨询',
				caseTypeList: [{
						id: 1,
						caseTypeName: "婚姻家庭",
						caseTypeIcon: "/static/images/casetype/married&family.png"
					},
					{
						id: 2,
						caseTypeName: "刑事案件",
						caseTypeIcon: "/static/images/casetype/married&family.png"
					},
					{
						id: 3,
						caseTypeName: "劳动工伤",
						caseTypeIcon: "/static/images/casetype/married&family.png"
					},
					{
						id: 4,
						caseTypeName: "债权债务",
						caseTypeIcon: "/static/images/casetype/married&family.png"
					},
					{
						id: 5,
						caseTypeName: "医疗赔偿",
						caseTypeIcon: "/static/images/casetype/married&family.png"
					},
					{
						id: 5,
						caseTypeName: "医疗赔偿",
						caseTypeIcon: "/static/images/casetype/married&family.png"
					},
					{
						id: 5,
						caseTypeName: "医疗赔偿",
						caseTypeIcon: "/static/images/casetype/married&family.png"
					},
					{
						id: 5,
						caseTypeName: "更多",
						caseTypeIcon: "/static/images/casetype/moreCaseType.png"
					}
				]
			}
		},
		onLoad() {


		},
		methods: {
			toChat() {
				uni.navigateTo({
					url: './chat/chat'
				})
			}
		}
	}
</script>

<style lang="scss" scoped>
	$consultTitleRpx: 40rpx;
	$consultTitleWeight: 700;

	$consultSubTitleRpx: 25rpx;
	$consultSubTitleColor: #888;

	.consult {
		font-size: $consultTitleRpx;
		font-weight: $consultTitleWeight;
	}

	.consult-sub {
		font-size: $consultSubTitleRpx;
		color: $consultSubTitleColor;
		margin-left: 0;
	}

	.card {
		margin-left: 5%;
		margin-right: 5%;
		width: 90%;
		box-shadow: 7rpx 8rpx 20rpx #eee;
	}



	.case-type {
		text-align: center;
		border: 1rpx solid transparent;
		background-color: #f8f8fa;
		padding-top: 30rpx;
		padding-bottom: 30rpx;
		border-radius: 10rpx;
		color: #555;
		width: 90%;
		margin-top: 20rpx;
		font-size: 25rpx;
	}
</style>

四、推荐阅读

🥇入门和进阶小程序开发,不可错误的精彩内容🥇 :

  • 小程序开发必备功能的吐血整理【个人中心界面样式大全】》
  • 《微信小程序 | 人脸识别的最终解决方案》
  • 《吐血整理的几十款小程序登陆界面【附完整代码】》

http://www.niftyadmin.cn/n/354019.html

相关文章

什么是jquery jq的基本使用

JQuery的概述 jQuery是一个快速的&#xff0c;简洁的javaScript库&#xff0c;使用户能更方便地处理HTML documents、events、实现动画效果&#xff0c;并且方便地为网站提供AJAX交互。 jQuery能够使用户的html页保持代码和html内容分离&#xff0c;也就是说&#xff0c…

私有化部署即时通讯为什么更安全

即时通讯作为企业沟通工具&#xff0c;在企业的内部沟通和外部交流中发挥着越来越重要的作用。同时&#xff0c;企业即时通讯在提升企业内部效率的同时&#xff0c;也面临着巨大的安全威胁。 根据数据显示&#xff0c;全球有超过4亿人在使用 IM。而其中因用户隐私泄露导致的数据…

Python自动化办公对每个子文件夹的Excel表加个表头(Excel不同名)(下篇)

点击上方“Python爬虫与数据挖掘”&#xff0c;进行关注 回复“书籍”即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 昭阳殿里恩爱绝&#xff0c;蓬莱宫中日月长。 大家好&#xff0c;我是皮皮。 一、前言 上一篇文章&#xff0c;我们抛出了一个问题&#xff0c;这篇文章…

游戏洞察丨自来水还是井水,后流量时代的私域挑战

流量生意本质上是买卖用户浏览时间的生意&#xff0c;如果用户增长到顶&#xff0c;那就意味着供给到顶。对比 2021 年&#xff0c;2022 年的游戏出海在谷歌和 Facebook 上投入的广告成本几乎翻了一倍。新晋“渠道王者”TikTok 逐渐走进大家的视野。该现象背后的原因在于&#…

OpenAI Whisper + FFmpeg + TTS:动态实现跨语言视频音频翻译

本文作者系360奇舞团前端开发工程师 摘要&#xff1a; 本文介绍了如何结合 OpenAI Whisper、FFmpeg 和 TTS&#xff08;Text-to-Speech&#xff09;技术&#xff0c;以实现将视频翻译为其他语言并更换声音的过程。我们将探讨如何使用 OpenAI Whisper 进行语音识别和翻译&#x…

数据结构和算法基础学习1

​​​​​​​ 网址第01周b--1.1数据结构研究_哔哩哔哩_bilibili

如何从文档中提取结构化数据?parsio.io

parsio.io 产品名&#xff1a;Parsio电子邮件解析器 技术&#xff1a;采用人工智能技术的电子邮件解析器。 支持多种格式&#xff1a; 可以解析电子邮件和附件中的数据&#xff0c;包括PDF、HTML、XLSX&#xff08;Excel&#xff09;、CSV、DOCX、XML、TXT等格式。 提取模版&am…

网关网卡配置

Vmvare虚拟机设置外网IP 查看当前主机的网卡名/当前IP/子网掩码&#xff0c;网关地址 ifconfig route -n 查看DNS nslookup hcos 网卡名称为enp0s3&#xff0c;IP地址为10.0.2.15&#xff0c;子网掩码为255.255.255.0&#xff0c;网关为10.0.2.2&#xff1b; Centos设置IP/网…