Python,学霸
简洁
大家好!今天给你们带来了了AI搜索2.0,新增多人使用互不干扰,ui优化。 前期调用的是CSDN搜索结果,后面会改进。
演示地址
https://ai.zhizhuya.cn/
后端
from flask import Flask, render_template, request, jsonify, session
import re
import requests
from bs4 import BeautifulSoup
import json
import mechanicalsoup
import dashscope
from http import HTTPStatus
import secrets
import hashlib
import random
from urllib.parse import quote
app = Flask(__name__)
app.secret_key = secrets.token_hex(16)
dashscope.api_key = "sk-97ea5c1c83ec4ef7bb7a90397d544e3d"
history = {}
def generate_user_id():
user_id = hashlib.md5(str(random.random()).encode()).hexdigest()
return"user_" + str(user_id)
def crawl_pages(keyword):
keyword=quote(keyword, safe='')
base_url = f"https://blog.csdn.net/nav/{keyword}"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
}
all_links = []
url = f"{base_url}"
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, 'html.parser')
links = soup.find_all('a', class_="blog")
contents = soup.find_all('p', class_="desc")
for link, content in zip(links, contents):
href = link['href']
text = link.get_text()
ct = content.get_text()
if href and text and re.match(r'^https://blog\.csdn\.net/.+?/article/details/\d+', href):
all_links.append({"title": text, "link": href, "content": ct})
return all_links
def generate_text(model, prompt, seed=1234, top_p=0.8, result_format='message', enable_search=False, max_tokens=1500, temperature=1.0, repetition_penalty=1.0):
user_id = session.get('user_id')
if not user_id:
user_id = generate_user_id()
session['user_id'] = user_id
history[user_id] = []
response = dashscope.Generation.call(
model=model,
prompt=prompt,
seed=seed,
top_p=top_p,
result_format=result_format,
enable_search=enable_search,
max_tokens=max_tokens,
temperature=temperature,
repetition_penalty=repetition_penalty,
history=history.get(user_id, [])[:5]
)
if response.status_code == HTTPStatus.OK:
generated_text = response['output']['choices'][0]['message']['content']
history[user_id].append({"user": prompt, "bot": generated_text})
return {"status": "success", "response": generated_text}
else:
return {"status": "error", "message": 'Request id: %s, Status code: %s, error code: %s, error message: %s' % (
response.request_id, response.status_code,
response.code, response.message
)}
@app.route('/', methods=['GET'])
def index():
user_id = session.get('user_id')
if user_id is not None:
chat_history = history.get(user_id, [])
else:
chat_history = []
return render_template('index.html', history=chat_history)
@app.route('/generate-text', methods=['POST'])
def generate_text_api():
prompt = request.json['prompt']
result = generate_text('qwen-max', prompt)
return jsonify(result)
@app.route('/clear', methods=['POST'])
def clear():
user_id = session.get('user_id')
if user_id inhistory:
del history[user_id]
session.pop('user_id', None)
return'', HTTPStatus.NO_CONTENT
else:
return'User not found', HTTPStatus.NOT_FOUND
@app.route('/search', methods=['GET', 'POST'])
def search():
if request.method == 'POST':
keyword = request.form['keyword']
elif request.method == 'GET':
keyword = request.args.get('keyword', '')
else:
keyword = ''
results = crawl_pages(keyword)
output = ""
for result in results:
output += f"<li><a id='myID' href='{result['link']}'>{result['title']}</a></li><br>"
return output
@app.route('/page_content')
def page_content():
url = request.args.get('url', '')
if not url:
return'缺少 url 参数'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
}
response = requests.get(url, headers=headers)
response.encoding = 'utf-8'
soup = BeautifulSoup(response.text, 'html.parser')
article_content = soup.find('div', class_='article_content')
if article_content:
article_text = article_content.get_text()
else:
article_text = '未找到文章内容'
return f"{article_text}"
if __name__ == '__main__':
app.run(debug=True, threaded=True)<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<title>AI搜索-PYTHON学霸</title>
< style>
body {
display: flex;
flex-direction: column;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
}
.website-container {
position: fixed;
top: 0;
left: 0;
width: 65%;
height: 100%;
display: grid;
place-items: center;
border: 1px solid #ccc;
overflow-y: auto;
background-color: #f9f9f9;
padding: 10px;
}
.chat-container {
height: 100%;
width: 100%;
overflow: hidden;
margin-left: 70%;
overflow-y: auto;
padding: 10px;
}
.chat-container::-webkit-scrollbar {
width: 20px;
}
.chat-container::-webkit-scrollbar-track {
background-color: #f2f2f2;
border-radius: 10px;
}
.chat-container::-webkit-scrollbar-thumb {
background-color: #84d19a;
border-radius: 10px;
}
.avatar-user {
width: 40px;
height: 40px;
background-color: #2ea44f; /* 设置用户头像颜色 */
border-radius: 50%; /* 将头像设置为圆形 */
margin-left: 10px; /* 调整头像与消息之间的间距 */
}
.avatar-bot {
width: 40px;
height: 40px;
right: 0;
background-color: #b6bbb7; /* 设置机器人头像颜色 */
border-radius: 50%; /* 将头像设置为圆形 */
margin-right: 10px; /* 调整头像与消息之间的间距 */
object-fit: cover; /* 防止头像变形 */
}
.message {
display: flex;
align-items: center; /* 垂直居中消息和头像 */
margin-bottom: 1rem;
}
.message-text {
padding: 10px;
word-wrap: break-word;
border-radius: 6px;
%;
margin:100px;
}
.message-text-user {
padding: 10px;
border-radius: 6px;
%;
margin:100px;
word-wrap: break-word;
background-color: #2ea44f;
color:white;
}
.user-message {
display: flex;
justify-content: flex-end;
}
.bot-message .message-text {
background-color: #f4f4f4;
color: black;
}
.input-container {
position: fixed;
bottom: 0;
right: 0;
width: calc(100% - 220px); /* 考虑右侧栏的宽度 */
display: flex;
align-items: center;
background-color: #f9f9f9;
padding: 10px;
}
.input-field {
flex-grow: 1;
padding: 0.75rem;
border: 1px solid #d1d5da;
border-radius: 6px;
margin-right: 1rem;
}
.send-button {
padding: 0.75rem 1rem;
background-color: #2ea44f;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
.del-button {
padding: 0.75rem 1rem;
background-color: #aeaeae;
color: white;
border: none;
margin-right: 10px;
border-radius: 6px;
cursor: pointer;
}
.send-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.typing-indicator {
position: absolute;
margin-bottom:50px
font-size: 0.8rem;
color: #586069;
}
.typing:before,
.typing:after {
content: '';
display: inline-block;
width: 0.75rem;
height: 0.75rem;
border-radius: 50%;
margin-right: 0.25rem;
animation: typing 1s infinite;
}
@keyframes typing {
0% {
transform: scale(0);
}
50% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
/* 样式定义 */
.listView {
list- style-type: none;
margin: 0;
margin-top: 30px;
padding: 0;
}
.listView li {
background-color: #f4f4f4;
padding: 10px;
margin-bottom: 5px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.3s ease;
}
.listView li:hover {
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2);
}
body {
font-family: Arial, sans-serif;
}
/* styles specific to PC */
@media (min-width: 768px) {
/* Adjust the layout for larger screens */
.container {
width: 80%;
margin: 0 auto;
}
}
.listView li a {
text-decoration: none;
color: #333;
display: block;
transition: color 0.3s ease;
}
.listView li a:hover {
color: #ff6600;
}
.search-container {
display: flex;
justify-content: center;
align-items: center;
height: 50px;
background-color: #f2f2f2;
border-radius: 25px;
width: 300px;
}
.search-box {
width: 100%;
height: 100%;
padding: 0 20px;
border: none;
background: none;
font-size: 16px;
color: #333;
outline: none;
}
.search-button {
height: 100%;
padding: 0 15px;
background-color: #2ea44f;
color: #fff;
border: none;
width:100px;
border-radius: 0 25px 25px 0;
cursor: pointer;
}
pre {
position: relative;
background-color: #f6f8fa; /* 设置代码块背景颜色 */
padding: 16px;
margin-bottom: 20px;
border-radius: 6px;
border: 1px solid #e1e4e8;
font-size: 14px;
tab-size: 4;
}
pre code {
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
}
</ style>
</head>
<body >"display: flex; flex-direction: column; height: 100vh;">
<div >"height: 90%; width:100%; overflow-y: auto; display: flex;">
<ul class="chat-container" id="chat-container">
{% for item inhistory %}
{% if loop.index == 1 %}
<li class="message user-message">
<div class="message-text-user">{{ item.user }}</div>
<div class="avatar-user"></div>
</li>
<li class="message bot-message">
<div class="avatar-bot"></div>
<div class="message-text">{{ item.bot }}</div>
</li>
{% else %}
<li class="message user-message">
<div class="message-text-user">{{ item.user }}</div>
<div class="avatar-user"></div>
</li>
<li class="message bot-message">
<div class="avatar-bot"></div>
<div class="message-text">{{ item.bot }}</div>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
<form class="input-container" id="input-form" method="POST" >"position: fixed; bottom: 0; right: 0; width: 30%;">
<button type="button" class="del-button" id="del-button" >"width: 100px;"onclick='del()'>清除</button>
<input type="text" placeholder="输入内容" class="input-field" autocomplete="off" id="input-field" name="prompt"
autocomplete="off" >"width: calc(100% - 100px);">
<button type="submit" class="send-button" id="send-button" disabled >"width: 100px;">搜索</button>
</form>
<div id="website-container" class="website-container">
<form class="search-container" id ="souba" method="POST">
<input type="text" autocomplete="off" id ="souba-input" class="search-box" placeholder="搜吧...">
<button class="search-button">搜索</button>
</form>
<ul class="listView" id="listView"></ul>
</div>
<link rel=" stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.27.0/themes/prism.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.1/showdown.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/typeit/dist/typeit.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.27.0/components/prism-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.27.0/plugins/autoloader/prism-autoloader.min.js"></script>
<script>
//初始化
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.message-text').forEach(function(element) {
const converter = new showdown.Converter({ tables: true });
const htmlContent = converter.makeHtml(element.innerHTML);
element.innerHTML = htmlContent;
Prism.highlightAll();
});
});
// 搜索环节
const sou = document.getElementById('souba');
const souInput = document.getElementById('souba-input');
sou.addEventListener('submit', async (event) => {
event.preventDefault();
const keyword = souInput.value.trim();
const aa = document.getElementById('listView');
aa.innerHTML = await getA(keyword);
const hr = document.getElementById('myID');
const url = await hr.getAttribute('href');
const tx = await getPageContent(url);
const message =await document.createElement('li');
message. classList.add('user-message');
message. classList.add('message');
const chatContainer =await document.getElementById('chat-container');
message.innerHTML = `
<div class="message-text-user">${tx}</div>
<div class="avatar-user"></div>
`;
chatContainer.appendChild(message);
showTypingAnimation(message);
const response = await generateText(tx);
if (response.status === 'success') {
const message =await document.createElement('li');
printMarkdownText( chatContainer,response.response,50);
hideTypingAnimation(message);
}
});
// 聊天环节
const inputForm = document.getElementById('input-form');
const inputField = document.getElementById('input-field');
const chatContainer = document.getElementById('chat-container');
inputField.addEventListener('input', () => {
const userInput = inputField.value.trim();
document.getElementById('send-button').disabled = !userInput;
});
inputForm.addEventListener('submit', async (event) => {
event.preventDefault();
const userInput = inputField.value.trim();
const chatContainer = document.getElementById('chat-container');
if (!userInput) {
return;
}
const message = document.createElement('li');
message. classList.add('user-message');
message. classList.add('message');
message.innerHTML = `
<div class="message-text-user">${userInput}</div>
<div class="avatar-user"></div>`;
chatContainer.appendChild(message);
inputField.value = '';
chatContainer.scrollTop = chatContainer.scrollHeight;
inputField.disabled = true;
document.getElementById('send-button').disabled = true;
showTypingAnimation(message);
const response = await generateText(userInput);
hideTypingAnimation(message);
if (response.status === 'success') {
printMarkdownText( chatContainer,response.response,50);
} else {
alert(response.message);
}
inputField.disabled = false;
inputField.focus();
});
async function generateText(prompt) {
const response = await fetch('/generate-text', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
prompt
})
});
return await response.json();
}
async function getA(prompt) {
const response = await fetch(`/search?keyword=${prompt}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
return await response.text();
}
function showTypingAnimation(element) {
const chatContainer = document.getElementById('chat-container');
chatContainer.scrollTop = chatContainer.scrollHeight + 10;
const rect = element.getBoundingClientRect();
const topPosition = rect.top + window.scrollY + rect.height;
const leftPosition = rect.left + window.scrollX;
const typingIndicator = document.createElement('div');
typingIndicator. classList.add('typing-indicator');
typingIndicator. style.top = `${topPosition-20}px`;
typingIndicator. style.left = `${leftPosition}px`;
typingIndicator.innerHTML = '思考中...';
document.body.appendChild(typingIndicator);
}
function hideTypingAnimation(element) {
const typingIndicator = document.querySelector('.typing-indicator');
if (typingIndicator) {
typingIndicator.remove();
}
element. classList.remove('typing');
}
async function del(url) {
const response = await fetch(`/clear`, {
method: 'POST'
});
location.replace("/");
return 0;
}
async function getPageContent(url) {
const response = await fetch(`/page_content?url=${url}`, {
method: 'GET'
});
return await response.text();
}
function printMarkdownText(container, markdownText, num) {
const converter = new showdown.Converter({ tables: true });
const htmlContent = converter.makeHtml(markdownText);
const cleanedHtml = htmlContent.replace(/<ol[^>]*>|<\/ol>|<li[^>]*>|<\/li><\/span>/g, '');
const paragraphs = cleanedHtml.split('<p>').map(p => p.replace('</p>', '<br>'));
const message = document.createElement('li');
message. classList.add('bot-message');
message. classList.add('message');
const icon = document.createElement('div');
icon. classList.add('avatar-bot');
const msText = document.createElement('div');
msText. classList.add('message-text');
message.appendChild(icon);
message.appendChild(msText);
container.appendChild(message);
let currentIndex = 0;
let currentText = '';
const printInterval = setInterval(() => {
if (currentIndex < paragraphs.length) {
currentText += paragraphs[currentIndex];
msText.innerHTML = currentText;
Prism.highlightAll();
container.scrollTop = container.scrollHeight;
currentIndex++;
} else {
clearInterval(printInterval);
}
}, num);
}
</script>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<title>AI搜索-PYTHON学霸</title>
< style>
body {
display: flex;
flex-direction: column;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
}
.website-container {
position: fixed;
top: 0;
left: 0;
width: 65%;
height: 100%;
display: grid;
place-items: center;
border: 1px solid #ccc;
overflow-y: auto;
background-color: #f9f9f9;
padding: 10px;
}
.chat-container {
height: 100%;
width: 100%;
overflow: hidden;
margin-left: 70%;
overflow-y: auto;
padding: 10px;
}
.chat-container::-webkit-scrollbar {
width: 20px;
}
.chat-container::-webkit-scrollbar-track {
background-color: #f2f2f2;
border-radius: 10px;
}
.chat-container::-webkit-scrollbar-thumb {
background-color: #84d19a;
border-radius: 10px;
}
.avatar-user {
width: 40px;
height: 40px;
background-color: #2ea44f; /* 设置用户头像颜色 */
border-radius: 50%; /* 将头像设置为圆形 */
margin-left: 10px; /* 调整头像与消息之间的间距 */
}
.avatar-bot {
width: 40px;
height: 40px;
right: 0;
background-color: #b6bbb7; /* 设置机器人头像颜色 */
border-radius: 50%; /* 将头像设置为圆形 */
margin-right: 10px; /* 调整头像与消息之间的间距 */
object-fit: cover; /* 防止头像变形 */
}
.message {
display: flex;
align-items: center; /* 垂直居中消息和头像 */
margin-bottom: 1rem;
}
.message-text {
padding: 10px;
word-wrap: break-word;
border-radius: 6px;
%;
margin:100px;
}
.message-text-user {
padding: 10px;
border-radius: 6px;
%;
margin:100px;
word-wrap: break-word;
background-color: #2ea44f;
color:white;
}
.user-message {
display: flex;
justify-content: flex-end;
}
.bot-message .message-text {
background-color: #f4f4f4;
color: black;
}
.input-container {
position: fixed;
bottom: 0;
right: 0;
width: calc(100% - 220px); /* 考虑右侧栏的宽度 */
display: flex;
align-items: center;
background-color: #f9f9f9;
padding: 10px;
}
.input-field {
flex-grow: 1;
padding: 0.75rem;
border: 1px solid #d1d5da;
border-radius: 6px;
margin-right: 1rem;
}
.send-button {
padding: 0.75rem 1rem;
background-color: #2ea44f;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
.del-button {
padding: 0.75rem 1rem;
background-color: #aeaeae;
color: white;
border: none;
margin-right: 10px;
border-radius: 6px;
cursor: pointer;
}
.send-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.typing-indicator {
position: absolute;
margin-bottom:50px
font-size: 0.8rem;
color: #586069;
}
.typing:before,
.typing:after {
content: '';
display: inline-block;
width: 0.75rem;
height: 0.75rem;
border-radius: 50%;
margin-right: 0.25rem;
animation: typing 1s infinite;
}
@keyframes typing {
0% {
transform: scale(0);
}
50% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
/* 样式定义 */
.listView {
list- style-type: none;
margin: 0;
margin-top: 30px;
padding: 0;
}
.listView li {
background-color: #f4f4f4;
padding: 10px;
margin-bottom: 5px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.3s ease;
}
.listView li:hover {
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2);
}
body {
font-family: Arial, sans-serif;
}
/* styles specific to PC */
@media (min-width: 768px) {
/* Adjust the layout for larger screens */
.container {
width: 80%;
margin: 0 auto;
}
}
.listView li a {
text-decoration: none;
color: #333;
display: block;
transition: color 0.3s ease;
}
.listView li a:hover {
color: #ff6600;
}
.search-container {
display: flex;
justify-content: center;
align-items: center;
height: 50px;
background-color: #f2f2f2;
border-radius: 25px;
width: 300px;
}
.search-box {
width: 100%;
height: 100%;
padding: 0 20px;
border: none;
background: none;
font-size: 16px;
color: #333;
outline: none;
}
.search-button {
height: 100%;
padding: 0 15px;
background-color: #2ea44f;
color: #fff;
border: none;
width:100px;
border-radius: 0 25px 25px 0;
cursor: pointer;
}
pre {
position: relative;
background-color: #f6f8fa; /* 设置代码块背景颜色 */
padding: 16px;
margin-bottom: 20px;
border-radius: 6px;
border: 1px solid #e1e4e8;
font-size: 14px;
tab-size: 4;
}
pre code {
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
}
</ style>
</head>
<body >"display: flex; flex-direction: column; height: 100vh;">
<div >"height: 90%; width:100%; overflow-y: auto; display: flex;">
<ul class="chat-container" id="chat-container">
{% for item inhistory %}
{% if loop.index == 1 %}
<li class="message user-message">
<div class="message-text-user">{{ item.user }}</div>
<div class="avatar-user"></div>
</li>
<li class="message bot-message">
<div class="avatar-bot"></div>
<div class="message-text">{{ item.bot }}</div>
</li>
{% else %}
<li class="message user-message">
<div class="message-text-user">{{ item.user }}</div>
<div class="avatar-user"></div>
</li>
<li class="message bot-message">
<div class="avatar-bot"></div>
<div class="message-text">{{ item.bot }}</div>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
<form class="input-container" id="input-form" method="POST" >"position: fixed; bottom: 0; right: 0; width: 30%;">
<button type="button" class="del-button" id="del-button" >"width: 100px;"onclick='del()'>清除</button>
<input type="text" placeholder="输入内容" class="input-field" autocomplete="off" id="input-field" name="prompt"
autocomplete="off" >"width: calc(100% - 100px);">
<button type="submit" class="send-button" id="send-button" disabled >"width: 100px;">搜索</button>
</form>
<div id="website-container" class="website-container">
<form class="search-container" id ="souba" method="POST">
<input type="text" autocomplete="off" id ="souba-input" class="search-box" placeholder="搜吧...">
<button class="search-button">搜索</button>
</form>
<ul class="listView" id="listView"></ul>
</div>
<link rel=" stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.27.0/themes/prism.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.1/showdown.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/typeit/dist/typeit.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.27.0/components/prism-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.27.0/plugins/autoloader/prism-autoloader.min.js"></script>
<script>
//初始化
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.message-text').forEach(function(element) {
const converter = new showdown.Converter({ tables: true });
const htmlContent = converter.makeHtml(element.innerHTML);
element.innerHTML = htmlContent;
Prism.highlightAll();
});
});
// 搜索环节
const sou = document.getElementById('souba');
const souInput = document.getElementById('souba-input');
sou.addEventListener('submit', async (event) => {
event.preventDefault();
const keyword = souInput.value.trim();
const aa = document.getElementById('listView');
aa.innerHTML = await getA(keyword);
const hr = document.getElementById('myID');
const url = await hr.getAttribute('href');
const tx = await getPageContent(url);
const message =await document.createElement('li');
message. classList.add('user-message');
message. classList.add('message');
const chatContainer =await document.getElementById('chat-container');
message.innerHTML = `
<div class="message-text-user">${tx}</div>
<div class="avatar-user"></div>
`;
chatContainer.appendChild(message);
showTypingAnimation(message);
const response = await generateText(tx);
if (response.status === 'success') {
const message =await document.createElement('li');
printMarkdownText( chatContainer,response.response,50);
hideTypingAnimation(message);
}
});
// 聊天环节
const inputForm = document.getElementById('input-form');
const inputField = document.getElementById('input-field');
const chatContainer = document.getElementById('chat-container');
inputField.addEventListener('input', () => {
const userInput = inputField.value.trim();
document.getElementById('send-button').disabled = !userInput;
});
inputForm.addEventListener('submit', async (event) => {
event.preventDefault();
const userInput = inputField.value.trim();
const chatContainer = document.getElementById('chat-container');
if (!userInput) {
return;
}
const message = document.createElement('li');
message. classList.add('user-message');
message. classList.add('message');
message.innerHTML = `
<div class="message-text-user">${userInput}</div>
<div class="avatar-user"></div>`;
chatContainer.appendChild(message);
inputField.value = '';
chatContainer.scrollTop = chatContainer.scrollHeight;
inputField.disabled = true;
document.getElementById('send-button').disabled = true;
showTypingAnimation(message);
const response = await generateText(userInput);
hideTypingAnimation(message);
if (response.status === 'success') {
printMarkdownText( chatContainer,response.response,50);
} else {
alert(response.message);
}
inputField.disabled = false;
inputField.focus();
});
async function generateText(prompt) {
const response = await fetch('/generate-text', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
prompt
})
});
return await response.json();
}
async function getA(prompt) {
const response = await fetch(`/search?keyword=${prompt}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
return await response.text();
}
function showTypingAnimation(element) {
const chatContainer = document.getElementById('chat-container');
chatContainer.scrollTop = chatContainer.scrollHeight + 10;
const rect = element.getBoundingClientRect();
const topPosition = rect.top + window.scrollY + rect.height;
const leftPosition = rect.left + window.scrollX;
const typingIndicator = document.createElement('div');
typingIndicator. classList.add('typing-indicator');
typingIndicator. style.top = `${topPosition-20}px`;
typingIndicator. style.left = `${leftPosition}px`;
typingIndicator.innerHTML = '思考中...';
document.body.appendChild(typingIndicator);
}
function hideTypingAnimation(element) {
const typingIndicator = document.querySelector('.typing-indicator');
if (typingIndicator) {
typingIndicator.remove();
}
element. classList.remove('typing');
}
async function del(url) {
const response = await fetch(`/clear`, {
method: 'POST'
});
location.replace("/");
return 0;
}
async function getPageContent(url) {
const response = await fetch(`/page_content?url=${url}`, {
method: 'GET'
});
return await response.text();
}
function printMarkdownText(container, markdownText, num) {
const converter = new showdown.Converter({ tables: true });
const htmlContent = converter.makeHtml(markdownText);
const cleanedHtml = htmlContent.replace(/<ol[^>]*>|<\/ol>|<li[^>]*>|<\/li><\/span>/g, '');
const paragraphs = cleanedHtml.split('<p>').map(p => p.replace('</p>', '<br>'));
const message = document.createElement('li');
message. classList.add('bot-message');
message. classList.add('message');
const icon = document.createElement('div');
icon. classList.add('avatar-bot');
const msText = document.createElement('div');
msText. classList.add('message-text');
message.appendChild(icon);
message.appendChild(msText);
container.appendChild(message);
let currentIndex = 0;
let currentText = '';
const printInterval = setInterval(() => {
if (currentIndex < paragraphs.length) {
currentText += paragraphs[currentIndex];
msText.innerHTML = currentText;
Prism.highlightAll();
container.scrollTop = container.scrollHeight;
currentIndex++;
} else {
clearInterval(printInterval);
}
}, num);
}
</script>
</body>
</html>
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat with AI</title>
< style>
body {
display: flex;
flex-direction: column;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
}
.website-container {
position: fixed;
top: 0;
right: 0;
width: 350px;
height: 100%;
border: 1px solid #ccc;
overflow-y: auto;
background-color: #f9f9f9;
padding: 10px;
}
.chat-container {
height: 100%;
width: 85%;
overflow: hidden;
overflow-y: auto;
padding: 10px;
margin-right: 220px; /* 腾出右侧栏的宽度 */
}
.chat-container::-webkit-scrollbar {
display: none;
}
.avatar-user {
width: 40px;
height: 40px;
background-color: #7fb8e7; /* 设置用户头像颜色 */
border-radius: 50%; /* 将头像设置为圆形 */
margin-left: 10px; /* 调整头像与消息之间的间距 */
}
.avatar-bot {
width: 40px;
height: 40px;
right: 0;
background-color: #28a745; /* 设置机器人头像颜色 */
border-radius: 50%; /* 将头像设置为圆形 */
margin-right: 10px; /* 调整头像与消息之间的间距 */
object-fit: cover; /* 防止头像变形 */
}
.message {
display: flex;
align-items: center; /* 垂直居中消息和头像 */
margin-bottom: 1rem;
}
.message-text {
padding: 10px;
word-wrap: break-word;
border-radius: 6px;
%;
margin:100px;
}
.message-text-user {
padding: 10px;
border-radius: 6px;
%;
margin:100px;
word-wrap: break-word;
background-color: #ececec;
}
.user-message {
display: flex;
justify-content: flex-end;
}
.bot-message .message-text {
background-color: #2ea44f;
color: white;
}
.input-container {
position: fixed;
bottom: 0;
left: 0;
width: calc(100% - 220px); /* 考虑右侧栏的宽度 */
display: flex;
align-items: center;
background-color: #f9f9f9;
padding: 10px;
}
.input-field {
flex-grow: 1;
padding: 0.75rem;
border: 1px solid #d1d5da;
border-radius: 6px;
margin-right: 1rem;
}
.send-button {
padding: 0.75rem 1rem;
background-color: #2ea44f;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
.del-button {
padding: 0.75rem 1rem;
background-color: #aeaeae;
color: white;
border: none;
margin-right: 10px;
border-radius: 6px;
cursor: pointer;
}
.send-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.typing-indicator {
position: absolute;
margin-bottom:50px
font-size: 0.8rem;
color: #586069;
}
.typing:before,
.typing:after {
content: '';
display: inline-block;
width: 0.75rem;
height: 0.75rem;
border-radius: 50%;
margin-right: 0.25rem;
animation: typing 1s infinite;
}
@keyframes typing {
0% {
transform: scale(0);
}
50% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
/* 样式定义 */
.listView {
list- style-type: none;
margin: 0;
padding: 0;
}
.listView li {
background-color: #f4f4f4;
padding: 10px;
margin-bottom: 5px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.3s ease;
}
.listView li:hover {
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2);
}
.listView li a {
text-decoration: none;
color: #333;
display: block;
transition: color 0.3s ease;
}
.listView li a:hover {
color: #ff6600;
}
</ style>
</head>
<body >"display: flex; flex-direction: column; height: 100vh;">
<div id="website-container" class="website-container">
<ul class="listView" id="listView"></ul>
</div>
<div >"height: 90%; width:80%; overflow-y: auto; display: flex;">
<ul class="chat-container" id="chat-container">
{% for item inhistory %}
{% if loop.index == 1 %}
<li class="message user-message">
<div class="message-text-user">{{ item.user }}</div>
<div class="avatar-user"></div>
</li>
<li class="message bot-message">
<div class="avatar-bot"></div>
<div class="message-text">{{ item.bot }}</div>
</li>
{% else %}
<li class="message user-message">
<div class="message-text-user">{{ item.user }}</div>
<div class="avatar-user"></div>
</li>
<li class="message bot-message">
<div class="avatar-bot"></div>
<div class="message-text">{{ item.bot }}</div>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
<form class="input-container" id="input-form" method="POST" >"position: fixed; bottom: 0; left: 0; width: 65%;">
<button type="button" class="del-button" id="del-button" >"width: 100px;"onclick='del()'>清除</button>
<input type="text" placeholder="你负责搜,我负责找" class="input-field" id="input-field" name="prompt"
autocomplete="off" >"width: calc(100% - 100px);">
<button type="submit" class="send-button" id="send-button" disabled >"width: 100px;">搜索</button>
</form>
<script>
const inputForm = document.getElementById('input-form');
const inputField = document.getElementById('input-field');
const chatContainer = document.getElementById('chat-container');
inputField.addEventListener('input', () => {
const userInput = inputField.value.trim();
document.getElementById('send-button').disabled = !userInput;
});
inputForm.addEventListener('submit', async (event) => {
event.preventDefault();
const userInput = inputField.value.trim();
const chatContainer = document.getElementById('chat-container');
if (!userInput) {
return;
}
const userMessage = createMessageElement(userInput, 'user-message',"message-text-user","avatar-user");
chatContainer.appendChild(userMessage);
inputField.value = '';
chatContainer.scrollTop = chatContainer.scrollHeight;
inputField.disabled = true;
document.getElementById('send-button').disabled = true;
showTypingAnimation(userMessage);
const aa = document.getElementById('listView');
aa.innerHTML = await getA(userInput);
const response = await generateText(userInput);
hideTypingAnimation(userMessage);
if (response.status === 'success') {
const botResponse = createMessageElement(response.response, 'bot-message',"message-text","avatar-bot");
chatContainer.appendChild(botResponse);
printMessageText(botResponse);
} else {
alert(response.message);
}
inputField.disabled = false;
inputField.focus();
});
async function generateText(prompt) {
const response = await fetch('/generate-text', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
prompt
})
});
return await response.json();
}
async function getA(prompt) {
const response = await fetch(`/search?keyword=${prompt}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
return await response.text();
}
function createMessageElement(text, className,name,bot) {
const message = document.createElement('li');
message. classList.add('message', className, 'typing');
if (bot=="avatar-bot"){
message.innerHTML = `
<div class=${bot}></div>
<div class=${name}>${text}</div>
`;
}else{
message.innerHTML = `
<div class=${name}>${text}</div>
<div class=${bot}></div>
`;
}
return message;
}
function showTypingAnimation(element) {
const chatContainer = document.getElementById('chat-container');
chatContainer.scrollTop = chatContainer.scrollHeight+10 ;
const rect = element.getBoundingClientRect();
const topPosition = rect.top + window.scrollY + rect.height;
const leftPosition = rect.left + window.scrollX;
const typingIndicator = document.createElement('div');
typingIndicator. classList.add('typing-indicator');
typingIndicator. style.top = `${topPosition}px`;
typingIndicator. style.left = `${leftPosition}px`;
typingIndicator.innerHTML = '思考中...';
document.body.appendChild(typingIndicator);
}
function hideTypingAnimation(element) {
const typingIndicator = document.querySelector('.typing-indicator');
if (typingIndicator) {
typingIndicator.remove();
}
element. classList.remove('typing');
}
// 添加逐字打印效果
function printMessageText(message) {
const chatContainer = document.getElementById('chat-container');
const text = message.querySelector('.message-text');
const textContent = text.textContent;
text.textContent = '';
for (let i = 0; i < textContent.length; i++) {
setTimeout(() => {
text.textContent += textContent.charAt(i);
chatContainer.scrollTop = chatContainer.scrollHeight ;
}, i * 10); // 控制打印速度
}
}
async function handleLinkClick(link) {
const content = await getPageContent(link);
console.log(link);
console.log(content);
const userMessage = createMessageElement("总结中:"+link, 'user-message',"message-text-user","avatar-user");
showTypingAnimation(userMessage);
const chatContainer = document.getElementById('chat-container');
chatContainer.appendChild(userMessage);
const response = await generateText("总结内容:"+content);
hideTypingAnimation(userMessage);
if (response.status === 'success') {
const botResponse = createMessageElement(response.response, 'bot-message',"message-text","avatar-bot");
chatContainer.appendChild(botResponse);
printMessageText(botResponse);
} else {
alert(response.message);
}
}
function del(url) {
const response = fetch(`/clear`, {
method: 'POST'
});
location.replace("/");
return 0;
}
// 获取页面内容
async function getPageContent(url) {
const response = await fetch(`/page_content?url=${url}`, {
method: 'GET'
});
return await response.text();
}
</script>
</body>
</html>