Commit cabd4e3b authored by Muhammad Rizqi Nur's avatar Muhammad Rizqi Nur

Merge branch 'master' into gradient-clipping

parents bb832d77 804d9fb8
* @AUTOMATIC1111 * @AUTOMATIC1111
/localizations/ar_AR.json @xmodar @blackneoo
/localizations/de_DE.json @LunixWasTaken # if you were managing a localization and were removed from this file, this is because
/localizations/es_ES.json @innovaciones # the intended way to do localizations now is via extensions. See:
/localizations/fr_FR.json @tumbly # https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Developing-extensions
/localizations/it_IT.json @EugenioBuffo # Make a repo with your localization and since you are still listed as a collaborator
/localizations/ja_JP.json @yuuki76 # you can add it to the wiki page yourself. This change is because some people complained
/localizations/ko_KR.json @36DB # the git commit log is cluttered with things unrelated to almost everyone and
/localizations/pt_BR.json @M-art-ucci # because I believe this is the best overall for the project to handle localizations almost
/localizations/ru_RU.json @kabachuha # entirely without my oversight.
/localizations/tr_TR.json @camenduru
/localizations/zh_CN.json @dtlnor @bgluminous
/localizations/zh_TW.json @benlisquare
...@@ -208,4 +208,6 @@ function update_token_counter(button_id) { ...@@ -208,4 +208,6 @@ function update_token_counter(button_id) {
function restart_reload(){ function restart_reload(){
document.body.innerHTML='<h1 style="font-family:monospace;margin-top:20%;color:lightgray;text-align:center;">Reloading...</h1>'; document.body.innerHTML='<h1 style="font-family:monospace;margin-top:20%;color:lightgray;text-align:center;">Reloading...</h1>';
setTimeout(function(){location.reload()},2000) setTimeout(function(){location.reload()},2000)
return []
} }
...@@ -142,7 +142,7 @@ def prepare_enviroment(): ...@@ -142,7 +142,7 @@ def prepare_enviroment():
stable_diffusion_commit_hash = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', "69ae4b35e0a0f6ee1af8bb9a5d0016ccb27e36dc") stable_diffusion_commit_hash = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', "69ae4b35e0a0f6ee1af8bb9a5d0016ccb27e36dc")
taming_transformers_commit_hash = os.environ.get('TAMING_TRANSFORMERS_COMMIT_HASH', "24268930bf1dce879235a7fddd0b2355b84d7ea6") taming_transformers_commit_hash = os.environ.get('TAMING_TRANSFORMERS_COMMIT_HASH', "24268930bf1dce879235a7fddd0b2355b84d7ea6")
k_diffusion_commit_hash = os.environ.get('K_DIFFUSION_COMMIT_HASH', "f4e99857772fc3a126ba886aadf795a332774878") k_diffusion_commit_hash = os.environ.get('K_DIFFUSION_COMMIT_HASH', "60e5042ca0da89c14d1dd59d73883280f8fce991")
codeformer_commit_hash = os.environ.get('CODEFORMER_COMMIT_HASH', "c5b4593074ba6214284d6acd5f1719b6c5d739af") codeformer_commit_hash = os.environ.get('CODEFORMER_COMMIT_HASH', "c5b4593074ba6214284d6acd5f1719b6c5d739af")
blip_commit_hash = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9") blip_commit_hash = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9")
......
This diff is collapsed.
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
"A merger of the two checkpoints will be generated in your": "체크포인트들이 병합된 결과물이 당신의", "A merger of the two checkpoints will be generated in your": "체크포인트들이 병합된 결과물이 당신의",
"A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.", "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result": "난수 생성기의 결과물을 지정하는 값 - 동일한 설정값과 동일한 시드를 적용 시, 완전히 똑같은 결과물을 얻게 됩니다.",
"Action": "작업", "Action": "작업",
"Add a button to convert the prompts used in NovelAI for use in the WebUI. In addition, add a button that allows you to recall a previously used prompt.": "NovelAI에서 사용되는 프롬프트를 WebUI에서 사용할 수 있게 변환하는 버튼을 추가합니다. 덤으로 이전에 사용한 프롬프트를 불러오는 버튼도 추가됩니다.",
"Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가", "Add a random artist to the prompt.": "프롬프트에 랜덤한 작가 추가",
"Add a second progress bar to the console that shows progress for an entire job.": "콘솔에 전체 작업의 진행도를 보여주는 2번째 프로그레스 바 추가하기", "Add a second progress bar to the console that shows progress for an entire job.": "콘솔에 전체 작업의 진행도를 보여주는 2번째 프로그레스 바 추가하기",
"Add difference": "차이점 추가", "Add difference": "차이점 추가",
...@@ -24,6 +25,7 @@ ...@@ -24,6 +25,7 @@
"Add model hash to generation information": "생성 정보에 모델 해시 추가", "Add model hash to generation information": "생성 정보에 모델 해시 추가",
"Add model name to generation information": "생성 정보에 모델 이름 추가", "Add model name to generation information": "생성 정보에 모델 이름 추가",
"Add number to filename when saving": "이미지를 저장할 때 파일명에 숫자 추가하기", "Add number to filename when saving": "이미지를 저장할 때 파일명에 숫자 추가하기",
"Adds a tab that lets you preview how CLIP model would tokenize your text.": "CLIP 모델이 텍스트를 어떻게 토큰화할지 미리 보여주는 탭을 추가합니다.",
"Adds a tab to the webui that allows the user to automatically extract keyframes from video, and manually extract 512x512 crops of those frames for use in model training.": "WebUI에 비디오로부터 자동으로 키프레임을 추출하고, 그 키프레임으로부터 모델 훈련에 사용될 512x512 이미지를 잘라낼 수 있는 탭을 추가합니다.", "Adds a tab to the webui that allows the user to automatically extract keyframes from video, and manually extract 512x512 crops of those frames for use in model training.": "WebUI에 비디오로부터 자동으로 키프레임을 추출하고, 그 키프레임으로부터 모델 훈련에 사용될 512x512 이미지를 잘라낼 수 있는 탭을 추가합니다.",
"Aesthetic Gradients": "스타일 그라디언트", "Aesthetic Gradients": "스타일 그라디언트",
"Aesthetic Image Scorer": "스타일 이미지 스코어러", "Aesthetic Image Scorer": "스타일 이미지 스코어러",
...@@ -33,6 +35,7 @@ ...@@ -33,6 +35,7 @@
"Aesthetic text for imgs": "스타일 텍스트", "Aesthetic text for imgs": "스타일 텍스트",
"Aesthetic weight": "스타일 가중치", "Aesthetic weight": "스타일 가중치",
"Allowed categories for random artists selection when using the Roll button": "랜덤 버튼을 눌러 무작위 작가를 선택할 때 허용된 카테고리", "Allowed categories for random artists selection when using the Roll button": "랜덤 버튼을 눌러 무작위 작가를 선택할 때 허용된 카테고리",
"Allows you to include various shortcodes in your prompts. You can pull text from files, set up your own variables, process text through conditional functions, and so much more - it's like wildcards on steroids.": "프롬프트에 다양한 숏코드를 추가할 수 있게 해줍니다. 파일로부터 텍스트 추출, 변수 설정, 조건 함수로 텍스트 처리 등등 - 스테로이드를 맞은 와일드카드라 할 수 있죠.",
"Always print all generation info to standard output": "기본 아웃풋에 모든 생성 정보 항상 출력하기", "Always print all generation info to standard output": "기본 아웃풋에 모든 생성 정보 항상 출력하기",
"Always save all generated image grids": "생성된 이미지 그리드 항상 저장하기", "Always save all generated image grids": "생성된 이미지 그리드 항상 저장하기",
"Always save all generated images": "생성된 이미지 항상 저장하기", "Always save all generated images": "생성된 이미지 항상 저장하기",
...@@ -54,6 +57,7 @@ ...@@ -54,6 +57,7 @@
"Batch Process": "이미지 여러장 처리", "Batch Process": "이미지 여러장 처리",
"Batch size": "배치 크기", "Batch size": "배치 크기",
"behind": "최신 아님", "behind": "최신 아님",
"Booru tag autocompletion": "Booru 태그 자동완성",
"BSRGAN 4x": "BSRGAN 4x", "BSRGAN 4x": "BSRGAN 4x",
"built with gradio": "gradio로 제작되었습니다", "built with gradio": "gradio로 제작되었습니다",
"Calculates aesthetic score for generated images using CLIP+MLP Aesthetic Score Predictor based on Chad Scorer": "Chad 스코어러를 기반으로 한 CLIP+MLP 스타일 점수 예측기를 이용해 생성된 이미지의 스타일 점수를 계산합니다.", "Calculates aesthetic score for generated images using CLIP+MLP Aesthetic Score Predictor based on Chad Scorer": "Chad 스코어러를 기반으로 한 CLIP+MLP 스타일 점수 예측기를 이용해 생성된 이미지의 스타일 점수를 계산합니다.",
...@@ -114,6 +118,7 @@ ...@@ -114,6 +118,7 @@
"Directory for saving images using the Save button": "저장 버튼을 이용해 저장하는 이미지들의 저장 경로", "Directory for saving images using the Save button": "저장 버튼을 이용해 저장하는 이미지들의 저장 경로",
"Directory name pattern": "디렉토리명 패턴", "Directory name pattern": "디렉토리명 패턴",
"directory.": "저장 경로에 저장됩니다.", "directory.": "저장 경로에 저장됩니다.",
"Displays autocompletion hints for tags from image booru boards such as Danbooru. Uses local tag CSV files and includes a config for customization.": "Danbooru 같은 이미지 booru 보드의 태그에 대한 자동완성 힌트를 보여줍니다. 로컬 환경에 저장된 CSV 파일을 사용하고 조정 가능한 설정 파일이 포함되어 있습니다.",
"Do not add watermark to images": "이미지에 워터마크 추가하지 않기", "Do not add watermark to images": "이미지에 워터마크 추가하지 않기",
"Do not do anything special": "아무것도 하지 않기", "Do not do anything special": "아무것도 하지 않기",
"Do not save grids consisting of one picture": "이미지가 1개뿐인 그리드는 저장하지 않기", "Do not save grids consisting of one picture": "이미지가 1개뿐인 그리드는 저장하지 않기",
...@@ -317,6 +322,7 @@ ...@@ -317,6 +322,7 @@
"None": "없음", "None": "없음",
"Nothing": "없음", "Nothing": "없음",
"Nothing found in the image.": "Nothing found in the image.", "Nothing found in the image.": "Nothing found in the image.",
"novelai-2-local-prompt": "NovelAI 프롬프트 변환기",
"Number of columns on the page": "각 페이지마다 표시할 가로줄 수", "Number of columns on the page": "각 페이지마다 표시할 가로줄 수",
"Number of grids in each row": "각 세로줄마다 표시될 그리드 수", "Number of grids in each row": "각 세로줄마다 표시될 그리드 수",
"number of images to delete consecutively next": "연속적으로 삭제할 이미지 수", "number of images to delete consecutively next": "연속적으로 삭제할 이미지 수",
...@@ -431,6 +437,7 @@ ...@@ -431,6 +437,7 @@
"Save images with embedding in PNG chunks": "PNG 청크로 이미지에 임베딩을 포함시켜 저장", "Save images with embedding in PNG chunks": "PNG 청크로 이미지에 임베딩을 포함시켜 저장",
"Save style": "스타일 저장", "Save style": "스타일 저장",
"Save text information about generation parameters as chunks to png files": "이미지 생성 설정값을 PNG 청크에 텍스트로 저장", "Save text information about generation parameters as chunks to png files": "이미지 생성 설정값을 PNG 청크에 텍스트로 저장",
"Saves Optimizer state as separate *.optim file. Training can be resumed with HN itself and matching optim file.": "옵티마이저 상태를 별개의 *.optim 파일로 저장하기. 하이퍼네트워크 파일과 일치하는 optim 파일로부터 훈련을 재개할 수 있습니다.",
"Saving images/grids": "이미지/그리드 저장", "Saving images/grids": "이미지/그리드 저장",
"Saving to a directory": "디렉토리에 저장", "Saving to a directory": "디렉토리에 저장",
"Scale by": "스케일링 배수 지정", "Scale by": "스케일링 배수 지정",
...@@ -515,6 +522,7 @@ ...@@ -515,6 +522,7 @@
"Tile size for ESRGAN upscalers. 0 = no tiling.": "ESRGAN 업스케일러들의 타일 사이즈. 0 = 타일링 없음.", "Tile size for ESRGAN upscalers. 0 = no tiling.": "ESRGAN 업스케일러들의 타일 사이즈. 0 = 타일링 없음.",
"Tiling": "타일링", "Tiling": "타일링",
"Time taken:": "소요 시간 : ", "Time taken:": "소요 시간 : ",
"tokenizer": "토크나이저",
"Torch active/reserved:": "활성화/예약된 Torch 양 : ", "Torch active/reserved:": "활성화/예약된 Torch 양 : ",
"Torch active: Peak amount of VRAM used by Torch during generation, excluding cached data.\nTorch reserved: Peak amount of VRAM allocated by Torch, including all active and cached data.\nSys VRAM: Peak amount of VRAM allocation across all applications / total GPU VRAM (peak utilization%).": "활성화된 Torch : 생성 도중 캐시된 데이터를 포함해 사용된 VRAM의 최대량\n예약된 Torch : 활성화되고 캐시된 모든 데이터를 포함해 Torch에게 할당된 VRAM의 최대량\n시스템 VRAM : 모든 어플리케이션에 할당된 VRAM 최대량 / 총 GPU VRAM (최고 이용도%)", "Torch active: Peak amount of VRAM used by Torch during generation, excluding cached data.\nTorch reserved: Peak amount of VRAM allocated by Torch, including all active and cached data.\nSys VRAM: Peak amount of VRAM allocation across all applications / total GPU VRAM (peak utilization%).": "활성화된 Torch : 생성 도중 캐시된 데이터를 포함해 사용된 VRAM의 최대량\n예약된 Torch : 활성화되고 캐시된 모든 데이터를 포함해 Torch에게 할당된 VRAM의 최대량\n시스템 VRAM : 모든 어플리케이션에 할당된 VRAM 최대량 / 총 GPU VRAM (최고 이용도%)",
"Train": "훈련", "Train": "훈련",
......
...@@ -109,7 +109,7 @@ ...@@ -109,7 +109,7 @@
"Sigma noise": "Sigma noise", "Sigma noise": "Sigma noise",
"Eta": "Eta", "Eta": "Eta",
"Clip skip": "Clip 跳过", "Clip skip": "Clip 跳过",
"Denoising": "去噪", "Denoising": "重绘幅度",
"Cond. Image Mask Weight": "图像调节屏蔽度", "Cond. Image Mask Weight": "图像调节屏蔽度",
"X values": "X轴数值", "X values": "X轴数值",
"Y type": "Y轴类型", "Y type": "Y轴类型",
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
"Loading...": "載入中…", "Loading...": "載入中…",
"view": "檢視", "view": "檢視",
"api": "api", "api": "api",
"•": "", "•": "",
"built with gradio": "基於 Gradio 構建", "built with gradio": "基於 Gradio 構建",
"Stable Diffusion checkpoint": "Stable Diffusion 模型權重存檔點", "Stable Diffusion checkpoint": "Stable Diffusion 模型權重存檔點",
"txt2img": "文生圖", "txt2img": "文生圖",
...@@ -70,12 +70,12 @@ ...@@ -70,12 +70,12 @@
"Variation strength": "差異強度", "Variation strength": "差異強度",
"Resize seed from width": "自寬度縮放隨機種子", "Resize seed from width": "自寬度縮放隨機種子",
"Resize seed from height": "自高度縮放隨機種子", "Resize seed from height": "自高度縮放隨機種子",
"Open for Clip Aesthetic!": "打開美術風格 Clip!", "Open for Clip Aesthetic!": "打開以調整 Clip 的美術風格!",
"▼": "▼", "▼": "▼",
"Aesthetic weight": "美術風格權重", "Aesthetic weight": "美術風格權重",
"Aesthetic steps": "美術風格疊代步數", "Aesthetic steps": "美術風格疊代步數",
"Aesthetic learning rate": "美術風格學習率", "Aesthetic learning rate": "美術風格學習率",
"Slerp interpolation": "Slerp 插值", "Slerp interpolation": "球面線性插值角度",
"Aesthetic imgs embedding": "美術風格圖集 embedding", "Aesthetic imgs embedding": "美術風格圖集 embedding",
"None": "無", "None": "無",
"Aesthetic text for imgs": "該圖集的美術風格描述", "Aesthetic text for imgs": "該圖集的美術風格描述",
...@@ -105,15 +105,15 @@ ...@@ -105,15 +105,15 @@
"Prompt order": "提示詞順序", "Prompt order": "提示詞順序",
"Sampler": "採樣器", "Sampler": "採樣器",
"Checkpoint name": "模型權重存檔點的名稱", "Checkpoint name": "模型權重存檔點的名稱",
"Hypernetwork": "超網路", "Hypernetwork": "超網路(Hypernetwork)",
"Hypernet str.": "超網路強度", "Hypernet str.": "超網路(Hypernetwork)強度",
"Sigma Churn": "Sigma Churn", "Sigma Churn": "Sigma Churn",
"Sigma min": "最小 Sigma", "Sigma min": "最小 Sigma",
"Sigma max": "最大 Sigma", "Sigma max": "最大 Sigma",
"Sigma noise": "Sigma noise", "Sigma noise": "Sigma noise",
"Eta": "Eta", "Eta": "Eta",
"Clip skip": "Clip 跳過", "Clip skip": "Clip 跳過",
"Denoising": "去噪", "Denoising": "重繪幅度",
"Cond. Image Mask Weight": "圖像調節屏蔽度", "Cond. Image Mask Weight": "圖像調節屏蔽度",
"X values": "X軸數值", "X values": "X軸數值",
"Y type": "Y軸類型", "Y type": "Y軸類型",
...@@ -189,6 +189,7 @@ ...@@ -189,6 +189,7 @@
"Tile overlap": "圖塊重疊的畫素", "Tile overlap": "圖塊重疊的畫素",
"Upscaler": "放大演算法", "Upscaler": "放大演算法",
"Lanczos": "Lanczos", "Lanczos": "Lanczos",
"Nearest": "最鄰近(整數縮放)",
"LDSR": "LDSR", "LDSR": "LDSR",
"BSRGAN 4x": "BSRGAN 4x", "BSRGAN 4x": "BSRGAN 4x",
"ESRGAN_4x": "ESRGAN_4x", "ESRGAN_4x": "ESRGAN_4x",
...@@ -230,15 +231,15 @@ ...@@ -230,15 +231,15 @@
"for detailed explanation.": "以了解詳細說明", "for detailed explanation.": "以了解詳細說明",
"Create embedding": "生成 embedding", "Create embedding": "生成 embedding",
"Create aesthetic images embedding": "生成美術風格圖集 embedding", "Create aesthetic images embedding": "生成美術風格圖集 embedding",
"Create hypernetwork": "生成 hypernetwork", "Create hypernetwork": "生成超網路(Hypernetwork)",
"Preprocess images": "圖像預處理", "Preprocess images": "圖像預處理",
"Name": "名稱", "Name": "名稱",
"Initialization text": "初始化文字", "Initialization text": "初始化文字",
"Number of vectors per token": "每個 token 的向量數", "Number of vectors per token": "每個 token 的向量數",
"Overwrite Old Embedding": "覆寫舊的 Embedding", "Overwrite Old Embedding": "覆寫舊的 Embedding",
"Modules": "模組", "Modules": "模組",
"Enter hypernetwork layer structure": "輸入 hypernetwork 層結構", "Enter hypernetwork layer structure": "輸入超網路(Hypernetwork)層結構",
"Select activation function of hypernetwork": "選擇 hypernetwork 的激活函數", "Select activation function of hypernetwork": "選擇超網路(Hypernetwork)的激活函數",
"linear": "linear", "linear": "linear",
"relu": "relu", "relu": "relu",
"leakyrelu": "leakyrelu", "leakyrelu": "leakyrelu",
...@@ -276,7 +277,7 @@ ...@@ -276,7 +277,7 @@
"XavierNormal": "Xavier 正態", "XavierNormal": "Xavier 正態",
"Add layer normalization": "加入層標準化", "Add layer normalization": "加入層標準化",
"Use dropout": "採用 dropout 防止過擬合", "Use dropout": "採用 dropout 防止過擬合",
"Overwrite Old Hypernetwork": "覆寫舊的 Hypernetwork", "Overwrite Old Hypernetwork": "覆寫舊的超網路(Hypernetwork)",
"Source directory": "來源目錄", "Source directory": "來源目錄",
"Destination directory": "目標目錄", "Destination directory": "目標目錄",
"Existing Caption txt Action": "對已有的TXT說明文字的行為", "Existing Caption txt Action": "對已有的TXT說明文字的行為",
...@@ -298,11 +299,11 @@ ...@@ -298,11 +299,11 @@
"Create debug image": "生成除錯圖片", "Create debug image": "生成除錯圖片",
"Preprocess": "預處理", "Preprocess": "預處理",
"Train an embedding; must specify a directory with a set of 1:1 ratio images": "訓練 embedding; 必須指定一組具有 1:1 比例圖像的目錄", "Train an embedding; must specify a directory with a set of 1:1 ratio images": "訓練 embedding; 必須指定一組具有 1:1 比例圖像的目錄",
"Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images": "訓練 embedding 或者 hypernetwork; 必須指定一組具有 1:1 比例圖像的目錄", "Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images": "訓練 embedding 或者超網路(Hypernetwork); 必須指定一組具有 1:1 比例圖像的目錄",
"[wiki]": "[wiki]", "[wiki]": "[wiki文件]",
"Embedding": "Embedding", "Embedding": "Embedding",
"Embedding Learning rate": "Embedding 學習率", "Embedding Learning rate": "Embedding 學習率",
"Hypernetwork Learning rate": "Hypernetwork 學習率", "Hypernetwork Learning rate": "超網路(Hypernetwork)學習率",
"Learning rate": "學習率", "Learning rate": "學習率",
"Dataset directory": "資料集目錄", "Dataset directory": "資料集目錄",
"Log directory": "日誌目錄", "Log directory": "日誌目錄",
...@@ -312,7 +313,7 @@ ...@@ -312,7 +313,7 @@
"Save a copy of embedding to log directory every N steps, 0 to disable": "每 N 步將 embedding 的副本儲存到日誌目錄,0 表示禁用", "Save a copy of embedding to log directory every N steps, 0 to disable": "每 N 步將 embedding 的副本儲存到日誌目錄,0 表示禁用",
"Save images with embedding in PNG chunks": "儲存圖像,並在 PNG 圖片檔案中嵌入 embedding 檔案", "Save images with embedding in PNG chunks": "儲存圖像,並在 PNG 圖片檔案中嵌入 embedding 檔案",
"Read parameters (prompt, etc...) from txt2img tab when making previews": "進行預覽時,從文生圖頁籤中讀取參數(提示詞等)", "Read parameters (prompt, etc...) from txt2img tab when making previews": "進行預覽時,從文生圖頁籤中讀取參數(提示詞等)",
"Train Hypernetwork": "訓練 Hypernetwork", "Train Hypernetwork": "訓練超網路(Hypernetwork)",
"Train Embedding": "訓練 Embedding", "Train Embedding": "訓練 Embedding",
"Create an aesthetic embedding out of any number of images": "從任意數量的圖像中建立美術風格 embedding", "Create an aesthetic embedding out of any number of images": "從任意數量的圖像中建立美術風格 embedding",
"Create images embedding": "生成圖集 embedding", "Create images embedding": "生成圖集 embedding",
...@@ -418,7 +419,7 @@ ...@@ -418,7 +419,7 @@
"Checkpoints to cache in RAM": "快取在內存(RAM)中的模型權重存檔點", "Checkpoints to cache in RAM": "快取在內存(RAM)中的模型權重存檔點",
"SD VAE": "模型的VAE", "SD VAE": "模型的VAE",
"auto": "自動", "auto": "自動",
"Hypernetwork strength": "Hypernetwork 強度", "Hypernetwork strength": "超網路(Hypernetwork)強度",
"Inpainting conditioning mask strength": "局部重繪時圖像調節的蒙版屏蔽強度", "Inpainting conditioning mask strength": "局部重繪時圖像調節的蒙版屏蔽強度",
"Apply color correction to img2img results to match original colors.": "對圖生圖結果套用顏色校正以匹配原始顏色", "Apply color correction to img2img results to match original colors.": "對圖生圖結果套用顏色校正以匹配原始顏色",
"With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "在進行圖生圖的時候,確切地執行滑塊指定的疊代步數(正常情況下更弱的重繪幅度需要更少的疊代步數)", "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising).": "在進行圖生圖的時候,確切地執行滑塊指定的疊代步數(正常情況下更弱的重繪幅度需要更少的疊代步數)",
...@@ -488,7 +489,17 @@ ...@@ -488,7 +489,17 @@
"Extension": "擴充", "Extension": "擴充",
"URL": "網址", "URL": "網址",
"Update": "更新", "Update": "更新",
"a1111-sd-webui-tagcomplete": "標記自動補全",
"unknown": "未知", "unknown": "未知",
"deforum-for-automatic1111-webui": "Deforum",
"sd-dynamic-prompting": "動態提示詞",
"stable-diffusion-webui-aesthetic-gradients": "美術風格梯度",
"stable-diffusion-webui-aesthetic-image-scorer": "美術風格評等",
"stable-diffusion-webui-artists-to-study": "藝術家圖庫",
"stable-diffusion-webui-dataset-tag-editor": "資料集標記編輯器",
"stable-diffusion-webui-images-browser": "圖庫瀏覽器",
"stable-diffusion-webui-inspiration": "靈感",
"stable-diffusion-webui-wildcards": "萬用字元",
"Load from:": "載入自", "Load from:": "載入自",
"Extension index URL": "擴充清單連結", "Extension index URL": "擴充清單連結",
"URL for extension's git repository": "擴充的 git 倉庫連結", "URL for extension's git repository": "擴充的 git 倉庫連結",
...@@ -527,8 +538,8 @@ ...@@ -527,8 +538,8 @@
"What to put inside the masked area before processing it with Stable Diffusion.": "在使用 Stable Diffusion 處理蒙版區域之前要在蒙版區域內放置什麼", "What to put inside the masked area before processing it with Stable Diffusion.": "在使用 Stable Diffusion 處理蒙版區域之前要在蒙版區域內放置什麼",
"fill it with colors of the image": "用圖像的顏色(高強度模糊)填充它", "fill it with colors of the image": "用圖像的顏色(高強度模糊)填充它",
"keep whatever was there originally": "保留原來的圖像,不進行預處理", "keep whatever was there originally": "保留原來的圖像,不進行預處理",
"fill it with latent space noise": "用潛空間的噪聲填充它", "fill it with latent space noise": "於潛空間填充噪聲",
"fill it with latent space zeroes": "用潛空間的零填充它", "fill it with latent space zeroes": "於潛空間填零",
"Upscale masked region to target resolution, do inpainting, downscale back and paste into original image": "將蒙版區域(包括預留畫素長度的緩衝區域)放大到目標解析度,進行局部重繪。\n然後縮小並粘貼回原始圖像中", "Upscale masked region to target resolution, do inpainting, downscale back and paste into original image": "將蒙版區域(包括預留畫素長度的緩衝區域)放大到目標解析度,進行局部重繪。\n然後縮小並粘貼回原始圖像中",
"Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.": "將圖像大小調整為目標解析度。除非高度和寬度匹配,否則你將獲得不正確的縱橫比", "Resize image to target resolution. Unless height and width match, you will get incorrect aspect ratio.": "將圖像大小調整為目標解析度。除非高度和寬度匹配,否則你將獲得不正確的縱橫比",
"Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.": "調整圖像大小,使整個目標解析度都被圖像填充。裁剪多出來的部分", "Resize the image so that entirety of target resolution is filled with the image. Crop parts that stick out.": "調整圖像大小,使整個目標解析度都被圖像填充。裁剪多出來的部分",
...@@ -560,6 +571,8 @@ ...@@ -560,6 +571,8 @@
"Select which Real-ESRGAN models to show in the web UI. (Requires restart)": "選擇哪些 Real-ESRGAN 模型顯示在網頁使用者介面。(需要重新啟動)", "Select which Real-ESRGAN models to show in the web UI. (Requires restart)": "選擇哪些 Real-ESRGAN 模型顯示在網頁使用者介面。(需要重新啟動)",
"Allowed categories for random artists selection when using the Roll button": "使用抽選藝術家按鈕時將會隨機的藝術家類別", "Allowed categories for random artists selection when using the Roll button": "使用抽選藝術家按鈕時將會隨機的藝術家類別",
"Append commas": "附加逗號", "Append commas": "附加逗號",
"latest": "最新",
"behind": "落後",
"Roll three": "抽三位出來", "Roll three": "抽三位出來",
"Generate forever": "無限生成", "Generate forever": "無限生成",
"Cancel generate forever": "停止無限生成", "Cancel generate forever": "停止無限生成",
...@@ -581,10 +594,9 @@ ...@@ -581,10 +594,9 @@
"Start drawing": "開始繪製", "Start drawing": "開始繪製",
"Description": "描述", "Description": "描述",
"Action": "行動", "Action": "行動",
"Aesthetic Gradients": "美術風格", "Aesthetic Gradients": "美術風格梯度",
"aesthetic-gradients": "美術風格", "aesthetic-gradients": "美術風格梯度",
"stable-diffusion-webui-wildcards": "萬用字元", "Dynamic Prompts": "動態提示詞",
"Dynamic Prompts": "動態提示",
"images-browser": "圖庫瀏覽器", "images-browser": "圖庫瀏覽器",
"Inspiration": "靈感", "Inspiration": "靈感",
"Deforum": "Deforum", "Deforum": "Deforum",
......
...@@ -10,6 +10,7 @@ from modules.api.models import * ...@@ -10,6 +10,7 @@ from modules.api.models import *
from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images
from modules.sd_samplers import all_samplers from modules.sd_samplers import all_samplers
from modules.extras import run_extras, run_pnginfo from modules.extras import run_extras, run_pnginfo
from PIL import PngImagePlugin
from modules.sd_models import checkpoints_list from modules.sd_models import checkpoints_list
from modules.realesrgan_model import get_realesrgan_models from modules.realesrgan_model import get_realesrgan_models
from typing import List from typing import List
...@@ -34,9 +35,21 @@ def setUpscalers(req: dict): ...@@ -34,9 +35,21 @@ def setUpscalers(req: dict):
def encode_pil_to_base64(image): def encode_pil_to_base64(image):
buffer = io.BytesIO() with io.BytesIO() as output_bytes:
image.save(buffer, format="png")
return base64.b64encode(buffer.getvalue()) # Copy any text-only metadata
use_metadata = False
metadata = PngImagePlugin.PngInfo()
for key, value in image.info.items():
if isinstance(key, str) and isinstance(value, str):
metadata.add_text(key, value)
use_metadata = True
image.save(
output_bytes, "PNG", pnginfo=(metadata if use_metadata else None)
)
bytes_data = output_bytes.getvalue()
return base64.b64encode(bytes_data)
class Api: class Api:
...@@ -50,6 +63,7 @@ class Api: ...@@ -50,6 +63,7 @@ class Api:
self.app.add_api_route("/sdapi/v1/extra-batch-images", self.extras_batch_images_api, methods=["POST"], response_model=ExtrasBatchImagesResponse) self.app.add_api_route("/sdapi/v1/extra-batch-images", self.extras_batch_images_api, methods=["POST"], response_model=ExtrasBatchImagesResponse)
self.app.add_api_route("/sdapi/v1/png-info", self.pnginfoapi, methods=["POST"], response_model=PNGInfoResponse) self.app.add_api_route("/sdapi/v1/png-info", self.pnginfoapi, methods=["POST"], response_model=PNGInfoResponse)
self.app.add_api_route("/sdapi/v1/progress", self.progressapi, methods=["GET"], response_model=ProgressResponse) self.app.add_api_route("/sdapi/v1/progress", self.progressapi, methods=["GET"], response_model=ProgressResponse)
self.app.add_api_route("/sdapi/v1/interrogate", self.interrogateapi, methods=["POST"])
self.app.add_api_route("/sdapi/v1/interrupt", self.interruptapi, methods=["POST"]) self.app.add_api_route("/sdapi/v1/interrupt", self.interruptapi, methods=["POST"])
self.app.add_api_route("/sdapi/v1/options", self.get_config, methods=["GET"], response_model=OptionsModel) self.app.add_api_route("/sdapi/v1/options", self.get_config, methods=["GET"], response_model=OptionsModel)
self.app.add_api_route("/sdapi/v1/options", self.set_config, methods=["POST"]) self.app.add_api_route("/sdapi/v1/options", self.set_config, methods=["POST"])
...@@ -201,11 +215,24 @@ class Api: ...@@ -201,11 +215,24 @@ class Api:
return ProgressResponse(progress=progress, eta_relative=eta_relative, state=shared.state.dict(), current_image=current_image) return ProgressResponse(progress=progress, eta_relative=eta_relative, state=shared.state.dict(), current_image=current_image)
def interrogateapi(self, interrogatereq: InterrogateRequest):
image_b64 = interrogatereq.image
if image_b64 is None:
raise HTTPException(status_code=404, detail="Image not found")
img = self.__base64_to_image(image_b64)
# Override object param
with self.queue_lock:
processed = shared.interrogator.interrogate(img)
return InterrogateResponse(caption=processed)
def interruptapi(self): def interruptapi(self):
shared.state.interrupt() shared.state.interrupt()
return {} return {}
def get_config(self): def get_config(self):
options = {} options = {}
for key in shared.opts.data.keys(): for key in shared.opts.data.keys():
...@@ -214,10 +241,14 @@ class Api: ...@@ -214,10 +241,14 @@ class Api:
options.update({key: shared.opts.data.get(key, shared.opts.data_labels.get(key).default)}) options.update({key: shared.opts.data.get(key, shared.opts.data_labels.get(key).default)})
else: else:
options.update({key: shared.opts.data.get(key, None)}) options.update({key: shared.opts.data.get(key, None)})
return options return options
def set_config(self, req: OptionsModel): def set_config(self, req: OptionsModel):
# currently req has all options fields even if you send a dict like { "send_seed": false }, which means it will
# overwrite all options with default values.
raise RuntimeError('Setting options via API is not supported')
reqDict = vars(req) reqDict = vars(req)
for o in reqDict: for o in reqDict:
setattr(shared.opts, o, reqDict[o]) setattr(shared.opts, o, reqDict[o])
...@@ -233,13 +264,13 @@ class Api: ...@@ -233,13 +264,13 @@ class Api:
def get_upscalers(self): def get_upscalers(self):
upscalers = [] upscalers = []
for upscaler in shared.sd_upscalers: for upscaler in shared.sd_upscalers:
u = upscaler.scaler u = upscaler.scaler
upscalers.append({"name":u.name, "model_name":u.model_name, "model_path":u.model_path, "model_url":u.model_url}) upscalers.append({"name":u.name, "model_name":u.model_name, "model_path":u.model_path, "model_url":u.model_url})
return upscalers return upscalers
def get_sd_models(self): def get_sd_models(self):
return [{"title":x.title, "model_name":x.model_name, "hash":x.hash, "filename": x.filename, "config": x.config} for x in checkpoints_list.values()] return [{"title":x.title, "model_name":x.model_name, "hash":x.hash, "filename": x.filename, "config": x.config} for x in checkpoints_list.values()]
...@@ -251,11 +282,11 @@ class Api: ...@@ -251,11 +282,11 @@ class Api:
def get_realesrgan_models(self): def get_realesrgan_models(self):
return [{"name":x.name,"path":x.data_path, "scale":x.scale} for x in get_realesrgan_models(None)] return [{"name":x.name,"path":x.data_path, "scale":x.scale} for x in get_realesrgan_models(None)]
def get_promp_styles(self): def get_promp_styles(self):
styleList = [] styleList = []
for k in shared.prompt_styles.styles: for k in shared.prompt_styles.styles:
style = shared.prompt_styles.styles[k] style = shared.prompt_styles.styles[k]
styleList.append({"name":style[0], "prompt": style[1], "negative_prompr": style[2]}) styleList.append({"name":style[0], "prompt": style[1], "negative_prompr": style[2]})
return styleList return styleList
......
import inspect import inspect
from pydantic import BaseModel, Field, create_model from pydantic import BaseModel, Field, create_model
from typing import Any, Optional, Union from typing import Any, Optional
from typing_extensions import Literal from typing_extensions import Literal
from inflection import underscore from inflection import underscore
from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img
from modules.shared import sd_upscalers, opts, parser from modules.shared import sd_upscalers, opts, parser
from typing import List from typing import Dict, List
API_NOT_ALLOWED = [ API_NOT_ALLOWED = [
"self", "self",
...@@ -65,6 +65,7 @@ class PydanticModelGenerator: ...@@ -65,6 +65,7 @@ class PydanticModelGenerator:
self._model_name = model_name self._model_name = model_name
self._class_data = merge_class_params(class_instance) self._class_data = merge_class_params(class_instance)
self._model_def = [ self._model_def = [
ModelDef( ModelDef(
field=underscore(k), field=underscore(k),
...@@ -167,6 +168,12 @@ class ProgressResponse(BaseModel): ...@@ -167,6 +168,12 @@ class ProgressResponse(BaseModel):
state: dict = Field(title="State", description="The current state snapshot") state: dict = Field(title="State", description="The current state snapshot")
current_image: str = Field(default=None, title="Current image", description="The current image in base64 format. opts.show_progress_every_n_steps is required for this to work.") current_image: str = Field(default=None, title="Current image", description="The current image in base64 format. opts.show_progress_every_n_steps is required for this to work.")
class InterrogateRequest(BaseModel):
image: str = Field(default="", title="Image", description="Image to work on, must be a Base64 string containing the image's data.")
class InterrogateResponse(BaseModel):
caption: str = Field(default=None, title="Caption", description="The generated caption for the image.")
fields = {} fields = {}
for key, value in opts.data.items(): for key, value in opts.data.items():
metadata = opts.data_labels.get(key) metadata = opts.data_labels.get(key)
...@@ -185,22 +192,22 @@ _options = vars(parser)['_option_string_actions'] ...@@ -185,22 +192,22 @@ _options = vars(parser)['_option_string_actions']
for key in _options: for key in _options:
if(_options[key].dest != 'help'): if(_options[key].dest != 'help'):
flag = _options[key] flag = _options[key]
_type = str _type = str
if(_options[key].default != None): _type = type(_options[key].default) if _options[key].default is not None: _type = type(_options[key].default)
flags.update({flag.dest: (_type,Field(default=flag.default, description=flag.help))}) flags.update({flag.dest: (_type,Field(default=flag.default, description=flag.help))})
FlagsModel = create_model("Flags", **flags) FlagsModel = create_model("Flags", **flags)
class SamplerItem(BaseModel): class SamplerItem(BaseModel):
name: str = Field(title="Name") name: str = Field(title="Name")
aliases: list[str] = Field(title="Aliases") aliases: List[str] = Field(title="Aliases")
options: dict[str, str] = Field(title="Options") options: Dict[str, str] = Field(title="Options")
class UpscalerItem(BaseModel): class UpscalerItem(BaseModel):
name: str = Field(title="Name") name: str = Field(title="Name")
model_name: str | None = Field(title="Model Name") model_name: Optional[str] = Field(title="Model Name")
model_path: str | None = Field(title="Path") model_path: Optional[str] = Field(title="Path")
model_url: str | None = Field(title="URL") model_url: Optional[str] = Field(title="URL")
class SDModelItem(BaseModel): class SDModelItem(BaseModel):
title: str = Field(title="Title") title: str = Field(title="Title")
...@@ -211,23 +218,24 @@ class SDModelItem(BaseModel): ...@@ -211,23 +218,24 @@ class SDModelItem(BaseModel):
class HypernetworkItem(BaseModel): class HypernetworkItem(BaseModel):
name: str = Field(title="Name") name: str = Field(title="Name")
path: str | None = Field(title="Path") path: Optional[str] = Field(title="Path")
class FaceRestorerItem(BaseModel): class FaceRestorerItem(BaseModel):
name: str = Field(title="Name") name: str = Field(title="Name")
cmd_dir: str | None = Field(title="Path") cmd_dir: Optional[str] = Field(title="Path")
class RealesrganItem(BaseModel): class RealesrganItem(BaseModel):
name: str = Field(title="Name") name: str = Field(title="Name")
path: str | None = Field(title="Path") path: Optional[str] = Field(title="Path")
scale: int | None = Field(title="Scale") scale: Optional[int] = Field(title="Scale")
class PromptStyleItem(BaseModel): class PromptStyleItem(BaseModel):
name: str = Field(title="Name") name: str = Field(title="Name")
prompt: str | None = Field(title="Prompt") prompt: Optional[str] = Field(title="Prompt")
negative_prompt: str | None = Field(title="Negative Prompt") negative_prompt: Optional[str] = Field(title="Negative Prompt")
class ArtistItem(BaseModel): class ArtistItem(BaseModel):
name: str = Field(title="Name") name: str = Field(title="Name")
score: float = Field(title="Score") score: float = Field(title="Score")
category: str = Field(title="Category") category: str = Field(title="Category")
\ No newline at end of file
...@@ -34,8 +34,11 @@ class Extension: ...@@ -34,8 +34,11 @@ class Extension:
if repo is None or repo.bare: if repo is None or repo.bare:
self.remote = None self.remote = None
else: else:
self.remote = next(repo.remote().urls, None) try:
self.status = 'unknown' self.remote = next(repo.remote().urls, None)
self.status = 'unknown'
except Exception:
self.remote = None
def list_files(self, subdir, extension): def list_files(self, subdir, extension):
from modules import scripts from modules import scripts
......
...@@ -22,6 +22,8 @@ from collections import defaultdict, deque ...@@ -22,6 +22,8 @@ from collections import defaultdict, deque
from statistics import stdev, mean from statistics import stdev, mean
optimizer_dict = {optim_name : cls_obj for optim_name, cls_obj in inspect.getmembers(torch.optim, inspect.isclass) if optim_name != "Optimizer"}
class HypernetworkModule(torch.nn.Module): class HypernetworkModule(torch.nn.Module):
multiplier = 1.0 multiplier = 1.0
activation_dict = { activation_dict = {
...@@ -142,6 +144,8 @@ class Hypernetwork: ...@@ -142,6 +144,8 @@ class Hypernetwork:
self.use_dropout = use_dropout self.use_dropout = use_dropout
self.activate_output = activate_output self.activate_output = activate_output
self.last_layer_dropout = kwargs['last_layer_dropout'] if 'last_layer_dropout' in kwargs else True self.last_layer_dropout = kwargs['last_layer_dropout'] if 'last_layer_dropout' in kwargs else True
self.optimizer_name = None
self.optimizer_state_dict = None
for size in enable_sizes or []: for size in enable_sizes or []:
self.layers[size] = ( self.layers[size] = (
...@@ -163,6 +167,7 @@ class Hypernetwork: ...@@ -163,6 +167,7 @@ class Hypernetwork:
def save(self, filename): def save(self, filename):
state_dict = {} state_dict = {}
optimizer_saved_dict = {}
for k, v in self.layers.items(): for k, v in self.layers.items():
state_dict[k] = (v[0].state_dict(), v[1].state_dict()) state_dict[k] = (v[0].state_dict(), v[1].state_dict())
...@@ -178,8 +183,15 @@ class Hypernetwork: ...@@ -178,8 +183,15 @@ class Hypernetwork:
state_dict['sd_checkpoint_name'] = self.sd_checkpoint_name state_dict['sd_checkpoint_name'] = self.sd_checkpoint_name
state_dict['activate_output'] = self.activate_output state_dict['activate_output'] = self.activate_output
state_dict['last_layer_dropout'] = self.last_layer_dropout state_dict['last_layer_dropout'] = self.last_layer_dropout
if self.optimizer_name is not None:
optimizer_saved_dict['optimizer_name'] = self.optimizer_name
torch.save(state_dict, filename) torch.save(state_dict, filename)
if shared.opts.save_optimizer_state and self.optimizer_state_dict:
optimizer_saved_dict['hash'] = sd_models.model_hash(filename)
optimizer_saved_dict['optimizer_state_dict'] = self.optimizer_state_dict
torch.save(optimizer_saved_dict, filename + '.optim')
def load(self, filename): def load(self, filename):
self.filename = filename self.filename = filename
...@@ -202,6 +214,18 @@ class Hypernetwork: ...@@ -202,6 +214,18 @@ class Hypernetwork:
print(f"Activate last layer is set to {self.activate_output}") print(f"Activate last layer is set to {self.activate_output}")
self.last_layer_dropout = state_dict.get('last_layer_dropout', False) self.last_layer_dropout = state_dict.get('last_layer_dropout', False)
optimizer_saved_dict = torch.load(self.filename + '.optim', map_location = 'cpu') if os.path.exists(self.filename + '.optim') else {}
self.optimizer_name = optimizer_saved_dict.get('optimizer_name', 'AdamW')
print(f"Optimizer name is {self.optimizer_name}")
if sd_models.model_hash(filename) == optimizer_saved_dict.get('hash', None):
self.optimizer_state_dict = optimizer_saved_dict.get('optimizer_state_dict', None)
else:
self.optimizer_state_dict = None
if self.optimizer_state_dict:
print("Loaded existing optimizer from checkpoint")
else:
print("No saved optimizer exists in checkpoint")
for size, sd in state_dict.items(): for size, sd in state_dict.items():
if type(size) == int: if type(size) == int:
self.layers[size] = ( self.layers[size] = (
...@@ -219,11 +243,11 @@ class Hypernetwork: ...@@ -219,11 +243,11 @@ class Hypernetwork:
def list_hypernetworks(path): def list_hypernetworks(path):
res = {} res = {}
for filename in glob.iglob(os.path.join(path, '**/*.pt'), recursive=True): for filename in sorted(glob.iglob(os.path.join(path, '**/*.pt'), recursive=True)):
name = os.path.splitext(os.path.basename(filename))[0] name = os.path.splitext(os.path.basename(filename))[0]
# Prevent a hypothetical "None.pt" from being listed. # Prevent a hypothetical "None.pt" from being listed.
if name != "None": if name != "None":
res[name] = filename res[name + f"({sd_models.model_hash(filename)})"] = filename
return res return res
...@@ -358,6 +382,7 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log ...@@ -358,6 +382,7 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log
shared.state.textinfo = "Initializing hypernetwork training..." shared.state.textinfo = "Initializing hypernetwork training..."
shared.state.job_count = steps shared.state.job_count = steps
hypernetwork_name = hypernetwork_name.rsplit('(', 1)[0]
filename = os.path.join(shared.cmd_opts.hypernetwork_dir, f'{hypernetwork_name}.pt') filename = os.path.join(shared.cmd_opts.hypernetwork_dir, f'{hypernetwork_name}.pt')
log_directory = os.path.join(log_directory, datetime.datetime.now().strftime("%Y-%m-%d"), hypernetwork_name) log_directory = os.path.join(log_directory, datetime.datetime.now().strftime("%Y-%m-%d"), hypernetwork_name)
...@@ -410,8 +435,22 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log ...@@ -410,8 +435,22 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log
weights = hypernetwork.weights() weights = hypernetwork.weights()
for weight in weights: for weight in weights:
weight.requires_grad = True weight.requires_grad = True
# if optimizer == "AdamW": or else Adam / AdamW / SGD, etc...
optimizer = torch.optim.AdamW(weights, lr=scheduler.learn_rate) # Here we use optimizer from saved HN, or we can specify as UI option.
if hypernetwork.optimizer_name in optimizer_dict:
optimizer = optimizer_dict[hypernetwork.optimizer_name](params=weights, lr=scheduler.learn_rate)
optimizer_name = hypernetwork.optimizer_name
else:
print(f"Optimizer type {hypernetwork.optimizer_name} is not defined!")
optimizer = torch.optim.AdamW(params=weights, lr=scheduler.learn_rate)
optimizer_name = 'AdamW'
if hypernetwork.optimizer_state_dict: # This line must be changed if Optimizer type can be different from saved optimizer.
try:
optimizer.load_state_dict(hypernetwork.optimizer_state_dict)
except RuntimeError as e:
print("Cannot resume from saved optimizer!")
print(e)
steps_without_grad = 0 steps_without_grad = 0
...@@ -479,7 +518,11 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log ...@@ -479,7 +518,11 @@ def train_hypernetwork(hypernetwork_name, learn_rate, batch_size, data_root, log
# Before saving, change name to match current checkpoint. # Before saving, change name to match current checkpoint.
hypernetwork_name_every = f'{hypernetwork_name}-{steps_done}' hypernetwork_name_every = f'{hypernetwork_name}-{steps_done}'
last_saved_file = os.path.join(hypernetwork_dir, f'{hypernetwork_name_every}.pt') last_saved_file = os.path.join(hypernetwork_dir, f'{hypernetwork_name_every}.pt')
hypernetwork.optimizer_name = optimizer_name
if shared.opts.save_optimizer_state:
hypernetwork.optimizer_state_dict = optimizer.state_dict()
save_hypernetwork(hypernetwork, checkpoint, hypernetwork_name, last_saved_file) save_hypernetwork(hypernetwork, checkpoint, hypernetwork_name, last_saved_file)
hypernetwork.optimizer_state_dict = None # dereference it after saving, to save memory.
textual_inversion.write_loss(log_directory, "hypernetwork_loss.csv", hypernetwork.step, len(ds), { textual_inversion.write_loss(log_directory, "hypernetwork_loss.csv", hypernetwork.step, len(ds), {
"loss": f"{previous_mean_loss:.7f}", "loss": f"{previous_mean_loss:.7f}",
...@@ -542,8 +585,12 @@ Last saved image: {html.escape(last_saved_image)}<br/> ...@@ -542,8 +585,12 @@ Last saved image: {html.escape(last_saved_image)}<br/>
report_statistics(loss_dict) report_statistics(loss_dict)
filename = os.path.join(shared.cmd_opts.hypernetwork_dir, f'{hypernetwork_name}.pt') filename = os.path.join(shared.cmd_opts.hypernetwork_dir, f'{hypernetwork_name}.pt')
hypernetwork.optimizer_name = optimizer_name
if shared.opts.save_optimizer_state:
hypernetwork.optimizer_state_dict = optimizer.state_dict()
save_hypernetwork(hypernetwork, checkpoint, hypernetwork_name, filename) save_hypernetwork(hypernetwork, checkpoint, hypernetwork_name, filename)
del optimizer
hypernetwork.optimizer_state_dict = None # dereference it after saving, to save memory.
return hypernetwork, filename return hypernetwork, filename
def save_hypernetwork(hypernetwork, checkpoint, hypernetwork_name, filename): def save_hypernetwork(hypernetwork, checkpoint, hypernetwork_name, filename):
......
...@@ -9,7 +9,7 @@ from modules import devices, sd_hijack, shared ...@@ -9,7 +9,7 @@ from modules import devices, sd_hijack, shared
from modules.hypernetworks import hypernetwork from modules.hypernetworks import hypernetwork
not_available = ["hardswish", "multiheadattention"] not_available = ["hardswish", "multiheadattention"]
keys = ["linear"] + list(x for x in hypernetwork.HypernetworkModule.activation_dict.keys() if x not in not_available) keys = list(x for x in hypernetwork.HypernetworkModule.activation_dict.keys() if x not in not_available)
def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None, activation_func=None, weight_init=None, add_layer_norm=False, use_dropout=False): def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None, activation_func=None, weight_init=None, add_layer_norm=False, use_dropout=False):
# Remove illegal characters from name. # Remove illegal characters from name.
......
...@@ -101,8 +101,8 @@ class LDSR: ...@@ -101,8 +101,8 @@ class LDSR:
down_sample_rate = target_scale / 4 down_sample_rate = target_scale / 4
wd = width_og * down_sample_rate wd = width_og * down_sample_rate
hd = height_og * down_sample_rate hd = height_og * down_sample_rate
width_downsampled_pre = int(wd) width_downsampled_pre = int(np.ceil(wd))
height_downsampled_pre = int(hd) height_downsampled_pre = int(np.ceil(hd))
if down_sample_rate != 1: if down_sample_rate != 1:
print( print(
...@@ -110,7 +110,12 @@ class LDSR: ...@@ -110,7 +110,12 @@ class LDSR:
im_og = im_og.resize((width_downsampled_pre, height_downsampled_pre), Image.LANCZOS) im_og = im_og.resize((width_downsampled_pre, height_downsampled_pre), Image.LANCZOS)
else: else:
print(f"Down sample rate is 1 from {target_scale} / 4 (Not downsampling)") print(f"Down sample rate is 1 from {target_scale} / 4 (Not downsampling)")
logs = self.run(model["model"], im_og, diffusion_steps, eta)
# pad width and height to multiples of 64, pads with the edge values of image to avoid artifacts
pad_w, pad_h = np.max(((2, 2), np.ceil(np.array(im_og.size) / 64).astype(int)), axis=0) * 64 - im_og.size
im_padded = Image.fromarray(np.pad(np.array(im_og), ((0, pad_h), (0, pad_w), (0, 0)), mode='edge'))
logs = self.run(model["model"], im_padded, diffusion_steps, eta)
sample = logs["sample"] sample = logs["sample"]
sample = sample.detach().cpu() sample = sample.detach().cpu()
...@@ -120,6 +125,9 @@ class LDSR: ...@@ -120,6 +125,9 @@ class LDSR:
sample = np.transpose(sample, (0, 2, 3, 1)) sample = np.transpose(sample, (0, 2, 3, 1))
a = Image.fromarray(sample[0]) a = Image.fromarray(sample[0])
# remove padding
a = a.crop((0, 0) + tuple(np.array(im_og.size) * 4))
del model del model
gc.collect() gc.collect()
torch.cuda.empty_cache() torch.cuda.empty_cache()
......
...@@ -3,6 +3,7 @@ import os ...@@ -3,6 +3,7 @@ import os
import sys import sys
import traceback import traceback
localizations = {} localizations = {}
...@@ -16,6 +17,11 @@ def list_localizations(dirname): ...@@ -16,6 +17,11 @@ def list_localizations(dirname):
localizations[fn] = os.path.join(dirname, file) localizations[fn] = os.path.join(dirname, file)
from modules import scripts
for file in scripts.list_scripts("localizations", ".json"):
fn, ext = os.path.splitext(file.filename)
localizations[fn] = file.path
def localization_js(current_localization_name): def localization_js(current_localization_name):
fn = localizations.get(current_localization_name, None) fn = localizations.get(current_localization_name, None)
......
...@@ -23,11 +23,18 @@ def encode(*args): ...@@ -23,11 +23,18 @@ def encode(*args):
class RestrictedUnpickler(pickle.Unpickler): class RestrictedUnpickler(pickle.Unpickler):
extra_handler = None
def persistent_load(self, saved_id): def persistent_load(self, saved_id):
assert saved_id[0] == 'storage' assert saved_id[0] == 'storage'
return TypedStorage() return TypedStorage()
def find_class(self, module, name): def find_class(self, module, name):
if self.extra_handler is not None:
res = self.extra_handler(module, name)
if res is not None:
return res
if module == 'collections' and name == 'OrderedDict': if module == 'collections' and name == 'OrderedDict':
return getattr(collections, name) return getattr(collections, name)
if module == 'torch._utils' and name in ['_rebuild_tensor_v2', '_rebuild_parameter']: if module == 'torch._utils' and name in ['_rebuild_tensor_v2', '_rebuild_parameter']:
...@@ -52,7 +59,7 @@ class RestrictedUnpickler(pickle.Unpickler): ...@@ -52,7 +59,7 @@ class RestrictedUnpickler(pickle.Unpickler):
return set return set
# Forbid everything else. # Forbid everything else.
raise pickle.UnpicklingError(f"global '{module}/{name}' is forbidden") raise Exception(f"global '{module}/{name}' is forbidden")
allowed_zip_names = ["archive/data.pkl", "archive/version"] allowed_zip_names = ["archive/data.pkl", "archive/version"]
...@@ -69,7 +76,7 @@ def check_zip_filenames(filename, names): ...@@ -69,7 +76,7 @@ def check_zip_filenames(filename, names):
raise Exception(f"bad file inside {filename}: {name}") raise Exception(f"bad file inside {filename}: {name}")
def check_pt(filename): def check_pt(filename, extra_handler):
try: try:
# new pytorch format is a zip file # new pytorch format is a zip file
...@@ -78,6 +85,7 @@ def check_pt(filename): ...@@ -78,6 +85,7 @@ def check_pt(filename):
with z.open('archive/data.pkl') as file: with z.open('archive/data.pkl') as file:
unpickler = RestrictedUnpickler(file) unpickler = RestrictedUnpickler(file)
unpickler.extra_handler = extra_handler
unpickler.load() unpickler.load()
except zipfile.BadZipfile: except zipfile.BadZipfile:
...@@ -85,16 +93,42 @@ def check_pt(filename): ...@@ -85,16 +93,42 @@ def check_pt(filename):
# if it's not a zip file, it's an olf pytorch format, with five objects written to pickle # if it's not a zip file, it's an olf pytorch format, with five objects written to pickle
with open(filename, "rb") as file: with open(filename, "rb") as file:
unpickler = RestrictedUnpickler(file) unpickler = RestrictedUnpickler(file)
unpickler.extra_handler = extra_handler
for i in range(5): for i in range(5):
unpickler.load() unpickler.load()
def load(filename, *args, **kwargs): def load(filename, *args, **kwargs):
return load_with_extra(filename, *args, **kwargs)
def load_with_extra(filename, extra_handler=None, *args, **kwargs):
"""
this functon is intended to be used by extensions that want to load models with
some extra classes in them that the usual unpickler would find suspicious.
Use the extra_handler argument to specify a function that takes module and field name as text,
and returns that field's value:
```python
def extra(module, name):
if module == 'collections' and name == 'OrderedDict':
return collections.OrderedDict
return None
safe.load_with_extra('model.pt', extra_handler=extra)
```
The alternative to this is just to use safe.unsafe_torch_load('model.pt'), which as the name implies is
definitely unsafe.
"""
from modules import shared from modules import shared
try: try:
if not shared.cmd_opts.disable_safe_unpickle: if not shared.cmd_opts.disable_safe_unpickle:
check_pt(filename) check_pt(filename, extra_handler)
except pickle.UnpicklingError: except pickle.UnpicklingError:
print(f"Error verifying pickled file from {filename}:", file=sys.stderr) print(f"Error verifying pickled file from {filename}:", file=sys.stderr)
......
...@@ -46,25 +46,23 @@ class CFGDenoiserParams: ...@@ -46,25 +46,23 @@ class CFGDenoiserParams:
ScriptCallback = namedtuple("ScriptCallback", ["script", "callback"]) ScriptCallback = namedtuple("ScriptCallback", ["script", "callback"])
callbacks_app_started = [] callback_map = dict(
callbacks_model_loaded = [] callbacks_app_started=[],
callbacks_ui_tabs = [] callbacks_model_loaded=[],
callbacks_ui_settings = [] callbacks_ui_tabs=[],
callbacks_before_image_saved = [] callbacks_ui_settings=[],
callbacks_image_saved = [] callbacks_before_image_saved=[],
callbacks_cfg_denoiser = [] callbacks_image_saved=[],
callbacks_cfg_denoiser=[]
)
def clear_callbacks(): def clear_callbacks():
callbacks_model_loaded.clear() for callback_list in callback_map.values():
callbacks_ui_tabs.clear() callback_list.clear()
callbacks_ui_settings.clear()
callbacks_before_image_saved.clear()
callbacks_image_saved.clear()
callbacks_cfg_denoiser.clear()
def app_started_callback(demo: Optional[Blocks], app: FastAPI): def app_started_callback(demo: Optional[Blocks], app: FastAPI):
for c in callbacks_app_started: for c in callback_map['callbacks_app_started']:
try: try:
c.callback(demo, app) c.callback(demo, app)
except Exception: except Exception:
...@@ -72,7 +70,7 @@ def app_started_callback(demo: Optional[Blocks], app: FastAPI): ...@@ -72,7 +70,7 @@ def app_started_callback(demo: Optional[Blocks], app: FastAPI):
def model_loaded_callback(sd_model): def model_loaded_callback(sd_model):
for c in callbacks_model_loaded: for c in callback_map['callbacks_model_loaded']:
try: try:
c.callback(sd_model) c.callback(sd_model)
except Exception: except Exception:
...@@ -82,7 +80,7 @@ def model_loaded_callback(sd_model): ...@@ -82,7 +80,7 @@ def model_loaded_callback(sd_model):
def ui_tabs_callback(): def ui_tabs_callback():
res = [] res = []
for c in callbacks_ui_tabs: for c in callback_map['callbacks_ui_tabs']:
try: try:
res += c.callback() or [] res += c.callback() or []
except Exception: except Exception:
...@@ -92,7 +90,7 @@ def ui_tabs_callback(): ...@@ -92,7 +90,7 @@ def ui_tabs_callback():
def ui_settings_callback(): def ui_settings_callback():
for c in callbacks_ui_settings: for c in callback_map['callbacks_ui_settings']:
try: try:
c.callback() c.callback()
except Exception: except Exception:
...@@ -100,7 +98,7 @@ def ui_settings_callback(): ...@@ -100,7 +98,7 @@ def ui_settings_callback():
def before_image_saved_callback(params: ImageSaveParams): def before_image_saved_callback(params: ImageSaveParams):
for c in callbacks_before_image_saved: for c in callback_map['callbacks_before_image_saved']:
try: try:
c.callback(params) c.callback(params)
except Exception: except Exception:
...@@ -108,7 +106,7 @@ def before_image_saved_callback(params: ImageSaveParams): ...@@ -108,7 +106,7 @@ def before_image_saved_callback(params: ImageSaveParams):
def image_saved_callback(params: ImageSaveParams): def image_saved_callback(params: ImageSaveParams):
for c in callbacks_image_saved: for c in callback_map['callbacks_image_saved']:
try: try:
c.callback(params) c.callback(params)
except Exception: except Exception:
...@@ -116,7 +114,7 @@ def image_saved_callback(params: ImageSaveParams): ...@@ -116,7 +114,7 @@ def image_saved_callback(params: ImageSaveParams):
def cfg_denoiser_callback(params: CFGDenoiserParams): def cfg_denoiser_callback(params: CFGDenoiserParams):
for c in callbacks_cfg_denoiser: for c in callback_map['callbacks_cfg_denoiser']:
try: try:
c.callback(params) c.callback(params)
except Exception: except Exception:
...@@ -129,17 +127,33 @@ def add_callback(callbacks, fun): ...@@ -129,17 +127,33 @@ def add_callback(callbacks, fun):
callbacks.append(ScriptCallback(filename, fun)) callbacks.append(ScriptCallback(filename, fun))
def remove_current_script_callbacks():
stack = [x for x in inspect.stack() if x.filename != __file__]
filename = stack[0].filename if len(stack) > 0 else 'unknown file'
if filename == 'unknown file':
return
for callback_list in callback_map.values():
for callback_to_remove in [cb for cb in callback_list if cb.script == filename]:
callback_list.remove(callback_to_remove)
def remove_callbacks_for_function(callback_func):
for callback_list in callback_map.values():
for callback_to_remove in [cb for cb in callback_list if cb.callback == callback_func]:
callback_list.remove(callback_to_remove)
def on_app_started(callback): def on_app_started(callback):
"""register a function to be called when the webui started, the gradio `Block` component and """register a function to be called when the webui started, the gradio `Block` component and
fastapi `FastAPI` object are passed as the arguments""" fastapi `FastAPI` object are passed as the arguments"""
add_callback(callbacks_app_started, callback) add_callback(callback_map['callbacks_app_started'], callback)
def on_model_loaded(callback): def on_model_loaded(callback):
"""register a function to be called when the stable diffusion model is created; the model is """register a function to be called when the stable diffusion model is created; the model is
passed as an argument""" passed as an argument"""
add_callback(callbacks_model_loaded, callback) add_callback(callback_map['callbacks_model_loaded'], callback)
def on_ui_tabs(callback): def on_ui_tabs(callback):
...@@ -152,13 +166,13 @@ def on_ui_tabs(callback): ...@@ -152,13 +166,13 @@ def on_ui_tabs(callback):
title is tab text displayed to user in the UI title is tab text displayed to user in the UI
elem_id is HTML id for the tab elem_id is HTML id for the tab
""" """
add_callback(callbacks_ui_tabs, callback) add_callback(callback_map['callbacks_ui_tabs'], callback)
def on_ui_settings(callback): def on_ui_settings(callback):
"""register a function to be called before UI settings are populated; add your settings """register a function to be called before UI settings are populated; add your settings
by using shared.opts.add_option(shared.OptionInfo(...)) """ by using shared.opts.add_option(shared.OptionInfo(...)) """
add_callback(callbacks_ui_settings, callback) add_callback(callback_map['callbacks_ui_settings'], callback)
def on_before_image_saved(callback): def on_before_image_saved(callback):
...@@ -166,7 +180,7 @@ def on_before_image_saved(callback): ...@@ -166,7 +180,7 @@ def on_before_image_saved(callback):
The callback is called with one argument: The callback is called with one argument:
- params: ImageSaveParams - parameters the image is to be saved with. You can change fields in this object. - params: ImageSaveParams - parameters the image is to be saved with. You can change fields in this object.
""" """
add_callback(callbacks_before_image_saved, callback) add_callback(callback_map['callbacks_before_image_saved'], callback)
def on_image_saved(callback): def on_image_saved(callback):
...@@ -174,7 +188,7 @@ def on_image_saved(callback): ...@@ -174,7 +188,7 @@ def on_image_saved(callback):
The callback is called with one argument: The callback is called with one argument:
- params: ImageSaveParams - parameters the image was saved with. Changing fields in this object does nothing. - params: ImageSaveParams - parameters the image was saved with. Changing fields in this object does nothing.
""" """
add_callback(callbacks_image_saved, callback) add_callback(callback_map['callbacks_image_saved'], callback)
def on_cfg_denoiser(callback): def on_cfg_denoiser(callback):
...@@ -182,5 +196,4 @@ def on_cfg_denoiser(callback): ...@@ -182,5 +196,4 @@ def on_cfg_denoiser(callback):
The callback is called with one argument: The callback is called with one argument:
- params: CFGDenoiserParams - parameters to be passed to the inner model and sampling state details. - params: CFGDenoiserParams - parameters to be passed to the inner model and sampling state details.
""" """
add_callback(callbacks_cfg_denoiser, callback) add_callback(callback_map['callbacks_cfg_denoiser'], callback)
...@@ -3,7 +3,6 @@ import sys ...@@ -3,7 +3,6 @@ import sys
import traceback import traceback
from collections import namedtuple from collections import namedtuple
import modules.ui as ui
import gradio as gr import gradio as gr
from modules.processing import StableDiffusionProcessing from modules.processing import StableDiffusionProcessing
......
...@@ -24,11 +24,15 @@ samplers_k_diffusion = [ ...@@ -24,11 +24,15 @@ samplers_k_diffusion = [
('Heun', 'sample_heun', ['k_heun'], {}), ('Heun', 'sample_heun', ['k_heun'], {}),
('DPM2', 'sample_dpm_2', ['k_dpm_2'], {}), ('DPM2', 'sample_dpm_2', ['k_dpm_2'], {}),
('DPM2 a', 'sample_dpm_2_ancestral', ['k_dpm_2_a'], {}), ('DPM2 a', 'sample_dpm_2_ancestral', ['k_dpm_2_a'], {}),
('DPM++ 2S a', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a'], {}),
('DPM++ 2M', 'sample_dpmpp_2m', ['k_dpmpp_2m'], {}),
('DPM fast', 'sample_dpm_fast', ['k_dpm_fast'], {}), ('DPM fast', 'sample_dpm_fast', ['k_dpm_fast'], {}),
('DPM adaptive', 'sample_dpm_adaptive', ['k_dpm_ad'], {}), ('DPM adaptive', 'sample_dpm_adaptive', ['k_dpm_ad'], {}),
('LMS Karras', 'sample_lms', ['k_lms_ka'], {'scheduler': 'karras'}), ('LMS Karras', 'sample_lms', ['k_lms_ka'], {'scheduler': 'karras'}),
('DPM2 Karras', 'sample_dpm_2', ['k_dpm_2_ka'], {'scheduler': 'karras'}), ('DPM2 Karras', 'sample_dpm_2', ['k_dpm_2_ka'], {'scheduler': 'karras'}),
('DPM2 a Karras', 'sample_dpm_2_ancestral', ['k_dpm_2_a_ka'], {'scheduler': 'karras'}), ('DPM2 a Karras', 'sample_dpm_2_ancestral', ['k_dpm_2_a_ka'], {'scheduler': 'karras'}),
('DPM++ 2S a Karras', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a_ka'], {'scheduler': 'karras'}),
('DPM++ 2M Karras', 'sample_dpmpp_2m', ['k_dpmpp_2m_ka'], {'scheduler': 'karras'}),
] ]
samplers_data_k_diffusion = [ samplers_data_k_diffusion = [
......
...@@ -86,6 +86,10 @@ parser.add_argument("--nowebui", action='store_true', help="use api=True to laun ...@@ -86,6 +86,10 @@ parser.add_argument("--nowebui", action='store_true', help="use api=True to laun
parser.add_argument("--ui-debug-mode", action='store_true', help="Don't load model to quickly launch UI") parser.add_argument("--ui-debug-mode", action='store_true', help="Don't load model to quickly launch UI")
parser.add_argument("--device-id", type=str, help="Select the default CUDA device to use (export CUDA_VISIBLE_DEVICES=0,1,etc might be needed before)", default=None) parser.add_argument("--device-id", type=str, help="Select the default CUDA device to use (export CUDA_VISIBLE_DEVICES=0,1,etc might be needed before)", default=None)
parser.add_argument("--administrator", action='store_true', help="Administrator rights", default=False) parser.add_argument("--administrator", action='store_true', help="Administrator rights", default=False)
parser.add_argument("--cors-allow-origins", type=str, help="Allowed CORS origins", default=None)
parser.add_argument("--tls-keyfile", type=str, help="Partially enables TLS, requires --tls-certfile to fully function", default=None)
parser.add_argument("--tls-certfile", type=str, help="Partially enables TLS, requires --tls-keyfile to fully function", default=None)
parser.add_argument("--server-name", type=str, help="Sets hostname of server", default=None)
cmd_opts = parser.parse_args() cmd_opts = parser.parse_args()
restricted_opts = { restricted_opts = {
...@@ -147,9 +151,9 @@ class State: ...@@ -147,9 +151,9 @@ class State:
self.interrupted = True self.interrupted = True
def nextjob(self): def nextjob(self):
if opts.show_progress_every_n_steps == -1: if opts.show_progress_every_n_steps == -1:
self.do_set_current_image() self.do_set_current_image()
self.job_no += 1 self.job_no += 1
self.sampling_step = 0 self.sampling_step = 0
self.current_image_sampling_step = 0 self.current_image_sampling_step = 0
...@@ -198,7 +202,7 @@ class State: ...@@ -198,7 +202,7 @@ class State:
return return
if self.current_latent is None: if self.current_latent is None:
return return
if opts.show_progress_grid: if opts.show_progress_grid:
self.current_image = sd_samplers.samples_to_image_grid(self.current_latent) self.current_image = sd_samplers.samples_to_image_grid(self.current_latent)
else: else:
...@@ -217,8 +221,6 @@ interrogator = modules.interrogate.InterrogateModels("interrogate") ...@@ -217,8 +221,6 @@ interrogator = modules.interrogate.InterrogateModels("interrogate")
face_restorers = [] face_restorers = []
localization.list_localizations(cmd_opts.localizations_dir)
def realesrgan_models_names(): def realesrgan_models_names():
import modules.realesrgan_model import modules.realesrgan_model
...@@ -317,6 +319,7 @@ options_templates.update(options_section(('system', "System"), { ...@@ -317,6 +319,7 @@ options_templates.update(options_section(('system', "System"), {
options_templates.update(options_section(('training', "Training"), { options_templates.update(options_section(('training', "Training"), {
"unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible. Saves VRAM."), "unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible. Saves VRAM."),
"save_optimizer_state": OptionInfo(False, "Saves Optimizer state as separate *.optim file. Training can be resumed with HN itself and matching optim file."),
"dataset_filename_word_regex": OptionInfo("", "Filename word regex"), "dataset_filename_word_regex": OptionInfo("", "Filename word regex"),
"dataset_filename_join_string": OptionInfo(" ", "Filename join string"), "dataset_filename_join_string": OptionInfo(" ", "Filename join string"),
"training_image_repeats_per_epoch": OptionInfo(1, "Number of repeats for a single input image per epoch; used only for displaying epoch number", gr.Number, {"precision": 0}), "training_image_repeats_per_epoch": OptionInfo(1, "Number of repeats for a single input image per epoch; used only for displaying epoch number", gr.Number, {"precision": 0}),
...@@ -406,7 +409,8 @@ class Options: ...@@ -406,7 +409,8 @@ class Options:
if key in self.data or key in self.data_labels: if key in self.data or key in self.data_labels:
assert not cmd_opts.freeze_settings, "changing settings is disabled" assert not cmd_opts.freeze_settings, "changing settings is disabled"
comp_args = opts.data_labels[key].component_args info = opts.data_labels.get(key, None)
comp_args = info.component_args if info else None
if isinstance(comp_args, dict) and comp_args.get('visible', True) is False: if isinstance(comp_args, dict) and comp_args.get('visible', True) is False:
raise RuntimeError(f"not possible to set {key} because it is restricted") raise RuntimeError(f"not possible to set {key} because it is restricted")
......
...@@ -174,9 +174,9 @@ def save_pil_to_file(pil_image, dir=None): ...@@ -174,9 +174,9 @@ def save_pil_to_file(pil_image, dir=None):
gr.processing_utils.save_pil_to_file = save_pil_to_file gr.processing_utils.save_pil_to_file = save_pil_to_file
def wrap_gradio_call(func, extra_outputs=None): def wrap_gradio_call(func, extra_outputs=None, add_stats=False):
def f(*args, extra_outputs_array=extra_outputs, **kwargs): def f(*args, extra_outputs_array=extra_outputs, **kwargs):
run_memmon = opts.memmon_poll_rate > 0 and not shared.mem_mon.disabled run_memmon = opts.memmon_poll_rate > 0 and not shared.mem_mon.disabled and add_stats
if run_memmon: if run_memmon:
shared.mem_mon.monitor() shared.mem_mon.monitor()
t = time.perf_counter() t = time.perf_counter()
...@@ -203,11 +203,18 @@ def wrap_gradio_call(func, extra_outputs=None): ...@@ -203,11 +203,18 @@ def wrap_gradio_call(func, extra_outputs=None):
res = extra_outputs_array + [f"<div class='error'>{plaintext_to_html(type(e).__name__+': '+str(e))}</div>"] res = extra_outputs_array + [f"<div class='error'>{plaintext_to_html(type(e).__name__+': '+str(e))}</div>"]
shared.state.skipped = False
shared.state.interrupted = False
shared.state.job_count = 0
if not add_stats:
return tuple(res)
elapsed = time.perf_counter() - t elapsed = time.perf_counter() - t
elapsed_m = int(elapsed // 60) elapsed_m = int(elapsed // 60)
elapsed_s = elapsed % 60 elapsed_s = elapsed % 60
elapsed_text = f"{elapsed_s:.2f}s" elapsed_text = f"{elapsed_s:.2f}s"
if (elapsed_m > 0): if elapsed_m > 0:
elapsed_text = f"{elapsed_m}m "+elapsed_text elapsed_text = f"{elapsed_m}m "+elapsed_text
if run_memmon: if run_memmon:
...@@ -225,10 +232,6 @@ def wrap_gradio_call(func, extra_outputs=None): ...@@ -225,10 +232,6 @@ def wrap_gradio_call(func, extra_outputs=None):
# last item is always HTML # last item is always HTML
res[-1] += f"<div class='performance'><p class='time'>Time taken: <wbr>{elapsed_text}</p>{vram_html}</div>" res[-1] += f"<div class='performance'><p class='time'>Time taken: <wbr>{elapsed_text}</p>{vram_html}</div>"
shared.state.skipped = False
shared.state.interrupted = False
shared.state.job_count = 0
return tuple(res) return tuple(res)
return f return f
...@@ -1138,7 +1141,7 @@ def create_ui(wrap_gradio_gpu_call): ...@@ -1138,7 +1141,7 @@ def create_ui(wrap_gradio_gpu_call):
outputs=[html, generation_info, html2], outputs=[html, generation_info, html2],
) )
with gr.Blocks() as modelmerger_interface: with gr.Blocks(analytics_enabled=False) as modelmerger_interface:
with gr.Row().style(equal_height=False): with gr.Row().style(equal_height=False):
with gr.Column(variant='panel'): with gr.Column(variant='panel'):
gr.HTML(value="<p>A merger of the two checkpoints will be generated in your <b>checkpoint</b> directory.</p>") gr.HTML(value="<p>A merger of the two checkpoints will be generated in your <b>checkpoint</b> directory.</p>")
...@@ -1158,7 +1161,7 @@ def create_ui(wrap_gradio_gpu_call): ...@@ -1158,7 +1161,7 @@ def create_ui(wrap_gradio_gpu_call):
sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings() sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings()
with gr.Blocks() as train_interface: with gr.Blocks(analytics_enabled=False) as train_interface:
with gr.Row().style(equal_height=False): with gr.Row().style(equal_height=False):
gr.HTML(value="<p style='margin-bottom: 0.7em'>See <b><a href=\"https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Textual-Inversion\">wiki</a></b> for detailed explanation.</p>") gr.HTML(value="<p style='margin-bottom: 0.7em'>See <b><a href=\"https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Textual-Inversion\">wiki</a></b> for detailed explanation.</p>")
...@@ -1423,15 +1426,14 @@ def create_ui(wrap_gradio_gpu_call): ...@@ -1423,15 +1426,14 @@ def create_ui(wrap_gradio_gpu_call):
if info.refresh is not None: if info.refresh is not None:
if is_quicksettings: if is_quicksettings:
res = comp(label=info.label, value=fun, elem_id=elem_id, **(args or {})) res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {}))
create_refresh_button(res, info.refresh, info.component_args, "refresh_" + key) create_refresh_button(res, info.refresh, info.component_args, "refresh_" + key)
else: else:
with gr.Row(variant="compact"): with gr.Row(variant="compact"):
res = comp(label=info.label, value=fun, elem_id=elem_id, **(args or {})) res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {}))
create_refresh_button(res, info.refresh, info.component_args, "refresh_" + key) create_refresh_button(res, info.refresh, info.component_args, "refresh_" + key)
else: else:
res = comp(label=info.label, value=fun, elem_id=elem_id, **(args or {})) res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {}))
return res return res
...@@ -1442,7 +1444,7 @@ def create_ui(wrap_gradio_gpu_call): ...@@ -1442,7 +1444,7 @@ def create_ui(wrap_gradio_gpu_call):
opts.reorder() opts.reorder()
def run_settings(*args): def run_settings(*args):
changed = 0 changed = []
for key, value, comp in zip(opts.data_labels.keys(), args, components): for key, value, comp in zip(opts.data_labels.keys(), args, components):
assert comp == dummy_component or opts.same_type(value, opts.data_labels[key].default), f"Bad value for setting {key}: {value}; expecting {type(opts.data_labels[key].default).__name__}" assert comp == dummy_component or opts.same_type(value, opts.data_labels[key].default), f"Bad value for setting {key}: {value}; expecting {type(opts.data_labels[key].default).__name__}"
...@@ -1452,18 +1454,20 @@ def create_ui(wrap_gradio_gpu_call): ...@@ -1452,18 +1454,20 @@ def create_ui(wrap_gradio_gpu_call):
continue continue
oldval = opts.data.get(key, None) oldval = opts.data.get(key, None)
try:
setattr(opts, key, value) setattr(opts, key, value)
except RuntimeError:
continue
if oldval != value: if oldval != value:
if opts.data_labels[key].onchange is not None: if opts.data_labels[key].onchange is not None:
opts.data_labels[key].onchange() opts.data_labels[key].onchange()
changed += 1 changed.append(key)
try:
opts.save(shared.config_filename) opts.save(shared.config_filename)
except RuntimeError:
return opts.dumpjson(), f'{changed} settings changed.' return opts.dumpjson(), f'{len(changed)} settings changed without save: {", ".join(changed)}.'
return opts.dumpjson(), f'{len(changed)} settings changed: {", ".join(changed)}.'
def run_settings_single(value, key): def run_settings_single(value, key):
if not opts.same_type(value, opts.data_labels[key].default): if not opts.same_type(value, opts.data_labels[key].default):
...@@ -1567,11 +1571,10 @@ def create_ui(wrap_gradio_gpu_call): ...@@ -1567,11 +1571,10 @@ def create_ui(wrap_gradio_gpu_call):
shared.state.need_restart = True shared.state.need_restart = True
restart_gradio.click( restart_gradio.click(
fn=request_restart, fn=request_restart,
_js='restart_reload',
inputs=[], inputs=[],
outputs=[], outputs=[],
_js='restart_reload'
) )
if column is not None: if column is not None:
...@@ -1641,6 +1644,17 @@ def create_ui(wrap_gradio_gpu_call): ...@@ -1641,6 +1644,17 @@ def create_ui(wrap_gradio_gpu_call):
outputs=[component, text_settings], outputs=[component, text_settings],
) )
component_keys = [k for k in opts.data_labels.keys() if k in component_dict]
def get_settings_values():
return [getattr(opts, key) for key in component_keys]
demo.load(
fn=get_settings_values,
inputs=[],
outputs=[component_dict[k] for k in component_keys],
)
def modelmerger(*args): def modelmerger(*args):
try: try:
results = modules.extras.run_modelmerger(*args) results = modules.extras.run_modelmerger(*args)
......
...@@ -140,13 +140,15 @@ def install_extension_from_url(dirname, url): ...@@ -140,13 +140,15 @@ def install_extension_from_url(dirname, url):
shutil.rmtree(tmpdir, True) shutil.rmtree(tmpdir, True)
def install_extension_from_index(url): def install_extension_from_index(url, hide_tags):
ext_table, message = install_extension_from_url(None, url) ext_table, message = install_extension_from_url(None, url)
return refresh_available_extensions_from_data(), ext_table, message code, _ = refresh_available_extensions_from_data(hide_tags)
return code, ext_table, message
def refresh_available_extensions(url):
def refresh_available_extensions(url, hide_tags):
global available_extensions global available_extensions
import urllib.request import urllib.request
...@@ -155,13 +157,25 @@ def refresh_available_extensions(url): ...@@ -155,13 +157,25 @@ def refresh_available_extensions(url):
available_extensions = json.loads(text) available_extensions = json.loads(text)
return url, refresh_available_extensions_from_data(), '' code, tags = refresh_available_extensions_from_data(hide_tags)
return url, code, gr.CheckboxGroup.update(choices=tags), ''
def refresh_available_extensions_for_tags(hide_tags):
code, _ = refresh_available_extensions_from_data(hide_tags)
return code, ''
def refresh_available_extensions_from_data():
def refresh_available_extensions_from_data(hide_tags):
extlist = available_extensions["extensions"] extlist = available_extensions["extensions"]
installed_extension_urls = {normalize_git_url(extension.remote): extension.name for extension in extensions.extensions} installed_extension_urls = {normalize_git_url(extension.remote): extension.name for extension in extensions.extensions}
tags = available_extensions.get("tags", {})
tags_to_hide = set(hide_tags)
hidden = 0
code = f"""<!-- {time.time()} --> code = f"""<!-- {time.time()} -->
<table id="available_extensions"> <table id="available_extensions">
<thead> <thead>
...@@ -178,17 +192,24 @@ def refresh_available_extensions_from_data(): ...@@ -178,17 +192,24 @@ def refresh_available_extensions_from_data():
name = ext.get("name", "noname") name = ext.get("name", "noname")
url = ext.get("url", None) url = ext.get("url", None)
description = ext.get("description", "") description = ext.get("description", "")
extension_tags = ext.get("tags", [])
if url is None: if url is None:
continue continue
if len([x for x in extension_tags if x in tags_to_hide]) > 0:
hidden += 1
continue
existing = installed_extension_urls.get(normalize_git_url(url), None) existing = installed_extension_urls.get(normalize_git_url(url), None)
install_code = f"""<input onclick="install_extension_from_index(this, '{html.escape(url)}')" type="button" value="{"Install" if not existing else "Installed"}" {"disabled=disabled" if existing else ""} class="gr-button gr-button-lg gr-button-secondary">""" install_code = f"""<input onclick="install_extension_from_index(this, '{html.escape(url)}')" type="button" value="{"Install" if not existing else "Installed"}" {"disabled=disabled" if existing else ""} class="gr-button gr-button-lg gr-button-secondary">"""
tags_text = ", ".join([f"<span class='extension-tag' title='{tags.get(x, '')}'>{x}</span>" for x in extension_tags])
code += f""" code += f"""
<tr> <tr>
<td><a href="{html.escape(url)}">{html.escape(name)}</a></td> <td><a href="{html.escape(url)}" target="_blank">{html.escape(name)}</a><br />{tags_text}</td>
<td>{html.escape(description)}</td> <td>{html.escape(description)}</td>
<td>{install_code}</td> <td>{install_code}</td>
</tr> </tr>
...@@ -199,7 +220,10 @@ def refresh_available_extensions_from_data(): ...@@ -199,7 +220,10 @@ def refresh_available_extensions_from_data():
</table> </table>
""" """
return code if hidden > 0:
code += f"<p>Extension hidden: {hidden}</p>"
return code, list(tags)
def create_ui(): def create_ui():
...@@ -238,21 +262,30 @@ def create_ui(): ...@@ -238,21 +262,30 @@ def create_ui():
extension_to_install = gr.Text(elem_id="extension_to_install", visible=False) extension_to_install = gr.Text(elem_id="extension_to_install", visible=False)
install_extension_button = gr.Button(elem_id="install_extension_button", visible=False) install_extension_button = gr.Button(elem_id="install_extension_button", visible=False)
with gr.Row():
hide_tags = gr.CheckboxGroup(value=["ads", "localization"], label="Hide extensions with tags", choices=["script", "ads", "localization"])
install_result = gr.HTML() install_result = gr.HTML()
available_extensions_table = gr.HTML() available_extensions_table = gr.HTML()
refresh_available_extensions_button.click( refresh_available_extensions_button.click(
fn=modules.ui.wrap_gradio_call(refresh_available_extensions, extra_outputs=[gr.update(), gr.update()]), fn=modules.ui.wrap_gradio_call(refresh_available_extensions, extra_outputs=[gr.update(), gr.update(), gr.update()]),
inputs=[available_extensions_index], inputs=[available_extensions_index, hide_tags],
outputs=[available_extensions_index, available_extensions_table, install_result], outputs=[available_extensions_index, available_extensions_table, hide_tags, install_result],
) )
install_extension_button.click( install_extension_button.click(
fn=modules.ui.wrap_gradio_call(install_extension_from_index, extra_outputs=[gr.update(), gr.update()]), fn=modules.ui.wrap_gradio_call(install_extension_from_index, extra_outputs=[gr.update(), gr.update()]),
inputs=[extension_to_install], inputs=[extension_to_install, hide_tags],
outputs=[available_extensions_table, extensions_table, install_result], outputs=[available_extensions_table, extensions_table, install_result],
) )
hide_tags.change(
fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]),
inputs=[hide_tags],
outputs=[available_extensions_table, install_result]
)
with gr.TabItem("Install from URL"): with gr.TabItem("Install from URL"):
install_url = gr.Text(label="URL for extension's git repository") install_url = gr.Text(label="URL for extension's git repository")
install_dirname = gr.Text(label="Local directory name", placeholder="Leave empty for auto") install_dirname = gr.Text(label="Local directory name", placeholder="Leave empty for auto")
......
...@@ -57,10 +57,18 @@ class Upscaler: ...@@ -57,10 +57,18 @@ class Upscaler:
self.scale = scale self.scale = scale
dest_w = img.width * scale dest_w = img.width * scale
dest_h = img.height * scale dest_h = img.height * scale
for i in range(3): for i in range(3):
if img.width > dest_w and img.height > dest_h: shape = (img.width, img.height)
break
img = self.do_upscale(img, selected_model) img = self.do_upscale(img, selected_model)
if shape == (img.width, img.height):
break
if img.width >= dest_w and img.height >= dest_h:
break
if img.width != dest_w or img.height != dest_h: if img.width != dest_w or img.height != dest_h:
img = img.resize((int(dest_w), int(dest_h)), resample=LANCZOS) img = img.resize((int(dest_w), int(dest_h)), resample=LANCZOS)
......
...@@ -2,7 +2,7 @@ transformers==4.19.2 ...@@ -2,7 +2,7 @@ transformers==4.19.2
diffusers==0.3.0 diffusers==0.3.0
basicsr==1.4.2 basicsr==1.4.2
gfpgan==1.3.8 gfpgan==1.3.8
gradio==3.8 gradio==3.9
numpy==1.23.3 numpy==1.23.3
Pillow==9.2.0 Pillow==9.2.0
realesrgan==0.3.0 realesrgan==0.3.0
......
...@@ -563,6 +563,11 @@ img2maskimg, #img2maskimg > .h-60, #img2maskimg > .h-60 > div, #img2maskimg > .h ...@@ -563,6 +563,11 @@ img2maskimg, #img2maskimg > .h-60, #img2maskimg > .h-60 > div, #img2maskimg > .h
opacity: 0.5; opacity: 0.5;
} }
.extension-tag{
font-weight: bold;
font-size: 95%;
}
/* The following handles localization for right-to-left (RTL) languages like Arabic. /* The following handles localization for right-to-left (RTL) languages like Arabic.
The rtl media type will only be activated by the logic in javascript/localization.js. The rtl media type will only be activated by the logic in javascript/localization.js.
If you change anything above, you need to make sure it is RTL compliant by just running If you change anything above, you need to make sure it is RTL compliant by just running
......
...@@ -5,11 +5,12 @@ import importlib ...@@ -5,11 +5,12 @@ import importlib
import signal import signal
import threading import threading
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware from fastapi.middleware.gzip import GZipMiddleware
from modules.paths import script_path from modules.paths import script_path
from modules import devices, sd_samplers, upscaler, extensions from modules import devices, sd_samplers, upscaler, extensions, localization
import modules.codeformer_model as codeformer import modules.codeformer_model as codeformer
import modules.extras import modules.extras
import modules.face_restoration import modules.face_restoration
...@@ -27,14 +28,12 @@ import modules.txt2img ...@@ -27,14 +28,12 @@ import modules.txt2img
import modules.script_callbacks import modules.script_callbacks
import modules.ui import modules.ui
from modules import devices
from modules import modelloader from modules import modelloader
from modules.paths import script_path
from modules.shared import cmd_opts from modules.shared import cmd_opts
import modules.hypernetworks.hypernetwork import modules.hypernetworks.hypernetwork
queue_lock = threading.Lock() queue_lock = threading.Lock()
server_name = "0.0.0.0" if cmd_opts.listen else cmd_opts.server_name
def wrap_queued_call(func): def wrap_queued_call(func):
def f(*args, **kwargs): def f(*args, **kwargs):
...@@ -58,11 +57,12 @@ def wrap_gradio_gpu_call(func, extra_outputs=None): ...@@ -58,11 +57,12 @@ def wrap_gradio_gpu_call(func, extra_outputs=None):
return res return res
return modules.ui.wrap_gradio_call(f, extra_outputs=extra_outputs) return modules.ui.wrap_gradio_call(f, extra_outputs=extra_outputs, add_stats=True)
def initialize(): def initialize():
extensions.list_extensions() extensions.list_extensions()
localization.list_localizations(cmd_opts.localizations_dir)
if cmd_opts.ui_debug_mode: if cmd_opts.ui_debug_mode:
shared.sd_upscalers = upscaler.UpscalerLanczos().scalers shared.sd_upscalers = upscaler.UpscalerLanczos().scalers
...@@ -85,6 +85,19 @@ def initialize(): ...@@ -85,6 +85,19 @@ def initialize():
shared.opts.onchange("sd_hypernetwork", wrap_queued_call(lambda: modules.hypernetworks.hypernetwork.load_hypernetwork(shared.opts.sd_hypernetwork))) shared.opts.onchange("sd_hypernetwork", wrap_queued_call(lambda: modules.hypernetworks.hypernetwork.load_hypernetwork(shared.opts.sd_hypernetwork)))
shared.opts.onchange("sd_hypernetwork_strength", modules.hypernetworks.hypernetwork.apply_strength) shared.opts.onchange("sd_hypernetwork_strength", modules.hypernetworks.hypernetwork.apply_strength)
if cmd_opts.tls_keyfile is not None and cmd_opts.tls_keyfile is not None:
try:
if not os.path.exists(cmd_opts.tls_keyfile):
print("Invalid path to TLS keyfile given")
if not os.path.exists(cmd_opts.tls_certfile):
print(f"Invalid path to TLS certfile: '{cmd_opts.tls_certfile}'")
except TypeError:
cmd_opts.tls_keyfile = cmd_opts.tls_certfile = None
print("TLS setup invalid, running webui without TLS")
else:
print("Running with TLS")
# make the program just exit at ctrl+c without waiting for anything # make the program just exit at ctrl+c without waiting for anything
def sigint_handler(sig, frame): def sigint_handler(sig, frame):
print(f'Interrupted with signal {sig} in {frame}') print(f'Interrupted with signal {sig} in {frame}')
...@@ -93,6 +106,11 @@ def initialize(): ...@@ -93,6 +106,11 @@ def initialize():
signal.signal(signal.SIGINT, sigint_handler) signal.signal(signal.SIGINT, sigint_handler)
def setup_cors(app):
if cmd_opts.cors_allow_origins:
app.add_middleware(CORSMiddleware, allow_origins=cmd_opts.cors_allow_origins.split(','), allow_methods=['*'])
def create_api(app): def create_api(app):
from modules.api.api import Api from modules.api.api import Api
api = Api(app, queue_lock) api = Api(app, queue_lock)
...@@ -114,6 +132,7 @@ def api_only(): ...@@ -114,6 +132,7 @@ def api_only():
initialize() initialize()
app = FastAPI() app = FastAPI()
setup_cors(app)
app.add_middleware(GZipMiddleware, minimum_size=1000) app.add_middleware(GZipMiddleware, minimum_size=1000)
api = create_api(app) api = create_api(app)
...@@ -131,8 +150,10 @@ def webui(): ...@@ -131,8 +150,10 @@ def webui():
app, local_url, share_url = demo.launch( app, local_url, share_url = demo.launch(
share=cmd_opts.share, share=cmd_opts.share,
server_name="0.0.0.0" if cmd_opts.listen else None, server_name=server_name,
server_port=cmd_opts.port, server_port=cmd_opts.port,
ssl_keyfile=cmd_opts.tls_keyfile,
ssl_certfile=cmd_opts.tls_certfile,
debug=cmd_opts.gradio_debug, debug=cmd_opts.gradio_debug,
auth=[tuple(cred.split(':')) for cred in cmd_opts.gradio_auth.strip('"').split(',')] if cmd_opts.gradio_auth else None, auth=[tuple(cred.split(':')) for cred in cmd_opts.gradio_auth.strip('"').split(',')] if cmd_opts.gradio_auth else None,
inbrowser=cmd_opts.autolaunch, inbrowser=cmd_opts.autolaunch,
...@@ -147,6 +168,8 @@ def webui(): ...@@ -147,6 +168,8 @@ def webui():
# runnnig its code. We disable this here. Suggested by RyotaK. # runnnig its code. We disable this here. Suggested by RyotaK.
app.user_middleware = [x for x in app.user_middleware if x.cls.__name__ != 'CORSMiddleware'] app.user_middleware = [x for x in app.user_middleware if x.cls.__name__ != 'CORSMiddleware']
setup_cors(app)
app.add_middleware(GZipMiddleware, minimum_size=1000) app.add_middleware(GZipMiddleware, minimum_size=1000)
if launch_api: if launch_api:
...@@ -160,6 +183,9 @@ def webui(): ...@@ -160,6 +183,9 @@ def webui():
print('Reloading extensions') print('Reloading extensions')
extensions.list_extensions() extensions.list_extensions()
localization.list_localizations(cmd_opts.localizations_dir)
print('Reloading custom scripts') print('Reloading custom scripts')
modules.scripts.reload_scripts() modules.scripts.reload_scripts()
print('Reloading modules: modules.ui') print('Reloading modules: modules.ui')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment