移除ai对话

This commit is contained in:
XIE7654
2025-07-16 14:18:39 +08:00
parent 3fe272ffca
commit ed3e325962
7 changed files with 13 additions and 362 deletions

View File

@@ -1,26 +0,0 @@
from channels.generic.websocket import AsyncWebsocketConsumer
import json
from ai.langchain_client import get_ai_reply_stream
from ai.utils import get_first_available_ai_config
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
async def disconnect(self, close_code):
pass
async def receive(self, text_data):
data = json.loads(text_data)
user_message = data.get("message", "")
model, api_key, api_base = await get_first_available_ai_config()
async def send_chunk(chunk):
await self.send(text_data=json.dumps({"is_streaming": True, "message": chunk}))
await get_ai_reply_stream(user_message, send_chunk, model_name=model, api_key=api_key, api_base=api_base)
# 结束标记
await self.send(text_data=json.dumps({"done": True}))

View File

@@ -1,25 +0,0 @@
from langchain.schema import HumanMessage
from langchain_core.callbacks import AsyncCallbackHandler
from langchain_community.chat_models import ChatOpenAI
class MyHandler(AsyncCallbackHandler):
def __init__(self, send_func):
super().__init__()
self.send_func = send_func
async def on_llm_new_token(self, token: str, **kwargs):
await self.send_func(token)
async def get_ai_reply_stream(message: str, send_func, api_key, api_base, model_name):
# 实例化时就带回调
chat = ChatOpenAI(
openai_api_key=api_key,
openai_api_base=api_base,
model_name=model_name,
temperature=0.7,
streaming=True,
callbacks=[MyHandler(send_func)]
)
await chat.ainvoke([HumanMessage(content=message)])

View File

@@ -1,7 +0,0 @@
from django.urls import re_path
from ai.chat import ChatConsumer
websocket_urlpatterns = [
re_path(r'ws/chat/$', ChatConsumer.as_asgi()),
]

View File

@@ -1,17 +1,16 @@
"""
ASGI config for backend project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
"""
import os import os
from django.core.asgi import get_asgi_application from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
# 延迟导入,避免 AppRegistryNotReady 错误 application = get_asgi_application()
def get_websocket_urlpatterns():
from ai.routing import websocket_urlpatterns
return websocket_urlpatterns
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": URLRouter(
get_websocket_urlpatterns()
),
})

View File

@@ -14,9 +14,4 @@ goofish_api==0.0.6
flower==2.0.1 flower==2.0.1
gunicorn==23.0.0 gunicorn==23.0.0
django_redis==6.0.0 django_redis==6.0.0
django-ninja==1.4.3 django-ninja==1.4.3
openai==1.95
daphne==4.2.1
langchain==0.3.26
langchain-community==0.3.27
channels==4.2.2

View File

@@ -94,7 +94,7 @@ services:
command: > command: >
sh -c "python manage.py makemigrations && sh -c "python manage.py makemigrations &&
python manage.py migrate && python manage.py migrate &&
daphne -b 0.0.0.0 backend.asgi:application" python manage.py runserver 0.0.0.0:8000"
web: web:
build: build:

View File

@@ -1,285 +0,0 @@
<script lang="ts" setup>
import { nextTick, onMounted, ref, watch } from 'vue';
interface Message {
type: 'ai' | 'system' | 'user' | string;
content: string;
isTyping?: boolean;
}
const input = ref<string>('');
const messages = ref<Message[]>([]);
const loading = ref<boolean>(false);
const isConnected = ref<boolean>(false);
let socket: null | WebSocket = null;
const messagesRef = ref<HTMLElement | null>(null);
onMounted(() => {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = window.location.host;
const wsUrl = `${protocol}//${host}/ws/chat/`;
socket = new WebSocket(wsUrl);
socket.addEventListener('open', () => {
isConnected.value = true;
messages.value.push({ type: 'system', content: '✅ WebSocket 连接成功' });
});
socket.addEventListener('message', (event: MessageEvent<string>) => {
const data = JSON.parse(event.data);
if (data.done) {
loading.value = false;
if (
messages.value.length > 0 &&
messages.value[messages.value.length - 1]?.type === 'ai'
) {
const lastMsg = messages.value[messages.value.length - 1];
if (lastMsg) {
lastMsg.isTyping = false;
}
}
} else if (data.is_streaming) {
if (
messages.value.length > 0 &&
messages.value[messages.value.length - 1]?.type === 'ai'
) {
const currentMessage = messages.value[messages.value.length - 1];
if (currentMessage) {
currentMessage.content += data.message;
currentMessage.isTyping = true;
}
} else {
messages.value.push({
type: 'ai',
content: data.message,
isTyping: true,
});
}
} else {
const messageType = data.type || 'system';
messages.value.push({ type: messageType, content: data.message });
}
});
socket.addEventListener('close', (event: CloseEvent) => {
isConnected.value = false;
messages.value.push({
type: 'system',
content: `❌ WebSocket 连接已断开 (${event.code})`,
});
});
socket.addEventListener('error', () => {
isConnected.value = false;
messages.value.push({ type: 'system', content: '❌ WebSocket 连接错误' });
});
});
// 自动滚动到底
watch(
messages,
() => {
nextTick(() => {
if (messagesRef.value) {
messagesRef.value.scrollTop = messagesRef.value.scrollHeight;
}
});
},
{ deep: true },
);
function send(): void {
if (!input.value.trim()) return;
if (socket && socket.readyState === WebSocket.OPEN) {
loading.value = true;
messages.value.push({ type: 'user', content: input.value });
socket.send(JSON.stringify({ message: input.value }));
input.value = '';
} else {
messages.value.push({
type: 'system',
content: '❌ WebSocket 未连接,无法发送消息',
});
}
}
</script>
<template>
<div class="chat-box">
<div class="messages" ref="messagesRef">
<div class="message" v-for="(msg, index) in messages" :key="index">
<span v-if="msg.type === 'user'" class="user-message"
>🧑: {{ msg.content }}</span
>
<span v-else-if="msg.type === 'ai'" class="ai-message"
>🤖: {{ msg.content }}</span
>
</div>
<div v-if="loading" class="loading">AI 正在思考...</div>
</div>
<div class="input-box">
<input v-model="input" @keyup.enter="send" placeholder="请输入问题..." />
<button @click="send">发送</button>
</div>
</div>
</template>
<style scoped>
.chat-box {
display: flex;
flex-direction: column;
height: 80vh;
width: 400px;
border: 1px solid #ddd;
}
.connection-status {
padding: 8px;
background-color: #f8f9fa;
border-bottom: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
}
.history-controls {
display: flex;
gap: 4px;
}
.history-btn {
padding: 2px 6px;
background-color: #6c757d;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 10px;
}
.history-btn:hover {
background-color: #5a6268;
}
.status-connected {
color: #28a745;
font-weight: bold;
}
.status-connecting {
color: #ffc107;
font-weight: bold;
}
.status-disconnected {
color: #dc3545;
font-weight: bold;
}
.status-disconnecting {
color: #6c757d;
font-weight: bold;
}
.status-unknown {
color: #6c757d;
font-weight: bold;
}
.messages {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.message {
margin: 4px 0;
word-wrap: break-word;
}
.user-message {
color: #007bff;
font-weight: bold;
}
.ai-message {
color: #28a745;
}
.typing-text {
display: inline;
}
.typing-cursor {
color: #28a745;
animation: blink 1s infinite;
font-weight: bold;
}
@keyframes blink {
0%,
50% {
opacity: 1;
}
51%,
100% {
opacity: 0;
}
}
.system-message {
color: #6c757d;
font-style: italic;
}
.history-message {
color: #17a2b8;
font-style: italic;
white-space: pre-line;
}
.loading {
color: gray;
font-style: italic;
}
.input-box {
display: flex;
padding: 8px;
border-top: 1px solid #ddd;
}
input {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
margin-right: 8px;
}
input:disabled {
background-color: #f8f9fa;
cursor: not-allowed;
}
button {
padding: 8px 16px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover:not(:disabled) {
background-color: #0056b3;
}
button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
</style>