mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Compare commits
12 Commits
feat/workf
...
feat/ui/up
Author | SHA1 | Date | |
---|---|---|---|
76f56c00df | |||
e007dbf32f | |||
7e36343cf1 | |||
748f94e0e3 | |||
e990235d32 | |||
5f122186bd | |||
1ca0901cbe | |||
2d7555b7b8 | |||
3c7d1fcd32 | |||
c7fa2db556 | |||
bdb0d13a2d | |||
2d2ef5d72c |
@ -36,7 +36,9 @@ class WorkflowCategory(str, Enum, metaclass=MetaEnum):
|
||||
|
||||
class WorkflowMeta(BaseModel):
|
||||
version: str = Field(description="The version of the workflow schema.")
|
||||
category: WorkflowCategory = Field(description="The category of the workflow (user or default).")
|
||||
category: WorkflowCategory = Field(
|
||||
default=WorkflowCategory.User, description="The category of the workflow (user or default)."
|
||||
)
|
||||
|
||||
@field_validator("version")
|
||||
def validate_version(cls, version: str):
|
||||
|
@ -342,14 +342,13 @@ class InvokeAILogger(object): # noqa D102
|
||||
cls, name: str = "InvokeAI", config: InvokeAIAppConfig = InvokeAIAppConfig.get_config()
|
||||
) -> logging.Logger: # noqa D102
|
||||
if name in cls.loggers:
|
||||
logger = cls.loggers[name]
|
||||
logger.handlers.clear()
|
||||
else:
|
||||
logger = logging.getLogger(name)
|
||||
return cls.loggers[name]
|
||||
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(config.log_level.upper()) # yes, strings work here
|
||||
for ch in cls.get_loggers(config):
|
||||
logger.addHandler(ch)
|
||||
cls.loggers[name] = logger
|
||||
cls.loggers[name] = logger
|
||||
return cls.loggers[name]
|
||||
|
||||
@classmethod
|
||||
@ -358,7 +357,7 @@ class InvokeAILogger(object): # noqa D102
|
||||
handlers = []
|
||||
for handler in handler_strs:
|
||||
handler_name, *args = handler.split("=", 2)
|
||||
args = args[0] if len(args) > 0 else None
|
||||
arg = args[0] if len(args) > 0 else None
|
||||
|
||||
# console and file get the fancy formatter.
|
||||
# syslog gets a simple one
|
||||
@ -370,16 +369,16 @@ class InvokeAILogger(object): # noqa D102
|
||||
handlers.append(ch)
|
||||
|
||||
elif handler_name == "syslog":
|
||||
ch = cls._parse_syslog_args(args)
|
||||
ch = cls._parse_syslog_args(arg)
|
||||
handlers.append(ch)
|
||||
|
||||
elif handler_name == "file":
|
||||
ch = cls._parse_file_args(args)
|
||||
ch = cls._parse_file_args(arg)
|
||||
ch.setFormatter(formatter())
|
||||
handlers.append(ch)
|
||||
|
||||
elif handler_name == "http":
|
||||
ch = cls._parse_http_args(args)
|
||||
ch = cls._parse_http_args(arg)
|
||||
handlers.append(ch)
|
||||
return handlers
|
||||
|
||||
|
@ -94,7 +94,7 @@
|
||||
"react-icons": "^4.11.0",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-redux": "^8.1.3",
|
||||
"react-resizable-panels": "^0.0.55",
|
||||
"react-resizable-panels": "^0.0.63",
|
||||
"react-use": "^17.4.0",
|
||||
"react-virtuoso": "^4.6.2",
|
||||
"reactflow": "^11.9.4",
|
||||
|
@ -72,5 +72,13 @@
|
||||
},
|
||||
"unifiedCanvas": {
|
||||
"betaPreserveMasked": "마스크 레이어 유지"
|
||||
},
|
||||
"accessibility": {
|
||||
"previousImage": "이전 이미지",
|
||||
"modifyConfig": "Config 수정",
|
||||
"nextImage": "다음 이미지",
|
||||
"mode": "모드",
|
||||
"menu": "메뉴",
|
||||
"modelSelect": "모델 선택"
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,17 @@
|
||||
"data": "数据",
|
||||
"safetensors": "Safetensors",
|
||||
"outpaint": "外扩绘制",
|
||||
"details": "详情"
|
||||
"details": "详情",
|
||||
"format": "格式",
|
||||
"unknown": "未知",
|
||||
"folder": "文件夹",
|
||||
"error": "错误",
|
||||
"installed": "已安装",
|
||||
"file": "文件",
|
||||
"somethingWentWrong": "出了点问题",
|
||||
"copyError": "$t(gallery.copy) 错误",
|
||||
"input": "输入",
|
||||
"notInstalled": "非 $t(common.installed)"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "生成的图像",
|
||||
@ -130,7 +140,12 @@
|
||||
"preparingDownload": "准备下载",
|
||||
"preparingDownloadFailed": "准备下载时出现问题",
|
||||
"downloadSelection": "下载所选内容",
|
||||
"noImageSelected": "无选中的图像"
|
||||
"noImageSelected": "无选中的图像",
|
||||
"deleteSelection": "删除所选内容",
|
||||
"image": "图像",
|
||||
"drop": "弃用",
|
||||
"dropOrUpload": "$t(gallery.drop) 或上传",
|
||||
"dropToUpload": "$t(gallery.drop) 以上传"
|
||||
},
|
||||
"hotkeys": {
|
||||
"keyboardShortcuts": "键盘快捷键",
|
||||
@ -486,7 +501,8 @@
|
||||
"alpha": "Alpha",
|
||||
"vaePrecision": "VAE 精度",
|
||||
"checkpointOrSafetensors": "$t(common.checkpoint) / $t(common.safetensors)",
|
||||
"noModelSelected": "无选中的模型"
|
||||
"noModelSelected": "无选中的模型",
|
||||
"conversionNotSupported": "转换尚未支持"
|
||||
},
|
||||
"parameters": {
|
||||
"images": "图像",
|
||||
@ -615,7 +631,10 @@
|
||||
"seamlessX": "无缝 X",
|
||||
"seamlessY": "无缝 Y",
|
||||
"maskEdge": "遮罩边缘",
|
||||
"unmasked": "取消遮罩"
|
||||
"unmasked": "取消遮罩",
|
||||
"cfgRescaleMultiplier": "CFG 重缩放倍数",
|
||||
"cfgRescale": "CFG 重缩放",
|
||||
"useSize": "使用尺寸"
|
||||
},
|
||||
"settings": {
|
||||
"models": "模型",
|
||||
@ -655,7 +674,8 @@
|
||||
"clearIntermediatesDisabled": "队列为空才能清理中间产物",
|
||||
"enableNSFWChecker": "启用成人内容检测器",
|
||||
"enableInvisibleWatermark": "启用不可见水印",
|
||||
"enableInformationalPopovers": "启用信息弹窗"
|
||||
"enableInformationalPopovers": "启用信息弹窗",
|
||||
"reloadingIn": "重新加载中"
|
||||
},
|
||||
"toast": {
|
||||
"tempFoldersEmptied": "临时文件夹已清空",
|
||||
@ -739,7 +759,8 @@
|
||||
"imageUploadFailed": "图像上传失败",
|
||||
"problemImportingMask": "导入遮罩时出现问题",
|
||||
"baseModelChangedCleared_other": "基础模型已更改, 已清除或禁用 {{count}} 个不兼容的子模型",
|
||||
"setAsCanvasInitialImage": "设为画布初始图像"
|
||||
"setAsCanvasInitialImage": "设为画布初始图像",
|
||||
"invalidUpload": "无效的上传"
|
||||
},
|
||||
"unifiedCanvas": {
|
||||
"layer": "图层",
|
||||
@ -748,7 +769,7 @@
|
||||
"maskingOptions": "遮罩选项",
|
||||
"enableMask": "启用遮罩",
|
||||
"preserveMaskedArea": "保留遮罩区域",
|
||||
"clearMask": "清除遮罩",
|
||||
"clearMask": "清除遮罩 (Shift+C)",
|
||||
"brush": "刷子",
|
||||
"eraser": "橡皮擦",
|
||||
"fillBoundingBox": "填充选择区域",
|
||||
@ -801,7 +822,8 @@
|
||||
"betaPreserveMasked": "保留遮罩层",
|
||||
"antialiasing": "抗锯齿",
|
||||
"showResultsOn": "显示结果 (开)",
|
||||
"showResultsOff": "显示结果 (关)"
|
||||
"showResultsOff": "显示结果 (关)",
|
||||
"saveMask": "保存 $t(unifiedCanvas.mask)"
|
||||
},
|
||||
"accessibility": {
|
||||
"modelSelect": "模型选择",
|
||||
@ -826,7 +848,9 @@
|
||||
"menu": "菜单",
|
||||
"showGalleryPanel": "显示图库浮窗",
|
||||
"loadMore": "加载更多",
|
||||
"mode": "模式"
|
||||
"mode": "模式",
|
||||
"resetUI": "$t(accessibility.reset) UI",
|
||||
"createIssue": "创建问题"
|
||||
},
|
||||
"ui": {
|
||||
"showProgressImages": "显示处理中的图片",
|
||||
@ -877,7 +901,7 @@
|
||||
"animatedEdges": "边缘动效",
|
||||
"nodeTemplate": "节点模板",
|
||||
"pickOne": "选择一个",
|
||||
"unableToLoadWorkflow": "无法验证工作流",
|
||||
"unableToLoadWorkflow": "无法加载工作流",
|
||||
"snapToGrid": "对齐网格",
|
||||
"noFieldsLinearview": "线性视图中未添加任何字段",
|
||||
"nodeSearch": "检索节点",
|
||||
@ -929,7 +953,7 @@
|
||||
"skippingUnknownOutputType": "跳过未知类型的输出",
|
||||
"latentsFieldDescription": "Latents 可以在节点间传递。",
|
||||
"denoiseMaskFieldDescription": "去噪遮罩可以在节点间传递",
|
||||
"missingTemplate": "缺失模板",
|
||||
"missingTemplate": "无效的节点:类型为 {{type}} 的节点 {{node}} 缺失模板(无已安装模板?)",
|
||||
"outputSchemaNotFound": "未找到输出模式",
|
||||
"latentsPolymorphicDescription": "Latents 可以在节点间传递。",
|
||||
"colorFieldDescription": "一种 RGBA 颜色。",
|
||||
@ -957,7 +981,7 @@
|
||||
"collectionItem": "项目合集",
|
||||
"controlCollectionDescription": "节点间传递的控制信息。",
|
||||
"skippedReservedInput": "跳过保留的输入",
|
||||
"outputFields": "输出",
|
||||
"outputFields": "输出区域",
|
||||
"edge": "边缘",
|
||||
"inputNode": "输入节点",
|
||||
"enumDescription": "枚举 (Enums) 可能是多个选项的一个数值。",
|
||||
@ -992,7 +1016,7 @@
|
||||
"string": "字符串",
|
||||
"inputFields": "输入",
|
||||
"uNetFieldDescription": "UNet 子模型。",
|
||||
"mismatchedVersion": "不匹配的版本",
|
||||
"mismatchedVersion": "无效的节点:类型为 {{type}} 的节点 {{node}} 版本不匹配(是否尝试更新?)",
|
||||
"vaeFieldDescription": "Vae 子模型。",
|
||||
"imageFieldDescription": "图像可以在节点间传递。",
|
||||
"outputNode": "输出节点",
|
||||
@ -1050,8 +1074,36 @@
|
||||
"latentsPolymorphic": "Latents 多态",
|
||||
"conditioningField": "条件",
|
||||
"latentsField": "Latents",
|
||||
"updateAllNodes": "更新所有节点",
|
||||
"unableToUpdateNodes_other": "{{count}} 个节点无法完成更新"
|
||||
"updateAllNodes": "更新节点",
|
||||
"unableToUpdateNodes_other": "{{count}} 个节点无法完成更新",
|
||||
"inputFieldTypeParseError": "无法解析 {{node}} 的输入类型 {{field}}。({{message}})",
|
||||
"unsupportedArrayItemType": "不支持的数组类型 \"{{type}}\"",
|
||||
"addLinearView": "添加到线性视图",
|
||||
"targetNodeFieldDoesNotExist": "无效的边缘:{{node}} 的目标/输入区域 {{field}} 不存在",
|
||||
"unsupportedMismatchedUnion": "合集或标量类型与基类 {{firstType}} 和 {{secondType}} 不匹配",
|
||||
"allNodesUpdated": "已更新所有节点",
|
||||
"sourceNodeDoesNotExist": "无效的边缘:{{node}} 的源/输出节点不存在",
|
||||
"unableToExtractEnumOptions": "无法提取枚举选项",
|
||||
"unableToParseFieldType": "无法解析类型",
|
||||
"outputFieldInInput": "输入中的输出区域",
|
||||
"unrecognizedWorkflowVersion": "无法识别的工作流架构版本:{{version}}",
|
||||
"outputFieldTypeParseError": "无法解析 {{node}} 的输出类型 {{field}}。({{message}})",
|
||||
"sourceNodeFieldDoesNotExist": "无效的边缘:{{node}} 的源/输出区域 {{field}} 不存在",
|
||||
"unableToGetWorkflowVersion": "无法获取工作流架构版本",
|
||||
"nodePack": "节点包",
|
||||
"unableToExtractSchemaNameFromRef": "无法从参考中提取架构名",
|
||||
"unableToMigrateWorkflow": "无法迁移工作流",
|
||||
"unknownOutput": "未知输出:{{name}}",
|
||||
"unableToUpdateNode": "无法更新节点",
|
||||
"unknownErrorValidatingWorkflow": "验证工作流时出现未知错误",
|
||||
"collectionFieldType": "{{name}} 合集",
|
||||
"unknownNodeType": "未知节点类型",
|
||||
"targetNodeDoesNotExist": "无效的边缘:{{node}} 的目标/输入节点不存在",
|
||||
"unknownFieldType": "$t(nodes.unknownField) 类型:{{type}}",
|
||||
"collectionOrScalarFieldType": "{{name}} 合集 | 标量",
|
||||
"nodeVersion": "节点版本",
|
||||
"deletedInvalidEdge": "已删除无效的边缘 {{source}} -> {{target}}",
|
||||
"unknownInput": "未知输入:{{name}}"
|
||||
},
|
||||
"controlnet": {
|
||||
"resize": "直接缩放",
|
||||
@ -1245,7 +1297,8 @@
|
||||
"fit": "图生图匹配",
|
||||
"recallParameters": "召回参数",
|
||||
"noRecallParameters": "未找到要召回的参数",
|
||||
"vae": "VAE"
|
||||
"vae": "VAE",
|
||||
"cfgRescaleMultiplier": "$t(parameters.cfgRescaleMultiplier)"
|
||||
},
|
||||
"models": {
|
||||
"noMatchingModels": "无相匹配的模型",
|
||||
@ -1258,7 +1311,8 @@
|
||||
"noRefinerModelsInstalled": "无已安装的 SDXL Refiner 模型",
|
||||
"noLoRAsInstalled": "无已安装的 LoRA",
|
||||
"esrganModel": "ESRGAN 模型",
|
||||
"addLora": "添加 LoRA"
|
||||
"addLora": "添加 LoRA",
|
||||
"noLoRAsLoaded": "无已加载的 LoRA"
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "自动添加面板",
|
||||
@ -1280,12 +1334,14 @@
|
||||
"deleteBoardOnly": "仅删除面板",
|
||||
"deleteBoard": "删除面板",
|
||||
"deleteBoardAndImages": "删除面板和图像",
|
||||
"deletedBoardsCannotbeRestored": "已删除的面板无法被恢复"
|
||||
"deletedBoardsCannotbeRestored": "已删除的面板无法被恢复",
|
||||
"movingImagesToBoard_other": "移动 {{count}} 张图像到面板:"
|
||||
},
|
||||
"embedding": {
|
||||
"noMatchingEmbedding": "不匹配的 Embedding",
|
||||
"addEmbedding": "添加 Embedding",
|
||||
"incompatibleModel": "不兼容的基础模型:"
|
||||
"incompatibleModel": "不兼容的基础模型:",
|
||||
"noEmbeddingsLoaded": "无已加载的 Embedding"
|
||||
},
|
||||
"dynamicPrompts": {
|
||||
"seedBehaviour": {
|
||||
@ -1514,6 +1570,12 @@
|
||||
"ControlNet 为生成过程提供引导,为生成具有受控构图、结构、样式的图像提供帮助,具体的功能由所选的模型决定。"
|
||||
],
|
||||
"heading": "ControlNet"
|
||||
},
|
||||
"paramCFGRescaleMultiplier": {
|
||||
"heading": "CFG 重缩放倍数",
|
||||
"paragraphs": [
|
||||
"CFG 引导的重缩放倍率,用于通过 zero-terminal SNR (ztsnr) 训练的模型。推荐设为 0.7。"
|
||||
]
|
||||
}
|
||||
},
|
||||
"invocationCache": {
|
||||
@ -1530,7 +1592,8 @@
|
||||
"enable": "启用",
|
||||
"clear": "清除",
|
||||
"maxCacheSize": "最大缓存大小",
|
||||
"cacheSize": "缓存大小"
|
||||
"cacheSize": "缓存大小",
|
||||
"useCache": "使用缓存"
|
||||
},
|
||||
"hrf": {
|
||||
"enableHrf": "启用高分辨率修复",
|
||||
|
@ -23,7 +23,8 @@ export type AppFeature =
|
||||
| 'resumeQueue'
|
||||
| 'prependQueue'
|
||||
| 'invocationCache'
|
||||
| 'bulkDownload';
|
||||
| 'bulkDownload'
|
||||
| 'workflowLibrary';
|
||||
|
||||
/**
|
||||
* A disable-able Stable Diffusion feature
|
||||
|
@ -5,8 +5,12 @@ import UploadWorkflowButton from 'features/workflowLibrary/components/LoadWorkfl
|
||||
import ResetWorkflowEditorButton from 'features/workflowLibrary/components/ResetWorkflowButton';
|
||||
import SaveWorkflowButton from 'features/workflowLibrary/components/SaveWorkflowButton';
|
||||
import SaveWorkflowAsButton from 'features/workflowLibrary/components/SaveWorkflowAsButton';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
|
||||
const TopCenterPanel = () => {
|
||||
const isWorkflowLibraryEnabled =
|
||||
useFeatureStatus('workflowLibrary').isFeatureEnabled;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
@ -19,8 +23,12 @@ const TopCenterPanel = () => {
|
||||
>
|
||||
<DownloadWorkflowButton />
|
||||
<UploadWorkflowButton />
|
||||
<SaveWorkflowButton />
|
||||
<SaveWorkflowAsButton />
|
||||
{isWorkflowLibraryEnabled && (
|
||||
<>
|
||||
<SaveWorkflowButton />
|
||||
<SaveWorkflowAsButton />
|
||||
</>
|
||||
)}
|
||||
<ResetWorkflowEditorButton />
|
||||
</Flex>
|
||||
);
|
||||
|
@ -2,11 +2,15 @@ import { Flex } from '@chakra-ui/react';
|
||||
import WorkflowLibraryButton from 'features/workflowLibrary/components/WorkflowLibraryButton';
|
||||
import { memo } from 'react';
|
||||
import WorkflowEditorSettings from './WorkflowEditorSettings';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
|
||||
const TopRightPanel = () => {
|
||||
const isWorkflowLibraryEnabled =
|
||||
useFeatureStatus('workflowLibrary').isFeatureEnabled;
|
||||
|
||||
return (
|
||||
<Flex sx={{ gap: 2, position: 'absolute', top: 2, insetInlineEnd: 2 }}>
|
||||
<WorkflowLibraryButton />
|
||||
{isWorkflowLibraryEnabled && <WorkflowLibraryButton />}
|
||||
<WorkflowEditorSettings />
|
||||
</Flex>
|
||||
);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations';
|
||||
import QueueControls from 'features/queue/components/QueueControls';
|
||||
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
||||
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
||||
@ -11,7 +12,6 @@ import {
|
||||
import 'reactflow/dist/style.css';
|
||||
import InspectorPanel from './inspector/InspectorPanel';
|
||||
import WorkflowPanel from './workflow/WorkflowPanel';
|
||||
import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations';
|
||||
|
||||
const NodeEditorPanelGroup = () => {
|
||||
const [isTopPanelCollapsed, setIsTopPanelCollapsed] = useState(false);
|
||||
@ -22,7 +22,23 @@ const NodeEditorPanelGroup = () => {
|
||||
if (!panelGroupRef.current) {
|
||||
return;
|
||||
}
|
||||
panelGroupRef.current.setLayout([50, 50]);
|
||||
panelGroupRef.current.setLayout([
|
||||
{ sizePercentage: 50 },
|
||||
{ sizePercentage: 50 },
|
||||
]);
|
||||
}, []);
|
||||
|
||||
const onCollapseTopPanel = useCallback(() => {
|
||||
setIsTopPanelCollapsed(true);
|
||||
}, []);
|
||||
const onExpandTopPanel = useCallback(() => {
|
||||
setIsTopPanelCollapsed(false);
|
||||
}, []);
|
||||
const onCollapseBottomPanel = useCallback(() => {
|
||||
setIsBottomPanelCollapsed(true);
|
||||
}, []);
|
||||
const onExpandBottomPanel = useCallback(() => {
|
||||
setIsBottomPanelCollapsed(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@ -53,8 +69,9 @@ const NodeEditorPanelGroup = () => {
|
||||
<Panel
|
||||
id="workflow"
|
||||
collapsible
|
||||
onCollapse={setIsTopPanelCollapsed}
|
||||
minSize={25}
|
||||
onCollapse={onCollapseTopPanel}
|
||||
onExpand={onExpandTopPanel}
|
||||
minSizePercentage={25}
|
||||
>
|
||||
<WorkflowPanel />
|
||||
</Panel>
|
||||
@ -72,8 +89,9 @@ const NodeEditorPanelGroup = () => {
|
||||
<Panel
|
||||
id="inspector"
|
||||
collapsible
|
||||
onCollapse={setIsBottomPanelCollapsed}
|
||||
minSize={25}
|
||||
onCollapse={onCollapseBottomPanel}
|
||||
onExpand={onExpandBottomPanel}
|
||||
minSizePercentage={25}
|
||||
>
|
||||
<InspectorPanel />
|
||||
</Panel>
|
||||
|
@ -14,7 +14,13 @@ import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent';
|
||||
import NodeEditorPanelGroup from 'features/nodes/components/sidePanel/NodeEditorPanelGroup';
|
||||
import { usePanel } from 'features/ui/hooks/usePanel';
|
||||
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
||||
import { InvokeTabName } from 'features/ui/store/tabMap';
|
||||
import {
|
||||
activeTabIndexSelector,
|
||||
activeTabNameSelector,
|
||||
} from 'features/ui/store/uiSelectors';
|
||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||
import { ResourceKey } from 'i18next';
|
||||
import { isEqual } from 'lodash-es';
|
||||
@ -25,12 +31,6 @@ import { FaCube, FaFont, FaImage, FaStream } from 'react-icons/fa';
|
||||
import { FaCircleNodes } from 'react-icons/fa6';
|
||||
import { MdGridOn } from 'react-icons/md';
|
||||
import { Panel, PanelGroup } from 'react-resizable-panels';
|
||||
import { usePanel } from 'features/ui/hooks/usePanel';
|
||||
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
||||
import {
|
||||
activeTabIndexSelector,
|
||||
activeTabNameSelector,
|
||||
} from 'features/ui/store/uiSelectors';
|
||||
import FloatingGalleryButton from './FloatingGalleryButton';
|
||||
import FloatingSidePanelButtons from './FloatingParametersPanelButtons';
|
||||
import ParametersPanel from './ParametersPanel';
|
||||
@ -160,31 +160,28 @@ const InvokeTabs = () => {
|
||||
);
|
||||
|
||||
const {
|
||||
minSize: sidePanelMinSize,
|
||||
isCollapsed: isSidePanelCollapsed,
|
||||
setIsCollapsed: setIsSidePanelCollapsed,
|
||||
ref: sidePanelRef,
|
||||
reset: resetSidePanel,
|
||||
expand: expandSidePanel,
|
||||
collapse: collapseSidePanel,
|
||||
toggle: toggleSidePanel,
|
||||
} = usePanel(SIDE_PANEL_MIN_SIZE_PX, 'pixels');
|
||||
} = usePanel({ sizePixels: SIDE_PANEL_MIN_SIZE_PX });
|
||||
|
||||
const {
|
||||
ref: galleryPanelRef,
|
||||
minSize: galleryPanelMinSize,
|
||||
isCollapsed: isGalleryPanelCollapsed,
|
||||
setIsCollapsed: setIsGalleryPanelCollapsed,
|
||||
reset: resetGalleryPanel,
|
||||
expand: expandGalleryPanel,
|
||||
collapse: collapseGalleryPanel,
|
||||
toggle: toggleGalleryPanel,
|
||||
} = usePanel(GALLERY_PANEL_MIN_SIZE_PX, 'pixels');
|
||||
} = usePanel({ sizePixels: GALLERY_PANEL_MIN_SIZE_PX });
|
||||
|
||||
useHotkeys(
|
||||
'f',
|
||||
() => {
|
||||
if (isGalleryPanelCollapsed || isSidePanelCollapsed) {
|
||||
if (
|
||||
sidePanelRef.current?.isCollapsed() ||
|
||||
galleryPanelRef.current?.isCollapsed()
|
||||
) {
|
||||
expandGalleryPanel();
|
||||
expandSidePanel();
|
||||
} else {
|
||||
@ -192,7 +189,7 @@ const InvokeTabs = () => {
|
||||
collapseGalleryPanel();
|
||||
}
|
||||
},
|
||||
[dispatch, isGalleryPanelCollapsed, isSidePanelCollapsed]
|
||||
[dispatch, sidePanelRef, galleryPanelRef]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
@ -213,6 +210,22 @@ const InvokeTabs = () => {
|
||||
|
||||
const panelStorage = usePanelStorage();
|
||||
|
||||
console.log({
|
||||
sidePanelRef,
|
||||
resetSidePanel,
|
||||
expandSidePanel,
|
||||
collapseSidePanel,
|
||||
toggleSidePanel,
|
||||
});
|
||||
|
||||
console.log({
|
||||
galleryPanelRef,
|
||||
resetGalleryPanel,
|
||||
expandGalleryPanel,
|
||||
collapseGalleryPanel,
|
||||
toggleGalleryPanel,
|
||||
});
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
variant="appTabs"
|
||||
@ -241,7 +254,6 @@ const InvokeTabs = () => {
|
||||
direction="horizontal"
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
storage={panelStorage}
|
||||
units="pixels"
|
||||
>
|
||||
{!NO_SIDE_PANEL_TABS.includes(activeTabName) && (
|
||||
<>
|
||||
@ -249,9 +261,8 @@ const InvokeTabs = () => {
|
||||
order={0}
|
||||
id="side"
|
||||
ref={sidePanelRef}
|
||||
defaultSize={sidePanelMinSize}
|
||||
minSize={sidePanelMinSize}
|
||||
onCollapse={setIsSidePanelCollapsed}
|
||||
defaultSizePixels={SIDE_PANEL_MIN_SIZE_PX}
|
||||
minSizePixels={SIDE_PANEL_MIN_SIZE_PX}
|
||||
collapsible
|
||||
>
|
||||
{activeTabName === 'nodes' ? (
|
||||
@ -262,15 +273,19 @@ const InvokeTabs = () => {
|
||||
</Panel>
|
||||
<ResizeHandle
|
||||
onDoubleClick={resetSidePanel}
|
||||
collapsedDirection={isSidePanelCollapsed ? 'left' : undefined}
|
||||
collapsedDirection={
|
||||
sidePanelRef.current?.isCollapsed() ? 'left' : undefined
|
||||
}
|
||||
/>
|
||||
<FloatingSidePanelButtons
|
||||
isSidePanelCollapsed={isSidePanelCollapsed}
|
||||
isSidePanelCollapsed={Boolean(
|
||||
sidePanelRef.current?.isCollapsed()
|
||||
)}
|
||||
sidePanelRef={sidePanelRef}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Panel id="main" order={1} minSize={MAIN_PANEL_MIN_SIZE_PX}>
|
||||
<Panel id="main" order={1} minSizePixels={MAIN_PANEL_MIN_SIZE_PX}>
|
||||
<TabPanels style={{ height: '100%', width: '100%' }}>
|
||||
{tabPanels}
|
||||
</TabPanels>
|
||||
@ -279,21 +294,24 @@ const InvokeTabs = () => {
|
||||
<>
|
||||
<ResizeHandle
|
||||
onDoubleClick={resetGalleryPanel}
|
||||
collapsedDirection={isGalleryPanelCollapsed ? 'right' : undefined}
|
||||
collapsedDirection={
|
||||
galleryPanelRef.current?.isCollapsed() ? 'right' : undefined
|
||||
}
|
||||
/>
|
||||
<Panel
|
||||
id="gallery"
|
||||
ref={galleryPanelRef}
|
||||
order={2}
|
||||
defaultSize={galleryPanelMinSize}
|
||||
minSize={galleryPanelMinSize}
|
||||
onCollapse={setIsGalleryPanelCollapsed}
|
||||
defaultSizePixels={GALLERY_PANEL_MIN_SIZE_PX}
|
||||
minSizePixels={GALLERY_PANEL_MIN_SIZE_PX}
|
||||
collapsible
|
||||
>
|
||||
<ImageGalleryContent />
|
||||
</Panel>
|
||||
<FloatingGalleryButton
|
||||
isGalleryCollapsed={isGalleryPanelCollapsed}
|
||||
isGalleryCollapsed={Boolean(
|
||||
galleryPanelRef.current?.isCollapsed()
|
||||
)}
|
||||
galleryPanelRef={galleryPanelRef}
|
||||
/>
|
||||
</>
|
||||
|
@ -17,7 +17,10 @@ const ImageToImageTab = () => {
|
||||
if (!panelGroupRef.current) {
|
||||
return;
|
||||
}
|
||||
panelGroupRef.current.setLayout([50, 50]);
|
||||
panelGroupRef.current.setLayout([
|
||||
{ sizePercentage: 50 },
|
||||
{ sizePercentage: 50 },
|
||||
]);
|
||||
}, []);
|
||||
|
||||
const panelStorage = usePanelStorage();
|
||||
@ -30,13 +33,12 @@ const ImageToImageTab = () => {
|
||||
direction="horizontal"
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
storage={panelStorage}
|
||||
units="percentages"
|
||||
>
|
||||
<Panel
|
||||
id="imageTab.content.initImage"
|
||||
order={0}
|
||||
defaultSize={50}
|
||||
minSize={25}
|
||||
defaultSizePercentage={50}
|
||||
minSizePercentage={25}
|
||||
style={{ position: 'relative' }}
|
||||
>
|
||||
<InitialImageDisplay />
|
||||
@ -45,8 +47,8 @@ const ImageToImageTab = () => {
|
||||
<Panel
|
||||
id="imageTab.content.selectedImage"
|
||||
order={1}
|
||||
defaultSize={50}
|
||||
minSize={25}
|
||||
defaultSizePercentage={50}
|
||||
minSizePercentage={25}
|
||||
>
|
||||
<TextToImageTabMain />
|
||||
</Panel>
|
||||
|
@ -1,16 +1,12 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { ImperativePanelHandle, Units } from 'react-resizable-panels';
|
||||
import { ImperativePanelHandle, MixedSizes } from 'react-resizable-panels';
|
||||
|
||||
export const usePanel = (minSize: number, units: Units) => {
|
||||
export const usePanel = (minSize: Partial<MixedSizes>) => {
|
||||
const ref = useRef<ImperativePanelHandle>(null);
|
||||
|
||||
const [isCollapsed, setIsCollapsed] = useState(() =>
|
||||
Boolean(ref.current?.getCollapsed())
|
||||
);
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
if (ref.current?.getCollapsed()) {
|
||||
if (ref.current?.isCollapsed()) {
|
||||
flushSync(() => {
|
||||
ref.current?.expand();
|
||||
});
|
||||
@ -35,15 +31,12 @@ export const usePanel = (minSize: number, units: Units) => {
|
||||
|
||||
const reset = useCallback(() => {
|
||||
flushSync(() => {
|
||||
ref.current?.resize(minSize, units);
|
||||
ref.current?.resize(minSize);
|
||||
});
|
||||
}, [minSize, units]);
|
||||
}, [minSize]);
|
||||
|
||||
return {
|
||||
ref,
|
||||
minSize,
|
||||
isCollapsed,
|
||||
setIsCollapsed,
|
||||
reset,
|
||||
toggle,
|
||||
expand,
|
||||
|
@ -5496,10 +5496,10 @@ react-remove-scroll@^2.5.5, react-remove-scroll@^2.5.6:
|
||||
use-callback-ref "^1.3.0"
|
||||
use-sidecar "^1.1.2"
|
||||
|
||||
react-resizable-panels@^0.0.55:
|
||||
version "0.0.55"
|
||||
resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-0.0.55.tgz#adf06d35ae09748ab7051a4bd2c5be8087ef1a66"
|
||||
integrity sha512-J/LTFzUEjJiqwSjVh8gjUXkQDA8MRPjARASfn++d2+KOgA+9UcRYUfE3QBJixer2vkk+ffQ4cq3QzWzzHgqYpQ==
|
||||
react-resizable-panels@^0.0.63:
|
||||
version "0.0.63"
|
||||
resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-0.0.63.tgz#d364d84ee5927bfb0e56c7ea75eb6504e9041a21"
|
||||
integrity sha512-AfA8b6kouhL4rBvgUGs17uzWVlYPaJIwwTCVeWRxNpUHJlCG1h9RIMlzA1849AZGsaNJO3j/SNdI5SS4GZDE3g==
|
||||
|
||||
react-style-singleton@^2.2.1:
|
||||
version "2.2.1"
|
||||
|
57
tests/backend/util/test_logging.py
Normal file
57
tests/backend/util/test_logging.py
Normal file
@ -0,0 +1,57 @@
|
||||
"""
|
||||
Test interaction of logging with configuration system.
|
||||
"""
|
||||
import io
|
||||
import logging
|
||||
import re
|
||||
|
||||
from invokeai.app.services.config import InvokeAIAppConfig
|
||||
from invokeai.backend.util.logging import LOG_FORMATTERS, InvokeAILogger
|
||||
|
||||
|
||||
# test formatting
|
||||
# Would prefer to use the capfd/capsys fixture here, but it is broken
|
||||
# when used with the logging module: https://github.com/pytest-dev/pytest/issue
|
||||
def test_formatting():
|
||||
logger = InvokeAILogger.get_logger()
|
||||
stream = io.StringIO()
|
||||
handler = logging.StreamHandler(stream)
|
||||
handler.setFormatter(LOG_FORMATTERS["plain"]())
|
||||
logger.addHandler(handler)
|
||||
logger.info("test1")
|
||||
output = stream.getvalue()
|
||||
assert re.search(r"\[InvokeAI\]::INFO --> test1$", output)
|
||||
|
||||
handler.setFormatter(LOG_FORMATTERS["legacy"]())
|
||||
logger.info("test2")
|
||||
output = stream.getvalue()
|
||||
assert re.search(r">> test2$", output)
|
||||
|
||||
|
||||
# test independence of two loggers with different names
|
||||
def test_independence():
|
||||
logger1 = InvokeAILogger.get_logger()
|
||||
logger2 = InvokeAILogger.get_logger("Test")
|
||||
assert logger1.name == "InvokeAI"
|
||||
assert logger2.name == "Test"
|
||||
assert logger1.level == logging.INFO
|
||||
assert logger2.level == logging.INFO
|
||||
logger2.setLevel(logging.DEBUG)
|
||||
assert logger1.level == logging.INFO
|
||||
assert logger2.level == logging.DEBUG
|
||||
|
||||
|
||||
# test that the logger is returned from two similar get_logger() calls
|
||||
def test_retrieval():
|
||||
logger1 = InvokeAILogger.get_logger()
|
||||
logger2 = InvokeAILogger.get_logger()
|
||||
logger3 = InvokeAILogger.get_logger("Test")
|
||||
assert logger1 == logger2
|
||||
assert logger1 != logger3
|
||||
|
||||
|
||||
# test that the configuration is used to set the initial logging level
|
||||
def test_config():
|
||||
config = InvokeAIAppConfig(log_level="debug")
|
||||
logger1 = InvokeAILogger.get_logger("DebugTest", config=config)
|
||||
assert logger1.level == logging.DEBUG
|
Reference in New Issue
Block a user