Improve image upload in debug drawer
This commit is contained in:
@@ -9,11 +9,14 @@ DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/postgres
|
|||||||
# ---- 服务监听 & 跨域 ----
|
# ---- 服务监听 & 跨域 ----
|
||||||
HOST=0.0.0.0
|
HOST=0.0.0.0
|
||||||
PORT=8000
|
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)
|
# 前端开发地址,允许跨域(公网部署时加上实际前端 origin)
|
||||||
CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
|
CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
|
||||||
|
|
||||||
# ---- RustFS / S3-compatible storage ----
|
# ---- 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_ACCESS_KEY=rustfsadmin
|
||||||
S3_SECRET_KEY=rustfsadmin
|
S3_SECRET_KEY=rustfsadmin
|
||||||
S3_BUCKET=ai-video
|
S3_BUCKET=ai-video
|
||||||
|
|||||||
@@ -12,6 +12,12 @@
|
|||||||
/api/webrtc/ice-servers WebRTC STUN/TURN 配置
|
/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
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
import settings
|
import settings
|
||||||
|
|||||||
@@ -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(
|
db.add(
|
||||||
ConversationArtifact(
|
ConversationArtifact(
|
||||||
id=artifact_id,
|
id=artifact_id,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ TURN_PASSWORD = os.getenv("TURN_PASSWORD", "")
|
|||||||
TURN_CREDENTIAL_TTL = int(os.getenv("TURN_CREDENTIAL_TTL", "86400"))
|
TURN_CREDENTIAL_TTL = int(os.getenv("TURN_CREDENTIAL_TTL", "86400"))
|
||||||
|
|
||||||
# ---- S3-compatible object storage (RustFS in local compose) ----
|
# ---- 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_ACCESS_KEY = os.getenv("S3_ACCESS_KEY", "rustfsadmin")
|
||||||
S3_SECRET_KEY = os.getenv("S3_SECRET_KEY", "rustfsadmin")
|
S3_SECRET_KEY = os.getenv("S3_SECRET_KEY", "rustfsadmin")
|
||||||
S3_BUCKET = os.getenv("S3_BUCKET", "ai-video")
|
S3_BUCKET = os.getenv("S3_BUCKET", "ai-video")
|
||||||
|
|||||||
@@ -122,6 +122,9 @@ class ConversationRecorderTest(unittest.IsolatedAsyncioTestCase):
|
|||||||
def add(self, value):
|
def add(self, value):
|
||||||
self.added.append(value)
|
self.added.append(value)
|
||||||
|
|
||||||
|
async def flush(self):
|
||||||
|
return None
|
||||||
|
|
||||||
async def get(self, _model, _session_id):
|
async def get(self, _model, _session_id):
|
||||||
return conversation
|
return conversation
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
AudioLines,
|
AudioLines,
|
||||||
Braces,
|
Braces,
|
||||||
@@ -1636,12 +1636,26 @@ function DebugTranscriptPanel({
|
|||||||
recording?: boolean;
|
recording?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// 新消息时滚到底部
|
const scrollToBottom = useCallback(() => {
|
||||||
useEffect(() => {
|
|
||||||
const el = scrollRef.current;
|
const el = scrollRef.current;
|
||||||
if (el) el.scrollTop = el.scrollHeight;
|
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) {
|
if (messages.length === 0) {
|
||||||
return (
|
return (
|
||||||
@@ -1671,7 +1685,7 @@ function DebugTranscriptPanel({
|
|||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
className="scrollbar-subtle flex min-h-0 flex-1 flex-col overflow-y-auto px-5 py-4"
|
className="scrollbar-subtle flex min-h-0 flex-1 flex-col overflow-y-auto px-5 py-4"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-4">
|
<div ref={contentRef} className="flex flex-col gap-4">
|
||||||
{messages.map((message) => {
|
{messages.map((message) => {
|
||||||
const time = formatMessageTime(message.timestamp);
|
const time = formatMessageTime(message.timestamp);
|
||||||
return message.role === "assistant" ? (
|
return message.role === "assistant" ? (
|
||||||
@@ -1707,6 +1721,7 @@ function DebugTranscriptPanel({
|
|||||||
src={attachment.url}
|
src={attachment.url}
|
||||||
alt={attachment.alt}
|
alt={attachment.alt}
|
||||||
className="max-h-72 w-full rounded-[0.8rem] object-cover"
|
className="max-h-72 w-full rounded-[0.8rem] object-cover"
|
||||||
|
onLoad={scrollToBottom}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{message.content && (
|
{message.content && (
|
||||||
|
|||||||
Reference in New Issue
Block a user