chore: docker add worker command
This commit is contained in:
+1
-1
@@ -14,7 +14,7 @@ const isProduction = env === 'production';
|
||||
const t = Date.now();
|
||||
export default defineConfig({
|
||||
proxy: {
|
||||
...proxy('http://192.168.50.2')
|
||||
...proxy('http://192.168.50.3')
|
||||
},
|
||||
history: {
|
||||
type: 'hash'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@font-face {
|
||||
font-family: iconfont; /* Project id 4613488 */
|
||||
src:
|
||||
url('iconfont.woff2?t=1732245003614') format('woff2'),
|
||||
url('iconfont.woff?t=1732245003614') format('woff'),
|
||||
url('iconfont.ttf?t=1732245003614') format('truetype');
|
||||
url('iconfont.woff2?t=1732806927800') format('woff2'),
|
||||
url('iconfont.woff?t=1732806927800') format('woff'),
|
||||
url('iconfont.ttf?t=1732806927800') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -14,6 +14,14 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-random::before {
|
||||
content: '\e603';
|
||||
}
|
||||
|
||||
.icon-suijisenlin::before {
|
||||
content: '\e60e';
|
||||
}
|
||||
|
||||
.icon-stop2::before {
|
||||
content: '\e8db';
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,20 @@
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "13059398",
|
||||
"name": "随机",
|
||||
"font_class": "random",
|
||||
"unicode": "e603",
|
||||
"unicode_decimal": 58883
|
||||
},
|
||||
{
|
||||
"icon_id": "5978743",
|
||||
"name": "random-forest",
|
||||
"font_class": "suijisenlin",
|
||||
"unicode": "e60e",
|
||||
"unicode_decimal": 58894
|
||||
},
|
||||
{
|
||||
"icon_id": "15617444",
|
||||
"name": "stop",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -82,8 +82,8 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
|
||||
<div className="speech-actions">
|
||||
<span className="tags">
|
||||
<span className="item">{props.format}</span>
|
||||
<span className="item splitor"></span>
|
||||
<span className="item">{props.speed}x</span>
|
||||
{/* <span className="item splitor"></span>
|
||||
<span className="item">{props.speed}x</span> */}
|
||||
</span>
|
||||
<div className="actions">
|
||||
<Tooltip
|
||||
|
||||
@@ -11,7 +11,8 @@ const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
|
||||
const currentVersion = getAtomStorage(GPUStackVersionAtom)?.version;
|
||||
|
||||
const isProd =
|
||||
currentVersion !== '0.0.0' && currentVersion?.indexOf('rc') === -1;
|
||||
currentVersion?.indexOf('rc') === -1 &&
|
||||
currentVersion?.indexOf('0.0.0') === -1;
|
||||
|
||||
const uiVersion = document.documentElement.getAttribute('data-version');
|
||||
|
||||
@@ -39,7 +40,7 @@ const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
|
||||
{' '}
|
||||
{intl.formatMessage({ id: 'common.footer.version.server' })}
|
||||
</span>
|
||||
{currentVersion === '0.0.0'
|
||||
{currentVersion.indexOf('0.0.0') > -1
|
||||
? getAtomStorage(GPUStackVersionAtom)?.git_commit
|
||||
: getAtomStorage(GPUStackVersionAtom)?.version}
|
||||
</span>
|
||||
@@ -55,7 +56,7 @@ const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
|
||||
<span className="m-l-5">
|
||||
{latestVersion &&
|
||||
latestVersion !== currentVersion &&
|
||||
latestVersion !== '0.0.0'
|
||||
latestVersion.indexOf('0.0.0') === -1
|
||||
? intl.formatMessage(
|
||||
{ id: 'users.version.update' },
|
||||
{ version: latestVersion }
|
||||
|
||||
@@ -182,7 +182,7 @@ export default (props: any) => {
|
||||
initialState?.currentUser?.is_admin &&
|
||||
updateCheck.latest_version &&
|
||||
updateCheck.latest_version !== version?.version &&
|
||||
updateCheck.latest_version !== '0.0.0' &&
|
||||
updateCheck.latest_version?.indexOf('0.0.0') === -1 &&
|
||||
updateCheck.latest_version?.indexOf('rc') === -1
|
||||
);
|
||||
}, [updateCheck, version, initialState]);
|
||||
|
||||
@@ -109,5 +109,6 @@ export default {
|
||||
'In the chart, the distance between points indicates the similarity between the corresponding documents. Closer points mean higher similarity.',
|
||||
'playground.audio.button.play': 'Play',
|
||||
'playground.audio.button.download': 'Download',
|
||||
'playground.audio.button.stop': 'Stop'
|
||||
'playground.audio.button.stop': 'Stop',
|
||||
'playground.image.prompt.random': 'Random Prompt'
|
||||
};
|
||||
|
||||
@@ -106,5 +106,6 @@ export default {
|
||||
'在图表中,点之间的距离表示相应文档之间的相似度。点越近意味着相似度越高。',
|
||||
'playground.audio.button.play': '播放',
|
||||
'playground.audio.button.download': '下载',
|
||||
'playground.audio.button.stop': '停止'
|
||||
'playground.audio.button.stop': '停止',
|
||||
'playground.image.prompt.random': '随机提示词'
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import AlertInfo from '@/components/alert-info';
|
||||
import IconFont from '@/components/icon-font';
|
||||
import FieldComponent from '@/components/seal-form/field-component';
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import SealSelect from '@/components/seal-form/seal-select';
|
||||
@@ -25,7 +26,7 @@ import React, {
|
||||
useState
|
||||
} from 'react';
|
||||
import { CREAT_IMAGE_API } from '../apis';
|
||||
import { OpenAIViewCode } from '../config';
|
||||
import { OpenAIViewCode, promptList } from '../config';
|
||||
import {
|
||||
ImageAdvancedParamsConfig,
|
||||
ImageconstExtraConfig,
|
||||
@@ -124,6 +125,7 @@ const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
const requestToken = useRef<any>(null);
|
||||
const [currentPrompt, setCurrentPrompt] = useState<string>('');
|
||||
const form = useRef<any>(null);
|
||||
const inputRef = useRef<any>(null);
|
||||
|
||||
const size = Form.useWatch('size', form.current?.form);
|
||||
|
||||
@@ -142,6 +144,20 @@ const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
};
|
||||
});
|
||||
|
||||
const generateNumber = (min: number, max: number) => {
|
||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||
};
|
||||
|
||||
const handleRandomPrompt = useCallback(() => {
|
||||
const randomIndex = generateNumber(0, promptList.length - 1);
|
||||
const randomPrompt = promptList[randomIndex];
|
||||
inputRef.current?.handleInputChange({
|
||||
target: {
|
||||
value: randomPrompt
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setImageSize = useCallback(() => {
|
||||
let size: Record<string, string | number> = {
|
||||
with: 256,
|
||||
@@ -497,10 +513,15 @@ const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
)}
|
||||
<div className="ground-left-footer">
|
||||
<MessageInput
|
||||
ref={inputRef}
|
||||
placeholer={intl.formatMessage({
|
||||
id: 'playground.input.prompt.holder'
|
||||
})}
|
||||
actions={['clear']}
|
||||
defaultSize={{
|
||||
minRows: 6,
|
||||
maxRows: 6
|
||||
}}
|
||||
loading={loading}
|
||||
disabled={!parameters.model}
|
||||
isEmpty={!imageList.length}
|
||||
@@ -509,9 +530,23 @@ const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
shouldResetMessage={false}
|
||||
clearAll={handleClear}
|
||||
tools={
|
||||
<span className="p-l-8 font-600">
|
||||
{intl.formatMessage({ id: 'playground.image.prompt' })}
|
||||
</span>
|
||||
<>
|
||||
<span className="p-l-8 font-600">
|
||||
{intl.formatMessage({ id: 'playground.image.prompt' })}
|
||||
</span>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id: 'playground.image.prompt.random'
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
onClick={handleRandomPrompt}
|
||||
size="middle"
|
||||
type="text"
|
||||
icon={<IconFont type="icon-random"></IconFont>}
|
||||
></Button>
|
||||
</Tooltip>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -270,7 +270,7 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
</div>
|
||||
|
||||
{audioData ? (
|
||||
<div className="flex-between flex-center justify-center">
|
||||
<div className="flex-between flex-center justify-center relative">
|
||||
<div style={{ width: 600 }}>
|
||||
<AudioPlayer
|
||||
url={audioData.url}
|
||||
@@ -278,6 +278,31 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
duration={audioData.duration}
|
||||
></AudioPlayer>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
padding: '16px 32px',
|
||||
textAlign: 'right',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)'
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id: 'playground.audio.button.generate'
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
style={{ width: 46 }}
|
||||
size="middle"
|
||||
disabled={!audioData}
|
||||
type="primary"
|
||||
onClick={handleOnGenerate}
|
||||
icon={<SendOutlined></SendOutlined>}
|
||||
></Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
renderAniamtion()
|
||||
@@ -327,22 +352,6 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '16px 32px', textAlign: 'right' }}>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id: 'playground.audio.button.generate'
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
style={{ width: 46 }}
|
||||
size="middle"
|
||||
disabled={!audioData}
|
||||
type="primary"
|
||||
onClick={handleOnGenerate}
|
||||
icon={<SendOutlined></SendOutlined>}
|
||||
></Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -4,7 +4,14 @@ import { ClearOutlined, SendOutlined, SwapOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Checkbox, Divider, Input, Tooltip } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { Roles } from '../config';
|
||||
import { MessageItem } from '../config/types';
|
||||
@@ -64,6 +71,7 @@ const layoutOptions = [
|
||||
];
|
||||
|
||||
interface MessageInputProps {
|
||||
ref?: any;
|
||||
handleSubmit: (params: CurrentMessage) => void;
|
||||
handleAbortFetch: () => void;
|
||||
updateLayout?: (value: { span: number; count: number }) => void;
|
||||
@@ -89,411 +97,425 @@ interface MessageInputProps {
|
||||
defaultSize?: { minRows: number; maxRows: number };
|
||||
}
|
||||
|
||||
const MessageInput: React.FC<MessageInputProps> = ({
|
||||
handleSubmit,
|
||||
handleAbortFetch,
|
||||
presetPrompt,
|
||||
clearAll,
|
||||
updateLayout,
|
||||
addMessage,
|
||||
onCheck,
|
||||
loading,
|
||||
disabled,
|
||||
isEmpty,
|
||||
submitIcon,
|
||||
placeholer,
|
||||
tools,
|
||||
style,
|
||||
checkLabel,
|
||||
defaultSize = { minRows: 3, maxRows: 8 },
|
||||
shouldResetMessage = true,
|
||||
actions = ['clear', 'layout', 'role', 'upload', 'add', 'paste']
|
||||
}) => {
|
||||
const { TextArea } = Input;
|
||||
const intl = useIntl();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [focused, setFocused] = useState(false);
|
||||
const [message, setMessage] = useState<CurrentMessage>({
|
||||
role: Roles.User,
|
||||
content: '',
|
||||
imgs: []
|
||||
});
|
||||
const imgCountRef = useRef(0);
|
||||
const inputRef = useRef<any>(null);
|
||||
|
||||
const isDisabled = useMemo(() => {
|
||||
return disabled
|
||||
? true
|
||||
: !message.content && isEmpty && !message.imgs?.length;
|
||||
}, [disabled, message, isEmpty]);
|
||||
|
||||
const resetMessage = () => {
|
||||
setMessage({
|
||||
role: message.role,
|
||||
content: '',
|
||||
imgs: []
|
||||
});
|
||||
};
|
||||
|
||||
const handleInputChange = (e: any) => {
|
||||
console.log('input change:', e.target?.value);
|
||||
setMessage({
|
||||
...message,
|
||||
content: e.target?.value
|
||||
});
|
||||
};
|
||||
const handleSendMessage = () => {
|
||||
handleSubmit({ ...message });
|
||||
if (shouldResetMessage) {
|
||||
resetMessage();
|
||||
}
|
||||
};
|
||||
const onStop = () => {
|
||||
handleAbortFetch();
|
||||
};
|
||||
const handleLayoutChange = (value: { span: number; count: number }) => {
|
||||
updateLayout?.(value);
|
||||
};
|
||||
|
||||
const handleToggleRole = () => {
|
||||
setMessage({
|
||||
...message,
|
||||
role: message.role === Roles.User ? Roles.Assistant : Roles.User
|
||||
});
|
||||
};
|
||||
|
||||
const handleClearAll = (e: any) => {
|
||||
e.stopPropagation();
|
||||
clearAll();
|
||||
setMessage({
|
||||
const MessageInput: React.FC<MessageInputProps> = forwardRef(
|
||||
(
|
||||
{
|
||||
handleSubmit,
|
||||
handleAbortFetch,
|
||||
presetPrompt,
|
||||
clearAll,
|
||||
updateLayout,
|
||||
addMessage,
|
||||
onCheck,
|
||||
loading,
|
||||
disabled,
|
||||
isEmpty,
|
||||
submitIcon,
|
||||
placeholer,
|
||||
tools,
|
||||
style,
|
||||
checkLabel,
|
||||
defaultSize = { minRows: 3, maxRows: 8 },
|
||||
shouldResetMessage = true,
|
||||
actions = ['clear', 'layout', 'role', 'upload', 'add', 'paste']
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { TextArea } = Input;
|
||||
const intl = useIntl();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [focused, setFocused] = useState(false);
|
||||
const [message, setMessage] = useState<CurrentMessage>({
|
||||
role: Roles.User,
|
||||
content: '',
|
||||
imgs: []
|
||||
});
|
||||
};
|
||||
const imgCountRef = useRef(0);
|
||||
const inputRef = useRef<any>(null);
|
||||
|
||||
const handleAddMessage = (e?: any) => {
|
||||
e?.preventDefault();
|
||||
addMessage?.({ ...message });
|
||||
resetMessage();
|
||||
setFocused(true);
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus?.();
|
||||
}, 100);
|
||||
};
|
||||
const isDisabled = useMemo(() => {
|
||||
return disabled
|
||||
? true
|
||||
: !message.content && isEmpty && !message.imgs?.length;
|
||||
}, [disabled, message, isEmpty]);
|
||||
|
||||
const getPasteContent = useCallback(
|
||||
async (event: any) => {
|
||||
const clipboardData = event.clipboardData || window.clipboardData;
|
||||
const items = clipboardData.items;
|
||||
const imgPromises: Promise<string>[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let item = items[i];
|
||||
|
||||
if (item.kind === 'file' && item.type.indexOf('image') !== -1) {
|
||||
const file = item.getAsFile();
|
||||
const imgPromise = new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
const base64String = event.target?.result as string;
|
||||
if (base64String) {
|
||||
resolve(base64String);
|
||||
} else {
|
||||
reject('Failed to convert image to base64');
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
imgPromises.push(imgPromise);
|
||||
} else if (item.kind === 'string') {
|
||||
// string
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const imgs = await Promise.all(imgPromises);
|
||||
|
||||
if (imgs.length) {
|
||||
const list = _.map(imgs, (img: string) => {
|
||||
imgCountRef.current += 1;
|
||||
return {
|
||||
uid: imgCountRef.current,
|
||||
dataUrl: img
|
||||
};
|
||||
});
|
||||
setMessage({
|
||||
...message,
|
||||
imgs: [...(message.imgs || []), ...list]
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing images:', error);
|
||||
}
|
||||
},
|
||||
[message]
|
||||
);
|
||||
|
||||
// ========== upload image ==========
|
||||
const handleUpdateImgList = (
|
||||
list: { uid: number | string; dataUrl: string }[]
|
||||
) => {
|
||||
setMessage({
|
||||
...message,
|
||||
imgs: [...(message.imgs || []), ...list]
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteImg = (uid: number | string) => {
|
||||
const list = _.filter(
|
||||
message.imgs,
|
||||
(item: MessageItem) => item.uid !== uid
|
||||
);
|
||||
setMessage({
|
||||
...message,
|
||||
imgs: list
|
||||
});
|
||||
};
|
||||
|
||||
const handleOnPaste = (e: any) => {
|
||||
const text = e.clipboardData.getData('text');
|
||||
if (!text) {
|
||||
e.preventDefault();
|
||||
getPasteContent(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteLastImage = useCallback(() => {
|
||||
if (message.imgs && message.imgs?.length > 0) {
|
||||
const newImgList = [...(message.imgs || [])];
|
||||
const lastImage = newImgList.pop();
|
||||
if (lastImage) {
|
||||
handleDeleteImg(lastImage.uid);
|
||||
}
|
||||
}
|
||||
}, [message.imgs, handleDeleteImg]);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(event: any) => {
|
||||
if (
|
||||
event.key === 'Backspace' &&
|
||||
message.content === '' &&
|
||||
message.imgs &&
|
||||
message.imgs?.length > 0
|
||||
) {
|
||||
// inputref blur
|
||||
event.preventDefault();
|
||||
handleDeleteLastImage();
|
||||
}
|
||||
},
|
||||
[message, handleDeleteLastImage]
|
||||
);
|
||||
|
||||
const handleSelectPrompt = (list: CurrentMessage[]) => {
|
||||
presetPrompt?.(list);
|
||||
};
|
||||
|
||||
useHotkeys(
|
||||
HotKeys.SUBMIT,
|
||||
(e: any) => {
|
||||
console.log('submit message', loading);
|
||||
e.preventDefault();
|
||||
handleSendMessage();
|
||||
},
|
||||
{
|
||||
enabled: !loading && !isDisabled,
|
||||
enableOnFormTags: focused,
|
||||
preventDefault: true
|
||||
}
|
||||
);
|
||||
useHotkeys(
|
||||
HotKeys.ADD,
|
||||
(e: any) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleAddMessage();
|
||||
},
|
||||
{
|
||||
enabled: !loading,
|
||||
enableOnFormTags: focused,
|
||||
preventDefault: true
|
||||
}
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
HotKeys.FOCUS,
|
||||
() => {
|
||||
inputRef.current?.focus?.({
|
||||
cursor: 'end'
|
||||
const resetMessage = () => {
|
||||
setMessage({
|
||||
role: message.role,
|
||||
content: '',
|
||||
imgs: []
|
||||
});
|
||||
},
|
||||
{ preventDefault: true }
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="messageInput" style={{ ...style }}>
|
||||
<div className="tool-bar">
|
||||
<div className="actions">
|
||||
{
|
||||
<>
|
||||
{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>
|
||||
)}
|
||||
</>
|
||||
const handleInputChange = (e: any) => {
|
||||
console.log('input change:', e.target?.value);
|
||||
setMessage({
|
||||
...message,
|
||||
content: e.target?.value
|
||||
});
|
||||
};
|
||||
const handleSendMessage = () => {
|
||||
handleSubmit({ ...message });
|
||||
if (shouldResetMessage) {
|
||||
resetMessage();
|
||||
}
|
||||
};
|
||||
const onStop = () => {
|
||||
handleAbortFetch();
|
||||
};
|
||||
const handleLayoutChange = (value: { span: number; count: number }) => {
|
||||
updateLayout?.(value);
|
||||
};
|
||||
|
||||
const handleToggleRole = () => {
|
||||
setMessage({
|
||||
...message,
|
||||
role: message.role === Roles.User ? Roles.Assistant : Roles.User
|
||||
});
|
||||
};
|
||||
|
||||
const handleClearAll = (e: any) => {
|
||||
e.stopPropagation();
|
||||
clearAll();
|
||||
setMessage({
|
||||
role: Roles.User,
|
||||
content: '',
|
||||
imgs: []
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddMessage = (e?: any) => {
|
||||
e?.preventDefault();
|
||||
addMessage?.({ ...message });
|
||||
resetMessage();
|
||||
setFocused(true);
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus?.();
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const getPasteContent = useCallback(
|
||||
async (event: any) => {
|
||||
const clipboardData = event.clipboardData || window.clipboardData;
|
||||
const items = clipboardData.items;
|
||||
const imgPromises: Promise<string>[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let item = items[i];
|
||||
|
||||
if (item.kind === 'file' && item.type.indexOf('image') !== -1) {
|
||||
const file = item.getAsFile();
|
||||
const imgPromise = new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
const base64String = event.target?.result as string;
|
||||
if (base64String) {
|
||||
resolve(base64String);
|
||||
} else {
|
||||
reject('Failed to convert image to base64');
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
imgPromises.push(imgPromise);
|
||||
} else if (item.kind === 'string') {
|
||||
// string
|
||||
}
|
||||
{tools}
|
||||
{actions.includes('check') && (
|
||||
<Checkbox onChange={onCheck} defaultChecked={true}>
|
||||
{checkLabel}
|
||||
</Checkbox>
|
||||
)}
|
||||
{actions.includes('clear') && (
|
||||
<Tooltip title={intl.formatMessage({ id: 'common.button.clear' })}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ClearOutlined />}
|
||||
size="middle"
|
||||
onClick={handleClearAll}
|
||||
></Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
}
|
||||
|
||||
{actions.includes('layout') && updateLayout && (
|
||||
<>
|
||||
<Divider type="vertical" style={{ margin: 0 }} />
|
||||
{layoutOptions.map((option) => (
|
||||
<Tooltip
|
||||
title={intl.formatMessage({ id: option.tips })}
|
||||
key={option.icon}
|
||||
>
|
||||
<Button
|
||||
key={option.icon}
|
||||
type="text"
|
||||
icon={<IconFont type={option.icon}></IconFont>}
|
||||
try {
|
||||
const imgs = await Promise.all(imgPromises);
|
||||
|
||||
if (imgs.length) {
|
||||
const list = _.map(imgs, (img: string) => {
|
||||
imgCountRef.current += 1;
|
||||
return {
|
||||
uid: imgCountRef.current,
|
||||
dataUrl: img
|
||||
};
|
||||
});
|
||||
setMessage({
|
||||
...message,
|
||||
imgs: [...(message.imgs || []), ...list]
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing images:', error);
|
||||
}
|
||||
},
|
||||
[message]
|
||||
);
|
||||
|
||||
// ========== upload image ==========
|
||||
const handleUpdateImgList = (
|
||||
list: { uid: number | string; dataUrl: string }[]
|
||||
) => {
|
||||
setMessage({
|
||||
...message,
|
||||
imgs: [...(message.imgs || []), ...list]
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteImg = (uid: number | string) => {
|
||||
const list = _.filter(
|
||||
message.imgs,
|
||||
(item: MessageItem) => item.uid !== uid
|
||||
);
|
||||
setMessage({
|
||||
...message,
|
||||
imgs: list
|
||||
});
|
||||
};
|
||||
|
||||
const handleOnPaste = (e: any) => {
|
||||
const text = e.clipboardData.getData('text');
|
||||
if (!text) {
|
||||
e.preventDefault();
|
||||
getPasteContent(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteLastImage = useCallback(() => {
|
||||
if (message.imgs && message.imgs?.length > 0) {
|
||||
const newImgList = [...(message.imgs || [])];
|
||||
const lastImage = newImgList.pop();
|
||||
if (lastImage) {
|
||||
handleDeleteImg(lastImage.uid);
|
||||
}
|
||||
}
|
||||
}, [message.imgs, handleDeleteImg]);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(event: any) => {
|
||||
if (
|
||||
event.key === 'Backspace' &&
|
||||
message.content === '' &&
|
||||
message.imgs &&
|
||||
message.imgs?.length > 0
|
||||
) {
|
||||
// inputref blur
|
||||
event.preventDefault();
|
||||
handleDeleteLastImage();
|
||||
}
|
||||
},
|
||||
[message, handleDeleteLastImage]
|
||||
);
|
||||
|
||||
const handleSelectPrompt = (list: CurrentMessage[]) => {
|
||||
presetPrompt?.(list);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleInputChange: handleInputChange
|
||||
}));
|
||||
|
||||
useHotkeys(
|
||||
HotKeys.SUBMIT,
|
||||
(e: any) => {
|
||||
console.log('submit message', loading);
|
||||
e.preventDefault();
|
||||
handleSendMessage();
|
||||
},
|
||||
{
|
||||
enabled: !loading && !isDisabled,
|
||||
enableOnFormTags: focused,
|
||||
preventDefault: true
|
||||
}
|
||||
);
|
||||
useHotkeys(
|
||||
HotKeys.ADD,
|
||||
(e: any) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleAddMessage();
|
||||
},
|
||||
{
|
||||
enabled: !loading,
|
||||
enableOnFormTags: focused,
|
||||
preventDefault: true
|
||||
}
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
HotKeys.FOCUS,
|
||||
() => {
|
||||
inputRef.current?.focus?.({
|
||||
cursor: 'end'
|
||||
});
|
||||
},
|
||||
{ preventDefault: true }
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="messageInput" style={{ ...style }}>
|
||||
<div className="tool-bar">
|
||||
<div className="actions">
|
||||
{
|
||||
<>
|
||||
{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"
|
||||
onClick={() => handleLayoutChange(option.value)}
|
||||
></Button>
|
||||
</Tooltip>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="actions">
|
||||
{actions.includes('add') && (
|
||||
<Tooltip
|
||||
title={
|
||||
<span>
|
||||
[{KeyMap.ADD.textKeybinding}]{' '}
|
||||
></UploadImg>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
{tools}
|
||||
{actions.includes('check') && (
|
||||
<Checkbox onChange={onCheck} defaultChecked={true}>
|
||||
{checkLabel}
|
||||
</Checkbox>
|
||||
)}
|
||||
{actions.includes('clear') && (
|
||||
<Tooltip
|
||||
title={intl.formatMessage({ id: 'common.button.clear' })}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ClearOutlined />}
|
||||
size="middle"
|
||||
onClick={handleClearAll}
|
||||
></Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{actions.includes('layout') && updateLayout && (
|
||||
<>
|
||||
<Divider type="vertical" style={{ margin: 0 }} />
|
||||
{layoutOptions.map((option) => (
|
||||
<Tooltip
|
||||
title={intl.formatMessage({ id: option.tips })}
|
||||
key={option.icon}
|
||||
>
|
||||
<Button
|
||||
key={option.icon}
|
||||
type="text"
|
||||
icon={<IconFont type={option.icon}></IconFont>}
|
||||
size="middle"
|
||||
onClick={() => handleLayoutChange(option.value)}
|
||||
></Button>
|
||||
</Tooltip>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="actions">
|
||||
{actions.includes('add') && (
|
||||
<Tooltip
|
||||
title={
|
||||
<span>
|
||||
[{KeyMap.ADD.textKeybinding}]{' '}
|
||||
{intl.formatMessage({ id: 'common.button.add' })}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Button type="default" size="middle" onClick={handleAddMessage}>
|
||||
{intl.formatMessage({ id: 'common.button.add' })}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Button type="default" size="middle" onClick={handleAddMessage}>
|
||||
{intl.formatMessage({ id: 'common.button.add' })}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!loading ? (
|
||||
<Tooltip
|
||||
title={
|
||||
<span>
|
||||
[{KeyMap.SUBMIT.textKeybinding}]{' '}
|
||||
{intl.formatMessage({ id: 'common.button.submit' })}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!loading ? (
|
||||
<Tooltip
|
||||
title={
|
||||
<span>
|
||||
[{KeyMap.SUBMIT.textKeybinding}]{' '}
|
||||
{intl.formatMessage({ id: 'common.button.submit' })}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
style={{ width: 46 }}
|
||||
type="primary"
|
||||
onClick={handleSendMessage}
|
||||
size="middle"
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{submitIcon ?? (
|
||||
<SendOutlined rotate={0} className="font-size-14" />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Button
|
||||
style={{ width: 46 }}
|
||||
type="primary"
|
||||
onClick={handleSendMessage}
|
||||
onClick={onStop}
|
||||
size="middle"
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{submitIcon ?? (
|
||||
<SendOutlined rotate={0} className="font-size-14" />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
icon={
|
||||
<IconFont
|
||||
type="icon-stop1"
|
||||
className="font-size-14"
|
||||
></IconFont>
|
||||
}
|
||||
></Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ThumbImg
|
||||
dataList={message.imgs || []}
|
||||
onDelete={handleDeleteImg}
|
||||
editable={true}
|
||||
></ThumbImg>
|
||||
<div className="input-box">
|
||||
{actions.includes('paste') ? (
|
||||
<TextArea
|
||||
ref={inputRef}
|
||||
autoSize={{ minRows: 3, maxRows: 8 }}
|
||||
onChange={handleInputChange}
|
||||
value={message.content}
|
||||
size="large"
|
||||
variant="borderless"
|
||||
onFocus={() => setFocused(true)}
|
||||
onBlur={() => setFocused(false)}
|
||||
onPaste={handleOnPaste}
|
||||
></TextArea>
|
||||
) : (
|
||||
<Button
|
||||
style={{ width: 46 }}
|
||||
type="primary"
|
||||
onClick={onStop}
|
||||
size="middle"
|
||||
icon={
|
||||
<IconFont type="icon-stop1" className="font-size-14"></IconFont>
|
||||
}
|
||||
></Button>
|
||||
<TextArea
|
||||
ref={inputRef}
|
||||
autoSize={{
|
||||
minRows: defaultSize.minRows,
|
||||
maxRows: defaultSize.maxRows
|
||||
}}
|
||||
onChange={handleInputChange}
|
||||
value={message.content}
|
||||
size="large"
|
||||
variant="borderless"
|
||||
onFocus={() => setFocused(true)}
|
||||
onBlur={() => setFocused(false)}
|
||||
></TextArea>
|
||||
)}
|
||||
{!message.content && !focused && (
|
||||
<span
|
||||
className="holder"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
placeholer ??
|
||||
intl.formatMessage({ id: 'playground.input.holder' })
|
||||
}}
|
||||
></span>
|
||||
)}
|
||||
</div>
|
||||
<PromptModal
|
||||
open={open}
|
||||
onCancel={() => setOpen(false)}
|
||||
onSelect={handleSelectPrompt}
|
||||
></PromptModal>
|
||||
</div>
|
||||
<ThumbImg
|
||||
dataList={message.imgs || []}
|
||||
onDelete={handleDeleteImg}
|
||||
editable={true}
|
||||
></ThumbImg>
|
||||
<div className="input-box">
|
||||
{actions.includes('paste') ? (
|
||||
<TextArea
|
||||
ref={inputRef}
|
||||
autoSize={{ minRows: 3, maxRows: 8 }}
|
||||
onChange={handleInputChange}
|
||||
value={message.content}
|
||||
size="large"
|
||||
variant="borderless"
|
||||
onFocus={() => setFocused(true)}
|
||||
onBlur={() => setFocused(false)}
|
||||
onPaste={handleOnPaste}
|
||||
></TextArea>
|
||||
) : (
|
||||
<TextArea
|
||||
ref={inputRef}
|
||||
autoSize={{
|
||||
minRows: defaultSize.minRows,
|
||||
maxRows: defaultSize.maxRows
|
||||
}}
|
||||
onChange={handleInputChange}
|
||||
value={message.content}
|
||||
size="large"
|
||||
variant="borderless"
|
||||
onFocus={() => setFocused(true)}
|
||||
onBlur={() => setFocused(false)}
|
||||
></TextArea>
|
||||
)}
|
||||
{!message.content && !focused && (
|
||||
<span
|
||||
className="holder"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
placeholer ??
|
||||
intl.formatMessage({ id: 'playground.input.holder' })
|
||||
}}
|
||||
></span>
|
||||
)}
|
||||
</div>
|
||||
<PromptModal
|
||||
open={open}
|
||||
onCancel={() => setOpen(false)}
|
||||
onSelect={handleSelectPrompt}
|
||||
></PromptModal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default MessageInput;
|
||||
|
||||
@@ -121,3 +121,15 @@ export const OpenAIViewCode = {
|
||||
logcommand: 'data[0].b64_json'
|
||||
}
|
||||
};
|
||||
|
||||
export const promptList = [
|
||||
'a lovely cat.',
|
||||
"Digital art, portrait of an anthropomorphic roaring Tiger warrior with full armor, close up in the middle of a battle, behind him there is a banner with the text 'Open Source'.",
|
||||
'An astronaut riding a green horse.',
|
||||
"a female character with long, flowing hair that appears to be made of ethereal, swirling patterns resembling the Northern Lights or Aurora Borealis. The background is dominated by deep blues and purples, creating a mysterious and dramatic atmosphere. The character's face is serene, with pale skin and striking features. She wears a dark-colored outfit with subtle patterns. The overall style of the artwork is reminiscent of fantasy or supernatural genres.",
|
||||
"A whimsical and creative image depicting a hybrid creature that is a mix of a waffle and a hippopotamus, basking in a river of melted butter amidst a breakfast-themed landscape. It features the distinctive, bulky body shape of a hippo. However, instead of the usual grey skin, the creature's body resembles a golden-brown, crispy waffle fresh off the griddle. The skin is textured with the familiar grid pattern of a waffle, each square filled with a glistening sheen of syrup. The environment combines the natural habitat of a hippo with elements of a breakfast table setting, a river of warm, melted butter, with oversized utensils or plates peeking out from the lush, pancake-like foliage in the background, a towering pepper mill standing in for a tree. As the sun rises in this fantastical world, it casts a warm, buttery glow over the scene. The creature, content in its butter river, lets out a yawn. Nearby, a flock of birds take flight.",
|
||||
'A cinematic shot of a baby racoon wearing an intricate italian priest robe.',
|
||||
'Create portraits of characters in the style of classical oil painting, Capturing the essence of Chinese aristocracy in the Han Dynasty period. This portrait should feature a Chinese male character dressed in exquisite and gorgeous Chinese Han style clothing, with a dignified demeanor. The background should be a simple and elegant wallpaper pattern, with the face and upper body of the character as the focus. The lighting should be soft and diffuse, highlighting the characteristics of the character and the complex details of the clothing. The color palette should be rich and warm, with red, gold, and dark green.',
|
||||
'photo of a young woman with long, wavy brown hair tied in a bun and glasses. She has a fair complexion and is wearing subtle makeup, emphasizing her eyes and lips. She is dressed in a black top. The background appears to be an urban setting with a building facade, and the sunlight casts a warm glow on her face.',
|
||||
'A portrait of women with blue eyes.'
|
||||
];
|
||||
|
||||
@@ -62,6 +62,11 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
|
||||
theme="dark"
|
||||
code={addWorkerGuide.win.registerWorker(origin)}
|
||||
></HighlightCode>
|
||||
<h4>Docker </h4>
|
||||
<HighlightCode
|
||||
theme="dark"
|
||||
code={addWorkerGuide.docker.registerWorker(origin)}
|
||||
></HighlightCode>
|
||||
<h3>3. {intl.formatMessage({ id: 'resources.worker.add.step3' })}</h3>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -28,5 +28,12 @@ export const addWorkerGuide = {
|
||||
registerWorker(server: string) {
|
||||
return `Invoke-Expression "& { $((Invoke-WebRequest -Uri "https://get.gpustack.ai" -UseBasicParsing).Content) } --server-url ${server} --token mytoken"`;
|
||||
}
|
||||
},
|
||||
docker: {
|
||||
getToken:
|
||||
'Get-Content -Path (Join-Path -Path $env:APPDATA -ChildPath "gpustack\\token") -Raw',
|
||||
registerWorker(server: string) {
|
||||
return `docker run -d --gpus all --ipc=host --network=host gpustack/gpustack --server-url ${server} --token mytoken`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user