Compare commits

..

7 Commits

Author SHA1 Message Date
Ryan Dick
e2684b45af Add cProfile for profiling graph execution. 2024-01-12 10:58:03 -05:00
Riccardo Giovanetti
d4c36da3ee translationBot(ui): update translation (Italian)
Currently translated at 97.3% (1365 of 1402 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2024-01-12 22:52:38 +11:00
psychedelicious
dfe0b73890 fix(ui): fix usages of panel helpers
Upstream breaking change.
2024-01-12 09:31:07 +11:00
psychedelicious
c0c8fa9a89 fix(ui): use nodrag on invinput in workflow editor
Closes #5476
2024-01-12 09:31:07 +11:00
psychedelicious
ad7139829c fix(ui): fix canvas space hotkey
Need to do some checks to ensure we aren't taking over input elements, and are focused on the canvas.

Closes #5478
2024-01-12 09:31:07 +11:00
psychedelicious
a24e63d440 fix(ui): do not focus board search on load 2024-01-12 09:31:07 +11:00
psychedelicious
59437a02c3 feat(ui): restore resizable prompt boxes
The autosize proved to be unpopular. Changed back to resizable.
2024-01-12 09:31:07 +11:00
20 changed files with 103 additions and 56 deletions

View File

@@ -1,3 +1,4 @@
import cProfile
import time
import traceback
from threading import BoundedSemaphore, Event, Thread
@@ -39,6 +40,9 @@ class DefaultInvocationProcessor(InvocationProcessorABC):
self.__threadLimit.acquire()
queue_item: Optional[InvocationQueueItem] = None
profiler = None
last_gesid = None
while not stop_event.is_set():
try:
queue_item = self.__invoker.services.queue.get()
@@ -49,6 +53,21 @@ class DefaultInvocationProcessor(InvocationProcessorABC):
# do not hammer the queue
time.sleep(0.5)
continue
if last_gesid != queue_item.graph_execution_state_id:
if profiler is not None:
# I'm not sure what would cause us to get here, but if we do, we should restart the profiler for
# the new graph_execution_state_id.
profiler.disable()
logger.info(f"Stopped profiler for {last_gesid}.")
profiler = None
last_gesid = None
profiler = cProfile.Profile()
profiler.enable()
last_gesid = queue_item.graph_execution_state_id
logger.info(f"Started profiling {last_gesid}.")
try:
graph_execution_state = self.__invoker.services.graph_execution_manager.get(
queue_item.graph_execution_state_id
@@ -201,6 +220,13 @@ class DefaultInvocationProcessor(InvocationProcessorABC):
queue_id=queue_item.session_queue_id,
graph_execution_state_id=graph_execution_state.id,
)
if profiler is not None:
profiler.disable()
dump_path = f"{last_gesid}.prof"
profiler.dump_stats(dump_path)
logger.info(f"Saved profile to {dump_path}.")
profiler = None
last_gesid = None
except KeyboardInterrupt:
pass # Log something? KeyboardInterrupt is probably not going to be seen by the processor

View File

@@ -96,7 +96,7 @@
"react-icons": "^4.12.0",
"react-konva": "^18.2.10",
"react-redux": "9.0.4",
"react-resizable-panels": "^1.0.7",
"react-resizable-panels": "^1.0.8",
"react-select": "5.8.0",
"react-textarea-autosize": "^8.5.3",
"react-use": "^17.4.2",

View File

@@ -143,8 +143,8 @@ dependencies:
specifier: 9.0.4
version: 9.0.4(@types/react@18.2.47)(react@18.2.0)(redux@5.0.1)
react-resizable-panels:
specifier: ^1.0.7
version: 1.0.7(react-dom@18.2.0)(react@18.2.0)
specifier: ^1.0.8
version: 1.0.8(react-dom@18.2.0)(react@18.2.0)
react-select:
specifier: 5.8.0
version: 5.8.0(@types/react@18.2.47)(react-dom@18.2.0)(react@18.2.0)
@@ -11638,8 +11638,8 @@ packages:
use-sidecar: 1.1.2(@types/react@18.2.47)(react@18.2.0)
dev: false
/react-resizable-panels@1.0.7(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-CluJkHQheeNqIJly2FYDfri3ME+2h2nCXpf0Y+hTO1K1eVtNxXFA5hVp5cUD6NS70iiufswOmnku9QZiLr1hYg==}
/react-resizable-panels@1.0.8(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-IuivK06FWN115VSN8TDGYuIoAzplC4oPUCDZ5d+VWJj0p6N3SMfwjggpjMUGSpQJLvMi0FXPSLLn4rGVmESjmA==}
peerDependencies:
react: ^16.14.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0

View File

@@ -116,7 +116,9 @@
"unsaved": "Non salvato",
"direction": "Direzione",
"advancedOptions": "Opzioni avanzate",
"free": "Libero"
"free": "Libero",
"or": "o",
"preferencesLabel": "Preferenze"
},
"gallery": {
"generations": "Generazioni",

View File

@@ -24,8 +24,8 @@ export const InvAutosizeTextarea = memo(
ref={ref}
overflow="scroll"
w="100%"
resize="none"
minRows={3}
minH={20}
onPaste={stopPastePropagation}
onKeyUp={onKeyUpDown}
onKeyDown={onKeyUpDown}

View File

@@ -23,6 +23,7 @@ export const InvTextarea = memo(
onPaste={stopPastePropagation}
onKeyUp={onKeyUpDown}
onKeyDown={onKeyUpDown}
minH={20}
{...rest}
/>
);

View File

@@ -0,0 +1,7 @@
export const isInputElement = (el: HTMLElement) => {
return (
el.tagName.toLowerCase() === 'input' ||
el.tagName.toLowerCase() === 'textarea' ||
el.tagName.toLowerCase() === 'select'
);
};

View File

@@ -1,4 +1,5 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { isInputElement } from 'common/util/isInputElement';
import {
$canvasStage,
$tool,
@@ -13,6 +14,7 @@ import {
setShouldShowBoundingBox,
setShouldSnapToGrid,
} from 'features/canvas/store/canvasSlice';
import { isElChildOfCanvasTab } from 'features/canvas/util/isElChildOfCanvasTab';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useCallback, useEffect } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
@@ -93,16 +95,13 @@ const useInpaintingCanvasHotkeys = () => {
[activeTabName, shouldShowBoundingBox]
);
useEffect(() => {
window.addEventListener('keydown', (e) => {
if (e.key === ' ' && !e.repeat) {
console.log('spaceeee');
}
});
}, []);
const onKeyDown = useCallback((e: KeyboardEvent) => {
if (e.repeat || e.key !== ' ') {
if (
e.repeat ||
e.key !== ' ' ||
isInputElement(e.target as HTMLElement) ||
!isElChildOfCanvasTab(e.target as HTMLElement)
) {
return;
}
if ($toolStash.get() || $tool.get() === 'move') {
@@ -114,7 +113,12 @@ const useInpaintingCanvasHotkeys = () => {
resetToolInteractionState();
}, []);
const onKeyUp = useCallback((e: KeyboardEvent) => {
if (e.repeat || e.key !== ' ') {
if (
e.repeat ||
e.key !== ' ' ||
isInputElement(e.target as HTMLElement) ||
!isElChildOfCanvasTab(e.target as HTMLElement)
) {
return;
}
if (!$toolStash.get() || $tool.get() !== 'move') {

View File

@@ -1,2 +1,3 @@
export const CANVAS_GRID_SIZE_COARSE = 64;
export const CANVAS_GRID_SIZE_FINE = 8;
export const CANVAS_TAB_TESTID = 'unified-canvas-tab';

View File

@@ -0,0 +1,14 @@
import { CANVAS_TAB_TESTID } from 'features/canvas/store/constants';
/**
* Determines if an element is a child of the canvas tab. Uses the canvas data-testid,
* actually checking against the *parent* of that element, which is the canvas's
* panel from `react-resizable-panels`. This panel element has dynamic children, so
* it's safer to check the canvas tab and grab its parent.
*/
export const isElChildOfCanvasTab = (el: HTMLElement) => {
const canvasContainerEl = document.querySelector(
`[data-testid="${CANVAS_TAB_TESTID}"]`
);
return Boolean(canvasContainerEl?.parentElement?.contains(el));
};

View File

@@ -3,14 +3,13 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
import type { ChangeEvent, KeyboardEvent } from 'react';
import { memo, useCallback, useEffect, useRef } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiXBold } from 'react-icons/pi';
const BoardsSearch = () => {
const dispatch = useAppDispatch();
const boardSearchText = useAppSelector((s) => s.gallery.boardSearchText);
const inputRef = useRef<HTMLInputElement>(null);
const { t } = useTranslation();
const handleBoardSearch = useCallback(
@@ -41,18 +40,9 @@ const BoardsSearch = () => {
[handleBoardSearch]
);
useEffect(() => {
// focus the search box on mount
if (!inputRef.current) {
return;
}
inputRef.current.focus();
}, []);
return (
<InputGroup>
<Input
ref={inputRef}
placeholder={t('boards.searchBoard')}
value={boardSearchText}
onKeyDown={handleKeydown}

View File

@@ -29,11 +29,13 @@ import { imagesSelectors } from 'services/api/util';
*/
const getImagesPerRow = (): number => {
const widthOfGalleryImage =
document?.querySelector(`[data-testid="${imageItemContainerTestId}"]`)
document
.querySelector(`[data-testid="${imageItemContainerTestId}"]`)
?.getBoundingClientRect().width ?? 1;
const widthOfGalleryGrid =
document?.querySelector(`[data-testid="${imageListContainerTestId}"]`)
document
.querySelector(`[data-testid="${imageListContainerTestId}"]`)
?.getBoundingClientRect().width ?? 0;
const imagesPerRow = Math.round(widthOfGalleryGrid / widthOfGalleryImage);
@@ -57,7 +59,7 @@ const scrollToImage = (imageName: string, index: number) => {
return;
}
const imageElement = document?.querySelector(
const imageElement = document.querySelector(
`[data-testid="${getGalleryImageDataTestId(imageName)}"]`
);
const itemRect = imageElement?.getBoundingClientRect();

View File

@@ -42,7 +42,13 @@ const StringFieldInputComponent = (
);
}
return <InvInput onChange={handleValueChanged} value={field.value} />;
return (
<InvInput
className="nodrag"
onChange={handleValueChanged}
value={field.value}
/>
);
};
export default memo(StringFieldInputComponent);

View File

@@ -1,6 +1,6 @@
import { Box } from '@chakra-ui/layout';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvAutosizeTextarea } from 'common/components/InvAutosizeTextarea/InvAutosizeTextarea';
import { InvTextarea } from 'common/components/InvTextarea/InvTextarea';
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
import { usePrompt } from 'features/embedding/usePrompt';
@@ -35,7 +35,7 @@ export const ParamNegativePrompt = memo(() => {
width={textareaRef.current?.clientWidth}
>
<Box pos="relative">
<InvAutosizeTextarea
<InvTextarea
id="negativePrompt"
name="negativePrompt"
ref={textareaRef}
@@ -43,10 +43,7 @@ export const ParamNegativePrompt = memo(() => {
placeholder={t('parameters.negativePromptPlaceholder')}
onChange={onChange}
onKeyDown={onKeyDown}
minH="unset"
fontSize="sm"
minRows={2}
maxRows={5}
variant="darkFilled"
/>
<PromptOverlayButtonWrapper>

View File

@@ -1,6 +1,6 @@
import { Box } from '@chakra-ui/layout';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvAutosizeTextarea } from 'common/components/InvAutosizeTextarea/InvAutosizeTextarea';
import { InvTextarea } from 'common/components/InvTextarea/InvTextarea';
import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton';
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
@@ -58,16 +58,15 @@ export const ParamPositivePrompt = memo(() => {
width={textareaRef.current?.clientWidth}
>
<Box pos="relative">
<InvAutosizeTextarea
<InvTextarea
id="prompt"
name="prompt"
ref={textareaRef}
value={prompt}
placeholder={t('parameters.positivePromptPlaceholder')}
onChange={onChange}
minH={28}
onKeyDown={onKeyDown}
minRows={4}
maxRows={7}
variant="darkFilled"
/>
<PromptOverlayButtonWrapper>

View File

@@ -1,6 +1,6 @@
import { Box } from '@chakra-ui/layout';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvAutosizeTextarea } from 'common/components/InvAutosizeTextarea/InvAutosizeTextarea';
import { InvTextarea } from 'common/components/InvTextarea/InvTextarea';
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
import { usePrompt } from 'features/embedding/usePrompt';
@@ -45,7 +45,7 @@ export const ParamSDXLNegativeStylePrompt = memo(() => {
width={textareaRef.current?.clientWidth}
>
<Box pos="relative">
<InvAutosizeTextarea
<InvTextarea
id="prompt"
name="prompt"
ref={textareaRef}
@@ -53,10 +53,7 @@ export const ParamSDXLNegativeStylePrompt = memo(() => {
placeholder={t('sdxl.negStylePrompt')}
onChange={onChange}
onKeyDown={onKeyDown}
minH="unset"
fontSize="sm"
minRows={2}
maxRows={5}
variant="darkFilled"
/>
<PromptOverlayButtonWrapper>

View File

@@ -1,6 +1,6 @@
import { Box } from '@chakra-ui/layout';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvAutosizeTextarea } from 'common/components/InvAutosizeTextarea/InvAutosizeTextarea';
import { InvTextarea } from 'common/components/InvTextarea/InvTextarea';
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
import { usePrompt } from 'features/embedding/usePrompt';
@@ -35,7 +35,7 @@ export const ParamSDXLPositiveStylePrompt = memo(() => {
width={textareaRef.current?.clientWidth}
>
<Box pos="relative">
<InvAutosizeTextarea
<InvTextarea
id="prompt"
name="prompt"
ref={textareaRef}
@@ -43,10 +43,7 @@ export const ParamSDXLPositiveStylePrompt = memo(() => {
placeholder={t('sdxl.posStylePrompt')}
onChange={onChange}
onKeyDown={onKeyDown}
minH="unset"
fontSize="sm"
minRows={2}
maxRows={5}
variant="darkFilled"
/>
<PromptOverlayButtonWrapper>

View File

@@ -2,6 +2,7 @@ import { Flex } from '@chakra-ui/react';
import IAIDropOverlay from 'common/components/IAIDropOverlay';
import IAICanvas from 'features/canvas/components/IAICanvas';
import IAICanvasToolbar from 'features/canvas/components/IAICanvasToolbar/IAICanvasToolbar';
import { CANVAS_TAB_TESTID } from 'features/canvas/store/constants';
import { useDroppableTypesafe } from 'features/dnd/hooks/typesafeHooks';
import type { CanvasInitialImageDropData } from 'features/dnd/types';
import { isValidDrop } from 'features/dnd/util/isValidDrop';
@@ -28,7 +29,6 @@ const UnifiedCanvasTab = () => {
<Flex
layerStyle="first"
ref={setDroppableRef}
tabIndex={-1}
flexDirection="column"
alignItems="center"
gap={4}
@@ -36,6 +36,7 @@ const UnifiedCanvasTab = () => {
borderRadius="base"
w="full"
h="full"
data-testid={CANVAS_TAB_TESTID}
>
<IAICanvasToolbar />
<IAICanvas />

View File

@@ -107,8 +107,11 @@ export const usePanel = (arg: UsePanelOptions): UsePanelReturn => {
return;
}
const id = arg.panelGroupRef.current.getId();
const panelGroupElement = getPanelGroupElement(id);
const panelGroupHandleElements = getResizeHandleElementsForGroup(id);
const panelGroupElement = getPanelGroupElement(id, document);
const panelGroupHandleElements = getResizeHandleElementsForGroup(
id,
document
);
if (!panelGroupElement) {
return;
}
@@ -244,7 +247,7 @@ const getSizeAsPercentage = (
return 0;
}
const id = panelGroupHandleRef.current.getId();
const panelGroupElement = getPanelGroupElement(id);
const panelGroupElement = getPanelGroupElement(id, document);
if (!panelGroupElement) {
// No panel group element, size is 0
return 0;
@@ -257,7 +260,7 @@ const getSizeAsPercentage = (
: panelGroupElement.offsetHeight;
// ...minus the width/height of the resize handles
getResizeHandleElementsForGroup(id).forEach((el) => {
getResizeHandleElementsForGroup(id, document).forEach((el) => {
availableSpace -=
panelGroupDirection === 'horizontal' ? el.offsetWidth : el.offsetHeight;
});