浏览代码

Merge branch 'open-webui:dev' into dev

smonux 7 月之前
父节点
当前提交
e039b4ec54
共有 50 个文件被更改,包括 966 次插入917 次删除
  1. 1 1
      README.md
  2. 7 4
      backend/open_webui/apps/openai/main.py
  3. 190 0
      backend/open_webui/apps/retrieval/loader/main.py
  4. 338 664
      backend/open_webui/apps/retrieval/main.py
  5. 81 0
      backend/open_webui/apps/retrieval/model/colbert.py
  6. 1 1
      backend/open_webui/apps/retrieval/utils.py
  7. 2 2
      backend/open_webui/apps/retrieval/vector/connector.py
  8. 1 1
      backend/open_webui/apps/retrieval/vector/dbs/chroma.py
  9. 5 2
      backend/open_webui/apps/retrieval/vector/dbs/milvus.py
  10. 0 0
      backend/open_webui/apps/retrieval/vector/main.py
  11. 1 1
      backend/open_webui/apps/retrieval/web/brave.py
  12. 1 1
      backend/open_webui/apps/retrieval/web/duckduckgo.py
  13. 1 1
      backend/open_webui/apps/retrieval/web/google_pse.py
  14. 1 1
      backend/open_webui/apps/retrieval/web/jina_search.py
  15. 0 0
      backend/open_webui/apps/retrieval/web/main.py
  16. 1 1
      backend/open_webui/apps/retrieval/web/searchapi.py
  17. 1 1
      backend/open_webui/apps/retrieval/web/searxng.py
  18. 1 1
      backend/open_webui/apps/retrieval/web/serper.py
  19. 1 1
      backend/open_webui/apps/retrieval/web/serply.py
  20. 1 1
      backend/open_webui/apps/retrieval/web/serpstack.py
  21. 1 1
      backend/open_webui/apps/retrieval/web/tavily.py
  22. 0 0
      backend/open_webui/apps/retrieval/web/testdata/brave.json
  23. 0 0
      backend/open_webui/apps/retrieval/web/testdata/google_pse.json
  24. 0 0
      backend/open_webui/apps/retrieval/web/testdata/searchapi.json
  25. 0 0
      backend/open_webui/apps/retrieval/web/testdata/searxng.json
  26. 0 0
      backend/open_webui/apps/retrieval/web/testdata/serper.json
  27. 0 0
      backend/open_webui/apps/retrieval/web/testdata/serply.json
  28. 0 0
      backend/open_webui/apps/retrieval/web/testdata/serpstack.json
  29. 97 0
      backend/open_webui/apps/retrieval/web/utils.py
  30. 11 0
      backend/open_webui/apps/webui/models/files.py
  31. 13 0
      backend/open_webui/apps/webui/routers/files.py
  32. 1 1
      backend/open_webui/apps/webui/routers/memories.py
  33. 1 1
      backend/open_webui/config.py
  34. 56 37
      backend/open_webui/main.py
  35. 2 0
      backend/requirements.txt
  36. 2 0
      pyproject.toml
  37. 114 146
      src/lib/apis/retrieval/index.ts
  38. 3 3
      src/lib/components/admin/Settings/Documents.svelte
  39. 1 1
      src/lib/components/admin/Settings/WebSearch.svelte
  40. 2 2
      src/lib/components/chat/Chat.svelte
  41. 3 0
      src/lib/components/chat/Controls/Controls.svelte
  42. 4 6
      src/lib/components/chat/MessageInput.svelte
  43. 3 3
      src/lib/components/chat/MessageInput/Commands.svelte
  44. 3 15
      src/lib/components/common/FileItem.svelte
  45. 3 6
      src/lib/components/documents/AddDocModal.svelte
  46. 2 2
      src/lib/components/workspace/Documents.svelte
  47. 1 1
      src/lib/constants.ts
  48. 5 5
      src/lib/i18n/locales/ca-ES/translation.json
  49. 1 1
      src/lib/utils/rag/index.ts
  50. 2 2
      src/routes/(app)/+layout.svelte

+ 1 - 1
README.md

@@ -170,7 +170,7 @@ docker run --rm --volume /var/run/docker.sock:/var/run/docker.sock containrrr/wa
 
 
 In the last part of the command, replace `open-webui` with your container name if it is different.
 In the last part of the command, replace `open-webui` with your container name if it is different.
 
 
-Check our Migration Guide available in our [Open WebUI Documentation](https://docs.openwebui.com/migration/).
+Check our Migration Guide available in our [Open WebUI Documentation](https://docs.openwebui.com/tutorials/migration/).
 
 
 ### Using the Dev Branch 🌙
 ### Using the Dev Branch 🌙
 
 

+ 7 - 4
backend/open_webui/apps/openai/main.py

@@ -27,7 +27,6 @@ from fastapi.responses import FileResponse, StreamingResponse
 from pydantic import BaseModel
 from pydantic import BaseModel
 from starlette.background import BackgroundTask
 from starlette.background import BackgroundTask
 
 
-
 from open_webui.utils.payload import (
 from open_webui.utils.payload import (
     apply_model_params_to_body_openai,
     apply_model_params_to_body_openai,
     apply_model_system_prompt_to_body,
     apply_model_system_prompt_to_body,
@@ -47,7 +46,6 @@ app.add_middleware(
     allow_headers=["*"],
     allow_headers=["*"],
 )
 )
 
 
-
 app.state.config = AppConfig()
 app.state.config = AppConfig()
 
 
 app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
 app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
@@ -407,20 +405,25 @@ async def generate_chat_completion(
 
 
     url = app.state.config.OPENAI_API_BASE_URLS[idx]
     url = app.state.config.OPENAI_API_BASE_URLS[idx]
     key = app.state.config.OPENAI_API_KEYS[idx]
     key = app.state.config.OPENAI_API_KEYS[idx]
+    is_o1 = payload["model"].lower().startswith("o1-")
 
 
     # Change max_completion_tokens to max_tokens (Backward compatible)
     # Change max_completion_tokens to max_tokens (Backward compatible)
-    if "api.openai.com" not in url and not payload["model"].lower().startswith("o1-"):
+    if "api.openai.com" not in url and not is_o1:
         if "max_completion_tokens" in payload:
         if "max_completion_tokens" in payload:
             # Remove "max_completion_tokens" from the payload
             # Remove "max_completion_tokens" from the payload
             payload["max_tokens"] = payload["max_completion_tokens"]
             payload["max_tokens"] = payload["max_completion_tokens"]
             del payload["max_completion_tokens"]
             del payload["max_completion_tokens"]
     else:
     else:
-        if payload["model"].lower().startswith("o1-") and "max_tokens" in payload:
+        if is_o1 and "max_tokens" in payload:
             payload["max_completion_tokens"] = payload["max_tokens"]
             payload["max_completion_tokens"] = payload["max_tokens"]
             del payload["max_tokens"]
             del payload["max_tokens"]
         if "max_tokens" in payload and "max_completion_tokens" in payload:
         if "max_tokens" in payload and "max_completion_tokens" in payload:
             del payload["max_tokens"]
             del payload["max_tokens"]
 
 
+    # Fix: O1 does not support the "system" parameter, Modify "system" to "user"
+    if is_o1 and payload["messages"][0]["role"] == "system":
+        payload["messages"][0]["role"] = "user"
+
     # Convert the modified body back to JSON
     # Convert the modified body back to JSON
     payload = json.dumps(payload)
     payload = json.dumps(payload)
 
 

+ 190 - 0
backend/open_webui/apps/retrieval/loader/main.py

@@ -0,0 +1,190 @@
+import requests
+import logging
+import ftfy
+
+from langchain_community.document_loaders import (
+    BSHTMLLoader,
+    CSVLoader,
+    Docx2txtLoader,
+    OutlookMessageLoader,
+    PyPDFLoader,
+    TextLoader,
+    UnstructuredEPubLoader,
+    UnstructuredExcelLoader,
+    UnstructuredMarkdownLoader,
+    UnstructuredPowerPointLoader,
+    UnstructuredRSTLoader,
+    UnstructuredXMLLoader,
+    YoutubeLoader,
+)
+from langchain_core.documents import Document
+from open_webui.env import SRC_LOG_LEVELS
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+known_source_ext = [
+    "go",
+    "py",
+    "java",
+    "sh",
+    "bat",
+    "ps1",
+    "cmd",
+    "js",
+    "ts",
+    "css",
+    "cpp",
+    "hpp",
+    "h",
+    "c",
+    "cs",
+    "sql",
+    "log",
+    "ini",
+    "pl",
+    "pm",
+    "r",
+    "dart",
+    "dockerfile",
+    "env",
+    "php",
+    "hs",
+    "hsc",
+    "lua",
+    "nginxconf",
+    "conf",
+    "m",
+    "mm",
+    "plsql",
+    "perl",
+    "rb",
+    "rs",
+    "db2",
+    "scala",
+    "bash",
+    "swift",
+    "vue",
+    "svelte",
+    "msg",
+    "ex",
+    "exs",
+    "erl",
+    "tsx",
+    "jsx",
+    "hs",
+    "lhs",
+]
+
+
+class TikaLoader:
+    def __init__(self, url, file_path, mime_type=None):
+        self.url = url
+        self.file_path = file_path
+        self.mime_type = mime_type
+
+    def load(self) -> list[Document]:
+        with open(self.file_path, "rb") as f:
+            data = f.read()
+
+        if self.mime_type is not None:
+            headers = {"Content-Type": self.mime_type}
+        else:
+            headers = {}
+
+        endpoint = self.url
+        if not endpoint.endswith("/"):
+            endpoint += "/"
+        endpoint += "tika/text"
+
+        r = requests.put(endpoint, data=data, headers=headers)
+
+        if r.ok:
+            raw_metadata = r.json()
+            text = raw_metadata.get("X-TIKA:content", "<No text content found>")
+
+            if "Content-Type" in raw_metadata:
+                headers["Content-Type"] = raw_metadata["Content-Type"]
+
+            log.info("Tika extracted text: %s", text)
+
+            return [Document(page_content=text, metadata=headers)]
+        else:
+            raise Exception(f"Error calling Tika: {r.reason}")
+
+
+class Loader:
+    def __init__(self, engine: str = "", **kwargs):
+        self.engine = engine
+        self.kwargs = kwargs
+
+    def load(
+        self, filename: str, file_content_type: str, file_path: str
+    ) -> list[Document]:
+        loader = self._get_loader(filename, file_content_type, file_path)
+        docs = loader.load()
+
+        return [
+            Document(
+                page_content=ftfy.fix_text(doc.page_content), metadata=doc.metadata
+            )
+            for doc in docs
+        ]
+
+    def _get_loader(self, filename: str, file_content_type: str, file_path: str):
+        file_ext = filename.split(".")[-1].lower()
+
+        if self.engine == "tika" and self.kwargs.get("TIKA_SERVER_URL"):
+            if file_ext in known_source_ext or (
+                file_content_type and file_content_type.find("text/") >= 0
+            ):
+                loader = TextLoader(file_path, autodetect_encoding=True)
+            else:
+                loader = TikaLoader(
+                    url=self.kwargs.get("TIKA_SERVER_URL"),
+                    file_path=file_path,
+                    mime_type=file_content_type,
+                )
+        else:
+            if file_ext == "pdf":
+                loader = PyPDFLoader(
+                    file_path, extract_images=self.kwargs.get("PDF_EXTRACT_IMAGES")
+                )
+            elif file_ext == "csv":
+                loader = CSVLoader(file_path)
+            elif file_ext == "rst":
+                loader = UnstructuredRSTLoader(file_path, mode="elements")
+            elif file_ext == "xml":
+                loader = UnstructuredXMLLoader(file_path)
+            elif file_ext in ["htm", "html"]:
+                loader = BSHTMLLoader(file_path, open_encoding="unicode_escape")
+            elif file_ext == "md":
+                loader = UnstructuredMarkdownLoader(file_path)
+            elif file_content_type == "application/epub+zip":
+                loader = UnstructuredEPubLoader(file_path)
+            elif (
+                file_content_type
+                == "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
+                or file_ext == "docx"
+            ):
+                loader = Docx2txtLoader(file_path)
+            elif file_content_type in [
+                "application/vnd.ms-excel",
+                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+            ] or file_ext in ["xls", "xlsx"]:
+                loader = UnstructuredExcelLoader(file_path)
+            elif file_content_type in [
+                "application/vnd.ms-powerpoint",
+                "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+            ] or file_ext in ["ppt", "pptx"]:
+                loader = UnstructuredPowerPointLoader(file_path)
+            elif file_ext == "msg":
+                loader = OutlookMessageLoader(file_path)
+            elif file_ext in known_source_ext or (
+                file_content_type and file_content_type.find("text/") >= 0
+            ):
+                loader = TextLoader(file_path, autodetect_encoding=True)
+            else:
+                loader = TextLoader(file_path, autodetect_encoding=True)
+
+        return loader

文件差异内容过多而无法显示
+ 338 - 664
backend/open_webui/apps/retrieval/main.py


+ 81 - 0
backend/open_webui/apps/retrieval/model/colbert.py

@@ -0,0 +1,81 @@
+import os
+import torch
+import numpy as np
+from colbert.infra import ColBERTConfig
+from colbert.modeling.checkpoint import Checkpoint
+
+
+class ColBERT:
+    def __init__(self, name, **kwargs) -> None:
+        print("ColBERT: Loading model", name)
+        self.device = "cuda" if torch.cuda.is_available() else "cpu"
+
+        DOCKER = kwargs.get("env") == "docker"
+        if DOCKER:
+            # This is a workaround for the issue with the docker container
+            # where the torch extension is not loaded properly
+            # and the following error is thrown:
+            # /root/.cache/torch_extensions/py311_cpu/segmented_maxsim_cpp/segmented_maxsim_cpp.so: cannot open shared object file: No such file or directory
+
+            lock_file = (
+                "/root/.cache/torch_extensions/py311_cpu/segmented_maxsim_cpp/lock"
+            )
+            if os.path.exists(lock_file):
+                os.remove(lock_file)
+
+        self.ckpt = Checkpoint(
+            name,
+            colbert_config=ColBERTConfig(model_name=name),
+        ).to(self.device)
+        pass
+
+    def calculate_similarity_scores(self, query_embeddings, document_embeddings):
+
+        query_embeddings = query_embeddings.to(self.device)
+        document_embeddings = document_embeddings.to(self.device)
+
+        # Validate dimensions to ensure compatibility
+        if query_embeddings.dim() != 3:
+            raise ValueError(
+                f"Expected query embeddings to have 3 dimensions, but got {query_embeddings.dim()}."
+            )
+        if document_embeddings.dim() != 3:
+            raise ValueError(
+                f"Expected document embeddings to have 3 dimensions, but got {document_embeddings.dim()}."
+            )
+        if query_embeddings.size(0) not in [1, document_embeddings.size(0)]:
+            raise ValueError(
+                "There should be either one query or queries equal to the number of documents."
+            )
+
+        # Transpose the query embeddings to align for matrix multiplication
+        transposed_query_embeddings = query_embeddings.permute(0, 2, 1)
+        # Compute similarity scores using batch matrix multiplication
+        computed_scores = torch.matmul(document_embeddings, transposed_query_embeddings)
+        # Apply max pooling to extract the highest semantic similarity across each document's sequence
+        maximum_scores = torch.max(computed_scores, dim=1).values
+
+        # Sum up the maximum scores across features to get the overall document relevance scores
+        final_scores = maximum_scores.sum(dim=1)
+
+        normalized_scores = torch.softmax(final_scores, dim=0)
+
+        return normalized_scores.detach().cpu().numpy().astype(np.float32)
+
+    def predict(self, sentences):
+
+        query = sentences[0][0]
+        docs = [i[1] for i in sentences]
+
+        # Embedding the documents
+        embedded_docs = self.ckpt.docFromText(docs, bsize=32)[0]
+        # Embedding the queries
+        embedded_queries = self.ckpt.queryFromText([query], bsize=32)
+        embedded_query = embedded_queries[0]
+
+        # Calculate retrieval scores for the query against all documents
+        scores = self.calculate_similarity_scores(
+            embedded_query.unsqueeze(0), embedded_docs
+        )
+
+        return scores

+ 1 - 1
backend/open_webui/apps/rag/utils.py → backend/open_webui/apps/retrieval/utils.py

@@ -15,7 +15,7 @@ from open_webui.apps.ollama.main import (
     GenerateEmbeddingsForm,
     GenerateEmbeddingsForm,
     generate_ollama_embeddings,
     generate_ollama_embeddings,
 )
 )
-from open_webui.apps.rag.vector.connector import VECTOR_DB_CLIENT
+from open_webui.apps.retrieval.vector.connector import VECTOR_DB_CLIENT
 from open_webui.utils.misc import get_last_user_message
 from open_webui.utils.misc import get_last_user_message
 
 
 from open_webui.env import SRC_LOG_LEVELS
 from open_webui.env import SRC_LOG_LEVELS

+ 2 - 2
backend/open_webui/apps/rag/vector/connector.py → backend/open_webui/apps/retrieval/vector/connector.py

@@ -1,5 +1,5 @@
-from open_webui.apps.rag.vector.dbs.chroma import ChromaClient
-from open_webui.apps.rag.vector.dbs.milvus import MilvusClient
+from open_webui.apps.retrieval.vector.dbs.chroma import ChromaClient
+from open_webui.apps.retrieval.vector.dbs.milvus import MilvusClient
 
 
 
 
 from open_webui.config import VECTOR_DB
 from open_webui.config import VECTOR_DB

+ 1 - 1
backend/open_webui/apps/rag/vector/dbs/chroma.py → backend/open_webui/apps/retrieval/vector/dbs/chroma.py

@@ -4,7 +4,7 @@ from chromadb.utils.batch_utils import create_batches
 
 
 from typing import Optional
 from typing import Optional
 
 
-from open_webui.apps.rag.vector.main import VectorItem, SearchResult, GetResult
+from open_webui.apps.retrieval.vector.main import VectorItem, SearchResult, GetResult
 from open_webui.config import (
 from open_webui.config import (
     CHROMA_DATA_PATH,
     CHROMA_DATA_PATH,
     CHROMA_HTTP_HOST,
     CHROMA_HTTP_HOST,

+ 5 - 2
backend/open_webui/apps/rag/vector/dbs/milvus.py → backend/open_webui/apps/retrieval/vector/dbs/milvus.py

@@ -4,7 +4,7 @@ import json
 
 
 from typing import Optional
 from typing import Optional
 
 
-from open_webui.apps.rag.vector.main import VectorItem, SearchResult, GetResult
+from open_webui.apps.retrieval.vector.main import VectorItem, SearchResult, GetResult
 from open_webui.config import (
 from open_webui.config import (
     MILVUS_URI,
     MILVUS_URI,
 )
 )
@@ -98,7 +98,10 @@ class MilvusClient:
 
 
         index_params = self.client.prepare_index_params()
         index_params = self.client.prepare_index_params()
         index_params.add_index(
         index_params.add_index(
-            field_name="vector", index_type="HNSW", metric_type="COSINE", params={}
+            field_name="vector",
+            index_type="HNSW",
+            metric_type="COSINE",
+            params={"M": 16, "efConstruction": 100},
         )
         )
 
 
         self.client.create_collection(
         self.client.create_collection(

+ 0 - 0
backend/open_webui/apps/rag/vector/main.py → backend/open_webui/apps/retrieval/vector/main.py


+ 1 - 1
backend/open_webui/apps/rag/search/brave.py → backend/open_webui/apps/retrieval/web/brave.py

@@ -2,7 +2,7 @@ import logging
 from typing import Optional
 from typing import Optional
 
 
 import requests
 import requests
-from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
 from open_webui.env import SRC_LOG_LEVELS
 from open_webui.env import SRC_LOG_LEVELS
 
 
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)

+ 1 - 1
backend/open_webui/apps/rag/search/duckduckgo.py → backend/open_webui/apps/retrieval/web/duckduckgo.py

@@ -1,7 +1,7 @@
 import logging
 import logging
 from typing import Optional
 from typing import Optional
 
 
-from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
 from duckduckgo_search import DDGS
 from duckduckgo_search import DDGS
 from open_webui.env import SRC_LOG_LEVELS
 from open_webui.env import SRC_LOG_LEVELS
 
 

+ 1 - 1
backend/open_webui/apps/rag/search/google_pse.py → backend/open_webui/apps/retrieval/web/google_pse.py

@@ -2,7 +2,7 @@ import logging
 from typing import Optional
 from typing import Optional
 
 
 import requests
 import requests
-from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
 from open_webui.env import SRC_LOG_LEVELS
 from open_webui.env import SRC_LOG_LEVELS
 
 
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)

+ 1 - 1
backend/open_webui/apps/rag/search/jina_search.py → backend/open_webui/apps/retrieval/web/jina_search.py

@@ -1,7 +1,7 @@
 import logging
 import logging
 
 
 import requests
 import requests
-from open_webui.apps.rag.search.main import SearchResult
+from open_webui.apps.retrieval.web.main import SearchResult
 from open_webui.env import SRC_LOG_LEVELS
 from open_webui.env import SRC_LOG_LEVELS
 from yarl import URL
 from yarl import URL
 
 

+ 0 - 0
backend/open_webui/apps/rag/search/main.py → backend/open_webui/apps/retrieval/web/main.py


+ 1 - 1
backend/open_webui/apps/rag/search/searchapi.py → backend/open_webui/apps/retrieval/web/searchapi.py

@@ -3,7 +3,7 @@ from typing import Optional
 from urllib.parse import urlencode
 from urllib.parse import urlencode
 
 
 import requests
 import requests
-from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
 from open_webui.env import SRC_LOG_LEVELS
 from open_webui.env import SRC_LOG_LEVELS
 
 
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)

+ 1 - 1
backend/open_webui/apps/rag/search/searxng.py → backend/open_webui/apps/retrieval/web/searxng.py

@@ -2,7 +2,7 @@ import logging
 from typing import Optional
 from typing import Optional
 
 
 import requests
 import requests
-from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
 from open_webui.env import SRC_LOG_LEVELS
 from open_webui.env import SRC_LOG_LEVELS
 
 
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)

+ 1 - 1
backend/open_webui/apps/rag/search/serper.py → backend/open_webui/apps/retrieval/web/serper.py

@@ -3,7 +3,7 @@ import logging
 from typing import Optional
 from typing import Optional
 
 
 import requests
 import requests
-from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
 from open_webui.env import SRC_LOG_LEVELS
 from open_webui.env import SRC_LOG_LEVELS
 
 
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)

+ 1 - 1
backend/open_webui/apps/rag/search/serply.py → backend/open_webui/apps/retrieval/web/serply.py

@@ -3,7 +3,7 @@ from typing import Optional
 from urllib.parse import urlencode
 from urllib.parse import urlencode
 
 
 import requests
 import requests
-from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
 from open_webui.env import SRC_LOG_LEVELS
 from open_webui.env import SRC_LOG_LEVELS
 
 
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)

+ 1 - 1
backend/open_webui/apps/rag/search/serpstack.py → backend/open_webui/apps/retrieval/web/serpstack.py

@@ -2,7 +2,7 @@ import logging
 from typing import Optional
 from typing import Optional
 
 
 import requests
 import requests
-from open_webui.apps.rag.search.main import SearchResult, get_filtered_results
+from open_webui.apps.retrieval.web.main import SearchResult, get_filtered_results
 from open_webui.env import SRC_LOG_LEVELS
 from open_webui.env import SRC_LOG_LEVELS
 
 
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)

+ 1 - 1
backend/open_webui/apps/rag/search/tavily.py → backend/open_webui/apps/retrieval/web/tavily.py

@@ -1,7 +1,7 @@
 import logging
 import logging
 
 
 import requests
 import requests
-from open_webui.apps.rag.search.main import SearchResult
+from open_webui.apps.retrieval.web.main import SearchResult
 from open_webui.env import SRC_LOG_LEVELS
 from open_webui.env import SRC_LOG_LEVELS
 
 
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)

+ 0 - 0
backend/open_webui/apps/rag/search/testdata/brave.json → backend/open_webui/apps/retrieval/web/testdata/brave.json


+ 0 - 0
backend/open_webui/apps/rag/search/testdata/google_pse.json → backend/open_webui/apps/retrieval/web/testdata/google_pse.json


+ 0 - 0
backend/open_webui/apps/rag/search/testdata/searchapi.json → backend/open_webui/apps/retrieval/web/testdata/searchapi.json


+ 0 - 0
backend/open_webui/apps/rag/search/testdata/searxng.json → backend/open_webui/apps/retrieval/web/testdata/searxng.json


+ 0 - 0
backend/open_webui/apps/rag/search/testdata/serper.json → backend/open_webui/apps/retrieval/web/testdata/serper.json


+ 0 - 0
backend/open_webui/apps/rag/search/testdata/serply.json → backend/open_webui/apps/retrieval/web/testdata/serply.json


+ 0 - 0
backend/open_webui/apps/rag/search/testdata/serpstack.json → backend/open_webui/apps/retrieval/web/testdata/serpstack.json


+ 97 - 0
backend/open_webui/apps/retrieval/web/utils.py

@@ -0,0 +1,97 @@
+import socket
+import urllib.parse
+import validators
+from typing import Union, Sequence, Iterator
+
+from langchain_community.document_loaders import (
+    WebBaseLoader,
+)
+from langchain_core.documents import Document
+
+
+from open_webui.constants import ERROR_MESSAGES
+from open_webui.config import ENABLE_RAG_LOCAL_WEB_FETCH
+from open_webui.env import SRC_LOG_LEVELS
+
+import logging
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+def validate_url(url: Union[str, Sequence[str]]):
+    if isinstance(url, str):
+        if isinstance(validators.url(url), validators.ValidationError):
+            raise ValueError(ERROR_MESSAGES.INVALID_URL)
+        if not ENABLE_RAG_LOCAL_WEB_FETCH:
+            # Local web fetch is disabled, filter out any URLs that resolve to private IP addresses
+            parsed_url = urllib.parse.urlparse(url)
+            # Get IPv4 and IPv6 addresses
+            ipv4_addresses, ipv6_addresses = resolve_hostname(parsed_url.hostname)
+            # Check if any of the resolved addresses are private
+            # This is technically still vulnerable to DNS rebinding attacks, as we don't control WebBaseLoader
+            for ip in ipv4_addresses:
+                if validators.ipv4(ip, private=True):
+                    raise ValueError(ERROR_MESSAGES.INVALID_URL)
+            for ip in ipv6_addresses:
+                if validators.ipv6(ip, private=True):
+                    raise ValueError(ERROR_MESSAGES.INVALID_URL)
+        return True
+    elif isinstance(url, Sequence):
+        return all(validate_url(u) for u in url)
+    else:
+        return False
+
+
+def resolve_hostname(hostname):
+    # Get address information
+    addr_info = socket.getaddrinfo(hostname, None)
+
+    # Extract IP addresses from address information
+    ipv4_addresses = [info[4][0] for info in addr_info if info[0] == socket.AF_INET]
+    ipv6_addresses = [info[4][0] for info in addr_info if info[0] == socket.AF_INET6]
+
+    return ipv4_addresses, ipv6_addresses
+
+
+class SafeWebBaseLoader(WebBaseLoader):
+    """WebBaseLoader with enhanced error handling for URLs."""
+
+    def lazy_load(self) -> Iterator[Document]:
+        """Lazy load text from the url(s) in web_path with error handling."""
+        for path in self.web_paths:
+            try:
+                soup = self._scrape(path, bs_kwargs=self.bs_kwargs)
+                text = soup.get_text(**self.bs_get_text_kwargs)
+
+                # Build metadata
+                metadata = {"source": path}
+                if title := soup.find("title"):
+                    metadata["title"] = title.get_text()
+                if description := soup.find("meta", attrs={"name": "description"}):
+                    metadata["description"] = description.get(
+                        "content", "No description found."
+                    )
+                if html := soup.find("html"):
+                    metadata["language"] = html.get("lang", "No language found.")
+
+                yield Document(page_content=text, metadata=metadata)
+            except Exception as e:
+                # Log the error and continue with the next URL
+                log.error(f"Error loading {path}: {e}")
+
+
+def get_web_loader(
+    url: Union[str, Sequence[str]],
+    verify_ssl: bool = True,
+    requests_per_second: int = 2,
+):
+    # Check if the URL is valid
+    if not validate_url(url):
+        raise ValueError(ERROR_MESSAGES.INVALID_URL)
+    return SafeWebBaseLoader(
+        url,
+        verify_ssl=verify_ssl,
+        requests_per_second=requests_per_second,
+        continue_on_failure=True,
+    )

+ 11 - 0
backend/open_webui/apps/webui/models/files.py

@@ -97,6 +97,17 @@ class FilesTable:
                 for file in db.query(File).filter_by(user_id=user_id).all()
                 for file in db.query(File).filter_by(user_id=user_id).all()
             ]
             ]
 
 
+    def update_files_metadata_by_id(self, id: str, meta: dict) -> Optional[FileModel]:
+        with get_db() as db:
+            try:
+                file = db.query(File).filter_by(id=id).first()
+                file.meta = {**file.meta, **meta}
+                db.commit()
+
+                return FileModel.model_validate(file)
+            except Exception:
+                return None
+
     def delete_file_by_id(self, id: str) -> bool:
     def delete_file_by_id(self, id: str) -> bool:
         with get_db() as db:
         with get_db() as db:
             try:
             try:

+ 13 - 0
backend/open_webui/apps/webui/routers/files.py

@@ -171,6 +171,19 @@ async def get_file_content_by_id(id: str, user=Depends(get_verified_user)):
         )
         )
 
 
 
 
+@router.get("/{id}/content/text")
+async def get_file_text_content_by_id(id: str, user=Depends(get_verified_user)):
+    file = Files.get_file_by_id(id)
+
+    if file and (file.user_id == user.id or user.role == "admin"):
+        return {"text": file.meta.get("content", {}).get("text", None)}
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
 @router.get("/{id}/content/{file_name}", response_model=Optional[FileModel])
 @router.get("/{id}/content/{file_name}", response_model=Optional[FileModel])
 async def get_file_content_by_id(id: str, user=Depends(get_verified_user)):
 async def get_file_content_by_id(id: str, user=Depends(get_verified_user)):
     file = Files.get_file_by_id(id)
     file = Files.get_file_by_id(id)

+ 1 - 1
backend/open_webui/apps/webui/routers/memories.py

@@ -4,7 +4,7 @@ import logging
 from typing import Optional
 from typing import Optional
 
 
 from open_webui.apps.webui.models.memories import Memories, MemoryModel
 from open_webui.apps.webui.models.memories import Memories, MemoryModel
-from open_webui.apps.rag.vector.connector import VECTOR_DB_CLIENT
+from open_webui.apps.retrieval.vector.connector import VECTOR_DB_CLIENT
 from open_webui.utils.utils import get_verified_user
 from open_webui.utils.utils import get_verified_user
 from open_webui.env import SRC_LOG_LEVELS
 from open_webui.env import SRC_LOG_LEVELS
 
 

+ 1 - 1
backend/open_webui/config.py

@@ -921,7 +921,7 @@ CHROMA_HTTP_SSL = os.environ.get("CHROMA_HTTP_SSL", "false").lower() == "true"
 MILVUS_URI = os.environ.get("MILVUS_URI", f"{DATA_DIR}/vector_db/milvus.db")
 MILVUS_URI = os.environ.get("MILVUS_URI", f"{DATA_DIR}/vector_db/milvus.db")
 
 
 ####################################
 ####################################
-# RAG
+# Information Retrieval (RAG)
 ####################################
 ####################################
 
 
 # RAG Content Extraction
 # RAG Content Extraction

+ 56 - 37
backend/open_webui/main.py

@@ -16,37 +16,45 @@ from typing import Optional
 import aiohttp
 import aiohttp
 import requests
 import requests
 
 
-
-from open_webui.apps.audio.main import app as audio_app
-from open_webui.apps.images.main import app as images_app
-from open_webui.apps.ollama.main import app as ollama_app
 from open_webui.apps.ollama.main import (
 from open_webui.apps.ollama.main import (
-    GenerateChatCompletionForm,
+    app as ollama_app,
+    get_all_models as get_ollama_models,
     generate_chat_completion as generate_ollama_chat_completion,
     generate_chat_completion as generate_ollama_chat_completion,
     generate_openai_chat_completion as generate_ollama_openai_chat_completion,
     generate_openai_chat_completion as generate_ollama_openai_chat_completion,
+    GenerateChatCompletionForm,
 )
 )
-from open_webui.apps.ollama.main import get_all_models as get_ollama_models
-from open_webui.apps.openai.main import app as openai_app
 from open_webui.apps.openai.main import (
 from open_webui.apps.openai.main import (
+    app as openai_app,
     generate_chat_completion as generate_openai_chat_completion,
     generate_chat_completion as generate_openai_chat_completion,
+    get_all_models as get_openai_models,
 )
 )
-from open_webui.apps.openai.main import get_all_models as get_openai_models
-from open_webui.apps.rag.main import app as rag_app
-from open_webui.apps.rag.utils import get_rag_context, rag_template
-from open_webui.apps.socket.main import app as socket_app, periodic_usage_pool_cleanup
-from open_webui.apps.socket.main import get_event_call, get_event_emitter
-from open_webui.apps.webui.internal.db import Session
-from open_webui.apps.webui.main import app as webui_app
+
+from open_webui.apps.retrieval.main import app as retrieval_app
+from open_webui.apps.retrieval.utils import get_rag_context, rag_template
+
+from open_webui.apps.socket.main import (
+    app as socket_app,
+    periodic_usage_pool_cleanup,
+    get_event_call,
+    get_event_emitter,
+)
+
 from open_webui.apps.webui.main import (
 from open_webui.apps.webui.main import (
+    app as webui_app,
     generate_function_chat_completion,
     generate_function_chat_completion,
     get_pipe_models,
     get_pipe_models,
 )
 )
+from open_webui.apps.webui.internal.db import Session
+
 from open_webui.apps.webui.models.auths import Auths
 from open_webui.apps.webui.models.auths import Auths
 from open_webui.apps.webui.models.functions import Functions
 from open_webui.apps.webui.models.functions import Functions
 from open_webui.apps.webui.models.models import Models
 from open_webui.apps.webui.models.models import Models
 from open_webui.apps.webui.models.users import UserModel, Users
 from open_webui.apps.webui.models.users import UserModel, Users
+
 from open_webui.apps.webui.utils import load_function_module_by_id
 from open_webui.apps.webui.utils import load_function_module_by_id
 
 
+from open_webui.apps.audio.main import app as audio_app
+from open_webui.apps.images.main import app as images_app
 
 
 from authlib.integrations.starlette_client import OAuth
 from authlib.integrations.starlette_client import OAuth
 from authlib.oidc.core import UserInfo
 from authlib.oidc.core import UserInfo
@@ -492,11 +500,11 @@ async def chat_completion_files_handler(body) -> tuple[dict, dict[str, list]]:
         contexts, citations = get_rag_context(
         contexts, citations = get_rag_context(
             files=files,
             files=files,
             messages=body["messages"],
             messages=body["messages"],
-            embedding_function=rag_app.state.EMBEDDING_FUNCTION,
-            k=rag_app.state.config.TOP_K,
-            reranking_function=rag_app.state.sentence_transformer_rf,
-            r=rag_app.state.config.RELEVANCE_THRESHOLD,
-            hybrid_search=rag_app.state.config.ENABLE_RAG_HYBRID_SEARCH,
+            embedding_function=retrieval_app.state.EMBEDDING_FUNCTION,
+            k=retrieval_app.state.config.TOP_K,
+            reranking_function=retrieval_app.state.sentence_transformer_rf,
+            r=retrieval_app.state.config.RELEVANCE_THRESHOLD,
+            hybrid_search=retrieval_app.state.config.ENABLE_RAG_HYBRID_SEARCH,
         )
         )
 
 
         log.debug(f"rag_contexts: {contexts}, citations: {citations}")
         log.debug(f"rag_contexts: {contexts}, citations: {citations}")
@@ -609,7 +617,7 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware):
             if prompt is None:
             if prompt is None:
                 raise Exception("No user message found")
                 raise Exception("No user message found")
             if (
             if (
-                rag_app.state.config.RELEVANCE_THRESHOLD == 0
+                retrieval_app.state.config.RELEVANCE_THRESHOLD == 0
                 and context_string.strip() == ""
                 and context_string.strip() == ""
             ):
             ):
                 log.debug(
                 log.debug(
@@ -621,14 +629,14 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware):
             if model["owned_by"] == "ollama":
             if model["owned_by"] == "ollama":
                 body["messages"] = prepend_to_first_user_message_content(
                 body["messages"] = prepend_to_first_user_message_content(
                     rag_template(
                     rag_template(
-                        rag_app.state.config.RAG_TEMPLATE, context_string, prompt
+                        retrieval_app.state.config.RAG_TEMPLATE, context_string, prompt
                     ),
                     ),
                     body["messages"],
                     body["messages"],
                 )
                 )
             else:
             else:
                 body["messages"] = add_or_update_system_message(
                 body["messages"] = add_or_update_system_message(
                     rag_template(
                     rag_template(
-                        rag_app.state.config.RAG_TEMPLATE, context_string, prompt
+                        retrieval_app.state.config.RAG_TEMPLATE, context_string, prompt
                     ),
                     ),
                     body["messages"],
                     body["messages"],
                 )
                 )
@@ -762,10 +770,22 @@ class PipelineMiddleware(BaseHTTPMiddleware):
         # Parse string to JSON
         # Parse string to JSON
         data = json.loads(body_str) if body_str else {}
         data = json.loads(body_str) if body_str else {}
 
 
-        user = get_current_user(
-            request,
-            get_http_authorization_cred(request.headers["Authorization"]),
-        )
+        try:
+            user = get_current_user(
+                request,
+                get_http_authorization_cred(request.headers["Authorization"]),
+            )
+        except KeyError as e:
+            if len(e.args) > 1:
+                return JSONResponse(
+                    status_code=e.args[0],
+                    content={"detail": e.args[1]},
+                )
+            else:
+                return JSONResponse(
+                    status_code=status.HTTP_401_UNAUTHORIZED,
+                    content={"detail": "Not authenticated"},
+                )
 
 
         try:
         try:
             data = filter_pipeline(data, user)
             data = filter_pipeline(data, user)
@@ -838,7 +858,7 @@ async def check_url(request: Request, call_next):
 async def update_embedding_function(request: Request, call_next):
 async def update_embedding_function(request: Request, call_next):
     response = await call_next(request)
     response = await call_next(request)
     if "/embedding/update" in request.url.path:
     if "/embedding/update" in request.url.path:
-        webui_app.state.EMBEDDING_FUNCTION = rag_app.state.EMBEDDING_FUNCTION
+        webui_app.state.EMBEDDING_FUNCTION = retrieval_app.state.EMBEDDING_FUNCTION
     return response
     return response
 
 
 
 
@@ -866,11 +886,12 @@ app.mount("/openai", openai_app)
 
 
 app.mount("/images/api/v1", images_app)
 app.mount("/images/api/v1", images_app)
 app.mount("/audio/api/v1", audio_app)
 app.mount("/audio/api/v1", audio_app)
-app.mount("/rag/api/v1", rag_app)
+app.mount("/retrieval/api/v1", retrieval_app)
 
 
 app.mount("/api/v1", webui_app)
 app.mount("/api/v1", webui_app)
 
 
-webui_app.state.EMBEDDING_FUNCTION = rag_app.state.EMBEDDING_FUNCTION
+
+webui_app.state.EMBEDDING_FUNCTION = retrieval_app.state.EMBEDDING_FUNCTION
 
 
 
 
 async def get_all_models():
 async def get_all_models():
@@ -2055,7 +2076,7 @@ async def get_app_config(request: Request):
             "enable_login_form": webui_app.state.config.ENABLE_LOGIN_FORM,
             "enable_login_form": webui_app.state.config.ENABLE_LOGIN_FORM,
             **(
             **(
                 {
                 {
-                    "enable_web_search": rag_app.state.config.ENABLE_RAG_WEB_SEARCH,
+                    "enable_web_search": retrieval_app.state.config.ENABLE_RAG_WEB_SEARCH,
                     "enable_image_generation": images_app.state.config.ENABLED,
                     "enable_image_generation": images_app.state.config.ENABLED,
                     "enable_community_sharing": webui_app.state.config.ENABLE_COMMUNITY_SHARING,
                     "enable_community_sharing": webui_app.state.config.ENABLE_COMMUNITY_SHARING,
                     "enable_message_rating": webui_app.state.config.ENABLE_MESSAGE_RATING,
                     "enable_message_rating": webui_app.state.config.ENABLE_MESSAGE_RATING,
@@ -2081,8 +2102,8 @@ async def get_app_config(request: Request):
                     },
                     },
                 },
                 },
                 "file": {
                 "file": {
-                    "max_size": rag_app.state.config.FILE_MAX_SIZE,
-                    "max_count": rag_app.state.config.FILE_MAX_COUNT,
+                    "max_size": retrieval_app.state.config.FILE_MAX_SIZE,
+                    "max_count": retrieval_app.state.config.FILE_MAX_COUNT,
                 },
                 },
                 "permissions": {**webui_app.state.config.USER_PERMISSIONS},
                 "permissions": {**webui_app.state.config.USER_PERMISSIONS},
             }
             }
@@ -2154,7 +2175,8 @@ async def get_app_changelog():
 @app.get("/api/version/updates")
 @app.get("/api/version/updates")
 async def get_app_latest_release_version():
 async def get_app_latest_release_version():
     try:
     try:
-        async with aiohttp.ClientSession(trust_env=True) as session:
+        timeout = aiohttp.ClientTimeout(total=1)
+        async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
             async with session.get(
             async with session.get(
                 "https://api.github.com/repos/open-webui/open-webui/releases/latest"
                 "https://api.github.com/repos/open-webui/open-webui/releases/latest"
             ) as response:
             ) as response:
@@ -2164,10 +2186,7 @@ async def get_app_latest_release_version():
 
 
                 return {"current": VERSION, "latest": latest_version[1:]}
                 return {"current": VERSION, "latest": latest_version[1:]}
     except aiohttp.ClientError:
     except aiohttp.ClientError:
-        raise HTTPException(
-            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
-            detail=ERROR_MESSAGES.RATE_LIMIT_EXCEEDED,
-        )
+        return {"current": VERSION, "latest": VERSION}
 
 
 
 
 ############################
 ############################

+ 2 - 0
backend/requirements.txt

@@ -46,6 +46,8 @@ sentence-transformers==3.0.1
 colbert-ai==0.2.21
 colbert-ai==0.2.21
 einops==0.8.0
 einops==0.8.0
 
 
+
+ftfy==6.2.3
 pypdf==4.3.1
 pypdf==4.3.1
 docx2txt==0.8
 docx2txt==0.8
 python-pptx==1.0.0
 python-pptx==1.0.0

+ 2 - 0
pyproject.toml

@@ -53,6 +53,8 @@ dependencies = [
     "colbert-ai==0.2.21",
     "colbert-ai==0.2.21",
     "einops==0.8.0",
     "einops==0.8.0",
     
     
+
+    "ftfy==6.2.3",
     "pypdf==4.3.1",
     "pypdf==4.3.1",
     "docx2txt==0.8",
     "docx2txt==0.8",
     "python-pptx==1.0.0",
     "python-pptx==1.0.0",

+ 114 - 146
src/lib/apis/rag/index.ts → src/lib/apis/retrieval/index.ts

@@ -170,27 +170,23 @@ export const updateQuerySettings = async (token: string, settings: QuerySettings
 	return res;
 	return res;
 };
 };
 
 
-export const processDocToVectorDB = async (token: string, file_id: string) => {
+export const getEmbeddingConfig = async (token: string) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${RAG_API_BASE_URL}/process/doc`, {
-		method: 'POST',
+	const res = await fetch(`${RAG_API_BASE_URL}/embedding`, {
+		method: 'GET',
 		headers: {
 		headers: {
-			Accept: 'application/json',
 			'Content-Type': 'application/json',
 			'Content-Type': 'application/json',
-			authorization: `Bearer ${token}`
-		},
-		body: JSON.stringify({
-			file_id: file_id
-		})
+			Authorization: `Bearer ${token}`
+		}
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
 			if (!res.ok) throw await res.json();
 			if (!res.ok) throw await res.json();
 			return res.json();
 			return res.json();
 		})
 		})
 		.catch((err) => {
 		.catch((err) => {
-			error = err.detail;
 			console.log(err);
 			console.log(err);
+			error = err.detail;
 			return null;
 			return null;
 		});
 		});
 
 
@@ -201,51 +197,29 @@ export const processDocToVectorDB = async (token: string, file_id: string) => {
 	return res;
 	return res;
 };
 };
 
 
-export const uploadDocToVectorDB = async (token: string, collection_name: string, file: File) => {
-	const data = new FormData();
-	data.append('file', file);
-	data.append('collection_name', collection_name);
-
-	let error = null;
-
-	const res = await fetch(`${RAG_API_BASE_URL}/doc`, {
-		method: 'POST',
-		headers: {
-			Accept: 'application/json',
-			authorization: `Bearer ${token}`
-		},
-		body: data
-	})
-		.then(async (res) => {
-			if (!res.ok) throw await res.json();
-			return res.json();
-		})
-		.catch((err) => {
-			error = err.detail;
-			console.log(err);
-			return null;
-		});
-
-	if (error) {
-		throw error;
-	}
+type OpenAIConfigForm = {
+	key: string;
+	url: string;
+	batch_size: number;
+};
 
 
-	return res;
+type EmbeddingModelUpdateForm = {
+	openai_config?: OpenAIConfigForm;
+	embedding_engine: string;
+	embedding_model: string;
 };
 };
 
 
-export const uploadWebToVectorDB = async (token: string, collection_name: string, url: string) => {
+export const updateEmbeddingConfig = async (token: string, payload: EmbeddingModelUpdateForm) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${RAG_API_BASE_URL}/web`, {
+	const res = await fetch(`${RAG_API_BASE_URL}/embedding/update`, {
 		method: 'POST',
 		method: 'POST',
 		headers: {
 		headers: {
-			Accept: 'application/json',
 			'Content-Type': 'application/json',
 			'Content-Type': 'application/json',
-			authorization: `Bearer ${token}`
+			Authorization: `Bearer ${token}`
 		},
 		},
 		body: JSON.stringify({
 		body: JSON.stringify({
-			url: url,
-			collection_name: collection_name
+			...payload
 		})
 		})
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
@@ -253,8 +227,8 @@ export const uploadWebToVectorDB = async (token: string, collection_name: string
 			return res.json();
 			return res.json();
 		})
 		})
 		.catch((err) => {
 		.catch((err) => {
-			error = err.detail;
 			console.log(err);
 			console.log(err);
+			error = err.detail;
 			return null;
 			return null;
 		});
 		});
 
 
@@ -265,27 +239,23 @@ export const uploadWebToVectorDB = async (token: string, collection_name: string
 	return res;
 	return res;
 };
 };
 
 
-export const uploadYoutubeTranscriptionToVectorDB = async (token: string, url: string) => {
+export const getRerankingConfig = async (token: string) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${RAG_API_BASE_URL}/youtube`, {
-		method: 'POST',
+	const res = await fetch(`${RAG_API_BASE_URL}/reranking`, {
+		method: 'GET',
 		headers: {
 		headers: {
-			Accept: 'application/json',
 			'Content-Type': 'application/json',
 			'Content-Type': 'application/json',
-			authorization: `Bearer ${token}`
-		},
-		body: JSON.stringify({
-			url: url
-		})
+			Authorization: `Bearer ${token}`
+		}
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
 			if (!res.ok) throw await res.json();
 			if (!res.ok) throw await res.json();
 			return res.json();
 			return res.json();
 		})
 		})
 		.catch((err) => {
 		.catch((err) => {
-			error = err.detail;
 			console.log(err);
 			console.log(err);
+			error = err.detail;
 			return null;
 			return null;
 		});
 		});
 
 
@@ -296,25 +266,21 @@ export const uploadYoutubeTranscriptionToVectorDB = async (token: string, url: s
 	return res;
 	return res;
 };
 };
 
 
-export const queryDoc = async (
-	token: string,
-	collection_name: string,
-	query: string,
-	k: number | null = null
-) => {
+type RerankingModelUpdateForm = {
+	reranking_model: string;
+};
+
+export const updateRerankingConfig = async (token: string, payload: RerankingModelUpdateForm) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${RAG_API_BASE_URL}/query/doc`, {
+	const res = await fetch(`${RAG_API_BASE_URL}/reranking/update`, {
 		method: 'POST',
 		method: 'POST',
 		headers: {
 		headers: {
-			Accept: 'application/json',
 			'Content-Type': 'application/json',
 			'Content-Type': 'application/json',
-			authorization: `Bearer ${token}`
+			Authorization: `Bearer ${token}`
 		},
 		},
 		body: JSON.stringify({
 		body: JSON.stringify({
-			collection_name: collection_name,
-			query: query,
-			k: k
+			...payload
 		})
 		})
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
@@ -322,6 +288,7 @@ export const queryDoc = async (
 			return res.json();
 			return res.json();
 		})
 		})
 		.catch((err) => {
 		.catch((err) => {
+			console.log(err);
 			error = err.detail;
 			error = err.detail;
 			return null;
 			return null;
 		});
 		});
@@ -333,15 +300,16 @@ export const queryDoc = async (
 	return res;
 	return res;
 };
 };
 
 
-export const queryCollection = async (
-	token: string,
-	collection_names: string,
-	query: string,
-	k: number | null = null
-) => {
+export interface SearchDocument {
+	status: boolean;
+	collection_name: string;
+	filenames: string[];
+}
+
+export const processFile = async (token: string, file_id: string) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${RAG_API_BASE_URL}/query/collection`, {
+	const res = await fetch(`${RAG_API_BASE_URL}/process/file`, {
 		method: 'POST',
 		method: 'POST',
 		headers: {
 		headers: {
 			Accept: 'application/json',
 			Accept: 'application/json',
@@ -349,9 +317,7 @@ export const queryCollection = async (
 			authorization: `Bearer ${token}`
 			authorization: `Bearer ${token}`
 		},
 		},
 		body: JSON.stringify({
 		body: JSON.stringify({
-			collection_names: collection_names,
-			query: query,
-			k: k
+			file_id: file_id
 		})
 		})
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
@@ -360,6 +326,7 @@ export const queryCollection = async (
 		})
 		})
 		.catch((err) => {
 		.catch((err) => {
 			error = err.detail;
 			error = err.detail;
+			console.log(err);
 			return null;
 			return null;
 		});
 		});
 
 
@@ -370,10 +337,10 @@ export const queryCollection = async (
 	return res;
 	return res;
 };
 };
 
 
-export const scanDocs = async (token: string) => {
+export const processDocsDir = async (token: string) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${RAG_API_BASE_URL}/scan`, {
+	const res = await fetch(`${RAG_API_BASE_URL}/process/dir`, {
 		method: 'GET',
 		method: 'GET',
 		headers: {
 		headers: {
 			Accept: 'application/json',
 			Accept: 'application/json',
@@ -396,15 +363,19 @@ export const scanDocs = async (token: string) => {
 	return res;
 	return res;
 };
 };
 
 
-export const resetUploadDir = async (token: string) => {
+export const processYoutubeVideo = async (token: string, url: string) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${RAG_API_BASE_URL}/reset/uploads`, {
+	const res = await fetch(`${RAG_API_BASE_URL}/process/youtube`, {
 		method: 'POST',
 		method: 'POST',
 		headers: {
 		headers: {
 			Accept: 'application/json',
 			Accept: 'application/json',
+			'Content-Type': 'application/json',
 			authorization: `Bearer ${token}`
 			authorization: `Bearer ${token}`
-		}
+		},
+		body: JSON.stringify({
+			url: url
+		})
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
 			if (!res.ok) throw await res.json();
 			if (!res.ok) throw await res.json();
@@ -412,6 +383,7 @@ export const resetUploadDir = async (token: string) => {
 		})
 		})
 		.catch((err) => {
 		.catch((err) => {
 			error = err.detail;
 			error = err.detail;
+			console.log(err);
 			return null;
 			return null;
 		});
 		});
 
 
@@ -422,15 +394,20 @@ export const resetUploadDir = async (token: string) => {
 	return res;
 	return res;
 };
 };
 
 
-export const resetVectorDB = async (token: string) => {
+export const processWeb = async (token: string, collection_name: string, url: string) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${RAG_API_BASE_URL}/reset/db`, {
+	const res = await fetch(`${RAG_API_BASE_URL}/process/web`, {
 		method: 'POST',
 		method: 'POST',
 		headers: {
 		headers: {
 			Accept: 'application/json',
 			Accept: 'application/json',
+			'Content-Type': 'application/json',
 			authorization: `Bearer ${token}`
 			authorization: `Bearer ${token}`
-		}
+		},
+		body: JSON.stringify({
+			url: url,
+			collection_name: collection_name
+		})
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
 			if (!res.ok) throw await res.json();
 			if (!res.ok) throw await res.json();
@@ -438,6 +415,7 @@ export const resetVectorDB = async (token: string) => {
 		})
 		})
 		.catch((err) => {
 		.catch((err) => {
 			error = err.detail;
 			error = err.detail;
+			console.log(err);
 			return null;
 			return null;
 		});
 		});
 
 
@@ -448,15 +426,23 @@ export const resetVectorDB = async (token: string) => {
 	return res;
 	return res;
 };
 };
 
 
-export const getEmbeddingConfig = async (token: string) => {
+export const processWebSearch = async (
+	token: string,
+	query: string,
+	collection_name?: string
+): Promise<SearchDocument | null> => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${RAG_API_BASE_URL}/embedding`, {
-		method: 'GET',
+	const res = await fetch(`${RAG_API_BASE_URL}/process/web/search`, {
+		method: 'POST',
 		headers: {
 		headers: {
 			'Content-Type': 'application/json',
 			'Content-Type': 'application/json',
 			Authorization: `Bearer ${token}`
 			Authorization: `Bearer ${token}`
-		}
+		},
+		body: JSON.stringify({
+			query,
+			collection_name: collection_name ?? ''
+		})
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
 			if (!res.ok) throw await res.json();
 			if (!res.ok) throw await res.json();
@@ -475,29 +461,25 @@ export const getEmbeddingConfig = async (token: string) => {
 	return res;
 	return res;
 };
 };
 
 
-type OpenAIConfigForm = {
-	key: string;
-	url: string;
-	batch_size: number;
-};
-
-type EmbeddingModelUpdateForm = {
-	openai_config?: OpenAIConfigForm;
-	embedding_engine: string;
-	embedding_model: string;
-};
-
-export const updateEmbeddingConfig = async (token: string, payload: EmbeddingModelUpdateForm) => {
+export const queryDoc = async (
+	token: string,
+	collection_name: string,
+	query: string,
+	k: number | null = null
+) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${RAG_API_BASE_URL}/embedding/update`, {
+	const res = await fetch(`${RAG_API_BASE_URL}/query/doc`, {
 		method: 'POST',
 		method: 'POST',
 		headers: {
 		headers: {
+			Accept: 'application/json',
 			'Content-Type': 'application/json',
 			'Content-Type': 'application/json',
-			Authorization: `Bearer ${token}`
+			authorization: `Bearer ${token}`
 		},
 		},
 		body: JSON.stringify({
 		body: JSON.stringify({
-			...payload
+			collection_name: collection_name,
+			query: query,
+			k: k
 		})
 		})
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
@@ -505,7 +487,6 @@ export const updateEmbeddingConfig = async (token: string, payload: EmbeddingMod
 			return res.json();
 			return res.json();
 		})
 		})
 		.catch((err) => {
 		.catch((err) => {
-			console.log(err);
 			error = err.detail;
 			error = err.detail;
 			return null;
 			return null;
 		});
 		});
@@ -517,22 +498,32 @@ export const updateEmbeddingConfig = async (token: string, payload: EmbeddingMod
 	return res;
 	return res;
 };
 };
 
 
-export const getRerankingConfig = async (token: string) => {
+export const queryCollection = async (
+	token: string,
+	collection_names: string,
+	query: string,
+	k: number | null = null
+) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${RAG_API_BASE_URL}/reranking`, {
-		method: 'GET',
+	const res = await fetch(`${RAG_API_BASE_URL}/query/collection`, {
+		method: 'POST',
 		headers: {
 		headers: {
+			Accept: 'application/json',
 			'Content-Type': 'application/json',
 			'Content-Type': 'application/json',
-			Authorization: `Bearer ${token}`
-		}
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			collection_names: collection_names,
+			query: query,
+			k: k
+		})
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
 			if (!res.ok) throw await res.json();
 			if (!res.ok) throw await res.json();
 			return res.json();
 			return res.json();
 		})
 		})
 		.catch((err) => {
 		.catch((err) => {
-			console.log(err);
 			error = err.detail;
 			error = err.detail;
 			return null;
 			return null;
 		});
 		});
@@ -544,29 +535,21 @@ export const getRerankingConfig = async (token: string) => {
 	return res;
 	return res;
 };
 };
 
 
-type RerankingModelUpdateForm = {
-	reranking_model: string;
-};
-
-export const updateRerankingConfig = async (token: string, payload: RerankingModelUpdateForm) => {
+export const resetUploadDir = async (token: string) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${RAG_API_BASE_URL}/reranking/update`, {
+	const res = await fetch(`${RAG_API_BASE_URL}/reset/uploads`, {
 		method: 'POST',
 		method: 'POST',
 		headers: {
 		headers: {
-			'Content-Type': 'application/json',
-			Authorization: `Bearer ${token}`
-		},
-		body: JSON.stringify({
-			...payload
-		})
+			Accept: 'application/json',
+			authorization: `Bearer ${token}`
+		}
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
 			if (!res.ok) throw await res.json();
 			if (!res.ok) throw await res.json();
 			return res.json();
 			return res.json();
 		})
 		})
 		.catch((err) => {
 		.catch((err) => {
-			console.log(err);
 			error = err.detail;
 			error = err.detail;
 			return null;
 			return null;
 		});
 		});
@@ -578,30 +561,21 @@ export const updateRerankingConfig = async (token: string, payload: RerankingMod
 	return res;
 	return res;
 };
 };
 
 
-export const runWebSearch = async (
-	token: string,
-	query: string,
-	collection_name?: string
-): Promise<SearchDocument | null> => {
+export const resetVectorDB = async (token: string) => {
 	let error = null;
 	let error = null;
 
 
-	const res = await fetch(`${RAG_API_BASE_URL}/web/search`, {
+	const res = await fetch(`${RAG_API_BASE_URL}/reset/db`, {
 		method: 'POST',
 		method: 'POST',
 		headers: {
 		headers: {
-			'Content-Type': 'application/json',
-			Authorization: `Bearer ${token}`
-		},
-		body: JSON.stringify({
-			query,
-			collection_name: collection_name ?? ''
-		})
+			Accept: 'application/json',
+			authorization: `Bearer ${token}`
+		}
 	})
 	})
 		.then(async (res) => {
 		.then(async (res) => {
 			if (!res.ok) throw await res.json();
 			if (!res.ok) throw await res.json();
 			return res.json();
 			return res.json();
 		})
 		})
 		.catch((err) => {
 		.catch((err) => {
-			console.log(err);
 			error = err.detail;
 			error = err.detail;
 			return null;
 			return null;
 		});
 		});
@@ -612,9 +586,3 @@ export const runWebSearch = async (
 
 
 	return res;
 	return res;
 };
 };
-
-export interface SearchDocument {
-	status: boolean;
-	collection_name: string;
-	filenames: string[];
-}

+ 3 - 3
src/lib/components/admin/Settings/Documents.svelte

@@ -7,7 +7,7 @@
 	import { deleteAllFiles, deleteFileById } from '$lib/apis/files';
 	import { deleteAllFiles, deleteFileById } from '$lib/apis/files';
 	import {
 	import {
 		getQuerySettings,
 		getQuerySettings,
-		scanDocs,
+		processDocsDir,
 		updateQuerySettings,
 		updateQuerySettings,
 		resetVectorDB,
 		resetVectorDB,
 		getEmbeddingConfig,
 		getEmbeddingConfig,
@@ -17,7 +17,7 @@
 		resetUploadDir,
 		resetUploadDir,
 		getRAGConfig,
 		getRAGConfig,
 		updateRAGConfig
 		updateRAGConfig
-	} from '$lib/apis/rag';
+	} from '$lib/apis/retrieval';
 	import ResetUploadDirConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
 	import ResetUploadDirConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
 	import ResetVectorDBConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
 	import ResetVectorDBConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
 
 
@@ -63,7 +63,7 @@
 
 
 	const scanHandler = async () => {
 	const scanHandler = async () => {
 		scanDirLoading = true;
 		scanDirLoading = true;
-		const res = await scanDocs(localStorage.token);
+		const res = await processDocsDir(localStorage.token);
 		scanDirLoading = false;
 		scanDirLoading = false;
 
 
 		if (res) {
 		if (res) {

+ 1 - 1
src/lib/components/admin/Settings/WebSearch.svelte

@@ -1,5 +1,5 @@
 <script lang="ts">
 <script lang="ts">
-	import { getRAGConfig, updateRAGConfig } from '$lib/apis/rag';
+	import { getRAGConfig, updateRAGConfig } from '$lib/apis/retrieval';
 	import Switch from '$lib/components/common/Switch.svelte';
 	import Switch from '$lib/components/common/Switch.svelte';
 
 
 	import { documents, models } from '$lib/stores';
 	import { documents, models } from '$lib/stores';

+ 2 - 2
src/lib/components/chat/Chat.svelte

@@ -52,7 +52,7 @@
 		updateChatById
 		updateChatById
 	} from '$lib/apis/chats';
 	} from '$lib/apis/chats';
 	import { generateOpenAIChatCompletion } from '$lib/apis/openai';
 	import { generateOpenAIChatCompletion } from '$lib/apis/openai';
-	import { runWebSearch } from '$lib/apis/rag';
+	import { processWebSearch } from '$lib/apis/retrieval';
 	import { createOpenAITextStream } from '$lib/apis/streaming';
 	import { createOpenAITextStream } from '$lib/apis/streaming';
 	import { queryMemory } from '$lib/apis/memories';
 	import { queryMemory } from '$lib/apis/memories';
 	import { getAndUpdateUserLocation, getUserSettings } from '$lib/apis/users';
 	import { getAndUpdateUserLocation, getUserSettings } from '$lib/apis/users';
@@ -1737,7 +1737,7 @@
 		});
 		});
 		history.messages[responseMessageId] = responseMessage;
 		history.messages[responseMessageId] = responseMessage;
 
 
-		const results = await runWebSearch(localStorage.token, searchQuery).catch((error) => {
+		const results = await processWebSearch(localStorage.token, searchQuery).catch((error) => {
 			console.log(error);
 			console.log(error);
 			toast.error(error);
 			toast.error(error);
 
 

+ 3 - 0
src/lib/components/chat/Controls/Controls.svelte

@@ -46,6 +46,9 @@
 								chatFiles.splice(fileIdx, 1);
 								chatFiles.splice(fileIdx, 1);
 								chatFiles = chatFiles;
 								chatFiles = chatFiles;
 							}}
 							}}
+							on:click={() => {
+								console.log(file);
+							}}
 						/>
 						/>
 					{/each}
 					{/each}
 				</div>
 				</div>

+ 4 - 6
src/lib/components/chat/MessageInput.svelte

@@ -17,7 +17,8 @@
 	import { blobToFile, findWordIndices } from '$lib/utils';
 	import { blobToFile, findWordIndices } from '$lib/utils';
 
 
 	import { transcribeAudio } from '$lib/apis/audio';
 	import { transcribeAudio } from '$lib/apis/audio';
-	import { processDocToVectorDB } from '$lib/apis/rag';
+
+	import { processFile } from '$lib/apis/retrieval';
 	import { uploadFile } from '$lib/apis/files';
 	import { uploadFile } from '$lib/apis/files';
 
 
 	import {
 	import {
@@ -158,17 +159,14 @@
 
 
 	const processFileItem = async (fileItem) => {
 	const processFileItem = async (fileItem) => {
 		try {
 		try {
-			const res = await processDocToVectorDB(localStorage.token, fileItem.id);
-
+			const res = await processFile(localStorage.token, fileItem.id);
 			if (res) {
 			if (res) {
 				fileItem.status = 'processed';
 				fileItem.status = 'processed';
 				fileItem.collection_name = res.collection_name;
 				fileItem.collection_name = res.collection_name;
 				files = files;
 				files = files;
 			}
 			}
 		} catch (e) {
 		} catch (e) {
-			// Remove the failed doc from the files array
-			// files = files.filter((f) => f.id !== fileItem.id);
-			toast.error(e);
+			// We keep the file in the files list even if it fails to process
 			fileItem.status = 'processed';
 			fileItem.status = 'processed';
 			files = files;
 			files = files;
 		}
 		}

+ 3 - 3
src/lib/components/chat/MessageInput/Commands.svelte

@@ -9,7 +9,7 @@
 	import Models from './Commands/Models.svelte';
 	import Models from './Commands/Models.svelte';
 
 
 	import { removeLastWordFromString } from '$lib/utils';
 	import { removeLastWordFromString } from '$lib/utils';
-	import { uploadWebToVectorDB, uploadYoutubeTranscriptionToVectorDB } from '$lib/apis/rag';
+	import { processWeb, processYoutubeVideo } from '$lib/apis/retrieval';
 
 
 	export let prompt = '';
 	export let prompt = '';
 	export let files = [];
 	export let files = [];
@@ -41,7 +41,7 @@
 
 
 		try {
 		try {
 			files = [...files, doc];
 			files = [...files, doc];
-			const res = await uploadWebToVectorDB(localStorage.token, '', url);
+			const res = await processWeb(localStorage.token, '', url);
 
 
 			if (res) {
 			if (res) {
 				doc.status = 'processed';
 				doc.status = 'processed';
@@ -69,7 +69,7 @@
 
 
 		try {
 		try {
 			files = [...files, doc];
 			files = [...files, doc];
-			const res = await uploadYoutubeTranscriptionToVectorDB(localStorage.token, url);
+			const res = await processYoutubeVideo(localStorage.token, url);
 
 
 			if (res) {
 			if (res) {
 				doc.status = 'processed';
 				doc.status = 'processed';

+ 3 - 15
src/lib/components/common/FileItem.svelte

@@ -8,8 +8,6 @@
 	export let colorClassName = 'bg-white dark:bg-gray-800';
 	export let colorClassName = 'bg-white dark:bg-gray-800';
 	export let url: string | null = null;
 	export let url: string | null = null;
 
 
-	export let clickHandler: Function | null = null;
-
 	export let dismissible = false;
 	export let dismissible = false;
 	export let status = 'processed';
 	export let status = 'processed';
 
 
@@ -17,7 +15,7 @@
 	export let type: string;
 	export let type: string;
 	export let size: number;
 	export let size: number;
 
 
-	function formatSize(size) {
+	const formatSize = (size) => {
 		if (size == null) return 'Unknown size';
 		if (size == null) return 'Unknown size';
 		if (typeof size !== 'number' || size < 0) return 'Invalid size';
 		if (typeof size !== 'number' || size < 0) return 'Invalid size';
 		if (size === 0) return '0 B';
 		if (size === 0) return '0 B';
@@ -29,7 +27,7 @@
 			unitIndex++;
 			unitIndex++;
 		}
 		}
 		return `${size.toFixed(1)} ${units[unitIndex]}`;
 		return `${size.toFixed(1)} ${units[unitIndex]}`;
-	}
+	};
 </script>
 </script>
 
 
 <div class="relative group">
 <div class="relative group">
@@ -37,17 +35,7 @@
 		class="h-14 {className} flex items-center space-x-3 {colorClassName} rounded-xl border border-gray-100 dark:border-gray-800 text-left"
 		class="h-14 {className} flex items-center space-x-3 {colorClassName} rounded-xl border border-gray-100 dark:border-gray-800 text-left"
 		type="button"
 		type="button"
 		on:click={async () => {
 		on:click={async () => {
-			if (clickHandler === null) {
-				if (url) {
-					if (type === 'file') {
-						window.open(`${url}/content`, '_blank').focus();
-					} else {
-						window.open(`${url}`, '_blank').focus();
-					}
-				}
-			} else {
-				clickHandler();
-			}
+			dispatch('click');
 		}}
 		}}
 	>
 	>
 		<div class="p-4 py-[1.1rem] bg-red-400 text-white rounded-l-xl">
 		<div class="p-4 py-[1.1rem] bg-red-400 text-white rounded-l-xl">

+ 3 - 6
src/lib/components/documents/AddDocModal.svelte

@@ -3,16 +3,13 @@
 	import dayjs from 'dayjs';
 	import dayjs from 'dayjs';
 	import { onMount, getContext } from 'svelte';
 	import { onMount, getContext } from 'svelte';
 
 
-	import { createNewDoc, getDocs, tagDocByName, updateDocByName } from '$lib/apis/documents';
+	import { getDocs } from '$lib/apis/documents';
 	import Modal from '../common/Modal.svelte';
 	import Modal from '../common/Modal.svelte';
 	import { documents } from '$lib/stores';
 	import { documents } from '$lib/stores';
-	import TagInput from '../common/Tags/TagInput.svelte';
-	import Tags from '../common/Tags.svelte';
-	import { addTagById } from '$lib/apis/chats';
-	import { uploadDocToVectorDB } from '$lib/apis/rag';
-	import { transformFileName } from '$lib/utils';
 	import { SUPPORTED_FILE_EXTENSIONS, SUPPORTED_FILE_TYPE } from '$lib/constants';
 	import { SUPPORTED_FILE_EXTENSIONS, SUPPORTED_FILE_TYPE } from '$lib/constants';
 
 
+	import Tags from '../common/Tags.svelte';
+
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
 	export let show = false;
 	export let show = false;

+ 2 - 2
src/lib/components/workspace/Documents.svelte

@@ -8,7 +8,7 @@
 	import { createNewDoc, deleteDocByName, getDocs } from '$lib/apis/documents';
 	import { createNewDoc, deleteDocByName, getDocs } from '$lib/apis/documents';
 
 
 	import { SUPPORTED_FILE_TYPE, SUPPORTED_FILE_EXTENSIONS } from '$lib/constants';
 	import { SUPPORTED_FILE_TYPE, SUPPORTED_FILE_EXTENSIONS } from '$lib/constants';
-	import { processDocToVectorDB, uploadDocToVectorDB } from '$lib/apis/rag';
+	import { processFile } from '$lib/apis/retrieval';
 	import { blobToFile, transformFileName } from '$lib/utils';
 	import { blobToFile, transformFileName } from '$lib/utils';
 
 
 	import Checkbox from '$lib/components/common/Checkbox.svelte';
 	import Checkbox from '$lib/components/common/Checkbox.svelte';
@@ -74,7 +74,7 @@
 			return null;
 			return null;
 		});
 		});
 
 
-		const res = await processDocToVectorDB(localStorage.token, uploadedFile.id).catch((error) => {
+		const res = await processFile(localStorage.token, uploadedFile.id).catch((error) => {
 			toast.error(error);
 			toast.error(error);
 			return null;
 			return null;
 		});
 		});

+ 1 - 1
src/lib/constants.ts

@@ -11,7 +11,7 @@ export const OLLAMA_API_BASE_URL = `${WEBUI_BASE_URL}/ollama`;
 export const OPENAI_API_BASE_URL = `${WEBUI_BASE_URL}/openai`;
 export const OPENAI_API_BASE_URL = `${WEBUI_BASE_URL}/openai`;
 export const AUDIO_API_BASE_URL = `${WEBUI_BASE_URL}/audio/api/v1`;
 export const AUDIO_API_BASE_URL = `${WEBUI_BASE_URL}/audio/api/v1`;
 export const IMAGES_API_BASE_URL = `${WEBUI_BASE_URL}/images/api/v1`;
 export const IMAGES_API_BASE_URL = `${WEBUI_BASE_URL}/images/api/v1`;
-export const RAG_API_BASE_URL = `${WEBUI_BASE_URL}/rag/api/v1`;
+export const RAG_API_BASE_URL = `${WEBUI_BASE_URL}/retrieval/api/v1`;
 
 
 export const WEBUI_VERSION = APP_VERSION;
 export const WEBUI_VERSION = APP_VERSION;
 export const WEBUI_BUILD_HASH = APP_BUILD_HASH;
 export const WEBUI_BUILD_HASH = APP_BUILD_HASH;

+ 5 - 5
src/lib/i18n/locales/ca-ES/translation.json

@@ -9,7 +9,7 @@
 	"{{user}}'s Chats": "Els xats de {{user}}",
 	"{{user}}'s Chats": "Els xats de {{user}}",
 	"{{webUIName}} Backend Required": "El Backend de {{webUIName}} és necessari",
 	"{{webUIName}} Backend Required": "El Backend de {{webUIName}} és necessari",
 	"*Prompt node ID(s) are required for image generation": "*Els identificadors de nodes d'indicacions són necessaris per a la generació d'imatges",
 	"*Prompt node ID(s) are required for image generation": "*Els identificadors de nodes d'indicacions són necessaris per a la generació d'imatges",
-	"A new version (v{{LATEST_VERSION}}) is now available.": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "Hi ha una nova versió disponible (v{{LATEST_VERSION}}).",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Un model de tasca s'utilitza quan es realitzen tasques com ara generar títols per a xats i consultes de cerca per a la web",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Un model de tasca s'utilitza quan es realitzen tasques com ara generar títols per a xats i consultes de cerca per a la web",
 	"a user": "un usuari",
 	"a user": "un usuari",
 	"About": "Sobre",
 	"About": "Sobre",
@@ -466,7 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ui! Sembla que l'URL no és vàlida. Si us plau, revisa-la i torna-ho a provar.",
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ui! Sembla que l'URL no és vàlida. Si us plau, revisa-la i torna-ho a provar.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Ui! Hi ha hagut un error en la resposta anterior. Torna a provar-ho o contacta amb un administrador",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Ui! Hi ha hagut un error en la resposta anterior. Torna a provar-ho o contacta amb un administrador",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ui! Estàs utilitzant un mètode no suportat (només frontend). Si us plau, serveix la WebUI des del backend.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ui! Estàs utilitzant un mètode no suportat (només frontend). Si us plau, serveix la WebUI des del backend.",
-	"Open file": "",
+	"Open file": "Obrir arxiu",
 	"Open new chat": "Obre un xat nou",
 	"Open new chat": "Obre un xat nou",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "La versió d'Open WebUI (v{{OPEN_WEBUI_VERSION}}) és inferior a la versió requerida (v{{REQUIRED_VERSION}})",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "La versió d'Open WebUI (v{{OPEN_WEBUI_VERSION}}) és inferior a la versió requerida (v{{REQUIRED_VERSION}})",
 	"OpenAI": "OpenAI",
 	"OpenAI": "OpenAI",
@@ -478,7 +478,7 @@
 	"Other": "Altres",
 	"Other": "Altres",
 	"Output format": "Format de sortida",
 	"Output format": "Format de sortida",
 	"Overview": "Vista general",
 	"Overview": "Vista general",
-	"page": "",
+	"page": "pàgina",
 	"Password": "Contrasenya",
 	"Password": "Contrasenya",
 	"PDF document (.pdf)": "Document PDF (.pdf)",
 	"PDF document (.pdf)": "Document PDF (.pdf)",
 	"PDF Extract Images (OCR)": "Extreu imatges del PDF (OCR)",
 	"PDF Extract Images (OCR)": "Extreu imatges del PDF (OCR)",
@@ -497,7 +497,7 @@
 	"Plain text (.txt)": "Text pla (.txt)",
 	"Plain text (.txt)": "Text pla (.txt)",
 	"Playground": "Zona de jocs",
 	"Playground": "Zona de jocs",
 	"Please carefully review the following warnings:": "Si us plau, revisa els següents avisos amb cura:",
 	"Please carefully review the following warnings:": "Si us plau, revisa els següents avisos amb cura:",
-	"Please select a reason": "",
+	"Please select a reason": "Si us plau, selecciona una raó",
 	"Positive attitude": "Actitud positiva",
 	"Positive attitude": "Actitud positiva",
 	"Previous 30 days": "30 dies anteriors",
 	"Previous 30 days": "30 dies anteriors",
 	"Previous 7 days": "7 dies anteriors",
 	"Previous 7 days": "7 dies anteriors",
@@ -704,7 +704,7 @@
 	"Unpin": "Alliberar",
 	"Unpin": "Alliberar",
 	"Update": "Actualitzar",
 	"Update": "Actualitzar",
 	"Update and Copy Link": "Actualitzar i copiar l'enllaç",
 	"Update and Copy Link": "Actualitzar i copiar l'enllaç",
-	"Update for the latest features and improvements.": "",
+	"Update for the latest features and improvements.": "Actualitza per a les darreres característiques i millores.",
 	"Update password": "Actualitzar la contrasenya",
 	"Update password": "Actualitzar la contrasenya",
 	"Updated at": "Actualitzat",
 	"Updated at": "Actualitzat",
 	"Upload": "Pujar",
 	"Upload": "Pujar",

+ 1 - 1
src/lib/utils/rag/index.ts

@@ -1,4 +1,4 @@
-import { getRAGTemplate } from '$lib/apis/rag';
+import { getRAGTemplate } from '$lib/apis/retrieval';
 
 
 export const RAGTemplate = async (token: string, context: string, query: string) => {
 export const RAGTemplate = async (token: string, context: string, query: string) => {
 	let template = await getRAGTemplate(token).catch(() => {
 	let template = await getRAGTemplate(token).catch(() => {

+ 2 - 2
src/routes/(app)/+layout.svelte

@@ -206,10 +206,10 @@
 					const now = new Date();
 					const now = new Date();
 
 
 					if (now - dismissedUpdateToast > 24 * 60 * 60 * 1000) {
 					if (now - dismissedUpdateToast > 24 * 60 * 60 * 1000) {
-						await checkForVersionUpdates();
+						checkForVersionUpdates();
 					}
 					}
 				} else {
 				} else {
-					await checkForVersionUpdates();
+					checkForVersionUpdates();
 				}
 				}
 			}
 			}
 			await tick();
 			await tick();

部分文件因为文件数量过多而无法显示