From 0c0a51da95378ed57a77fac6a75c2a9088409f25 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Wed, 5 Aug 2026 21:28:51 +0800 Subject: [PATCH] Improve image upload in debug drawer --- backend/.env.example | 5 +++- backend/app.py | 6 +++++ backend/services/conversation_history.py | 3 +++ backend/settings.py | 2 +- backend/tests/test_conversation_history.py | 3 +++ .../assistant-editor/debug-preview.tsx | 25 +++++++++++++++---- 6 files changed, 37 insertions(+), 7 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 0b78aba..1910aff 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -9,11 +9,14 @@ DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/postgres # ---- 服务监听 & 跨域 ---- HOST=0.0.0.0 PORT=8000 +# NLTK 3.9+ import guard vs local .venv under backend/ (set in app.py by default; override here if needed) +# NLTK_DISABLE_IMPORT_SECURITY=1 # 前端开发地址,允许跨域(公网部署时加上实际前端 origin) CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 # ---- RustFS / S3-compatible storage ---- -S3_ENDPOINT_URL=http://localhost:9000 +# Use 127.0.0.1 (not localhost) if HTTP_PROXY is set — proxies often return 502 for localhost. +S3_ENDPOINT_URL=http://127.0.0.1:9000 S3_ACCESS_KEY=rustfsadmin S3_SECRET_KEY=rustfsadmin S3_BUCKET=ai-video diff --git a/backend/app.py b/backend/app.py index 087ee57..15bb26b 100644 --- a/backend/app.py +++ b/backend/app.py @@ -12,6 +12,12 @@ /api/webrtc/ice-servers WebRTC STUN/TURN 配置 """ +import os + +# NLTK 3.9+ blocks dependency imports when .venv lives under cwd (local dev layout). +# pipecat -> nltk -> regex hits this false positive; disable before any nltk import. +os.environ.setdefault("NLTK_DISABLE_IMPORT_SECURITY", "1") + from contextlib import asynccontextmanager import settings diff --git a/backend/services/conversation_history.py b/backend/services/conversation_history.py index 6104526..79e62c1 100644 --- a/backend/services/conversation_history.py +++ b/backend/services/conversation_history.py @@ -235,6 +235,9 @@ class ConversationRecorder: }, ) ) + # Flush before artifact insert: db.get() below autoflushes and + # can otherwise write conversation_artifacts first (FK violation). + await db.flush() db.add( ConversationArtifact( id=artifact_id, diff --git a/backend/settings.py b/backend/settings.py index 4208e6a..31807fd 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -52,7 +52,7 @@ TURN_PASSWORD = os.getenv("TURN_PASSWORD", "") TURN_CREDENTIAL_TTL = int(os.getenv("TURN_CREDENTIAL_TTL", "86400")) # ---- S3-compatible object storage (RustFS in local compose) ---- -S3_ENDPOINT_URL = os.getenv("S3_ENDPOINT_URL", "http://localhost:9000") +S3_ENDPOINT_URL = os.getenv("S3_ENDPOINT_URL", "http://127.0.0.1:9000") S3_ACCESS_KEY = os.getenv("S3_ACCESS_KEY", "rustfsadmin") S3_SECRET_KEY = os.getenv("S3_SECRET_KEY", "rustfsadmin") S3_BUCKET = os.getenv("S3_BUCKET", "ai-video") diff --git a/backend/tests/test_conversation_history.py b/backend/tests/test_conversation_history.py index 885414d..52ae028 100644 --- a/backend/tests/test_conversation_history.py +++ b/backend/tests/test_conversation_history.py @@ -122,6 +122,9 @@ class ConversationRecorderTest(unittest.IsolatedAsyncioTestCase): def add(self, value): self.added.append(value) + async def flush(self): + return None + async def get(self, _model, _session_id): return conversation diff --git a/frontend/src/components/assistant-editor/debug-preview.tsx b/frontend/src/components/assistant-editor/debug-preview.tsx index 945d2c6..7f6bbc4 100644 --- a/frontend/src/components/assistant-editor/debug-preview.tsx +++ b/frontend/src/components/assistant-editor/debug-preview.tsx @@ -1,7 +1,7 @@ "use client"; import type React from "react"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react"; import { AudioLines, Braces, @@ -1636,12 +1636,26 @@ function DebugTranscriptPanel({ recording?: boolean; }) { const scrollRef = useRef(null); + const contentRef = useRef(null); - // 新消息时滚到底部 - useEffect(() => { + const scrollToBottom = useCallback(() => { const el = scrollRef.current; if (el) el.scrollTop = el.scrollHeight; - }, [messages]); + }, []); + + // Scroll when message list changes (before paint). + useLayoutEffect(() => { + scrollToBottom(); + }, [messages, scrollToBottom]); + + // Images load after the message row mounts; re-scroll when content height grows. + useEffect(() => { + const content = contentRef.current; + if (!content) return; + const observer = new ResizeObserver(() => scrollToBottom()); + observer.observe(content); + return () => observer.disconnect(); + }, [scrollToBottom]); if (messages.length === 0) { return ( @@ -1671,7 +1685,7 @@ function DebugTranscriptPanel({ ref={scrollRef} className="scrollbar-subtle flex min-h-0 flex-1 flex-col overflow-y-auto px-5 py-4" > -
+
{messages.map((message) => { const time = formatMessageTime(message.timestamp); return message.role === "assistant" ? ( @@ -1707,6 +1721,7 @@ function DebugTranscriptPanel({ src={attachment.url} alt={attachment.alt} className="max-h-72 w-full rounded-[0.8rem] object-cover" + onLoad={scrollToBottom} /> ))} {message.content && (