diff --git a/web/src/components/playground/DebugPanel.js b/web/src/components/playground/DebugPanel.js
index 935bd681..8487d555 100644
--- a/web/src/components/playground/DebugPanel.js
+++ b/web/src/components/playground/DebugPanel.js
@@ -1,17 +1,19 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import {
Card,
Typography,
Tabs,
TabPane,
Button,
+ Dropdown,
} from '@douyinfe/semi-ui';
import {
Code,
- FileText,
Zap,
Clock,
X,
+ Eye,
+ Send,
} from 'lucide-react';
import { useTranslation } from 'react-i18next';
@@ -24,6 +26,61 @@ const DebugPanel = ({
}) => {
const { t } = useTranslation();
+ const [activeKey, setActiveKey] = useState(activeDebugTab);
+
+ useEffect(() => {
+ setActiveKey(activeDebugTab);
+ }, [activeDebugTab]);
+
+ const handleTabChange = (key) => {
+ setActiveKey(key);
+ onActiveDebugTabChange(key);
+ };
+
+ const renderArrow = (items, pos, handleArrowClick, defaultNode) => {
+ const style = {
+ width: 32,
+ height: 32,
+ margin: '0 12px',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ borderRadius: '100%',
+ background: 'rgba(var(--semi-grey-1), 1)',
+ color: 'var(--semi-color-text)',
+ cursor: 'pointer',
+ };
+
+ return (
+
+ {items.map(item => {
+ return (
+ handleTabChange(item.itemKey)}
+ >
+ {item.tab}
+
+ );
+ })}
+
+ }
+ >
+ {pos === 'start' ? (
+
+ ←
+
+ ) : (
+
+ →
+
+ )}
+
+ );
+ };
+
return (
- {/* 移动端关闭按钮 */}
{styleState.isMobile && onCloseDebugPanel && (
}
@@ -59,23 +115,46 @@ const DebugPanel = ({
-
- {t('请求体')}
+
+ {t('预览请求体')}
+
+ } itemKey="preview">
+
+ {debugData.previewRequest ? (
+
+ {JSON.stringify(debugData.previewRequest, null, 2)}
+
+ ) : (
+
+ {t('正在构造请求体预览...')}
+
+ )}
+
+
+
+
+
+ {t('实际请求体')}
} itemKey="request">
{debugData.request ? (
-
- {JSON.stringify(debugData.request, null, 2)}
-
+ <>
+
+ {JSON.stringify(debugData.request, null, 2)}
+
+ >
) : (
{t('暂无请求数据')}
@@ -105,14 +184,20 @@ const DebugPanel = ({
- {debugData.timestamp && (
-
-
-
- {t('最后更新')}: {new Date(debugData.timestamp).toLocaleString()}
-
-
- )}
+
+ {(debugData.timestamp || debugData.previewTimestamp) && (
+
+
+
+ {activeKey === 'preview' && debugData.previewTimestamp
+ ? `${t('预览更新')}: ${new Date(debugData.previewTimestamp).toLocaleString()}`
+ : debugData.timestamp
+ ? `${t('最后请求')}: ${new Date(debugData.timestamp).toLocaleString()}`
+ : ''}
+
+
+ )}
+
);
};
diff --git a/web/src/components/playground/ImageUrlInput.js b/web/src/components/playground/ImageUrlInput.js
index 717bbf9d..df5e135f 100644
--- a/web/src/components/playground/ImageUrlInput.js
+++ b/web/src/components/playground/ImageUrlInput.js
@@ -38,9 +38,6 @@ const ImageUrlInput = ({ imageUrls, imageEnabled, onImageUrlsChange, onImageEnab
图片地址
-
- (多模态对话)
-
+
{
response: null,
timestamp: null
});
- const [activeDebugTab, setActiveDebugTab] = useState('request');
+ const [activeDebugTab, setActiveDebugTab] = useState('preview');
const [styleState, styleDispatch] = useContext(StyleContext);
const sseSourceRef = useRef(null);
const chatRef = useRef(null);
const saveConfigTimeoutRef = useRef(null);
+ const [previewPayload, setPreviewPayload] = useState(null);
+
+ const constructPreviewPayload = useCallback(() => {
+ try {
+ let systemMessage = null;
+ if (systemPrompt !== '') {
+ systemMessage = {
+ role: 'system',
+ id: '1',
+ createAt: 1715676751919,
+ content: systemPrompt,
+ };
+ }
+
+ let messages = message.map((item) => {
+ return {
+ role: item.role,
+ content: item.content,
+ };
+ });
+
+ if (messages.length === 0 || messages.every(msg => msg.role !== 'user')) {
+ const validImageUrls = inputs.imageUrls ? inputs.imageUrls.filter(url => url.trim() !== '') : [];
+
+ if (inputs.imageEnabled && validImageUrls.length > 0) {
+ const messageContent = [
+ {
+ type: 'text',
+ text: '你好'
+ },
+ ...validImageUrls.map(url => ({
+ type: 'image_url',
+ image_url: {
+ url: url.trim(),
+ },
+ })),
+ ];
+
+ messages.push({
+ role: 'user',
+ content: messageContent
+ });
+ } else {
+ messages.push({
+ role: 'user',
+ content: '你好'
+ });
+ }
+ } else {
+ const lastUserMessageIndex = messages.length - 1;
+ for (let i = messages.length - 1; i >= 0; i--) {
+ if (messages[i].role === 'user') {
+ if (inputs.imageEnabled && inputs.imageUrls) {
+ const validImageUrls = inputs.imageUrls.filter(url => url.trim() !== '');
+ if (validImageUrls.length > 0) {
+ let textContent = '示例消息';
+
+ if (typeof messages[i].content === 'string') {
+ textContent = messages[i].content;
+ } else if (Array.isArray(messages[i].content)) {
+ const textPart = messages[i].content.find(item => item.type === 'text');
+ if (textPart && textPart.text) {
+ textContent = textPart.text;
+ }
+ }
+
+ messages[i] = {
+ ...messages[i],
+ content: [
+ {
+ type: 'text',
+ text: textContent
+ },
+ ...validImageUrls.map(url => ({
+ type: 'image_url',
+ image_url: {
+ url: url.trim(),
+ },
+ })),
+ ]
+ };
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if (systemMessage) {
+ messages.unshift(systemMessage);
+ }
+
+ const payload = {
+ messages: messages,
+ stream: inputs.stream,
+ model: inputs.model,
+ group: inputs.group,
+ };
+
+ if (parameterEnabled.max_tokens && inputs.max_tokens > 0) {
+ payload.max_tokens = parseInt(inputs.max_tokens);
+ }
+ if (parameterEnabled.temperature) {
+ payload.temperature = inputs.temperature;
+ }
+ if (parameterEnabled.top_p) {
+ payload.top_p = inputs.top_p;
+ }
+ if (parameterEnabled.frequency_penalty) {
+ payload.frequency_penalty = inputs.frequency_penalty;
+ }
+ if (parameterEnabled.presence_penalty) {
+ payload.presence_penalty = inputs.presence_penalty;
+ }
+ if (parameterEnabled.seed && inputs.seed !== null && inputs.seed !== '') {
+ payload.seed = parseInt(inputs.seed);
+ }
+
+ return payload;
+ } catch (error) {
+ console.error('构造预览请求体失败:', error);
+ return null;
+ }
+ }, [inputs, parameterEnabled, systemPrompt, message]);
+
+ useEffect(() => {
+ const newPreviewPayload = constructPreviewPayload();
+ setPreviewPayload(newPreviewPayload);
+
+ setDebugData(prev => ({
+ ...prev,
+ previewRequest: newPreviewPayload,
+ previewTimestamp: new Date().toISOString()
+ }));
+ }, [constructPreviewPayload]);
+
const debouncedSaveConfig = useCallback(() => {
if (saveConfigTimeoutRef.current) {
clearTimeout(saveConfigTimeoutRef.current);