mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-15 22:05:19 +00:00
updated decryption patch to 0.6.18
This commit is contained in:
parent
b8912aa671
commit
5788293d23
6 changed files with 191 additions and 1 deletions
|
|
@ -2250,6 +2250,28 @@ RAG_ALLOWED_FILE_EXTENSIONS = PersistentConfig(
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# File decryption settings
|
||||||
|
ENABLE_FILE_DECRYPTION = PersistentConfig(
|
||||||
|
"ENABLE_FILE_DECRYPTION",
|
||||||
|
"file_decryption.enable",
|
||||||
|
os.environ.get("ENABLE_FILE_DECRYPTION", "False").lower() == "true",
|
||||||
|
)
|
||||||
|
FILE_DECRYPTION_ENDPOINT = PersistentConfig(
|
||||||
|
"FILE_DECRYPTION_ENDPOINT",
|
||||||
|
"file_decryption.endpoint",
|
||||||
|
os.environ.get("FILE_DECRYPTION_ENDPOINT", ""),
|
||||||
|
)
|
||||||
|
FILE_DECRYPTION_API_KEY = PersistentConfig(
|
||||||
|
"FILE_DECRYPTION_API_KEY",
|
||||||
|
"file_decryption.api_key",
|
||||||
|
os.environ.get("FILE_DECRYPTION_API_KEY", ""),
|
||||||
|
)
|
||||||
|
FILE_DECRYPTION_TIMEOUT = PersistentConfig(
|
||||||
|
"FILE_DECRYPTION_TIMEOUT",
|
||||||
|
"file_decryption.timeout",
|
||||||
|
int(os.environ.get("FILE_DECRYPTION_TIMEOUT", "30")),
|
||||||
|
)
|
||||||
|
|
||||||
RAG_EMBEDDING_ENGINE = PersistentConfig(
|
RAG_EMBEDDING_ENGINE = PersistentConfig(
|
||||||
"RAG_EMBEDDING_ENGINE",
|
"RAG_EMBEDDING_ENGINE",
|
||||||
"rag.embedding_engine",
|
"rag.embedding_engine",
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,10 @@ from open_webui.config import (
|
||||||
RAG_AZURE_OPENAI_API_VERSION,
|
RAG_AZURE_OPENAI_API_VERSION,
|
||||||
RAG_OLLAMA_BASE_URL,
|
RAG_OLLAMA_BASE_URL,
|
||||||
RAG_OLLAMA_API_KEY,
|
RAG_OLLAMA_API_KEY,
|
||||||
|
ENABLE_FILE_DECRYPTION,
|
||||||
|
FILE_DECRYPTION_ENDPOINT,
|
||||||
|
FILE_DECRYPTION_API_KEY,
|
||||||
|
FILE_DECRYPTION_TIMEOUT,
|
||||||
CHUNK_OVERLAP,
|
CHUNK_OVERLAP,
|
||||||
CHUNK_SIZE,
|
CHUNK_SIZE,
|
||||||
CONTENT_EXTRACTION_ENGINE,
|
CONTENT_EXTRACTION_ENGINE,
|
||||||
|
|
@ -818,6 +822,11 @@ app.state.config.RAG_AZURE_OPENAI_API_VERSION = RAG_AZURE_OPENAI_API_VERSION
|
||||||
app.state.config.RAG_OLLAMA_BASE_URL = RAG_OLLAMA_BASE_URL
|
app.state.config.RAG_OLLAMA_BASE_URL = RAG_OLLAMA_BASE_URL
|
||||||
app.state.config.RAG_OLLAMA_API_KEY = RAG_OLLAMA_API_KEY
|
app.state.config.RAG_OLLAMA_API_KEY = RAG_OLLAMA_API_KEY
|
||||||
|
|
||||||
|
app.state.config.ENABLE_FILE_DECRYPTION = ENABLE_FILE_DECRYPTION
|
||||||
|
app.state.config.FILE_DECRYPTION_ENDPOINT = FILE_DECRYPTION_ENDPOINT
|
||||||
|
app.state.config.FILE_DECRYPTION_API_KEY = FILE_DECRYPTION_API_KEY
|
||||||
|
app.state.config.FILE_DECRYPTION_TIMEOUT = FILE_DECRYPTION_TIMEOUT
|
||||||
|
|
||||||
app.state.config.PDF_EXTRACT_IMAGES = PDF_EXTRACT_IMAGES
|
app.state.config.PDF_EXTRACT_IMAGES = PDF_EXTRACT_IMAGES
|
||||||
|
|
||||||
app.state.config.YOUTUBE_LOADER_LANGUAGE = YOUTUBE_LOADER_LANGUAGE
|
app.state.config.YOUTUBE_LOADER_LANGUAGE = YOUTUBE_LOADER_LANGUAGE
|
||||||
|
|
@ -1616,6 +1625,7 @@ async def get_app_config(request: Request):
|
||||||
"width": app.state.config.FILE_IMAGE_COMPRESSION_WIDTH,
|
"width": app.state.config.FILE_IMAGE_COMPRESSION_WIDTH,
|
||||||
"height": app.state.config.FILE_IMAGE_COMPRESSION_HEIGHT,
|
"height": app.state.config.FILE_IMAGE_COMPRESSION_HEIGHT,
|
||||||
},
|
},
|
||||||
|
"decryption_enabled": app.state.config.ENABLE_FILE_DECRYPTION,
|
||||||
},
|
},
|
||||||
"permissions": {**app.state.config.USER_PERMISSIONS},
|
"permissions": {**app.state.config.USER_PERMISSIONS},
|
||||||
"google_drive": {
|
"google_drive": {
|
||||||
|
|
|
||||||
|
|
@ -397,6 +397,11 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
|
||||||
"TOP_K_RERANKER": request.app.state.config.TOP_K_RERANKER,
|
"TOP_K_RERANKER": request.app.state.config.TOP_K_RERANKER,
|
||||||
"RELEVANCE_THRESHOLD": request.app.state.config.RELEVANCE_THRESHOLD,
|
"RELEVANCE_THRESHOLD": request.app.state.config.RELEVANCE_THRESHOLD,
|
||||||
"HYBRID_BM25_WEIGHT": request.app.state.config.HYBRID_BM25_WEIGHT,
|
"HYBRID_BM25_WEIGHT": request.app.state.config.HYBRID_BM25_WEIGHT,
|
||||||
|
# File decryption settings
|
||||||
|
"ENABLE_FILE_DECRYPTION": request.app.state.config.ENABLE_FILE_DECRYPTION,
|
||||||
|
"FILE_DECRYPTION_ENDPOINT": request.app.state.config.FILE_DECRYPTION_ENDPOINT,
|
||||||
|
"FILE_DECRYPTION_API_KEY": request.app.state.config.FILE_DECRYPTION_API_KEY,
|
||||||
|
"FILE_DECRYPTION_TIMEOUT": request.app.state.config.FILE_DECRYPTION_TIMEOUT,
|
||||||
# Content extraction settings
|
# Content extraction settings
|
||||||
"CONTENT_EXTRACTION_ENGINE": request.app.state.config.CONTENT_EXTRACTION_ENGINE,
|
"CONTENT_EXTRACTION_ENGINE": request.app.state.config.CONTENT_EXTRACTION_ENGINE,
|
||||||
"PDF_EXTRACT_IMAGES": request.app.state.config.PDF_EXTRACT_IMAGES,
|
"PDF_EXTRACT_IMAGES": request.app.state.config.PDF_EXTRACT_IMAGES,
|
||||||
|
|
@ -611,6 +616,12 @@ class ConfigForm(BaseModel):
|
||||||
ENABLE_GOOGLE_DRIVE_INTEGRATION: Optional[bool] = None
|
ENABLE_GOOGLE_DRIVE_INTEGRATION: Optional[bool] = None
|
||||||
ENABLE_ONEDRIVE_INTEGRATION: Optional[bool] = None
|
ENABLE_ONEDRIVE_INTEGRATION: Optional[bool] = None
|
||||||
|
|
||||||
|
# File decryption settings
|
||||||
|
ENABLE_FILE_DECRYPTION: Optional[bool] = None
|
||||||
|
FILE_DECRYPTION_ENDPOINT: Optional[str] = None
|
||||||
|
FILE_DECRYPTION_API_KEY: Optional[str] = None
|
||||||
|
FILE_DECRYPTION_TIMEOUT: Optional[int] = None
|
||||||
|
|
||||||
# Web search settings
|
# Web search settings
|
||||||
web: Optional[WebConfig] = None
|
web: Optional[WebConfig] = None
|
||||||
|
|
||||||
|
|
@ -889,6 +900,28 @@ async def update_rag_config(
|
||||||
else request.app.state.config.ENABLE_ONEDRIVE_INTEGRATION
|
else request.app.state.config.ENABLE_ONEDRIVE_INTEGRATION
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# File decryption settings
|
||||||
|
request.app.state.config.ENABLE_FILE_DECRYPTION = (
|
||||||
|
form_data.ENABLE_FILE_DECRYPTION
|
||||||
|
if form_data.ENABLE_FILE_DECRYPTION is not None
|
||||||
|
else request.app.state.config.ENABLE_FILE_DECRYPTION
|
||||||
|
)
|
||||||
|
request.app.state.config.FILE_DECRYPTION_ENDPOINT = (
|
||||||
|
form_data.FILE_DECRYPTION_ENDPOINT
|
||||||
|
if form_data.FILE_DECRYPTION_ENDPOINT is not None
|
||||||
|
else request.app.state.config.FILE_DECRYPTION_ENDPOINT
|
||||||
|
)
|
||||||
|
request.app.state.config.FILE_DECRYPTION_API_KEY = (
|
||||||
|
form_data.FILE_DECRYPTION_API_KEY
|
||||||
|
if form_data.FILE_DECRYPTION_API_KEY is not None
|
||||||
|
else request.app.state.config.FILE_DECRYPTION_API_KEY
|
||||||
|
)
|
||||||
|
request.app.state.config.FILE_DECRYPTION_TIMEOUT = (
|
||||||
|
form_data.FILE_DECRYPTION_TIMEOUT
|
||||||
|
if form_data.FILE_DECRYPTION_TIMEOUT is not None
|
||||||
|
else request.app.state.config.FILE_DECRYPTION_TIMEOUT
|
||||||
|
)
|
||||||
|
|
||||||
if form_data.web is not None:
|
if form_data.web is not None:
|
||||||
# Web search settings
|
# Web search settings
|
||||||
request.app.state.config.ENABLE_WEB_SEARCH = form_data.web.ENABLE_WEB_SEARCH
|
request.app.state.config.ENABLE_WEB_SEARCH = form_data.web.ENABLE_WEB_SEARCH
|
||||||
|
|
@ -1045,6 +1078,11 @@ async def update_rag_config(
|
||||||
# Integration settings
|
# Integration settings
|
||||||
"ENABLE_GOOGLE_DRIVE_INTEGRATION": request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
|
"ENABLE_GOOGLE_DRIVE_INTEGRATION": request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
|
||||||
"ENABLE_ONEDRIVE_INTEGRATION": request.app.state.config.ENABLE_ONEDRIVE_INTEGRATION,
|
"ENABLE_ONEDRIVE_INTEGRATION": request.app.state.config.ENABLE_ONEDRIVE_INTEGRATION,
|
||||||
|
# File decryption settings
|
||||||
|
"ENABLE_FILE_DECRYPTION": request.app.state.config.ENABLE_FILE_DECRYPTION,
|
||||||
|
"FILE_DECRYPTION_ENDPOINT": request.app.state.config.FILE_DECRYPTION_ENDPOINT,
|
||||||
|
"FILE_DECRYPTION_API_KEY": request.app.state.config.FILE_DECRYPTION_API_KEY,
|
||||||
|
"FILE_DECRYPTION_TIMEOUT": request.app.state.config.FILE_DECRYPTION_TIMEOUT,
|
||||||
# Web search settings
|
# Web search settings
|
||||||
"web": {
|
"web": {
|
||||||
"ENABLE_WEB_SEARCH": request.app.state.config.ENABLE_WEB_SEARCH,
|
"ENABLE_WEB_SEARCH": request.app.state.config.ENABLE_WEB_SEARCH,
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ from open_webui.config import (
|
||||||
AZURE_STORAGE_KEY,
|
AZURE_STORAGE_KEY,
|
||||||
STORAGE_PROVIDER,
|
STORAGE_PROVIDER,
|
||||||
UPLOAD_DIR,
|
UPLOAD_DIR,
|
||||||
|
ENABLE_FILE_DECRYPTION,
|
||||||
|
FILE_DECRYPTION_ENDPOINT,
|
||||||
|
FILE_DECRYPTION_API_KEY,
|
||||||
|
FILE_DECRYPTION_TIMEOUT,
|
||||||
)
|
)
|
||||||
from google.cloud import storage
|
from google.cloud import storage
|
||||||
from google.cloud.exceptions import GoogleCloudError, NotFound
|
from google.cloud.exceptions import GoogleCloudError, NotFound
|
||||||
|
|
@ -35,11 +39,36 @@ from azure.storage.blob import BlobServiceClient
|
||||||
from azure.core.exceptions import ResourceNotFoundError
|
from azure.core.exceptions import ResourceNotFoundError
|
||||||
from open_webui.env import SRC_LOG_LEVELS
|
from open_webui.env import SRC_LOG_LEVELS
|
||||||
|
|
||||||
|
from open_webui.utils.decryption import (
|
||||||
|
decrypt_file_via_azure,
|
||||||
|
DecryptionError,
|
||||||
|
)
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.setLevel(SRC_LOG_LEVELS["MAIN"])
|
log.setLevel(SRC_LOG_LEVELS["MAIN"])
|
||||||
|
|
||||||
|
|
||||||
|
def _decrypt_content_if_enabled(filename: str, contents: bytes) -> bytes:
|
||||||
|
"""Checks config and decrypts file contents if enabled."""
|
||||||
|
|
||||||
|
if ENABLE_FILE_DECRYPTION and FILE_DECRYPTION_ENDPOINT and FILE_DECRYPTION_API_KEY:
|
||||||
|
try:
|
||||||
|
decrypted_contents = decrypt_file_via_azure(
|
||||||
|
filename,
|
||||||
|
contents,
|
||||||
|
FILE_DECRYPTION_ENDPOINT,
|
||||||
|
FILE_DECRYPTION_API_KEY,
|
||||||
|
FILE_DECRYPTION_TIMEOUT,
|
||||||
|
)
|
||||||
|
log.info(f"File {filename} decrypted successfully.")
|
||||||
|
return decrypted_contents
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"File decryption failed: {e}")
|
||||||
|
# Raise a specific error to be caught by the API layer
|
||||||
|
raise DecryptionError(f"File decryption failed: {e}")
|
||||||
|
return contents
|
||||||
|
|
||||||
|
|
||||||
class StorageProvider(ABC):
|
class StorageProvider(ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_file(self, file_path: str) -> str:
|
def get_file(self, file_path: str) -> str:
|
||||||
|
|
@ -68,6 +97,10 @@ class LocalStorageProvider(StorageProvider):
|
||||||
contents = file.read()
|
contents = file.read()
|
||||||
if not contents:
|
if not contents:
|
||||||
raise ValueError(ERROR_MESSAGES.EMPTY_CONTENT)
|
raise ValueError(ERROR_MESSAGES.EMPTY_CONTENT)
|
||||||
|
|
||||||
|
# Decrypt contents if decryption is enabled in the config
|
||||||
|
contents = _decrypt_content_if_enabled(filename, contents)
|
||||||
|
|
||||||
file_path = f"{UPLOAD_DIR}/{filename}"
|
file_path = f"{UPLOAD_DIR}/{filename}"
|
||||||
with open(file_path, "wb") as f:
|
with open(file_path, "wb") as f:
|
||||||
f.write(contents)
|
f.write(contents)
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,14 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitHandler = async () => {
|
const submitHandler = async () => {
|
||||||
|
if (
|
||||||
|
RAGConfig.ENABLE_FILE_DECRYPTION &&
|
||||||
|
(!RAGConfig.FILE_DECRYPTION_ENDPOINT || !RAGConfig.FILE_DECRYPTION_API_KEY)
|
||||||
|
) {
|
||||||
|
toast.error($i18n.t('Endpoint URL and API Key are required when file decryption is enabled.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
RAGConfig.CONTENT_EXTRACTION_ENGINE === 'external' &&
|
RAGConfig.CONTENT_EXTRACTION_ENGINE === 'external' &&
|
||||||
RAGConfig.EXTERNAL_DOCUMENT_LOADER_URL === ''
|
RAGConfig.EXTERNAL_DOCUMENT_LOADER_URL === ''
|
||||||
|
|
@ -192,6 +200,10 @@
|
||||||
|
|
||||||
const res = await updateRAGConfig(localStorage.token, {
|
const res = await updateRAGConfig(localStorage.token, {
|
||||||
...RAGConfig,
|
...RAGConfig,
|
||||||
|
ENABLE_FILE_DECRYPTION: RAGConfig.ENABLE_FILE_DECRYPTION ?? false,
|
||||||
|
FILE_DECRYPTION_ENDPOINT: RAGConfig.FILE_DECRYPTION_ENDPOINT ?? '',
|
||||||
|
FILE_DECRYPTION_API_KEY: RAGConfig.FILE_DECRYPTION_API_KEY ?? '',
|
||||||
|
FILE_DECRYPTION_TIMEOUT: RAGConfig.FILE_DECRYPTION_TIMEOUT ?? 30,
|
||||||
ALLOWED_FILE_EXTENSIONS: RAGConfig.ALLOWED_FILE_EXTENSIONS.split(',')
|
ALLOWED_FILE_EXTENSIONS: RAGConfig.ALLOWED_FILE_EXTENSIONS.split(',')
|
||||||
.map((ext) => ext.trim())
|
.map((ext) => ext.trim())
|
||||||
.filter((ext) => ext !== ''),
|
.filter((ext) => ext !== ''),
|
||||||
|
|
@ -1058,6 +1070,62 @@
|
||||||
|
|
||||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||||
|
|
||||||
|
<div class="mb-2.5 flex w-full justify-between">
|
||||||
|
<div class="self-center text-xs font-medium">{$i18n.t('File Decryption')}</div>
|
||||||
|
<div class="flex items-center relative">
|
||||||
|
<Switch bind:state={RAGConfig.ENABLE_FILE_DECRYPTION} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if RAGConfig.ENABLE_FILE_DECRYPTION}
|
||||||
|
<div
|
||||||
|
class="space-y-2.5 pl-6 border-l-2 border-gray-100 dark:border-gray-800 ml-1"
|
||||||
|
>
|
||||||
|
<div class="flex w-full justify-between items-center">
|
||||||
|
<div class="self-center text-xs font-medium">
|
||||||
|
{$i18n.t('Decryption Endpoint URL')}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="flex-1 w-full text-sm bg-transparent outline-hidden text-right"
|
||||||
|
type="text"
|
||||||
|
placeholder={$i18n.t('Enter Azure Function Endpoint')}
|
||||||
|
bind:value={RAGConfig.FILE_DECRYPTION_ENDPOINT}
|
||||||
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex w-full justify-between items-center">
|
||||||
|
<div class="self-center text-xs font-medium">{$i18n.t('API Key')}</div>
|
||||||
|
<div class="max-w-[180px]">
|
||||||
|
<SensitiveInput
|
||||||
|
placeholder={$i18n.t('Enter API Key')}
|
||||||
|
bind:value={RAGConfig.FILE_DECRYPTION_API_KEY}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex w-full justify-between items-center">
|
||||||
|
<div class="self-center text-xs font-medium">{$i18n.t('Timeout (seconds)')}</div>
|
||||||
|
<input
|
||||||
|
class="w-auto text-sm bg-transparent outline-hidden text-right"
|
||||||
|
type="number"
|
||||||
|
placeholder="30"
|
||||||
|
bind:value={RAGConfig.FILE_DECRYPTION_TIMEOUT}
|
||||||
|
autocomplete="off"
|
||||||
|
min="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if !RAGConfig.FILE_DECRYPTION_ENDPOINT || !RAGConfig.FILE_DECRYPTION_API_KEY}
|
||||||
|
<div class="text-xs text-red-500 text-right">
|
||||||
|
{$i18n.t('Endpoint URL and API Key are required when file decryption is enabled.')}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class=" mb-2.5 flex w-full justify-between">
|
<div class=" mb-2.5 flex w-full justify-between">
|
||||||
<div class=" self-center text-xs font-medium">{$i18n.t('Allowed File Extensions')}</div>
|
<div class=" self-center text-xs font-medium">{$i18n.t('Allowed File Extensions')}</div>
|
||||||
<div class="flex items-center relative">
|
<div class="flex items-center relative">
|
||||||
|
|
|
||||||
|
|
@ -533,7 +533,15 @@
|
||||||
files = [...files, fileItem];
|
files = [...files, fileItem];
|
||||||
|
|
||||||
if (!$temporaryChatEnabled) {
|
if (!$temporaryChatEnabled) {
|
||||||
|
let loadingToastId = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Show loading toast for decryption if enabled in config
|
||||||
|
if ($config?.file?.decryption_enabled ?? false) {
|
||||||
|
loadingToastId = toast.loading($i18n.t('Decrypting and uploading file...'));
|
||||||
|
} else {
|
||||||
|
loadingToastId = toast.loading($i18n.t('Uploading file...'));
|
||||||
|
}
|
||||||
// If the file is an audio file, provide the language for STT.
|
// If the file is an audio file, provide the language for STT.
|
||||||
let metadata = null;
|
let metadata = null;
|
||||||
if (
|
if (
|
||||||
|
|
@ -568,11 +576,22 @@
|
||||||
fileItem.url = `${WEBUI_API_BASE_URL}/files/${uploadedFile.id}`;
|
fileItem.url = `${WEBUI_API_BASE_URL}/files/${uploadedFile.id}`;
|
||||||
|
|
||||||
files = files;
|
files = files;
|
||||||
|
toast.success($i18n.t('File uploaded successfully.'), { id: loadingToastId });
|
||||||
} else {
|
} else {
|
||||||
files = files.filter((item) => item?.itemId !== tempItemId);
|
files = files.filter((item) => item?.itemId !== tempItemId);
|
||||||
|
toast.dismiss(loadingToastId);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(`${e}`);
|
toast.dismiss(loadingToastId);
|
||||||
|
const errMsg = typeof e === 'string' ? e : (e?.message || `${e}`);
|
||||||
|
if (
|
||||||
|
errMsg.toLowerCase().includes('decryption') ||
|
||||||
|
errMsg.toLowerCase().includes('azure function error')
|
||||||
|
) {
|
||||||
|
toast.error($i18n.t('File decryption failed: {{error}}', { error: errMsg }));
|
||||||
|
} else {
|
||||||
|
toast.error(`${errMsg}`);
|
||||||
|
}
|
||||||
files = files.filter((item) => item?.itemId !== tempItemId);
|
files = files.filter((item) => item?.itemId !== tempItemId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue