效果图
数据库记录:
流式输出就不展示了
准备工作
前往阿里云大模型服务平台使用自己账号开通大模型服务平台百炼
地址:大模型服务平台_通义大模型_自然语言处理_达摩院-阿里云 (aliyun.com)
1.进入自己的控制台--模型广场--通义千问--找到自己要使用的模型 我这里使用通义千问Max
一般是有免费额度可以使用的 后期可以自己买额度 很便宜
然后到我的应用 创建好自己的应用 选择相应的模型 这时我们就哟APIkey和 自己的appid了
2.在自己电脑安装好Redis (用于存储聊天缓存) 如果使用服务器就在服务器安装好并运行就行
3.安装好mysql数据库 推荐使用5.7版本
实施
创建一个Python Flask项目并创建好虚拟环境解释器使用python3.8 项目结构如下图所示
key.py及其下方文件为我个人测试部署时使用 可以忽略
app.py
运行项目的文件 也就是后端
# -*- coding: utf-8 -*-
from flask import Flask, request, jsonify, Response, render_template, session
from flask_session import Session
from dashscope import Generation
from http import HTTPStatus
from flask_cors import CORS
import redis
import json
from database import save_conversation
app = Flask(__name__)
CORS(app)
#可以自己生成一串
app.secret_key = '3ddd8f0da764cb34850e1da48d03da24'
# 配置 Redis
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.Redis(host='localhost', port=6379)
app.config['SESSION_PERMANENT'] = False
app.config['SESSION_USE_SIGNER'] = True
Session(app)
@app.route('/ChatView')
def index():
return render_template('index.html')
@app.route('/chat', methods=['POST'])
def chat():
user_message = request.json.get('message', '')
if not user_message:
return jsonify({'error': '未提供消息'}), HTTPStatus.BAD_REQUEST
# 从会话中加载消息或初始化一个新列表 可以自己自定义角色 修改'你是一个ai助手'内容就行
messages = session.get('messages', [{'role': 'system', 'content': '你是一个ai助手'}])
# 将用户的消息添加到列表中
messages.append({'role': 'user', 'content': user_message})
session['messages'] = messages # 在添加用户消息后立即保存
def generate():
responses = Generation.call(
"qwen-max",
app_id='自己的应用id',
api_key='自己的api key',
messages=messages,
result_format='message',
stream=True,
incremental_output=True
)
buffer = ''
for response in responses:
if response.status_code == HTTPStatus.OK:
content = response.output.choices[0]['message']['content'].strip()
print(content)
buffer += content
yield f"{content}"
else:
yield f"Error: {response.message}\n\n"
break
return Response(generate(), mimetype='text/event-stream')
@app.route('/update', methods=['POST'])
def update():
try:
data = request.json
bot_message = data.get('bot_message', '')
user_message = data.get('user_message', '')
conversation_id = data.get('conversation_id', '')
if not bot_message or not user_message or not conversation_id:
app.logger.error('Missing bot_message, user_message, or conversation_id')
return jsonify({'error': '未提供消息或对话ID'}), HTTPStatus.BAD_REQUEST
messages = session.get('messages', [])
messages.append({'role': 'assistant', 'content': bot_message})
session['messages'] = messages
save_conversation(conversation_id, user_message, bot_message) # 保存对话
return jsonify({'status': 'updated'}), HTTPStatus.OK
except Exception as e:
app.logger.error(f"Error in /update: {str(e)}")
return jsonify({'error': str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
@app.route('/clear', methods=['POST'])
def clear():
session.pop('messages', None)
return jsonify({'status': 'cleared'}), HTTPStatus.OK
if __name__ == '__main__':
app.run(debug=True)
# 这里是自定义本地运行的端口号和ip地址
# app.run(host='0.0.0.0', port=8080, debug=True)
database.py
指向数据库操作 保存记录的后端文件 需要修改为自己的数据库账户和密码
# -*- coding: utf-8 -*-
import mysql.connector
def get_db_connection():
return mysql.connector.connect(
host="localhost",
user="用户名",
password="密码",
database="数据库名"
)
def save_conversation(conversation_id, user_message, bot_message):
try:
connection = get_db_connection()
cursor = connection.cursor()
# 检查对话是否存在
conversation_query = "SELECT 1 FROM conversations WHERE conversation_id = %s"
cursor.execute(conversation_query, (conversation_id,))
conversation_exists = cursor.fetchone()
if not conversation_exists:
# 插入对话记录
conversation_query = """
INSERT INTO conversations (conversation_id)
VALUES (%s)
"""
cursor.execute(conversation_query, (conversation_id,))
# 插入聊天记录
chat_record_query = """
INSERT INTO chat_records (conversation_id, user_message, bot_message)
VALUES (%s, %s, %s)
"""
cursor.execute(chat_record_query, (conversation_id, user_message, bot_message))
connection.commit()
except mysql.connector.Error as err:
print(f"Error: {err}")
raise
finally:
if connection.is_connected():
cursor.close()
connection.close()
数据库结构 sql语句
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for chat_records
-- ----------------------------
DROP TABLE IF EXISTS `chat_records`;
CREATE TABLE `chat_records` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`conversation_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`user_message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
`bot_message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `conversation_id`(`conversation_id`) USING BTREE,
CONSTRAINT `chat_records_ibfk_1` FOREIGN KEY (`conversation_id`) REFERENCES `conversations` (`conversation_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for conversations
-- ----------------------------
DROP TABLE IF EXISTS `conversations`;
CREATE TABLE `conversations` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`conversation_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`conversation_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`) USING BTREE,
INDEX `conversation_id`(`conversation_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
前端页面和样式
/templates/index.html
头像图片可以自定义 修改就行
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0"/>
<title>顶顶顶顶顶</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css"/>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlightjs-line-numbers.js@2.8.0/dist/highlightjs-line-numbers.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.11/dist/clipboard.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"/>
<link rel="stylesheet" href="/static/css/style.css" />
<link rel="icon" href="/static/images/favicon.ico" type="image/x-icon">
</head>
<body class="bg-blue-200 font-sans leading-normal tracking-normal">
<div class="loader" id="loader">
<div class="loader-text">Loading...</div>
<h2>正在加载...请稍候</h2>
<div class="loader-bar"></div>
</div>
<div class="w-full h-full sm:container sm:mx-auto sm:my-8 sm:p-4 bg-white dark:bg-zinc-800 shadow-md rounded-lg sm:overflow-hidden max-w-4xl">
<div class="flex flex-col h-[100vh] sm:h-[80vh] md:h-[85vh]">
<div class="px-4 py-3 border-b dark:border-zinc-700">
<div class="flex justify-between items-center">
<h1 class="text-2xl font-semibold text-zinc-800 dark:text-white">👻Tongyi`Ai By Lwh<i class="bi bi-emoji-laughing"></i></h1>
<button class="text-white font-bold py-2 px-3 rounded-full transition ease-in-out delay-150 bg-blue-500 hover:-translate-y-1 hover:scale-110 hover:bg-indigo-500 duration-300" onclick="clearMessages()"><i class="bi bi-chat-right-dots"></i> 新对话</button>
</div>
</div>
<div class="flex-1 p-3 overflow-y-auto flex flex-col space-y-2" id="chat-box">
<div class="bot-message-container fade-in">
<img src="/static/images/Lwh.jpeg" alt="Lwh" />
<div class="message bot font-medium p-3 rounded-lg max-w-lg text-sm self-start bg-green-500 dark:bg-zinc-500 text-white">你好!有什么可以帮你的吗</div>
</div>
</div>
<div class="px-3 py-2 border-t-2 dark:border-zinc-700">
<div class="flex gap-2">
<textarea placeholder="输入你的问题..." class="flex-1 p-2 border rounded-lg dark:bg-zinc-700 dark:text-white dark:border-zinc-600 text-md" id="user-input" rows="1"></textarea>
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-3 rounded-full transition ease-in-out delay-150 bg-blue-500 hover:-translate-y-1 hover:scale-110 hover:bg-indigo-500 duration-300 text-sm flex items-center justify-center" id="sendButton" onclick="sendMessage()">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
<script src="/static/js/lwhapp.js" defer></script>
</body>
</html>
/static/css/styles.css
.message {
white-space: pre-wrap;
word-wrap: break-word;
max-width: 100%;
}
.bot-message-container {
display: flex;
align-items: flex-start;
}
.bot-message-container img {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 10px;
border: rgb(125, 125, 242) 3px solid;
}
pre {
border-bottom-left-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;
}
code {
font-family: 'Consolas', 'Courier New', monospace;
border-bottom-left-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;
/* 隐藏默认的滚动条样式 */
scrollbar-width: none;
/* Firefox */
-ms-overflow-style: none;
/* IE and Edge */
}
code::-webkit-scrollbar {
display: none;
/* Chrome, Safari, and Opera */
}
/* for block of numbers */
.hljs-ln-numbers {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
text-align: center;
color: #ccc;
border-right: 2px dashed #8f0feb;
vertical-align: top;
}
.hljs-ln td {
padding-right: 10px;
padding-left: 10px;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 1s ease-out;
}
.copy {
/* button */
--button-bg: #353434;
--button-hover-bg: #464646;
--button-text-color: #CCCCCC;
--button-hover-text-color: #8bb9fe;
--button-border-radius: 10px;
--button-diameter: 36px;
--button-outline-width: 1px;
--button-outline-color: rgb(141, 141, 141);
/* tooltip */
--tooltip-bg: #171212;
--toolptip-border-radius: 4px;
--tooltip-font-size:8px;
--tooltip-transition-duration: 0.3s;
--tootip-text-color: rgb(255, 255, 255);
--tooltip-padding-x: 5px;
--tooltip-padding-y: 5px;
--tooltip-offset: 8px;
font-weight: bold;
font-family: 'YouYuan', sans-serif;
}
.copy {
box-sizing: border-box;
width: var(--button-diameter);
height: var(--button-diameter);
border-radius: var(--button-border-radius);
background-color: var(--button-bg);
color: var(--button-text-color);
border: none;
cursor: pointer;
position: relative;
outline: none;
}
.tooltip {
position: absolute;
opacity: 0;
visibility: 0;
top: 0;
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
font: var(--tooltip-font-size) var(--tooltip-font-family);
color: var(--tootip-text-color);
background: var(--tooltip-bg);
padding: var(--tooltip-padding-y) var(--tooltip-padding-x);
border-radius: var(--toolptip-border-radius);
pointer-events: none;
transition: all var(--tooltip-transition-duration) cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.tooltip::before {
content: attr(data-text-initial);
}
.tooltip::after {
content: "";
position: absolute;
bottom: calc(var(--tooltip-padding-y) / 2 * -1);
width: var(--tooltip-padding-y);
height: var(--tooltip-padding-y);
background: inherit;
left: 50%;
transform: translateX(-50%) rotate(45deg);
z-index: -999;
pointer-events: none;
}
.copy svg {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.checkmark {
display: none;
}
/* actions */
.copy:hover .tooltip,
.copy:focus:not(:focus-visible) .tooltip {
opacity: 1;
visibility: visible;
top: calc((100% + var(--tooltip-offset)) * -1);
}
.copy:focus:not(:focus-visible) .tooltip::before {
content: attr(data-text-end);
}
.copy:focus:not(:focus-visible) .clipboard {
display: none;
}
.copy:focus:not(:focus-visible) .checkmark {
display: block;
}
.copy:hover,
.copy:focus {
background-color: var(--button-hover-bg);
}
.copy:active {
outline: var(--button-outline-width) solid var(--button-outline-color);
}
.copy:hover svg {
color: var(--button-hover-text-color);
}
@media screen and (max-width: 600px) {
pre {
white-space: pre-wrap;
/* 换行 */
word-wrap: break-word;
/* 防止超出屏幕宽度 */
}
.message.bot {
max-width: 100%;
/* 确保在小屏幕设备上不超出屏幕宽度 */
}
}
/* Loader styles */
.loader {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: white;
z-index: 9999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loader-text {
font-size: 24px;
color: rgb(0, 0, 0);
margin-bottom: 20px;
align-self: center;
}
.loader-bar {
width: 10%;
height: 10px;
border-radius: 5px;
background-color: rgb(0, 0, 0);
animation: loader-bar-animation 2s ease-in-out infinite;
}
@keyframes loader-bar-animation {
0% {
/* transform: translateX(-100%) rotate(270deg); */
transform: translateX(-100%);
}
50% {
/* transform: translateX(100%) rotate(-90deg); */
transform: translateX(100%);
}
100% {
/* transform: translateX(-100%) rotate(270deg); */
transform: translateX(-100%);
}
}
/static/js/lwhapp.js
这部分很重要!!!
可以修改每次对话可以对话几条 建议20以内
document.addEventListener("DOMContentLoaded", function() {
hljs.highlightAll();
hljs.initLineNumbersOnLoad();
const chatBox = document.getElementById("chat-box");
const userInput = document.getElementById("user-input");
const maxMessages = 10; // 定义最大消息数
let messageCount = 0;
userInput.addEventListener("keydown", function (event) {
if (event.keyCode === 13 && !event.shiftKey) {
event.preventDefault();
sendMessage();
}
});
document.getElementById("sendButton").addEventListener("click", sendMessage);
document.querySelector("button[onclick='clearMessages()']").addEventListener("click", clearMessages);
function sendMessage() {
if (messageCount >= maxMessages) {
disableInput();
return;
}
const message = userInput.value;
if (message.trim() === "") return;
addMessage("user", message, false);
const conversationId = localStorage.getItem("conversation_id") || generateConversationId();
saveMessage("user", message);
userInput.value = "";
messageCount++;
if (messageCount >= maxMessages) {
disableInput();
}
fetch("/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ message }),
})
.then((response) => {
if (!response.ok) {
throw new Error("网络回复未完成");
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let botMessageDiv = addMessage("bot", "", true); // 用于显示机器人的回复
function readStream() {
reader.read().then(({ done, value }) => {
if (done) {
const botResponse = buffer.trim();
saveMessage("bot", botResponse);
// 向后端发送完整的回复以更新会话历史
fetch("/update", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ conversation_id: conversationId, user_message: message, bot_message: botResponse }),
}).then(response => {
if (!response.ok) {
console.error('Error updating conversation:', response.statusText);
}
}).catch(error => {
console.error('Fetch error:', error);
});
return;
}
const text = decoder.decode(value, { stream: true }).trim();
buffer += text;
addTypingEffect(botMessageDiv, buffer);
applyStyles(botMessageDiv);
readStream();
});
}
readStream();
})
.catch((error) => {
console.error("Error:", error);
});
}
function generateConversationId() {
const conversationId = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
localStorage.setItem("conversation_id", conversationId);
return conversationId;
}
function addMessage(role, content, isTypingEffect = false) {
let messageDiv;
if (role === "bot") {
const containerDiv = document.createElement("div");
containerDiv.classList.add("bot-message-container", "fade-in");
const avatar = document.createElement("img");
avatar.src = "static/images/Lwh.jpeg";
avatar.alt = "Lwh";
containerDiv.appendChild(avatar);
messageDiv = document.createElement("div");
messageDiv.classList.add("message", role, "font-medium", "p-3", "rounded-lg", "max-w-lg", "text-sm", "self-start", "bg-green-500", "dark:bg-zinc-500", "text-white");
containerDiv.appendChild(messageDiv);
chatBox.appendChild(containerDiv);
} else {
messageDiv = document.createElement("div");
messageDiv.classList.add("message", role, "p-3", "rounded-lg", "max-w-lg", "text-sm", "self-end", "bg-blue-500", "text-white");
chatBox.appendChild(messageDiv);
}
if (isTypingEffect) {
return messageDiv;
}
const codeRegex = /```([\s\S]*?)```/g;
if (codeRegex.test(content)) {
const parts = content.split(codeRegex);
parts.forEach((part, index) => {
if (index % 2 === 0) {
const textNode = document.createTextNode(part);
messageDiv.appendChild(textNode);
} else {
const pre = document.createElement("pre");
const code = document.createElement("code");
const lang = part.split("\n")[0].trim();
const codeContent = part.split("\n").slice(1).join("\n");
const codeHeader = document.createElement("div");
codeHeader.classList.add(
"flex",
"justify-between",
"items-center",
"bg-gray-700",
"text-white",
"font-mono",
"px-2",
"py-1",
"text-base",
"rounded-t-lg"
);
codeHeader.innerHTML = `<span class="font-mono text-base">${lang}</span>
<button class="copy" data-clipboard-text="${codeContent.replace(/"/g, """).replace(/'/g,"'")}">
<span class="tooltip">复制代码</span>
<span>
<svg xml:space="preserve" style="enable-background:new 0 0 512 512" viewBox="0 0 6.35 6.35" y="0" x="0" height="20" width="20" xmlns="http://www.w3.org/2000/xlink" version="1.1" xmlns="http://www.w3.org/2000/xlink" class="clipboard">
<g>
<path fill="currentColor" d="M2.43.265c-.3 0-.548.236-.573.53h-.328a.74.74 0 0 0-.735.734v3.822a.74.74 0 0 0 .735.734H4.82a.74.74 0 0 0 .735-.734V1.529a.74.74 0 0 0-.735-.735h-.328a.58.58 0 0 0-.573-.53zm0 .529h1.49c.032 0 .049.017.049.049v.431c0 .032-.017.049-.049.049H2.43c-.032 0-.05-.017-.05-.049V.843c0-.032.018-.05.05-.05zm-.901.53h.328c.026.292.274.528.573.528h1.49a.58.58 0 0 0 .573-.529h.328a.2.2 0 0 1 .206.206v3.822a.2.2 0 0 1-.206.205H1.53a.2.2 0 0 1-.206-.205V1.529a.2.2 0 0 1 .206-.206z"></path>
</g>
</svg>
<svg xml:space="preserve" style="enable-background:new 0 0 512 512" viewBox="0 0 24 24" y="0" x="0" height="18" width="18" xmlns:xlink="http://www.w3.org/2000/xlink" version="1.1" xmlns="http://www.w3.org/2000/svg" class="checkmark">
<g>
<path data-original="#000000" fill="currentColor" d="M9.707 19.121a.997.997 0 0 1-1.414 0l-5.646-5.647a1.5 1.5 0 0 1 0-2.121l.707-.707a1.5 1.5 0 0 1 2.121 0L9 14.171l9.525-9.525a1.5 1.5 0 0 1 2.121 0l.707.707a1.5 1.5 0 0 1 0 2.121z"></path>
</g>
</svg>
</span>
</button>`;
code.textContent = codeContent;
if (lang) {
code.classList.add(...lang.split(/\s+/));
}
pre.appendChild(codeHeader);
pre.appendChild(code);
pre.classList.add("rounded-b-lg");
messageDiv.appendChild(pre);
new ClipboardJS(".copy");
}
});
} else {
messageDiv.appendChild(document.createTextNode(content));
}
chatBox.scrollTop = chatBox.scrollHeight;
applyStyles(messageDiv);
return messageDiv;
}
function addTypingEffect(messageDiv, text) {
const codeRegex = /```([\s\S]*?)```/g;
const parts = text.split(codeRegex);
messageDiv.innerHTML = "";
parts.forEach((part, index) => {
if (index % 2 === 0) {
const textNode = document.createTextNode(part);
messageDiv.appendChild(textNode);
} else {
const pre = document.createElement("pre");
const code = document.createElement("code");
const lang = part.split("\n")[0].trim();
const codeContent = part.split("\n").slice(1).join("\n");
const codeHeader = document.createElement("div");
codeHeader.classList.add(
"flex",
"justify-between",
"items-center",
"bg-gray-700",
"text-white",
"font-mono",
"px-2",
"py-1",
"text-base",
"rounded-t-lg"
);
codeHeader.innerHTML = `<span class="font-mono text-base">${lang}</span>
<button class="copy" data-clipboard-text="${codeContent.replace(/"/g, """).replace(/'/g,"'")}">
<span class="tooltip">复制代码</span>
<span>
<svg xml:space="preserve" style="enable-background:new 0 0 512 512" viewBox="0 0 6.35 6.35" y="0" x="0" height="20" width="20" xmlns="http://www.w3.org/2000/xlink" version="1.1" xmlns="http://www.w3.org/2000/xlink" class="clipboard">
<g>
<path fill="currentColor" d="M2.43.265c-.3 0-.548.236-.573.53h-.328a.74.74 0 0 0-.735.734v3.822a.74.74 0 0 0 .735.734H4.82a.74.74 0 0 0 .735-.734V1.529a.74.74 0 0 0-.735-.735h-.328a.58.58 0 0 0-.573-.53zm0 .529h1.49c.032 0 .049.017.049.049v.431c0 .032-.017.049-.049.049H2.43c-.032 0-.05-.017-.05-.049V.843c0-.032.018-.05.05-.05zm-.901.53h.328c.026.292.274.528.573.528h1.49a.58.58 0 0 0 .573-.529h.328a.2.2 0 0 1 .206.206v3.822a.2.2 0 0 1-.206.205H1.53a.2.2 0 0 1-.206-.205V1.529a.2.2 0 0 1 .206-.206z"></path>
</g>
</svg>
<svg xml:space="preserve" style="enable-background:new 0 0 512 512" viewBox="0 0 24 24" y="0" x="0" height="18" width="18" xmlns:xlink="http://www.w3.org/2000/xlink" version="1.1" xmlns="http://www.w3.org/2000/svg" class="checkmark">
<g>
<path data-original="#000000" fill="currentColor" d="M9.707 19.121a.997.997 0 0 1-1.414 0l-5.646-5.647a1.5 1.5 0 0 1 0-2.121l.707-.707a1.5 1.5 0 0 1 2.121 0L9 14.171l9.525-9.525a1.5 1.5 0 0 1 2.121 0l.707.707a1.5 1.5 0 0 1 0 2.121z"></path>
</g>
</svg>
</span>
</button>`;
code.textContent = codeContent;
if (lang) {
code.classList.add(...lang.split(/\s+/));
}
pre.appendChild(codeHeader);
pre.appendChild(code);
pre.classList.add("rounded-b-lg");
messageDiv.appendChild(pre);
new ClipboardJS(".copy");
}
});
chatBox.scrollTop = chatBox.scrollHeight;
}
function clearMessages() {
console.log('Clearing messages...');
fetch("/clear", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
.then(() => {
console.log('Cleared messages from server.');
localStorage.removeItem("conversation_id");
localStorage.removeItem("messages");
chatBox.innerHTML = "";
generateConversationId();
location.reload();
})
.catch((error) => {
console.error("Error:", error);
});
}
function disableInput() {
userInput.disabled = true;
userInput.placeholder = "对话上限喽!请开启新的对话";
}
function saveMessage(role, content) {
let messages = JSON.parse(localStorage.getItem("messages")) || [];
messages.push({ role, content });
localStorage.setItem("messages", JSON.stringify(messages));
}
function loadMessages() {
let messages = JSON.parse(localStorage.getItem("messages")) || [];
messageCount = messages.filter(msg => msg.role === 'user').length;
messages.forEach((msg) => {
const messageDiv = addMessage(msg.role, msg.content);
if (msg.role === "bot" && /```/.test(msg.content)) {
applyStyles(messageDiv);
}
});
if (messageCount >= maxMessages) {
disableInput();
}
}
function applyStyles(container) {
container.querySelectorAll("pre code").forEach((block) => {
hljs.highlightElement(block);
hljs.lineNumbersBlock(block);
});
}
window.onload = loadMessages;
const loader = document.getElementById('loader');
window.addEventListener('load', function() {
loader.style.display = 'none';
});
});
requirements.txt
需要安装的包 ,你需要在命令行中导航到该文件所在的目录,然后运行以下命令:
pip install -r requirements.txt
requirements.txt内容:
aiohttp==3.9.5
aiosignal==1.3.1
async-timeout==4.0.3
attrs==23.2.0
blinker==1.8.2
cachelib==0.13.0
certifi==2024.2.2
cffi==1.16.0
charset-normalizer==3.3.2
click==8.1.7
colorama==0.4.6
cors==1.0.1
dashscope==1.19.2
exceptiongroup==1.2.1
filelock==3.14.0
Flask==3.0.3
Flask-Cors==4.0.1
Flask-Session==0.8.0
frozenlist==1.4.1
future==1.0.0
gevent==24.2.1
greenlet==3.0.3
idna==3.7
importlib_metadata==7.1.0
iniconfig==2.0.0
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.5
msgspec==0.18.6
multidict==6.0.5
mysql-connector-python==8.4.0
packaging==24.0
pluggy==1.5.0
pycparser==2.22
PySocks==1.7.1
pytest==8.2.1
redis==5.0.4
requests==2.32.2
requests-file==2.1.0
tldextract==5.1.2
tomli==2.0.1
urllib3==2.2.1
Werkzeug==3.0.3
yarl==1.9.4
zipp==3.18.2
zope.event==5.0
zope.interface==6.4.post2
确保安装完成编译完成后运行app.py使用浏览器进入相应页面就可以使用了 (本人菜鸟 也是学着做的 请见谅 大佬勿喷)
key.py 可以用来生成secret_key
import secrets
secret_key = secrets.token_hex(16)
print(secret_key)