feat: generate images

main
jialin 1 year ago
parent 84e0c46711
commit f24aef300b

@ -33,6 +33,14 @@ export default [
icon: 'Comment',
component: './playground/speech'
},
{
name: 'text2images',
title: 'Text2Images',
path: '/playground/text-to-images',
key: 'text2images',
icon: 'Comment',
component: './playground/images'
},
{
name: 'embedding',
title: 'embedding',

@ -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>
)}
</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>
);
};
<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]
);
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 {

Loading…
Cancel
Save