1.0版本!

成功地standalone!
This commit is contained in:
高子兴 2024-10-25 22:53:08 +08:00
parent ebc5c01eb0
commit a434ef998c
6 changed files with 438 additions and 154 deletions

73
html/index.html Normal file
View File

@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>习题识别工具</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-plus/dist/index.css">
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f4f4f4;
}
#app {
width: 100%;
max-width: 600px;
padding: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: white;
border-radius: 8px;
}
.el-row {
margin-bottom: 20px;
display: flex;
justify-content: center;
}
.el-col {
display: flex;
justify-content: center;
}
@media (max-width: 600px) {
#app {
width: 90%;
padding: 10px;
}
}
</style>
</head>
<body>
<div id="app">
<h2 style="text-align: center;">导航页面</h2>
<p style="text-align: center; color: #999;">请选择您要前往的页面</p>
<el-row>
<el-col :span="24">
<el-button type="primary" @click="navigateTo('/upload')">上传页面</el-button>
<el-button type="success" @click="navigateTo('/terminal')">接收终端</el-button>
</el-col>
</el-row>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
<script src="https://cdn.jsdelivr.net/npm/element-plus"></script>
<script>
const {createApp} = Vue;
const {ElButton, ElRow, ElCol} = ElementPlus;
createApp({
methods: {
navigateTo(path) {
window.location.href = path;
}
}
}).use(ElementPlus).mount('#app');
</script>
</body>
</html>

153
html/terminal.html Normal file
View File

@ -0,0 +1,153 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>接收终端</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-plus/dist/index.css">
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f4f4f4;
}
#app {
width: 100%;
max-width: 600px;
padding: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: white;
border-radius: 8px;
}
.el-textarea__inner {
min-height: 150px;
resize: vertical;
}
.el-row {
margin-bottom: 20px;
display: flex;
justify-content: center;
}
.el-col {
display: flex;
justify-content: center;
}
.image-container {
text-align: center;
margin-bottom: 20px;
}
img {
max-width: 100%;
border-radius: 8px;
}
@media (max-width: 600px) {
#app {
width: 90%;
padding: 10px;
}
}
</style>
</head>
<body>
<div id="app">
<h2 style="text-align: center;">终端-习题识别工具</h2>
<p style="text-align: center; color: #999;">这里将接收来自上传端的习题图像及其识别结果</p>
<div class="image-container" v-if="imageUrl">
<img :src="imageUrl" alt="Received Image">
</div>
<el-row>
<el-col :span="24">
<el-input
v-model="text"
type="textarea"
:rows="4"
readonly
placeholder="接收到的文本将显示在这里..."
></el-input>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-button type="primary" @click="copyText">复制</el-button>
<el-button type="danger" @click="clearText">清空</el-button>
</el-col>
</el-row>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
<script src="https://cdn.jsdelivr.net/npm/element-plus"></script>
<script>
const {createApp} = Vue;
const {ElInput, ElButton, ElRow, ElCol, ElMessage} = ElementPlus;
createApp({
data() {
return {
text: '',
imageUrl: '',
websocket: null,
reconnectInterval: 10000,
};
},
computed: {
uri() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = window.location.host;
return `${protocol}//${host}/listener`;
}
},
mounted() {
this.connectWebSocket();
},
methods: {
connectWebSocket() {
this.websocket = new WebSocket(this.uri);
this.websocket.onopen = () => {
console.log("已连接到服务器。等待接收消息...");
};
this.websocket.onmessage = (listener) => {
const data = JSON.parse(listener.data);
if (data.type === 'text') {
this.text += data.content; // + '\n';
} else if (data.type === 'image') {
this.imageUrl = `data:image/jpeg;base64,${data.content}`;
this.text = '';
}
};
this.websocket.onclose = () => {
console.log("服务器关闭了连接。10秒后重新连接...");
setTimeout(() => {
this.connectWebSocket();
}, this.reconnectInterval);
};
this.websocket.onerror = (error) => {
console.error("WebSocket 错误:", error);
this.websocket.close();
};
},
copyText() {
navigator.clipboard.writeText(this.text).then(() => {
ElMessage.success('文本已复制到剪贴板');
}).catch((error) => {
ElMessage.error('复制文本失败: ' + error);
});
},
clearText() {
this.text = '';
this.imageUrl = '';
}
}
}).use(ElementPlus).mount('#app');
</script>
</body>
</html>

186
html/upload.html Normal file
View File

@ -0,0 +1,186 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>习题上传</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-plus/dist/index.css">
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f4f4f4;
}
.container {
width: 100%;
max-width: 600px;
padding: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: white;
border-radius: 8px;
}
.upload-area {
border: 1px dashed #ccc;
padding: 20px;
text-align: center;
cursor: pointer;
margin-bottom: 20px;
}
.upload-area.dragging {
border-color: #409EFF;
}
.el-textarea__inner {
min-height: 150px;
resize: vertical;
}
.image-container {
text-align: center;
margin-bottom: 20px;
}
img {
max-width: 100%;
border-radius: 8px;
}
@media (max-width: 600px) {
.container {
width: 90%;
padding: 10px;
}
}
#buttons{
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<div id="app" class="container">
<h2 style="text-align: center;">上传-习题识别工具</h2>
<p style="text-align: center; color: #999;">请上传习题图片AI将自动处理并返回结果。</p>
<el-upload
drag
action="/predict"
:on-change="handleChange"
:auto-upload="false"
:show-file-list="false"
:before-upload="beforeUpload"
:accept="'image/*'"
>
<div class="upload-area" :class="{ dragging: dragging }">
<i class="el-icon-upload" style="font-size: 20px;"></i>
<p>请直接粘贴图片</p>
<p>或将图像拖放到此处</p>
<div class="image-container" v-if="uploadedImageUrl">
<img :src="uploadedImageUrl" alt="Uploaded Image">
</div>
<p>点击上传或替换当前图片</p>
</div>
</el-upload>
<el-input
v-model="result"
type="textarea"
:rows="6"
readonly
placeholder="AI生成的文本将显示在这里..."
></el-input>
<div id="buttons" style="margin-top: 20px; text-align: right;">
<el-button type="primary" @click="submit">发送</el-button>
<el-button type="danger" @click="clear">清空</el-button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
<script src="https://cdn.jsdelivr.net/npm/element-plus"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
const {createApp} = Vue;
const {ElUpload, ElInput, ElButton, ElMessage} = ElementPlus;
const app = createApp({
data() {
return {
dragging: false,
file: null,
result: '',
uploadedImageUrl: ''
};
},
methods: {
handleChange(file, fileList) {
this.file = file.raw;
this.uploadedImageUrl = URL.createObjectURL(file.raw);
},
beforeUpload(file) {
// 文件类型和大小的校验
return true;
},
async submit() {
if (!this.file) {
this.$message.error('请选择文件');
return;
}
let formData = new FormData();
formData.append('file', this.file);
try {
const response = await fetch('/predict', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
this.result = ''; // 清空之前的结果
let receivedLength = 0; // 接收到的字节数
while (true) {
const {done, value} = await reader.read();
if (done) {
// 流已完全读取
console.log('Stream complete');
break;
}
const chunk = decoder.decode(value, {stream: true});
this.result += chunk;
receivedLength += value.length;
console.log(`Received ${receivedLength} bytes of data so far`);
}
} catch (error) {
this.$message.error('上传失败: ' + error.message);
console.error('Fetch error:', error);
}
},
clear() {
this.file = null;
this.result = '';
this.uploadedImageUrl = '';
}
}
});
app.use(ElementPlus); // 使用Element Plus
app.mount('#app');
</script>
</body>
</html>

47
main.py
View File

@ -1,27 +1,16 @@
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, UploadFile, File
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, UploadFile, File
from fastapi.responses import StreamingResponse, HTMLResponse
from pydantic import BaseModel
from typing import List
import base64
import asyncio
import openai
app = FastAPI()
with open("key", "r") as f:
key = f.read()
with open("key", "r") as k:
key = k.read()
client = openai.OpenAI(api_key=key,
base_url="https://open.bigmodel.cn/api/paas/v4/"
)
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.get("/hello/{name}")
async def say_hello(name: str):
return {"message": f"Hello {name}"}
base_url="https://open.bigmodel.cn/api/paas/v4/")
with open("prompt", "r", encoding="utf-8") as p:
prompt = p.read()
# WebSocket连接管理器
@ -36,7 +25,8 @@ class ConnectionManager:
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def send_personal_message(self, message: str, websocket: WebSocket):
@staticmethod
async def send_personal_message(message: str, websocket: WebSocket):
await websocket.send_text(message)
async def broadcast(self, message: str):
@ -52,9 +42,10 @@ manager = ConnectionManager()
# WebSocket端点
@app.websocket("/event")
@app.websocket("/listener")
async def event(websocket: WebSocket):
await manager.connect(websocket)
print("Client connected")
try:
while True:
# 这里可以添加逻辑处理来自客户端的消息
@ -80,7 +71,7 @@ async def predict(file: UploadFile = File(...)):
"content": [
{
"type": "text",
"text": "请描述该图片",
"text": prompt,
},
{
"type": "image_url",
@ -109,7 +100,19 @@ async def predict(file: UploadFile = File(...)):
return StreamingResponse(stream_response(), media_type="text/plain")
@app.get("/test", response_class=HTMLResponse)
@app.get("/terminal", response_class=HTMLResponse)
async def test():
with open("test.html", "r", encoding="utf-8") as f:
with open("html/terminal.html", "r", encoding="utf-8") as f:
return f.read()
@app.get("/upload", response_class=HTMLResponse)
async def test():
with open("html/upload.html", "r", encoding="utf-8") as f:
return f.read()
@app.get("/", response_class=HTMLResponse)
async def test():
with open("html/index.html", "r", encoding="utf-8") as f:
return f.read()

1
prompt Normal file
View File

@ -0,0 +1 @@
请直接输出图片中的习题内容。不要输出除了题目内容外的各种提示语,比如“好的,下面我将...”。不要输出已经图中的题目里已经填写的答案只要题干。请将按照方便粘贴到word文档的格式进行输出避免使用markdown格式。遇到题干里有无法转为文字的部分比如题目的选项为图片等情况预留好空位并放置一个标记[图片]即可。

132
test.html
View File

@ -1,132 +0,0 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket 文本和图片接收器</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-plus/dist/index.css">
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f4f4f4;
}
#app {
width: 100%;
max-width: 600px;
padding: 20px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
background-color: white;
border-radius: 8px;
}
.el-textarea__inner {
min-height: 150px;
resize: vertical;
}
.el-row {
margin-bottom: 20px;
}
.image-container {
text-align: center;
margin-bottom: 20px;
}
img {
max-width: 100%;
border-radius: 8px;
}
@media (max-width: 600px) {
#app {
width: 90%;
padding: 10px;
}
}
</style>
</head>
<body>
<div id="app">
<div class="image-container" v-if="imageUrl">
<img :src="imageUrl" alt="Received Image">
</div>
<el-row>
<el-col :span="24">
<el-input
v-model="text"
type="textarea"
:rows="4"
readonly
placeholder="接收到的文本将显示在这里..."
></el-input>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-button type="primary" @click="copyText">复制</el-button>
<el-button type="danger" @click="clearText">清空</el-button>
</el-col>
</el-row>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
<script src="https://cdn.jsdelivr.net/npm/element-plus"></script>
<script>
const { createApp } = Vue;
const { ElInput, ElButton, ElRow, ElCol, ElMessage } = ElementPlus;
createApp({
data() {
return {
text: '',
imageUrl: '',
websocket: null,
reconnectInterval: 10000,
uri: "ws://localhost:8000/event"
};
},
mounted() {
this.connectWebSocket();
},
methods: {
connectWebSocket() {
this.websocket = new WebSocket(this.uri);
this.websocket.onopen = () => {
console.log("已连接到服务器。等待接收消息...");
};
this.websocket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'text') {
this.text += data.content; // + '\n';
} else if (data.type === 'image') {
this.imageUrl = `data:image/jpeg;base64,${data.content}`;
this.text = '';
}
};
this.websocket.onclose = () => {
console.log("服务器关闭了连接。10秒后重新连接...");
setTimeout(() => {
this.connectWebSocket();
}, this.reconnectInterval);
};
this.websocket.onerror = (error) => {
console.error("WebSocket 错误:", error);
this.websocket.close();
};
},
copyText() {
navigator.clipboard.writeText(this.text).then(() => {
ElMessage.success('文本已复制到剪贴板');
}).catch((error) => {
ElMessage.error('复制文本失败: ' + error);
});
},
clearText() {
this.text = '';
this.imageUrl = '';
}
}
}).use(ElementPlus).mount('#app');
</script>
</body>
</html>