Add support for image input in model resources and enhance related configurations
- Introduce a new `support_image_input` field in model resources, allowing models to indicate support for image input. - Update the backend models, schemas, and database seed scripts to accommodate the new field. - Enhance the AssistantConfig and related routes to handle image input capabilities, ensuring proper validation and error handling. - Modify the frontend components to include toggles for enabling visual understanding and filtering models based on image input support. - Implement necessary adjustments in the voice preview and pipeline to integrate video stream handling alongside audio functionalities.
This commit is contained in:
@@ -3,9 +3,8 @@
|
||||
/**
|
||||
* 摄像头预览:纯前端 getUserMedia 视频流,供调试抽屉的「视频流」视图使用。
|
||||
*
|
||||
* 与 use-voice-preview 分离:麦克风/语音会话走 WebRTC 到后端,摄像头目前只在
|
||||
* 本地预览。后续接入视觉理解管线时,把这条 video track 加进现有 PeerConnection
|
||||
* (pc.addTrack)并在后端 transport 打开 video_in_enabled 即可。
|
||||
* 与 use-voice-preview 分离:这里负责摄像头设备枚举与本地预览;开始视觉会话时,
|
||||
* 调用方会把这条 video track 加进语音 WebRTC PeerConnection。
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
@@ -50,12 +49,12 @@ export function useCameraPreview() {
|
||||
setStream(null);
|
||||
}, []);
|
||||
|
||||
const start = useCallback(async () => {
|
||||
if (streamRef.current) return;
|
||||
const start = useCallback(async (): Promise<MediaStream | null> => {
|
||||
if (streamRef.current) return streamRef.current;
|
||||
setError(null);
|
||||
if (!window.isSecureContext || !navigator.mediaDevices?.getUserMedia) {
|
||||
setError("当前页面无法访问摄像头,请通过 https 或 localhost 访问。");
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
setStarting(true);
|
||||
try {
|
||||
@@ -66,21 +65,24 @@ export function useCameraPreview() {
|
||||
streamRef.current = media;
|
||||
setStream(media);
|
||||
void refreshCameras();
|
||||
return media;
|
||||
} catch (mediaError) {
|
||||
setError(cameraErrorMessage(mediaError));
|
||||
return null;
|
||||
} finally {
|
||||
setStarting(false);
|
||||
}
|
||||
}, [refreshCameras]);
|
||||
|
||||
const selectCamera = useCallback(
|
||||
async (id: string) => {
|
||||
async (id: string): Promise<MediaStream | null> => {
|
||||
deviceIdRef.current = id;
|
||||
setDeviceId(id);
|
||||
if (streamRef.current) {
|
||||
stop();
|
||||
await start();
|
||||
return await start();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
[start, stop],
|
||||
);
|
||||
|
||||
@@ -23,6 +23,11 @@ import { API_BASE, webrtcApi } from "@/lib/api";
|
||||
|
||||
export type VoicePreviewStatus = "idle" | "connecting" | "connected" | "failed";
|
||||
|
||||
type ConnectOptions = {
|
||||
visionEnabled?: boolean;
|
||||
videoStream?: MediaStream | null;
|
||||
};
|
||||
|
||||
export type ChatMessage = {
|
||||
id: string;
|
||||
role: "user" | "assistant";
|
||||
@@ -218,7 +223,7 @@ export function useVoicePreview(
|
||||
[disconnect, fail],
|
||||
);
|
||||
|
||||
const connect = useCallback(async () => {
|
||||
const connect = useCallback(async (options: ConnectOptions = {}) => {
|
||||
if (startingRef.current || pcRef.current || wsRef.current) return;
|
||||
if (!assistantId) {
|
||||
setError("请先保存助手,再开始语音预览。");
|
||||
@@ -456,6 +461,11 @@ export function useVoicePreview(
|
||||
} else {
|
||||
pc.addTransceiver("audio", { direction: "recvonly" });
|
||||
}
|
||||
if (options.videoStream) {
|
||||
options.videoStream
|
||||
.getVideoTracks()
|
||||
.forEach((track) => pc.addTrack(track, options.videoStream!));
|
||||
}
|
||||
|
||||
// 4) 生成 offer 并发给后端(assistant_id 在 payload 顶层)
|
||||
const offer = await pc.createOffer();
|
||||
@@ -472,6 +482,7 @@ export function useVoicePreview(
|
||||
sdp: localDescription.sdp,
|
||||
type: localDescription.type,
|
||||
assistant_id: assistantId,
|
||||
vision_enabled: Boolean(options.visionEnabled),
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -482,6 +493,17 @@ export function useVoicePreview(
|
||||
}
|
||||
}, [assistantId, fail, closeOnRemoteEnd, refreshDevices]);
|
||||
|
||||
const replaceVideoStream = useCallback(
|
||||
async (videoStream: MediaStream | null) => {
|
||||
const pc = pcRef.current;
|
||||
if (!pc) return;
|
||||
const sender = pc.getSenders().find((s) => s.track?.kind === "video");
|
||||
if (!sender) return;
|
||||
await sender.replaceTrack(videoStream?.getVideoTracks()[0] ?? null);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// 选择麦克风:更新选择;若会话正在发送麦克风音频,则用 WebRTC replaceTrack
|
||||
// 热切换轨道(无需重新协商),并把波形可视化重新接到新流。
|
||||
// 未连接时仅记下选择,留待下次 connect 生效。
|
||||
@@ -558,6 +580,7 @@ export function useVoicePreview(
|
||||
selectDevice,
|
||||
sendText,
|
||||
connect,
|
||||
replaceVideoStream,
|
||||
disconnect,
|
||||
audioRef,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user