feat: generate images
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
.toolbar-wrapper {
|
||||
padding: 0 24px;
|
||||
color: rgba(255, 255, 255, 65%);
|
||||
font-size: 16px;
|
||||
background-color: rgba(0, 0, 0, 10%);
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.toolbar-wrapper .anticon {
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toolbar-wrapper .anticon[disabled] {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.toolbar-wrapper .anticon:hover {
|
||||
opacity: 0.3;
|
||||
}
|
||||
@@ -1,6 +1,17 @@
|
||||
import { Image as AntImage, ImageProps } from 'antd';
|
||||
import {
|
||||
DownloadOutlined,
|
||||
EyeOutlined,
|
||||
RotateLeftOutlined,
|
||||
RotateRightOutlined,
|
||||
SwapOutlined,
|
||||
UndoOutlined,
|
||||
ZoomInOutlined,
|
||||
ZoomOutOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { Image as AntImage, ImageProps, Space } from 'antd';
|
||||
import { round } from 'lodash';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import './index.less';
|
||||
|
||||
const AutoImage: React.FC<ImageProps & { height: number }> = (props) => {
|
||||
const { height = 100, ...rest } = props;
|
||||
@@ -24,11 +35,58 @@ const AutoImage: React.FC<ImageProps & { height: number }> = (props) => {
|
||||
setWidth(height * ratio);
|
||||
}, [getImgRatio, height, props.src]);
|
||||
|
||||
const onDownload = () => {
|
||||
const url = props.src || '';
|
||||
const filename = Date.now() + '';
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleOnLoad();
|
||||
}, [handleOnLoad]);
|
||||
|
||||
return <AntImage {...rest} height={height} width={width} />;
|
||||
return (
|
||||
<AntImage
|
||||
{...rest}
|
||||
height={height}
|
||||
width={width}
|
||||
preview={{
|
||||
mask: <EyeOutlined />,
|
||||
toolbarRender: (
|
||||
_,
|
||||
{
|
||||
transform: { scale },
|
||||
actions: {
|
||||
onFlipY,
|
||||
onFlipX,
|
||||
onRotateLeft,
|
||||
onRotateRight,
|
||||
onZoomOut,
|
||||
onZoomIn,
|
||||
onReset
|
||||
}
|
||||
}
|
||||
) => (
|
||||
<Space size={12} className="toolbar-wrapper">
|
||||
<DownloadOutlined onClick={onDownload} />
|
||||
<SwapOutlined rotate={90} onClick={onFlipY} />
|
||||
<SwapOutlined onClick={onFlipX} />
|
||||
<RotateLeftOutlined onClick={onRotateLeft} />
|
||||
<RotateRightOutlined onClick={onRotateRight} />
|
||||
<ZoomOutOutlined disabled={scale === 1} onClick={onZoomOut} />
|
||||
<ZoomInOutlined disabled={scale === 50} onClick={onZoomIn} />
|
||||
<UndoOutlined onClick={onReset} />
|
||||
</Space>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutoImage;
|
||||
|
||||
@@ -5,6 +5,7 @@ export default {
|
||||
'menu.playground.embedding': 'Embedding',
|
||||
'menu.playground.chat': 'Chat',
|
||||
'menu.playground.speech': 'Speech',
|
||||
'menu.playground.text2images': 'Text to Images',
|
||||
'menu.compare': 'Compare',
|
||||
'menu.models': 'Models',
|
||||
'menu.resources': 'Resources',
|
||||
|
||||
@@ -5,6 +5,7 @@ export default {
|
||||
'menu.playground.embedding': '文本嵌入',
|
||||
'menu.playground.chat': '对话',
|
||||
'menu.playground.speech': '语音',
|
||||
'menu.playground.text2images': '文生图',
|
||||
'menu.compare': '多模型对比',
|
||||
'menu.models': '模型',
|
||||
'menu.resources': '资源',
|
||||
|
||||
@@ -2,6 +2,8 @@ import { request } from '@umijs/max';
|
||||
|
||||
export const CHAT_API = '/v1-openai/chat/completions';
|
||||
|
||||
export const CREAT_IMAGE_API = '/v1-openai/images/generations';
|
||||
|
||||
export const EMBEDDING_API = '/v1-openai/embeddings';
|
||||
|
||||
export const OPENAI_MODELS = '/v1-openai/models';
|
||||
@@ -51,3 +53,19 @@ export const handleEmbedding = async (
|
||||
cancelToken: options?.cancelToken
|
||||
});
|
||||
};
|
||||
|
||||
export const createImages = async (
|
||||
params: {
|
||||
model: string;
|
||||
prompt: string;
|
||||
n: number;
|
||||
size: string;
|
||||
},
|
||||
options?: any
|
||||
) => {
|
||||
return request(`${CREAT_IMAGE_API}`, {
|
||||
method: 'POST',
|
||||
data: params,
|
||||
cancelToken: options?.cancelToken
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
import useOverlayScroller from '@/hooks/use-overlay-scroller';
|
||||
import useRequestToken from '@/hooks/use-request-token';
|
||||
import { useIntl, useSearchParams } from '@umijs/max';
|
||||
import { Spin } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import 'overlayscrollbars/overlayscrollbars.css';
|
||||
import {
|
||||
forwardRef,
|
||||
memo,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import { createImages } from '../apis';
|
||||
import { Roles, generateMessages } from '../config';
|
||||
import { ImageParamsConfig as paramsConfig } from '../config/params-config';
|
||||
import { MessageItem } from '../config/types';
|
||||
import '../style/ground-left.less';
|
||||
import '../style/system-message-wrap.less';
|
||||
import MessageInput from './message-input';
|
||||
import MessageContent from './multiple-chat/message-content';
|
||||
import ReferenceParams from './reference-params';
|
||||
import RerankerParams from './reranker-params';
|
||||
import ViewCodeModal from './view-code-modal';
|
||||
|
||||
interface MessageProps {
|
||||
modelList: Global.BaseOption<string>[];
|
||||
loaded?: boolean;
|
||||
ref?: any;
|
||||
}
|
||||
|
||||
const initialValues = {
|
||||
n: 1,
|
||||
size: '512x512',
|
||||
quality: 'standard',
|
||||
style: 'vivid'
|
||||
};
|
||||
|
||||
const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
const { modelList } = props;
|
||||
const messageId = useRef<number>(0);
|
||||
const [messageList, setMessageList] = useState<MessageItem[]>([]);
|
||||
|
||||
const intl = useIntl();
|
||||
const requestSource = useRequestToken();
|
||||
const [searchParams] = useSearchParams();
|
||||
const selectModel = searchParams.get('model') || '';
|
||||
const [parameters, setParams] = useState<any>({});
|
||||
const [systemMessage, setSystemMessage] = useState('');
|
||||
const [show, setShow] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [tokenResult, setTokenResult] = useState<any>(null);
|
||||
const [collapse, setCollapse] = useState(false);
|
||||
const contentRef = useRef<any>('');
|
||||
const scroller = useRef<any>(null);
|
||||
const currentMessageRef = useRef<any>(null);
|
||||
const paramsRef = useRef<any>(null);
|
||||
const messageListLengthCache = useRef<number>(0);
|
||||
const requestToken = useRef<any>(null);
|
||||
|
||||
const { initialize, updateScrollerPosition } = useOverlayScroller();
|
||||
const { initialize: innitializeParams } = useOverlayScroller();
|
||||
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
viewCode() {
|
||||
setShow(true);
|
||||
},
|
||||
setCollapse() {
|
||||
setCollapse(!collapse);
|
||||
},
|
||||
collapse: collapse
|
||||
};
|
||||
});
|
||||
|
||||
const viewCodeMessage = useMemo(() => {
|
||||
return generateMessages([
|
||||
{ role: Roles.System, content: systemMessage },
|
||||
...messageList
|
||||
]);
|
||||
}, [messageList, systemMessage]);
|
||||
|
||||
const setMessageId = () => {
|
||||
messageId.current = messageId.current + 1;
|
||||
return messageId.current;
|
||||
};
|
||||
|
||||
const handleNewMessage = (message?: { role: string; content: string }) => {
|
||||
const newMessage = message || {
|
||||
role:
|
||||
_.last(messageList)?.role === Roles.User ? Roles.Assistant : Roles.User,
|
||||
content: ''
|
||||
};
|
||||
messageList.push({
|
||||
...newMessage,
|
||||
uid: messageId.current + 1
|
||||
});
|
||||
setMessageId();
|
||||
setMessageList([...messageList]);
|
||||
};
|
||||
|
||||
const handleStopConversation = () => {
|
||||
requestToken.current?.cancel?.();
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const submitMessage = async (current?: { role: string; content: string }) => {
|
||||
if (!parameters.model) return;
|
||||
try {
|
||||
setLoading(true);
|
||||
setMessageId();
|
||||
|
||||
requestToken.current?.cancel?.();
|
||||
requestToken.current = requestSource();
|
||||
|
||||
currentMessageRef.current = current
|
||||
? [
|
||||
{
|
||||
...current,
|
||||
uid: messageId.current
|
||||
}
|
||||
]
|
||||
: [];
|
||||
|
||||
contentRef.current = '';
|
||||
setMessageList((pre) => {
|
||||
return [...pre, ...currentMessageRef.current];
|
||||
});
|
||||
|
||||
const params = {
|
||||
prompt: current?.content || '',
|
||||
...parameters
|
||||
};
|
||||
|
||||
const result = await createImages(params, {
|
||||
cancelToken: requestToken.current.token
|
||||
});
|
||||
|
||||
const imgList = _.map(result.data, (item: any, index: number) => {
|
||||
return {
|
||||
dataUrl: `data:image/png;base64,${item.b64_json}`,
|
||||
created: result.created,
|
||||
uid: index
|
||||
};
|
||||
});
|
||||
setMessageList((pre) => {
|
||||
return [
|
||||
...pre,
|
||||
{
|
||||
content: '',
|
||||
role: Roles.Assistant,
|
||||
imgs: imgList,
|
||||
uid: messageId.current
|
||||
}
|
||||
];
|
||||
});
|
||||
console.log('result:', imgList);
|
||||
|
||||
setMessageId();
|
||||
} catch (error) {
|
||||
// console.log('error:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
const handleClear = () => {
|
||||
if (!messageList.length) {
|
||||
return;
|
||||
}
|
||||
setMessageId();
|
||||
setMessageList([]);
|
||||
setTokenResult(null);
|
||||
};
|
||||
|
||||
const handleSendMessage = (message: Omit<MessageItem, 'uid'>) => {
|
||||
console.log('message:', message);
|
||||
const currentMessage =
|
||||
message.content || message.imgs?.length ? message : undefined;
|
||||
submitMessage(currentMessage);
|
||||
};
|
||||
|
||||
const handleCloseViewCode = () => {
|
||||
setShow(false);
|
||||
};
|
||||
|
||||
const handleSelectModel = () => {};
|
||||
|
||||
const handlePresetPrompt = (list: { role: string; content: string }[]) => {
|
||||
const sysMsg = list.filter((item) => item.role === 'system');
|
||||
const userMsg = list
|
||||
.filter((item) => item.role === 'user')
|
||||
.map((item) => {
|
||||
setMessageId();
|
||||
return {
|
||||
...item,
|
||||
uid: messageId.current
|
||||
};
|
||||
});
|
||||
setSystemMessage(sysMsg[0]?.content || '');
|
||||
setMessageList(userMsg);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (scroller.current) {
|
||||
initialize(scroller.current);
|
||||
}
|
||||
}, [scroller.current, initialize]);
|
||||
|
||||
useEffect(() => {
|
||||
if (paramsRef.current) {
|
||||
innitializeParams(paramsRef.current);
|
||||
}
|
||||
}, [paramsRef.current, innitializeParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
updateScrollerPosition();
|
||||
}
|
||||
}, [messageList, loading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (messageList.length > messageListLengthCache.current) {
|
||||
updateScrollerPosition();
|
||||
}
|
||||
messageListLengthCache.current = messageList.length;
|
||||
}, [messageList.length]);
|
||||
|
||||
return (
|
||||
<div className="ground-left-wrapper">
|
||||
<div className="ground-left">
|
||||
<div className="message-list-wrap" ref={scroller}>
|
||||
<>
|
||||
<div className="content">
|
||||
<MessageContent
|
||||
spans={{
|
||||
span: 24,
|
||||
count: 1
|
||||
}}
|
||||
actions={[]}
|
||||
messageList={messageList}
|
||||
setMessageList={setMessageList}
|
||||
editable={false}
|
||||
loading={loading}
|
||||
/>
|
||||
{loading && (
|
||||
<Spin size="small">
|
||||
<div style={{ height: '46px' }}></div>
|
||||
</Spin>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
{tokenResult && (
|
||||
<div style={{ height: 40 }}>
|
||||
<ReferenceParams usage={tokenResult}></ReferenceParams>
|
||||
</div>
|
||||
)}
|
||||
<div className="ground-left-footer">
|
||||
<MessageInput
|
||||
scope="chat"
|
||||
placeholer="Type <kbd>/</kbd> to input prompt"
|
||||
actions={['clear']}
|
||||
loading={loading}
|
||||
disabled={!parameters.model}
|
||||
isEmpty={!messageList.length}
|
||||
handleSubmit={handleSendMessage}
|
||||
addMessage={handleNewMessage}
|
||||
handleAbortFetch={handleStopConversation}
|
||||
clearAll={handleClear}
|
||||
setModelSelections={handleSelectModel}
|
||||
presetPrompt={handlePresetPrompt}
|
||||
modelList={modelList}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={classNames('params-wrapper', {
|
||||
collapsed: collapse
|
||||
})}
|
||||
ref={paramsRef}
|
||||
>
|
||||
<div className="box">
|
||||
<RerankerParams
|
||||
setParams={setParams}
|
||||
paramsConfig={paramsConfig}
|
||||
initialValues={initialValues}
|
||||
params={parameters}
|
||||
selectedModel={selectModel}
|
||||
modelList={modelList}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ViewCodeModal
|
||||
open={show}
|
||||
payLoad={{
|
||||
prompt: currentMessageRef.current?.[0]?.content
|
||||
}}
|
||||
parameters={parameters}
|
||||
onCancel={handleCloseViewCode}
|
||||
title={intl.formatMessage({ id: 'playground.viewcode' })}
|
||||
></ViewCodeModal>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default memo(GroundImages);
|
||||
@@ -281,6 +281,7 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
<div className="ground-left-footer">
|
||||
<MessageInput
|
||||
scope="reranker"
|
||||
actions={[]}
|
||||
submitIcon={<SearchOutlined className="font-size-16" />}
|
||||
loading={loading}
|
||||
disabled={!parameters.model}
|
||||
|
||||
@@ -33,6 +33,12 @@ interface MessageProps {
|
||||
ref?: any;
|
||||
}
|
||||
|
||||
const initialValues = {
|
||||
voice: 'Alloy',
|
||||
response_format: 'mp3',
|
||||
speed: 1
|
||||
};
|
||||
|
||||
const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
const { modelList } = props;
|
||||
const messageId = useRef<number>(0);
|
||||
@@ -57,12 +63,6 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
const { initialize, updateScrollerPosition } = useOverlayScroller();
|
||||
const { initialize: innitializeParams } = useOverlayScroller();
|
||||
|
||||
const initialValues = {
|
||||
voice: 'Alloy',
|
||||
response_format: 'mp3',
|
||||
speed: 1
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
viewCode() {
|
||||
|
||||
@@ -15,6 +15,8 @@ import UploadImg from './upload-img';
|
||||
|
||||
type CurrentMessage = Omit<MessageItem, 'uid'>;
|
||||
|
||||
type ActionType = 'clear' | 'layout' | 'role' | 'upload' | 'add' | 'paste';
|
||||
|
||||
const layoutOptions = [
|
||||
{
|
||||
label: '2 columns',
|
||||
@@ -77,6 +79,7 @@ interface MessageInputProps {
|
||||
placeholer?: string;
|
||||
shouldResetMessage?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
actions?: ActionType[];
|
||||
}
|
||||
|
||||
const MessageInput: React.FC<MessageInputProps> = ({
|
||||
@@ -97,7 +100,8 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
placeholer,
|
||||
tools,
|
||||
style,
|
||||
shouldResetMessage = true
|
||||
shouldResetMessage = true,
|
||||
actions = ['clear', 'layout', 'role', 'upload', 'add', 'paste']
|
||||
}) => {
|
||||
const { TextArea } = Input;
|
||||
const intl = useIntl();
|
||||
@@ -256,25 +260,8 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
};
|
||||
|
||||
const handleOnPaste = (e: any) => {
|
||||
// e.preventDefault();
|
||||
|
||||
const text = e.clipboardData.getData('text');
|
||||
if (text) {
|
||||
// const startPos = e.target.selectionStart;
|
||||
// const endPos = e.target.selectionEnd;
|
||||
// setMessage?.({
|
||||
// ...message,
|
||||
// content:
|
||||
// message.content.slice(0, startPos) +
|
||||
// text +
|
||||
// message.content.slice(endPos)
|
||||
// });
|
||||
// if (endPos !== startPos) {
|
||||
// setTimeout(() => {
|
||||
// e.target.setSelectionRange(endPos, endPos);
|
||||
// }, 0);
|
||||
// }
|
||||
} else {
|
||||
if (!text) {
|
||||
e.preventDefault();
|
||||
getPasteContent(e);
|
||||
}
|
||||
@@ -352,26 +339,30 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
<div className="tool-bar">
|
||||
<div className="actions">
|
||||
{tools}
|
||||
{scope !== 'reranker' && (
|
||||
{
|
||||
<>
|
||||
<Button
|
||||
type="text"
|
||||
size="middle"
|
||||
onClick={handleToggleRole}
|
||||
icon={<SwapOutlined rotate={90} />}
|
||||
>
|
||||
{intl.formatMessage({ id: `playground.${message.role}` })}
|
||||
</Button>
|
||||
<Divider type="vertical" style={{ margin: 0 }} />
|
||||
{message.role === Roles.User && (
|
||||
{actions.includes('role') && (
|
||||
<>
|
||||
<Button
|
||||
type="text"
|
||||
size="middle"
|
||||
onClick={handleToggleRole}
|
||||
icon={<SwapOutlined rotate={90} />}
|
||||
>
|
||||
{intl.formatMessage({ id: `playground.${message.role}` })}
|
||||
</Button>
|
||||
<Divider type="vertical" style={{ margin: 0 }} />
|
||||
</>
|
||||
)}
|
||||
{actions.includes('upload') && message.role === Roles.User && (
|
||||
<UploadImg
|
||||
handleUpdateImgList={handleUpdateImgList}
|
||||
size="middle"
|
||||
></UploadImg>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{scope !== 'reranker' && (
|
||||
}
|
||||
{actions.includes('clear') && (
|
||||
<Tooltip
|
||||
title={intl.formatMessage({ id: 'playground.toolbar.clearmsg' })}
|
||||
>
|
||||
@@ -383,7 +374,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
></Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{updateLayout && (
|
||||
{actions.includes('layout') && updateLayout && (
|
||||
<>
|
||||
<Divider type="vertical" style={{ margin: 0 }} />
|
||||
{layoutOptions.map((option) => (
|
||||
@@ -418,7 +409,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
></Select>
|
||||
)}
|
||||
|
||||
{scope !== 'reranker' && (
|
||||
{actions.includes('add') && (
|
||||
<Tooltip
|
||||
title={
|
||||
<span>
|
||||
@@ -471,7 +462,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
onDelete={handleDeleteImg}
|
||||
></ThumbImg>
|
||||
<div className="input-box">
|
||||
{scope !== 'reranker' ? (
|
||||
{actions.includes('paste') ? (
|
||||
<TextArea
|
||||
ref={inputRef}
|
||||
autoSize={{ minRows: 3, maxRows: 8 }}
|
||||
|
||||
@@ -6,7 +6,7 @@ import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import { Roles } from '../../config';
|
||||
import { MessageItem } from '../../config/types';
|
||||
import { MessageItem, MessageItemAction } from '../../config/types';
|
||||
import '../../style/content-item.less';
|
||||
import ThumbImg from '../thumb-img';
|
||||
import UploadImg from '../upload-img';
|
||||
@@ -15,7 +15,7 @@ interface MessageItemProps {
|
||||
data: MessageItem;
|
||||
editable?: boolean;
|
||||
loading?: boolean;
|
||||
actions?: string[];
|
||||
actions?: MessageItemAction[];
|
||||
updateMessage?: (message: MessageItem) => void;
|
||||
onDelete?: () => void;
|
||||
}
|
||||
@@ -105,23 +105,8 @@ const ContentItem: React.FC<MessageItemProps> = ({
|
||||
);
|
||||
|
||||
const handleOnPaste = (e: any) => {
|
||||
// e.preventDefault();
|
||||
const text = e.clipboardData.getData('text');
|
||||
if (text) {
|
||||
// const startPos = e.target.selectionStart;
|
||||
// const endPos = e.target.selectionEnd;
|
||||
// updateMessage?.({
|
||||
// role: data.role,
|
||||
// content:
|
||||
// data.content.slice(0, startPos) + text + data.content.slice(endPos),
|
||||
// uid: data.uid
|
||||
// });
|
||||
// if (endPos !== startPos) {
|
||||
// setTimeout(() => {
|
||||
// e.target.setSelectionRange(endPos, endPos);
|
||||
// }, 0);
|
||||
// }
|
||||
} else {
|
||||
if (!text) {
|
||||
e.preventDefault();
|
||||
getPasteContent(e);
|
||||
}
|
||||
@@ -221,6 +206,7 @@ const ContentItem: React.FC<MessageItemProps> = ({
|
||||
onClick={handleClickWrapper}
|
||||
>
|
||||
<ThumbImg
|
||||
editable={editable}
|
||||
dataList={data.imgs || []}
|
||||
onDelete={handleDeleteImg}
|
||||
></ThumbImg>
|
||||
@@ -239,7 +225,18 @@ const ContentItem: React.FC<MessageItemProps> = ({
|
||||
></Input.TextArea>
|
||||
</div>
|
||||
) : (
|
||||
<div className="content-item-content">{data.content}</div>
|
||||
<div
|
||||
className={classNames('content-item-content', {
|
||||
'has-img': data.imgs?.length
|
||||
})}
|
||||
>
|
||||
<ThumbImg
|
||||
editable={editable}
|
||||
dataList={data.imgs || []}
|
||||
onDelete={handleDeleteImg}
|
||||
></ThumbImg>
|
||||
{data.content && <div className="text">{data.content}</div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import 'simplebar-react/dist/simplebar.min.css';
|
||||
import { MessageItem } from '../../config/types';
|
||||
import { MessageItem, MessageItemAction } from '../../config/types';
|
||||
import ContentItem from './content-item';
|
||||
|
||||
interface MessageContentProps {
|
||||
@@ -9,7 +9,7 @@ interface MessageContentProps {
|
||||
span: number;
|
||||
count: number;
|
||||
};
|
||||
actions?: string[];
|
||||
actions?: MessageItemAction[];
|
||||
editable?: boolean;
|
||||
messageList: MessageItem[];
|
||||
setMessageList?: (list: any) => void;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form, InputNumber, Slider, Tooltip } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import { memo, useCallback, useEffect, useId } from 'react';
|
||||
import { memo, useCallback, useEffect, useId, useMemo } from 'react';
|
||||
import { ParamsSchema } from '../config/types';
|
||||
import CustomLabelStyles from '../style/custom-label.less';
|
||||
|
||||
@@ -105,42 +105,41 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
||||
form.setFieldsValue(globalParams);
|
||||
}, [globalParams]);
|
||||
|
||||
const renderLabel = (args: {
|
||||
field: string;
|
||||
label: string;
|
||||
description: string;
|
||||
}) => {
|
||||
return (
|
||||
<span
|
||||
className={CustomLabelStyles.label}
|
||||
style={{ width: INPUT_WIDTH.mini }}
|
||||
>
|
||||
<span className="text">
|
||||
{args.description ? (
|
||||
<Tooltip title={args.description}>
|
||||
<span> {args.label}</span>
|
||||
<span className="m-l-5">
|
||||
<InfoCircleOutlined />
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span>{args.label}</span>
|
||||
)}
|
||||
const renderLabel = useCallback(
|
||||
(args: { field: string; label: string; description: string }) => {
|
||||
return (
|
||||
<span
|
||||
className={CustomLabelStyles.label}
|
||||
style={{ width: INPUT_WIDTH.mini }}
|
||||
>
|
||||
<span className="text">
|
||||
{args.description ? (
|
||||
<Tooltip title={args.description}>
|
||||
<span> {args.label}</span>
|
||||
<span className="m-l-5">
|
||||
<InfoCircleOutlined />
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span>{args.label}</span>
|
||||
)}
|
||||
</span>
|
||||
|
||||
<InputNumber
|
||||
className="label-val"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
value={form.getFieldValue(args.field)}
|
||||
controls={false}
|
||||
onChange={(val) => handleFieldValueChange(val, args.field)}
|
||||
></InputNumber>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
[form, handleFieldValueChange]
|
||||
);
|
||||
|
||||
<InputNumber
|
||||
className="label-val"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
value={form.getFieldValue(args.field)}
|
||||
controls={false}
|
||||
onChange={(val) => handleFieldValueChange(val, args.field)}
|
||||
></InputNumber>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const renderFields = useCallback(() => {
|
||||
const renderFields = useMemo(() => {
|
||||
console.log('paramsConfig:', paramsConfig);
|
||||
if (!paramsConfig?.length) {
|
||||
return null;
|
||||
@@ -243,7 +242,7 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
||||
</Form.Item>
|
||||
</>
|
||||
}
|
||||
{renderFields()}
|
||||
{renderFields}
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import AutoImage from '@/components/auto-image';
|
||||
import { CloseCircleOutlined, EyeOutlined } from '@ant-design/icons';
|
||||
import { CloseCircleOutlined } from '@ant-design/icons';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback } from 'react';
|
||||
import '../style/thumb-img.less';
|
||||
|
||||
const ThumbImg: React.FC<{
|
||||
dataList: any[];
|
||||
editable?: boolean;
|
||||
onDelete: (uid: number) => void;
|
||||
}> = ({ dataList, onDelete }) => {
|
||||
}> = ({ dataList, editable, onDelete }) => {
|
||||
const handleOnDelete = useCallback(
|
||||
(uid: number) => {
|
||||
onDelete(uid);
|
||||
@@ -32,18 +33,14 @@ const ThumbImg: React.FC<{
|
||||
}}
|
||||
>
|
||||
<span className="img">
|
||||
<AutoImage
|
||||
src={item.dataUrl}
|
||||
height={100}
|
||||
preview={{
|
||||
mask: <EyeOutlined />
|
||||
}}
|
||||
/>
|
||||
<AutoImage src={item.dataUrl} height={100} />
|
||||
</span>
|
||||
|
||||
<span className="del" onClick={() => handleOnDelete(item.uid)}>
|
||||
<CloseCircleOutlined />
|
||||
</span>
|
||||
{editable && (
|
||||
<span className="del" onClick={() => handleOnDelete(item.uid)}>
|
||||
<CloseCircleOutlined />
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -65,3 +65,77 @@ export const TTSParamsConfig: ParamsSchema[] = [
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const ImageParamsConfig: ParamsSchema[] = [
|
||||
{
|
||||
type: 'InputNumber',
|
||||
name: 'n',
|
||||
label: {
|
||||
text: 'Counts',
|
||||
isLocalized: false
|
||||
},
|
||||
attrs: {
|
||||
min: 1,
|
||||
max: 10
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'Select',
|
||||
name: 'size',
|
||||
options: [
|
||||
{ label: '256x256', value: '256x256' },
|
||||
{ label: '512x512', value: '512x512' },
|
||||
{ label: '1024x1024', value: '1024x1024' },
|
||||
{ label: '1792x1024', value: '1792x1024' },
|
||||
{ label: '1024x1792', value: '1024x1792' }
|
||||
],
|
||||
label: {
|
||||
text: 'Size',
|
||||
isLocalized: false
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'Select',
|
||||
name: 'quality',
|
||||
options: [
|
||||
{ label: 'standard', value: 'standard' },
|
||||
{ label: 'hd', value: 'hd' }
|
||||
],
|
||||
label: {
|
||||
text: 'Quality',
|
||||
isLocalized: false
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'Select',
|
||||
name: 'style',
|
||||
options: [
|
||||
{ label: 'vivid', value: 'vivid' },
|
||||
{ label: 'natural', value: 'natural' }
|
||||
],
|
||||
label: {
|
||||
text: 'Style',
|
||||
isLocalized: false
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -3,7 +3,7 @@ export interface ModelSelectionItem extends Global.BaseOption<string> {
|
||||
instanceId: symbol;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export type MessageItemAction = 'upload' | 'delete' | 'copy';
|
||||
export interface MessageItem {
|
||||
content: string;
|
||||
imgs?: { uid: string | number; dataUrl: string }[];
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import IconFont from '@/components/icon-font';
|
||||
import breakpoints from '@/config/breakpoints';
|
||||
import HotKeys from '@/config/hotkeys';
|
||||
import useWindowResize from '@/hooks/use-window-resize';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Space } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { queryModelsList } from './apis';
|
||||
import GroundImages from './components/ground-images';
|
||||
import './style/play-ground.less';
|
||||
|
||||
const TextToImages: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const { size } = useWindowResize();
|
||||
const groundTabRef1 = useRef<any>(null);
|
||||
const [modelList, setModelList] = useState<Global.BaseOption<string>[]>([]);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
const handleViewCode = useCallback(() => {
|
||||
groundTabRef1.current?.viewCode?.();
|
||||
}, []);
|
||||
|
||||
const handleToggleCollapse = useCallback(() => {
|
||||
groundTabRef1.current?.setCollapse?.();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (size.width < breakpoints.lg) {
|
||||
if (!groundTabRef1.current?.collapse) {
|
||||
groundTabRef1.current?.setCollapse?.();
|
||||
}
|
||||
}
|
||||
}, [size.width]);
|
||||
|
||||
useEffect(() => {
|
||||
const getModelList = async () => {
|
||||
try {
|
||||
const params = {
|
||||
embedding_only: false
|
||||
};
|
||||
const res = await queryModelsList(params);
|
||||
const list = _.map(res.data || [], (item: any) => {
|
||||
return {
|
||||
value: item.id,
|
||||
label: item.id
|
||||
};
|
||||
}) as Global.BaseOption<string>[];
|
||||
return list;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const modelist = await getModelList();
|
||||
setModelList(modelist);
|
||||
} catch (error) {
|
||||
setLoaded(true);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const renderExtra = () => {
|
||||
return (
|
||||
<Space key="buttons">
|
||||
<Button
|
||||
size="middle"
|
||||
onClick={handleViewCode}
|
||||
icon={<IconFont type="icon-code" className="font-size-16"></IconFont>}
|
||||
>
|
||||
{intl.formatMessage({ id: 'playground.viewcode' })}
|
||||
</Button>
|
||||
<Button
|
||||
size="middle"
|
||||
onClick={handleToggleCollapse}
|
||||
icon={
|
||||
<IconFont
|
||||
type="icon-a-layout6-line"
|
||||
className="font-size-16"
|
||||
></IconFont>
|
||||
}
|
||||
></Button>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
useHotkeys(
|
||||
HotKeys.RIGHT.join(','),
|
||||
() => {
|
||||
groundTabRef1.current?.setCollapse?.();
|
||||
},
|
||||
{
|
||||
preventDefault: true
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
ghost
|
||||
header={{
|
||||
title: intl.formatMessage({ id: 'menu.playground.text2images' }),
|
||||
breadcrumb: {}
|
||||
}}
|
||||
extra={renderExtra()}
|
||||
className={classNames('playground-container chat')}
|
||||
>
|
||||
<div className="play-ground">
|
||||
<div className="chat">
|
||||
<GroundImages
|
||||
ref={groundTabRef1}
|
||||
modelList={modelList}
|
||||
></GroundImages>
|
||||
</div>
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default TextToImages;
|
||||
@@ -31,10 +31,19 @@
|
||||
|
||||
&-content {
|
||||
word-break: break-word;
|
||||
padding: 8px 14px;
|
||||
min-height: 38px;
|
||||
border-radius: var(--border-radius-mini);
|
||||
background-color: var(--ant-color-fill-tertiary);
|
||||
|
||||
.text {
|
||||
padding: 8px 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&.has-img {
|
||||
border: 1px solid var(--ant-color-fill-secondary);
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
.message-content-input {
|
||||
|
||||
Reference in New Issue
Block a user