chore: image edit invert painting area

This commit is contained in:
jialin
2025-03-04 17:52:14 +08:00
parent bb47b4475e
commit 8a98aef28c
15 changed files with 171 additions and 45 deletions
+4
View File
@@ -50,6 +50,10 @@
margin-top: 10px; margin-top: 10px;
} }
.m-0 {
margin: 0;
}
.m-r-10 { .m-r-10 {
margin-right: 10px; margin-right: 10px;
} }
@@ -8,6 +8,19 @@
border-radius: var(--border-radius-base); border-radius: var(--border-radius-base);
overflow: hidden; overflow: hidden;
.label {
position: absolute;
top: 4px;
left: 4px;
height: 20px;
line-height: 20px;
border-radius: 12px;
padding: 0 8px;
background-color: var(--ant-geekblue-1);
z-index: 10;
transform: scale(0.9);
}
.progress-wrapper { .progress-wrapper {
position: absolute; position: absolute;
bottom: 20px; bottom: 20px;
@@ -14,6 +14,7 @@ interface SingleImageProps {
maxHeight?: number; maxHeight?: number;
maxWidth?: number; maxWidth?: number;
dataUrl: string; dataUrl: string;
label?: React.ReactNode;
uid: number; uid: number;
preview?: boolean; preview?: boolean;
autoSize?: boolean; autoSize?: boolean;
@@ -42,6 +43,7 @@ const SingleImage: React.FC<SingleImageProps> = (props) => {
maxHeight, maxHeight,
maxWidth, maxWidth,
dataUrl = '', dataUrl = '',
label,
style, style,
autoBgColor, autoBgColor,
preview = true, preview = true,
@@ -119,6 +121,7 @@ const SingleImage: React.FC<SingleImageProps> = (props) => {
ref={imgWrapper} ref={imgWrapper}
> >
<> <>
{label && <div className="label">{label}</div>}
{loading ? ( {loading ? (
<span <span
className="progress-wrap" className="progress-wrap"
+50 -20
View File
@@ -10,7 +10,13 @@ import { useIntl } from '@umijs/max';
import { Button, Checkbox, Slider, Tooltip } from 'antd'; import { Button, Checkbox, Slider, Tooltip } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import _ from 'lodash'; import _ from 'lodash';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState
} from 'react';
import IconFont from '../icon-font'; import IconFont from '../icon-font';
import './index.less'; import './index.less';
@@ -38,7 +44,7 @@ const COLOR = 'rgba(0, 0, 255, 0.3)';
const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
imageSrc, imageSrc,
disabled, disabled: isDisabled,
imageStatus, imageStatus,
clearUploadMask, clearUploadMask,
onSave, onSave,
@@ -68,8 +74,13 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
const preImguid = useRef<string | number>(''); const preImguid = useRef<string | number>('');
const [activeScale, setActiveScale] = useState<number>(1); const [activeScale, setActiveScale] = useState<number>(1);
const negativeMaskRef = useRef<boolean>(false); const negativeMaskRef = useRef<boolean>(false);
const [invertMask, setInvertMask] = useState<boolean>(false);
const mouseDownState = useRef<boolean>(false); const mouseDownState = useRef<boolean>(false);
const disabled = useMemo(() => {
return isDisabled || invertMask;
}, [isDisabled, invertMask]);
const getTransformedPoint = useCallback( const getTransformedPoint = useCallback(
(offsetX: number, offsetY: number) => { (offsetX: number, offsetY: number) => {
const { current: scale } = autoScale; const { current: scale } = autoScale;
@@ -225,11 +236,7 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
); );
const data = imageData.data; const data = imageData.data;
if (negativeMaskRef.current) { inpaintArea(data);
inpaintBackground(data);
} else {
inpaintArea(data);
}
maskCtx.putImageData(imageData, 0, 0); maskCtx.putImageData(imageData, 0, 0);
@@ -605,6 +612,39 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
overlayCtx!.resetTransform(); overlayCtx!.resetTransform();
}, []); }, []);
const invertPainting = (isChecked: boolean) => {
const ctx = overlayCanvasRef.current!.getContext('2d');
if (!ctx) return;
if (isChecked) {
const canvasWidth = overlayCanvasRef.current!.width;
const canvasHeight = overlayCanvasRef.current!.height;
clearOverlayCanvas();
ctx.fillStyle = COLOR;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
setTransform();
ctx.globalCompositeOperation = 'destination-out';
strokesRef.current.forEach((stroke) => {
stroke.forEach((point: Point) => {
const { x, y } = getTransformedPoint(point.x, point.y);
const lineWidth = getTransformLineWidth(point.lineWidth);
ctx.fillStyle = 'rgba(0,0,0,1)';
ctx.beginPath();
ctx.arc(x, y, lineWidth / 2, 0, Math.PI * 2);
ctx.fill();
});
});
ctx.globalCompositeOperation = 'source-out';
} else {
redrawStrokes(strokesRef.current);
}
};
const updateCursorSize = () => { const updateCursorSize = () => {
cursorRef.current!.style.width = `${lineWidth * autoScale.current}px`; cursorRef.current!.style.width = `${lineWidth * autoScale.current}px`;
cursorRef.current!.style.height = `${lineWidth * autoScale.current}px`; cursorRef.current!.style.height = `${lineWidth * autoScale.current}px`;
@@ -630,11 +670,7 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
resetCanvas(); resetCanvas();
} }
preImguid.current = imguid; preImguid.current = imguid;
console.log(
'Image initialized:',
strokesRef.current.length,
imageStatus.isOriginal
);
if (strokesRef.current.length && imageStatus.isOriginal) { if (strokesRef.current.length && imageStatus.isOriginal) {
redrawStrokes(strokesRef.current); redrawStrokes(strokesRef.current);
saveImage(); saveImage();
@@ -710,6 +746,8 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
const handleOnChangeMask = (e: any) => { const handleOnChangeMask = (e: any) => {
negativeMaskRef.current = e.target.checked; negativeMaskRef.current = e.target.checked;
invertPainting(e.target.checked);
setInvertMask(e.target.checked);
saveImage(); saveImage();
}; };
@@ -746,14 +784,6 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
}; };
}, []); }, []);
// useEffect(() => {
// if (disabled) {
// overlayCanvasRef.current!.style.pointerEvents = 'none';
// } else {
// overlayCanvasRef.current!.style.pointerEvents = 'auto';
// }
// }, [disabled]);
return ( return (
<div className="editor-wrapper"> <div className="editor-wrapper">
<div className="flex-between"> <div className="flex-between">
-4
View File
@@ -29,7 +29,3 @@ To add a new language configuration, follow these steps:
- Review and ensure all translations are complete. - Review and ensure all translations are complete.
- Your new language configuration is now ready for use! - Your new language configuration is now ready for use!
5. **Check the Dir and File**
- `npm run check:locales`
+3 -1
View File
@@ -141,5 +141,7 @@ export default {
'playground.image.fitview': 'Fit View', 'playground.image.fitview': 'Fit View',
'playground.chat.aithought': 'CoT', 'playground.chat.aithought': 'CoT',
'playground.image.mask.uploaded': 'Mask Uploaded', 'playground.image.mask.uploaded': 'Mask Uploaded',
'playground.image.mask.upload': 'Upload Mask' 'playground.image.mask.upload': 'Upload Mask',
'playground.params.frequency_penalty.tips': `Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.`,
'playground.params.presence_penalty.tips': `Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.`
}; };
+3 -1
View File
@@ -136,5 +136,7 @@ export default {
'playground.image.fitview': '适应视图', 'playground.image.fitview': '适应视图',
'playground.chat.aithought': '思考过程', 'playground.chat.aithought': '思考过程',
'playground.image.mask.uploaded': '遮罩已上传', 'playground.image.mask.uploaded': '遮罩已上传',
'playground.image.mask.upload': '上传遮罩' 'playground.image.mask.upload': '上传遮罩',
'playground.params.frequency_penalty.tips': `数值介于 -2.0 和 2.0 之间。正值会根据新词在文本中已出现的频率对其进行惩罚,从而降低模型逐字重复相同内容的可能性。`,
'playground.params.presence_penalty.tips': `数值介于 -2.0 和 2.0 之间。正值会根据新词是否已在文本中出现过对其进行惩罚,从而增加模型谈论新话题的可能性。`
}; };
+4 -6
View File
@@ -3,6 +3,8 @@ import { initialPasswordAtom, userAtom } from '@/atoms/user';
import LangSelect from '@/components/lang-select'; import LangSelect from '@/components/lang-select';
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import { import {
CRYPT_TEXT,
REMEMBER_ME_KEY,
getRememberMe, getRememberMe,
rememberMe, rememberMe,
removeRememberMe removeRememberMe
@@ -12,14 +14,11 @@ import { useIntl, useModel } from '@umijs/max';
import { Button, Checkbox, Form } from 'antd'; import { Button, Checkbox, Form } from 'antd';
import CryptoJS from 'crypto-js'; import CryptoJS from 'crypto-js';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { memo, useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { flushSync } from 'react-dom'; import { flushSync } from 'react-dom';
import { login } from '../apis'; import { login } from '../apis';
import { checkDefaultPage } from '../utils'; import { checkDefaultPage } from '../utils';
const REMEMBER_ME_KEY = 'r_m';
const CRYPT_TEXT = 'seal';
const LoginForm = () => { const LoginForm = () => {
const [userInfo, setUserInfo] = useAtom(userAtom); const [userInfo, setUserInfo] = useAtom(userAtom);
const [initialPassword, setInitialPassword] = useAtom(initialPasswordAtom); const [initialPassword, setInitialPassword] = useAtom(initialPasswordAtom);
@@ -27,7 +26,6 @@ const LoginForm = () => {
const intl = useIntl(); const intl = useIntl();
const [form] = Form.useForm(); const [form] = Form.useForm();
console.log('initialState+++++++=login', userInfo, initialState);
const renderWelCome = useMemo(() => { const renderWelCome = useMemo(() => {
return ( return (
<div <div
@@ -206,4 +204,4 @@ const LoginForm = () => {
); );
}; };
export default memo(LoginForm); export default LoginForm;
+1 -3
View File
@@ -1,6 +1,7 @@
import { initialPasswordAtom, userAtom } from '@/atoms/user'; import { initialPasswordAtom, userAtom } from '@/atoms/user';
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import { PasswordReg } from '@/config'; import { PasswordReg } from '@/config';
import { CRYPT_TEXT } from '@/utils/localstore/index';
import { GlobalOutlined, LockOutlined } from '@ant-design/icons'; import { GlobalOutlined, LockOutlined } from '@ant-design/icons';
import { SelectLang, useIntl } from '@umijs/max'; import { SelectLang, useIntl } from '@umijs/max';
import { Button, Form, message } from 'antd'; import { Button, Form, message } from 'antd';
@@ -9,8 +10,6 @@ import { useAtom } from 'jotai';
import { updatePassword } from '../apis'; import { updatePassword } from '../apis';
import { checkDefaultPage } from '../utils'; import { checkDefaultPage } from '../utils';
const CRYPT_TEXT = 'seal';
const PasswordForm: React.FC = () => { const PasswordForm: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -28,7 +27,6 @@ const PasswordForm: React.FC = () => {
}; };
const handleSubmit = async (values: any) => { const handleSubmit = async (values: any) => {
console.log('values', values, form);
try { try {
await updatePassword({ await updatePassword({
new_password: values.new_password, new_password: values.new_password,
@@ -132,6 +132,7 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = forwardRef(
onValuesChange={handleValuesChange} onValuesChange={handleValuesChange}
onFinish={handleOnFinish} onFinish={handleOnFinish}
onFinishFailed={handleOnFinishFailed} onFinishFailed={handleOnFinishFailed}
initialValues={initialValues}
> >
<div> <div>
{ {
+23 -5
View File
@@ -157,11 +157,6 @@ const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
}, [finalParameters, currentPrompt, parameters.size]); }, [finalParameters, currentPrompt, parameters.size]);
const handleClear = () => { const handleClear = () => {
// setImageList([]);
// setTokenResult(null);
// setMask('');
// setImage('');
// setUploadList([]);
setCurrentPrompt(''); setCurrentPrompt('');
}; };
@@ -393,11 +388,33 @@ const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
editable={false} editable={false}
autoBgColor={false} autoBgColor={false}
onClick={() => handleOnImgClick(uploadList[0], true)} onClick={() => handleOnImgClick(uploadList[0], true)}
label={<span>Origin</span>}
></SingleImage> ></SingleImage>
</> </>
); );
}, [uploadList, handleOnImgClick]); }, [uploadList, handleOnImgClick]);
const renderMaskImage = useMemo(() => {
if (!maskUpload.length) {
return null;
}
return (
<>
<SingleImage
{...maskUpload[0]}
height={125}
maxHeight={125}
preview={false}
loading={false}
autoSize={false}
editable={false}
autoBgColor={false}
label={<span>Mask</span>}
></SingleImage>
</>
);
}, [maskUpload]);
return ( return (
<div className="ground-left-wrapper"> <div className="ground-left-wrapper">
<div className="ground-left"> <div className="ground-left">
@@ -428,6 +445,7 @@ const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
alignItems: 'center' alignItems: 'center'
}} }}
> >
<div className="m-r-10">{renderMaskImage}</div>
{renderOriginImage} {renderOriginImage}
{imageList.length > 0 && ( {imageList.length > 0 && (
<> <>
+50 -2
View File
@@ -553,7 +553,7 @@ export const ChatParamsConfig: ParamsSchema[] = [
}, },
attrs: { attrs: {
max: 2, max: 2,
step: 0.1, step: 0.01,
inputnumber: true inputnumber: true
}, },
rules: [ rules: [
@@ -599,7 +599,55 @@ export const ChatParamsConfig: ParamsSchema[] = [
}, },
attrs: { attrs: {
max: 1, max: 1,
step: 0.1, step: 0.01,
inputnumber: true
},
rules: [
{
required: false
}
]
},
{
type: 'Slider',
name: 'frequency_penalty',
label: {
text: 'Frequency Penalty',
isLocalized: false
},
description: {
text: 'playground.params.frequency_penalty.tips',
html: false,
isLocalized: true
},
attrs: {
max: 2,
min: -2,
step: 0.01,
inputnumber: true
},
rules: [
{
required: false
}
]
},
{
type: 'Slider',
name: 'presence_penalty',
label: {
text: 'Presence Penalty',
isLocalized: false
},
description: {
text: 'playground.params.presence_penalty.tips',
html: false,
isLocalized: true
},
attrs: {
max: 2,
min: -2,
step: 0.01,
inputnumber: true inputnumber: true
}, },
rules: [ rules: [
+6 -2
View File
@@ -5,7 +5,9 @@ export const LLM_METAKEYS: Record<string, any> = {
top_p: 'top_p', top_p: 'top_p',
n_ctx: 'n_ctx', n_ctx: 'n_ctx',
n_slot: 'n_slot', n_slot: 'n_slot',
max_model_len: 'max_model_len' max_model_len: 'max_model_len',
frequency_penalty: 'frequency_penalty',
presence_penalty: 'presence_penalty'
}; };
export const IMG_METAKEYS = [ export const IMG_METAKEYS = [
@@ -23,7 +25,9 @@ export const llmInitialValues = {
stop: null, stop: null,
temperature: 1, temperature: 1,
top_p: 1, top_p: 1,
max_tokens: null max_tokens: null,
frequency_penalty: null,
presence_penalty: null
}; };
export const advancedFieldsDefaultValus = { export const advancedFieldsDefaultValus = {
+5 -1
View File
@@ -433,7 +433,11 @@ const Workers: React.FC = () => {
</span> </span>
<span> <span>
{intl.formatMessage({ id: 'resources.table.used' })}:{' '} {intl.formatMessage({ id: 'resources.table.used' })}:{' '}
{convertFileSize(record?.status?.memory?.used, 0)} {convertFileSize(
record?.status?.memory?.used ||
record?.status.memory?.allocated,
0
)}
</span> </span>
</span> </span>
} }
+5
View File
@@ -2,6 +2,9 @@ import localStore from './store';
const IS_FIRST_LOGIN = 'is_first_login'; const IS_FIRST_LOGIN = 'is_first_login';
const REMEMBER_ME_KEY = 'r_m';
const CRYPT_TEXT = 'seal';
const store = localStore.createInstance({ name: '_xWXJKJ_S1Sna_' }); const store = localStore.createInstance({ name: '_xWXJKJ_S1Sna_' });
const rememberMe = (key: string, data: any) => { const rememberMe = (key: string, data: any) => {
@@ -35,7 +38,9 @@ const writeState = (key: string, data: any) => {
}; };
export { export {
CRYPT_TEXT,
IS_FIRST_LOGIN, IS_FIRST_LOGIN,
REMEMBER_ME_KEY,
getRememberMe, getRememberMe,
readState, readState,
rememberMe, rememberMe,