feat: generate images

This commit is contained in:
jialin
2024-11-22 10:00:34 +08:00
parent 84e0c46711
commit f24aef300b
18 changed files with 725 additions and 114 deletions
@@ -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>
);
+9 -12
View File
@@ -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>
);
})}