mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 16:43:16 +00:00
Merge branch 'main' of https://github.com/utin-francis-peter/DocsGPT into feat/sources-in-react-widget
This commit is contained in:
24
.github/labeler.yml
vendored
24
.github/labeler.yml
vendored
@@ -1,23 +1,31 @@
|
||||
repo:
|
||||
- '*'
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '*'
|
||||
|
||||
github:
|
||||
- .github/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '.github/**/*'
|
||||
|
||||
application:
|
||||
- application/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'application/**/*'
|
||||
|
||||
docs:
|
||||
- docs/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'docs/**/*'
|
||||
|
||||
extensions:
|
||||
- extensions/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'extensions/**/*'
|
||||
|
||||
frontend:
|
||||
- frontend/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'frontend/**/*'
|
||||
|
||||
scripts:
|
||||
- scripts/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'scripts/**/*'
|
||||
|
||||
tests:
|
||||
- tests/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'tests/**/*'
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker images to docker.io and ghcr.io
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: './application/Dockerfile'
|
||||
platforms: linux/amd64
|
||||
|
||||
4
.github/workflows/cife.yml
vendored
4
.github/workflows/cife.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
# Runs a single command using the runners shell
|
||||
- name: Build and push Docker images to docker.io and ghcr.io
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: './frontend/Dockerfile'
|
||||
platforms: linux/amd64, linux/arm64
|
||||
|
||||
4
.github/workflows/docker-develop-build.yml
vendored
4
.github/workflows/docker-develop-build.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker images to docker.io and ghcr.io
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: './application/Dockerfile'
|
||||
platforms: linux/amd64
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker images to docker.io and ghcr.io
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: './frontend/Dockerfile'
|
||||
platforms: linux/amd64
|
||||
|
||||
2
.github/workflows/labeler.yml
vendored
2
.github/workflows/labeler.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v4
|
||||
- uses: actions/labeler@v5
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
sync-labels: true
|
||||
|
||||
@@ -23,8 +23,6 @@ Say goodbye to time-consuming manual searches, and let <strong><a href="https://
|
||||
|
||||
</div>
|
||||
|
||||
### 🎃 [Hacktoberfest Prizes, Rules & Q&A](https://github.com/arc53/DocsGPT/blob/main/HACKTOBERFEST.md) 🎃
|
||||
|
||||
### Production Support / Help for Companies:
|
||||
|
||||
We're eager to provide personalized assistance when deploying your DocsGPT to a live environment.
|
||||
|
||||
@@ -11,8 +11,8 @@ from bson.objectid import ObjectId
|
||||
from flask import Blueprint, current_app, make_response, request, Response
|
||||
from flask_restx import fields, Namespace, Resource
|
||||
|
||||
from pymongo import MongoClient
|
||||
|
||||
from application.core.mongo_db import MongoDB
|
||||
from application.core.settings import settings
|
||||
from application.error import bad_request
|
||||
from application.extensions import api
|
||||
@@ -22,7 +22,7 @@ from application.utils import check_required_fields
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
mongo = MongoClient(settings.MONGO_URI)
|
||||
mongo = MongoDB.get_client()
|
||||
db = mongo["docsgpt"]
|
||||
conversations_collection = db["conversations"]
|
||||
sources_collection = db["sources"]
|
||||
@@ -269,9 +269,6 @@ class Stream(Resource):
|
||||
"prompt_id": fields.String(
|
||||
required=False, default="default", description="Prompt ID"
|
||||
),
|
||||
"selectedDocs": fields.String(
|
||||
required=False, description="Selected documents"
|
||||
),
|
||||
"chunks": fields.Integer(
|
||||
required=False, default=2, description="Number of chunks"
|
||||
),
|
||||
@@ -303,10 +300,9 @@ class Stream(Resource):
|
||||
history = json.loads(history)
|
||||
conversation_id = data.get("conversation_id")
|
||||
prompt_id = data.get("prompt_id", "default")
|
||||
if "selectedDocs" in data and data["selectedDocs"] is None:
|
||||
chunks = 0
|
||||
else:
|
||||
chunks = int(data.get("chunks", 2))
|
||||
|
||||
|
||||
chunks = int(data.get("chunks", 2))
|
||||
token_limit = data.get("token_limit", settings.DEFAULT_MAX_HISTORY)
|
||||
retriever_name = data.get("retriever", "classic")
|
||||
|
||||
@@ -333,7 +329,8 @@ class Stream(Resource):
|
||||
)
|
||||
|
||||
prompt = get_prompt(prompt_id)
|
||||
|
||||
if "isNoneDoc" in data and data["isNoneDoc"] is True:
|
||||
chunks = 0
|
||||
retriever = RetrieverCreator.create_retriever(
|
||||
retriever_name,
|
||||
question=question,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import os
|
||||
import datetime
|
||||
from flask import Blueprint, request, send_from_directory
|
||||
from pymongo import MongoClient
|
||||
from werkzeug.utils import secure_filename
|
||||
from bson.objectid import ObjectId
|
||||
|
||||
from application.core.mongo_db import MongoDB
|
||||
from application.core.settings import settings
|
||||
|
||||
mongo = MongoClient(settings.MONGO_URI)
|
||||
mongo = MongoDB.get_client()
|
||||
db = mongo["docsgpt"]
|
||||
conversations_collection = db["conversations"]
|
||||
sources_collection = db["sources"]
|
||||
|
||||
@@ -8,17 +8,18 @@ from bson.dbref import DBRef
|
||||
from bson.objectid import ObjectId
|
||||
from flask import Blueprint, jsonify, make_response, request
|
||||
from flask_restx import inputs, fields, Namespace, Resource
|
||||
from pymongo import MongoClient
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from application.api.user.tasks import ingest, ingest_remote
|
||||
|
||||
from application.core.mongo_db import MongoDB
|
||||
from application.core.settings import settings
|
||||
from application.extensions import api
|
||||
from application.utils import check_required_fields
|
||||
from application.vectorstore.vector_creator import VectorCreator
|
||||
from application.tts.google_tts import GoogleTTS
|
||||
|
||||
mongo = MongoClient(settings.MONGO_URI)
|
||||
mongo = MongoDB.get_client()
|
||||
db = mongo["docsgpt"]
|
||||
conversations_collection = db["conversations"]
|
||||
sources_collection = db["sources"]
|
||||
@@ -342,6 +343,7 @@ class UploadFile(Resource):
|
||||
".mdx",
|
||||
".json",
|
||||
".xlsx",
|
||||
".pptx",
|
||||
],
|
||||
job_name,
|
||||
final_filename,
|
||||
@@ -1663,3 +1665,27 @@ class ManageSync(Resource):
|
||||
return make_response(jsonify({"success": False, "error": str(err)}), 400)
|
||||
|
||||
return make_response(jsonify({"success": True}), 200)
|
||||
|
||||
|
||||
@user_ns.route("/api/tts")
|
||||
class TextToSpeech(Resource):
|
||||
tts_model = api.model(
|
||||
"TextToSpeechModel",
|
||||
{
|
||||
"text": fields.String(required=True, description="Text to be synthesized as audio"),
|
||||
},
|
||||
)
|
||||
|
||||
@api.expect(tts_model)
|
||||
@api.doc(description="Synthesize audio speech from text")
|
||||
def post(self):
|
||||
data = request.get_json()
|
||||
text = data["text"]
|
||||
try:
|
||||
tts_instance = GoogleTTS()
|
||||
audio_base64, detected_language = tts_instance.text_to_speech(text)
|
||||
return make_response(jsonify({"success": True,'audio_base64': audio_base64,'lang':detected_language}), 200)
|
||||
except Exception as err:
|
||||
return make_response(jsonify({"success": False, "error": str(err)}), 400)
|
||||
|
||||
|
||||
|
||||
24
application/core/mongo_db.py
Normal file
24
application/core/mongo_db.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from application.core.settings import settings
|
||||
from pymongo import MongoClient
|
||||
|
||||
|
||||
class MongoDB:
|
||||
_client = None
|
||||
|
||||
@classmethod
|
||||
def get_client(cls):
|
||||
"""
|
||||
Get the MongoDB client instance, creating it if necessary.
|
||||
"""
|
||||
if cls._client is None:
|
||||
cls._client = MongoClient(settings.MONGO_URI)
|
||||
return cls._client
|
||||
|
||||
@classmethod
|
||||
def close_client(cls):
|
||||
"""
|
||||
Close the MongoDB client connection.
|
||||
"""
|
||||
if cls._client is not None:
|
||||
cls._client.close()
|
||||
cls._client = None
|
||||
@@ -12,6 +12,7 @@ from application.parser.file.markdown_parser import MarkdownParser
|
||||
from application.parser.file.rst_parser import RstParser
|
||||
from application.parser.file.tabular_parser import PandasCSVParser,ExcelParser
|
||||
from application.parser.file.json_parser import JSONParser
|
||||
from application.parser.file.pptx_parser import PPTXParser
|
||||
from application.parser.schema.base import Document
|
||||
|
||||
DEFAULT_FILE_EXTRACTOR: Dict[str, BaseParser] = {
|
||||
@@ -25,6 +26,7 @@ DEFAULT_FILE_EXTRACTOR: Dict[str, BaseParser] = {
|
||||
".html": HTMLParser(),
|
||||
".mdx": MarkdownParser(),
|
||||
".json":JSONParser(),
|
||||
".pptx":PPTXParser(),
|
||||
}
|
||||
|
||||
|
||||
|
||||
75
application/parser/file/pptx_parser.py
Normal file
75
application/parser/file/pptx_parser.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""PPT parser.
|
||||
Contains parsers for presentation (.pptx) files to extract slide text.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
from application.parser.file.base_parser import BaseParser
|
||||
|
||||
class PPTXParser(BaseParser):
|
||||
r"""PPTX (.pptx) parser for extracting text from PowerPoint slides.
|
||||
Args:
|
||||
concat_slides (bool): Specifies whether to concatenate all slide text into one document.
|
||||
- If True, slide texts will be joined together as a single string.
|
||||
- If False, each slide's text will be stored as a separate entry in a list.
|
||||
Set to True by default.
|
||||
slide_separator (str): Separator used to join slides' text content.
|
||||
Only used when `concat_slides=True`. Default is "\n".
|
||||
Refer to https://python-pptx.readthedocs.io/en/latest/ for more information.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*args: Any,
|
||||
concat_slides: bool = True,
|
||||
slide_separator: str = "\n",
|
||||
**kwargs: Any
|
||||
) -> None:
|
||||
"""Init params."""
|
||||
super().__init__(*args, **kwargs)
|
||||
self._concat_slides = concat_slides
|
||||
self._slide_separator = slide_separator
|
||||
|
||||
def _init_parser(self) -> Dict:
|
||||
"""Init parser."""
|
||||
return {}
|
||||
|
||||
def parse_file(self, file: Path, errors: str = "ignore") -> Union[str, List[str]]:
|
||||
r"""
|
||||
Parse a .pptx file and extract text from each slide.
|
||||
Args:
|
||||
file (Path): Path to the .pptx file.
|
||||
errors (str): Error handling policy ('ignore' by default).
|
||||
Returns:
|
||||
Union[str, List[str]]: Concatenated text if concat_slides is True,
|
||||
otherwise a list of slide texts.
|
||||
"""
|
||||
|
||||
try:
|
||||
from pptx import Presentation
|
||||
except ImportError:
|
||||
raise ImportError("pptx module is required to read .PPTX files.")
|
||||
|
||||
try:
|
||||
presentation = Presentation(file)
|
||||
slide_texts=[]
|
||||
|
||||
# Iterate over each slide in the presentation
|
||||
for slide in presentation.slides:
|
||||
slide_text=""
|
||||
|
||||
# Iterate over each shape in the slide
|
||||
for shape in slide.shapes:
|
||||
# Check if the shape has a 'text' attribute and append that to the slide_text
|
||||
if hasattr(shape,"text"):
|
||||
slide_text+=shape.text
|
||||
|
||||
slide_texts.append(slide_text.strip())
|
||||
|
||||
if self._concat_slides:
|
||||
return self._slide_separator.join(slide_texts)
|
||||
else:
|
||||
return slide_texts
|
||||
|
||||
except Exception as e:
|
||||
raise e
|
||||
@@ -14,6 +14,7 @@ esutils==1.0.1
|
||||
Flask==3.0.3
|
||||
faiss-cpu==1.8.0.post1
|
||||
flask-restx==1.3.0
|
||||
gTTS==2.3.2
|
||||
gunicorn==23.0.0
|
||||
html2text==2024.2.26
|
||||
javalang==0.13.0
|
||||
@@ -65,6 +66,7 @@ pymongo==4.8.0
|
||||
pypdf2==3.0.1
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.0.1
|
||||
python-pptx==1.0.2
|
||||
qdrant-client==1.11.0
|
||||
redis==5.0.1
|
||||
referencing==0.30.2
|
||||
@@ -84,4 +86,4 @@ urllib3==2.2.3
|
||||
vine==5.1.0
|
||||
wcwidth==0.2.13
|
||||
werkzeug==3.0.4
|
||||
yarl==1.11.1
|
||||
yarl==1.11.1
|
||||
@@ -75,7 +75,6 @@ class BraveRetSearch(BaseRetriever):
|
||||
if len(self.chat_history) > 1:
|
||||
tokens_current_history = 0
|
||||
# count tokens in history
|
||||
self.chat_history.reverse()
|
||||
for i in self.chat_history:
|
||||
if "prompt" in i and "response" in i:
|
||||
tokens_batch = num_tokens_from_string(i["prompt"]) + num_tokens_from_string(
|
||||
|
||||
@@ -78,7 +78,6 @@ class ClassicRAG(BaseRetriever):
|
||||
if len(self.chat_history) > 1:
|
||||
tokens_current_history = 0
|
||||
# count tokens in history
|
||||
self.chat_history.reverse()
|
||||
for i in self.chat_history:
|
||||
if "prompt" in i and "response" in i:
|
||||
tokens_batch = num_tokens_from_string(i["prompt"]) + num_tokens_from_string(
|
||||
@@ -97,7 +96,6 @@ class ClassicRAG(BaseRetriever):
|
||||
llm = LLMCreator.create_llm(
|
||||
settings.LLM_NAME, api_key=settings.API_KEY, user_api_key=self.user_api_key
|
||||
)
|
||||
|
||||
completion = llm.gen_stream(model=self.gpt_model, messages=messages_combine)
|
||||
for line in completion:
|
||||
yield {"answer": str(line)}
|
||||
|
||||
@@ -92,7 +92,6 @@ class DuckDuckSearch(BaseRetriever):
|
||||
if len(self.chat_history) > 1:
|
||||
tokens_current_history = 0
|
||||
# count tokens in history
|
||||
self.chat_history.reverse()
|
||||
for i in self.chat_history:
|
||||
if "prompt" in i and "response" in i:
|
||||
tokens_batch = num_tokens_from_string(i["prompt"]) + num_tokens_from_string(
|
||||
|
||||
10
application/tts/base.py
Normal file
10
application/tts/base.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class BaseTTS(ABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def text_to_speech(self, *args, **kwargs):
|
||||
pass
|
||||
29
application/tts/elevenlabs.py
Normal file
29
application/tts/elevenlabs.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from io import BytesIO
|
||||
import base64
|
||||
from application.tts.base import BaseTTS
|
||||
|
||||
|
||||
class ElevenlabsTTS(BaseTTS):
|
||||
def __init__(self):
|
||||
from elevenlabs.client import ElevenLabs
|
||||
|
||||
self.client = ElevenLabs(
|
||||
api_key="ELEVENLABS_API_KEY",
|
||||
)
|
||||
|
||||
|
||||
def text_to_speech(self, text):
|
||||
lang = "en"
|
||||
audio = self.client.generate(
|
||||
text=text,
|
||||
model="eleven_multilingual_v2",
|
||||
voice="Brian",
|
||||
)
|
||||
audio_data = BytesIO()
|
||||
for chunk in audio:
|
||||
audio_data.write(chunk)
|
||||
audio_bytes = audio_data.getvalue()
|
||||
|
||||
# Encode to base64
|
||||
audio_base64 = base64.b64encode(audio_bytes).decode("utf-8")
|
||||
return audio_base64, lang
|
||||
19
application/tts/google_tts.py
Normal file
19
application/tts/google_tts.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import io
|
||||
import base64
|
||||
from gtts import gTTS
|
||||
from application.tts.base import BaseTTS
|
||||
|
||||
|
||||
class GoogleTTS(BaseTTS):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
def text_to_speech(self, text):
|
||||
lang = "en"
|
||||
audio_fp = io.BytesIO()
|
||||
tts = gTTS(text=text, lang=lang, slow=False)
|
||||
tts.write_to_fp(audio_fp)
|
||||
audio_fp.seek(0)
|
||||
audio_base64 = base64.b64encode(audio_fp.read()).decode("utf-8")
|
||||
return audio_base64, lang
|
||||
@@ -1,10 +1,9 @@
|
||||
import sys
|
||||
from pymongo import MongoClient
|
||||
from datetime import datetime
|
||||
from application.core.settings import settings
|
||||
from application.core.mongo_db import MongoDB
|
||||
from application.utils import num_tokens_from_string
|
||||
|
||||
mongo = MongoClient(settings.MONGO_URI)
|
||||
mongo = MongoDB.get_client()
|
||||
db = mongo["docsgpt"]
|
||||
usage_collection = db["token_usage"]
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ from urllib.parse import urljoin
|
||||
|
||||
import requests
|
||||
from bson.objectid import ObjectId
|
||||
from pymongo import MongoClient
|
||||
|
||||
from application.core.mongo_db import MongoDB
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from application.parser.open_ai_func import call_openai_api
|
||||
@@ -18,7 +18,7 @@ from application.parser.schema.base import Document
|
||||
from application.parser.token_func import group_split
|
||||
from application.utils import count_tokens_docs
|
||||
|
||||
mongo = MongoClient(settings.MONGO_URI)
|
||||
mongo = MongoDB.get_client()
|
||||
db = mongo["docsgpt"]
|
||||
sources_collection = db["sources"]
|
||||
|
||||
|
||||
@@ -51,6 +51,9 @@ const config = {
|
||||
footer: {
|
||||
text: `MIT ${new Date().getFullYear()} © DocsGPT`,
|
||||
},
|
||||
editLink: {
|
||||
content: 'Edit this page on GitHub',
|
||||
},
|
||||
logo() {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -1,25 +1,60 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
import logging
|
||||
import aiohttp
|
||||
import discord
|
||||
import requests
|
||||
from discord.ext import commands
|
||||
import dotenv
|
||||
|
||||
dotenv.load_dotenv()
|
||||
|
||||
# Replace 'YOUR_BOT_TOKEN' with your bot's token
|
||||
# Enable logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Bot configuration
|
||||
TOKEN = os.getenv("DISCORD_TOKEN")
|
||||
PREFIX = '@DocsGPT'
|
||||
BASE_API_URL = 'http://localhost:7091'
|
||||
PREFIX = '!' # Command prefix
|
||||
BASE_API_URL = os.getenv("API_BASE", "https://gptcloud.arc53.com")
|
||||
API_URL = BASE_API_URL + "/api/answer"
|
||||
API_KEY = os.getenv("API_KEY")
|
||||
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
|
||||
bot = commands.Bot(command_prefix=PREFIX, intents=intents)
|
||||
|
||||
# Store conversation history per user
|
||||
conversation_histories = {}
|
||||
|
||||
def chunk_string(text, max_length=2000):
|
||||
"""Splits a string into chunks of a specified maximum length."""
|
||||
# Create list to store the split strings
|
||||
chunks = []
|
||||
# Loop through the text, create substrings with max_length
|
||||
while len(text) > max_length:
|
||||
# Find last space within the limit
|
||||
idx = text.rfind(' ', 0, max_length)
|
||||
# Ensure we don't have an empty part
|
||||
if idx == -1:
|
||||
# If no spaces, just take chunk
|
||||
chunks.append(text[:max_length])
|
||||
text = text[max_length:]
|
||||
else:
|
||||
# Push whatever we've got up to the last space
|
||||
chunks.append(text[:idx])
|
||||
text = text[idx+1:]
|
||||
# Catches the remaining part
|
||||
chunks.append(text)
|
||||
return chunks
|
||||
|
||||
def escape_markdown(text):
|
||||
"""Escapes Discord markdown characters."""
|
||||
escape_chars = r'\*_$$$$()~>#+-=|{}.!'
|
||||
return re.sub(f'([{re.escape(escape_chars)}])', r'\\\1', text)
|
||||
|
||||
def split_string(input_str):
|
||||
"""Splits the input string to detect bot mentions."""
|
||||
pattern = r'^<@!?{0}>\s*'.format(bot.user.id)
|
||||
match = re.match(pattern, input_str)
|
||||
if match:
|
||||
@@ -27,42 +62,97 @@ def split_string(input_str):
|
||||
return str(bot.user.id), content
|
||||
return None, input_str
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
print(f'{bot.user.name} has connected to Discord!')
|
||||
|
||||
|
||||
async def fetch_answer(question):
|
||||
data = {
|
||||
'sender': 'discord',
|
||||
'question': question,
|
||||
'history': ''
|
||||
async def generate_answer(question, messages, conversation_id):
|
||||
"""Generates an answer using the external API."""
|
||||
payload = {
|
||||
"question": question,
|
||||
"api_key": API_KEY,
|
||||
"history": messages,
|
||||
"conversation_id": conversation_id
|
||||
}
|
||||
headers = {"Content-Type": "application/json",
|
||||
"Accept": "application/json"}
|
||||
response = requests.post(BASE_API_URL + '/api/answer', json=data, headers=headers)
|
||||
if response.status_code == 200:
|
||||
return response.json()['answer']
|
||||
return 'Sorry, I could not fetch the answer.'
|
||||
headers = {
|
||||
"Content-Type": "application/json; charset=utf-8"
|
||||
}
|
||||
timeout = aiohttp.ClientTimeout(total=60)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.post(API_URL, json=payload, headers=headers) as resp:
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
conversation_id = data.get("conversation_id")
|
||||
answer = data.get("answer", "Sorry, I couldn't find an answer.")
|
||||
return {"answer": answer, "conversation_id": conversation_id}
|
||||
else:
|
||||
return {"answer": "Sorry, I couldn't find an answer.", "conversation_id": None}
|
||||
|
||||
@bot.command(name="start")
|
||||
async def start(ctx):
|
||||
"""Handles the /start command."""
|
||||
await ctx.send(f"Hi {ctx.author.mention}! How can I assist you today?")
|
||||
|
||||
@bot.command(name="custom_help")
|
||||
async def custom_help_command(ctx):
|
||||
"""Handles the /custom_help command."""
|
||||
help_text = (
|
||||
"Here are the available commands:\n"
|
||||
"`!start` - Begin a new conversation with the bot\n"
|
||||
"`!help` - Display this help message\n\n"
|
||||
"You can also mention me or send a direct message to ask a question!"
|
||||
)
|
||||
await ctx.send(help_text)
|
||||
|
||||
@bot.event
|
||||
async def on_message(message):
|
||||
if message.author == bot.user:
|
||||
return
|
||||
|
||||
content = message.content.strip()
|
||||
prefix, content = split_string(content)
|
||||
if prefix is None:
|
||||
return
|
||||
|
||||
part_prefix = str(bot.user.id)
|
||||
if part_prefix == prefix:
|
||||
answer = await fetch_answer(content)
|
||||
await message.channel.send(answer)
|
||||
|
||||
# Process commands first
|
||||
await bot.process_commands(message)
|
||||
|
||||
# Check if the message is in a DM channel
|
||||
if isinstance(message.channel, discord.DMChannel):
|
||||
content = message.content.strip()
|
||||
else:
|
||||
# In guild channels, check if the message mentions the bot at the start
|
||||
content = message.content.strip()
|
||||
prefix, content = split_string(content)
|
||||
if prefix is None:
|
||||
return
|
||||
part_prefix = str(bot.user.id)
|
||||
if part_prefix != prefix:
|
||||
return # Bot not mentioned at the start, so do not process
|
||||
|
||||
bot.run(TOKEN)
|
||||
# Now process the message
|
||||
user_id = message.author.id
|
||||
if user_id not in conversation_histories:
|
||||
conversation_histories[user_id] = {
|
||||
"history": [],
|
||||
"conversation_id": None
|
||||
}
|
||||
|
||||
conversation = conversation_histories[user_id]
|
||||
conversation["history"].append({"prompt": content})
|
||||
|
||||
# Generate the answer
|
||||
response_doc = await generate_answer(
|
||||
content,
|
||||
conversation["history"],
|
||||
conversation["conversation_id"]
|
||||
)
|
||||
answer = response_doc["answer"]
|
||||
conversation_id = response_doc["conversation_id"]
|
||||
|
||||
answer_chunks = chunk_string(answer)
|
||||
for chunk in answer_chunks:
|
||||
await message.channel.send(chunk)
|
||||
|
||||
conversation["history"][-1]["response"] = answer
|
||||
conversation["conversation_id"] = conversation_id
|
||||
|
||||
# Keep conversation history to last 10 exchanges
|
||||
conversation["history"] = conversation["history"][-10:]
|
||||
|
||||
bot.run(TOKEN)
|
||||
18
frontend/package-lock.json
generated
18
frontend/package-lock.json
generated
@@ -3075,6 +3075,24 @@
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/easy-speech": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/easy-speech/-/easy-speech-2.4.0.tgz",
|
||||
"integrity": "sha512-wpMv29DEoeP/eyXr4aXpDqd9DvlXl7aQs7BgfKbjGVxqkmQPgNmpbF5YULaTH5bc/5qrteg5MDfCD2Zd0qr4rQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "GitHub",
|
||||
"url": "https://github.com/sponsors/jankapunkt"
|
||||
},
|
||||
{
|
||||
"type": "PayPal",
|
||||
"url": "https://paypal.me/kuesterjan"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 14.x"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.11",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.11.tgz",
|
||||
|
||||
@@ -39,6 +39,8 @@ import {
|
||||
setSelectedDocs,
|
||||
setSourceDocs,
|
||||
} from './preferences/preferenceSlice';
|
||||
import Spinner from './assets/spinner.svg';
|
||||
import SpinnerDark from './assets/spinner-dark.svg';
|
||||
import { selectQueries } from './conversation/conversationSlice';
|
||||
import Upload from './upload/Upload';
|
||||
import Help from './components/Help';
|
||||
@@ -70,6 +72,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
const conversations = useSelector(selectConversations);
|
||||
const modalStateDeleteConv = useSelector(selectModalStateDeleteConv);
|
||||
const conversationId = useSelector(selectConversationId);
|
||||
const [isDeletingConversation, setIsDeletingConversation] = useState(false);
|
||||
|
||||
const { isMobile } = useMediaQuery();
|
||||
const [isDarkTheme] = useDarkTheme();
|
||||
@@ -91,25 +94,28 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!conversations) {
|
||||
if (!conversations?.data) {
|
||||
fetchConversations();
|
||||
}
|
||||
if (queries.length === 0) {
|
||||
resetConversation();
|
||||
}
|
||||
}, [conversations, dispatch]);
|
||||
}, [conversations?.data, dispatch]);
|
||||
|
||||
async function fetchConversations() {
|
||||
dispatch(setConversations({ ...conversations, loading: true }));
|
||||
return await getConversations()
|
||||
.then((fetchedConversations) => {
|
||||
dispatch(setConversations(fetchedConversations));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to fetch conversations: ', error);
|
||||
dispatch(setConversations({ data: null, loading: false }));
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteAllConversations = () => {
|
||||
setIsDeletingConversation(true);
|
||||
conversationService
|
||||
.deleteAll()
|
||||
.then(() => {
|
||||
@@ -119,6 +125,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
};
|
||||
|
||||
const handleDeleteConversation = (id: string) => {
|
||||
setIsDeletingConversation(true);
|
||||
conversationService
|
||||
.delete(id, {})
|
||||
.then(() => {
|
||||
@@ -205,6 +212,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
setNavOpen(!isMobile);
|
||||
}, [isMobile]);
|
||||
useDefaultDocument();
|
||||
|
||||
return (
|
||||
<>
|
||||
{!navOpen && (
|
||||
@@ -306,13 +314,22 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
id="conversationsMainDiv"
|
||||
className="mb-auto h-[78vh] overflow-y-auto overflow-x-hidden dark:text-white"
|
||||
>
|
||||
{conversations && conversations.length > 0 ? (
|
||||
{conversations?.loading && !isDeletingConversation && (
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
||||
<img
|
||||
src={isDarkTheme ? SpinnerDark : Spinner}
|
||||
className="animate-spin cursor-pointer bg-transparent"
|
||||
alt="Loading..."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{conversations?.data && conversations.data.length > 0 ? (
|
||||
<div>
|
||||
<div className=" my-auto mx-4 mt-2 flex h-6 items-center justify-between gap-4 rounded-3xl">
|
||||
<p className="mt-1 ml-4 text-sm font-semibold">{t('chats')}</p>
|
||||
</div>
|
||||
<div className="conversations-container">
|
||||
{conversations?.map((conversation) => (
|
||||
{conversations.data?.map((conversation) => (
|
||||
<ConversationTile
|
||||
key={conversation.id}
|
||||
conversation={conversation}
|
||||
|
||||
3
frontend/src/assets/Loading.svg
Normal file
3
frontend/src/assets/Loading.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 3.66667V1M12.7778 5.22222L14.6889 3.31111M14.3333 9H17M12.7778 12.7778L14.6889 14.6889M9 14.3333V17M5.22222 12.7778L3.31111 14.6889M3.66667 9H1M5.22222 5.22222L3.31111 3.31111" stroke="#949494" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 375 B |
4
frontend/src/assets/speaker.svg
Normal file
4
frontend/src/assets/speaker.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="18" height="18" viewBox="0 0 21 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 9.59652V6.40372C1 5.94773 1.18114 5.51041 1.50358 5.18797C1.82602 4.86553 2.26334 4.68439 2.71933 4.68439H5.21237C5.38045 4.68435 5.54483 4.63503 5.68518 4.54254L10.8432 1.1417C10.9728 1.05636 11.1231 1.00768 11.2781 1.00084C11.4331 0.993993 11.5871 1.02924 11.7237 1.10283C11.8603 1.17643 11.9745 1.28563 12.054 1.41885C12.1336 1.55207 12.1756 1.70435 12.1757 1.85952V14.1407C12.1756 14.2959 12.1336 14.4482 12.054 14.5814C11.9745 14.7146 11.8603 14.8238 11.7237 14.8974C11.5871 14.971 11.4331 15.0063 11.2781 14.9994C11.1231 14.9926 10.9728 14.9439 10.8432 14.8585L5.68518 11.4577C5.54483 11.3652 5.38045 11.3159 5.21237 11.3159H2.71933C2.26334 11.3159 1.82602 11.1347 1.50358 10.8123C1.18114 10.4898 1 10.0525 1 9.59652Z" stroke="#949494" stroke-width="1.4"/>
|
||||
<path d="M15.1846 4.13149C15.1846 4.13149 16.4741 5.42099 16.4741 7.57016C16.4741 9.71932 15.1846 11.0088 15.1846 11.0088M17.7636 1.55249C17.7636 1.55249 19.9127 3.70166 19.9127 7.57016C19.9127 11.4387 17.7636 13.5878 17.7636 13.5878" stroke="#949494" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
5
frontend/src/assets/stopspeech.svg
Normal file
5
frontend/src/assets/stopspeech.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 1.14286C9.35622 1.14286 10.682 1.54502 11.8096 2.29849C12.9373 3.05197 13.8162 4.1229 14.3352 5.37588C14.8542 6.62886 14.99 8.0076 14.7254 9.33776C14.4608 10.6679 13.8077 11.8897 12.8487 12.8487C11.8897 13.8077 10.6679 14.4608 9.33776 14.7254C8.00761 14.99 6.62887 14.8542 5.37589 14.3352C4.12291 13.8162 3.05197 12.9373 2.2985 11.8096C1.54502 10.682 1.14286 9.35621 1.14286 8C1.14286 6.18137 1.86531 4.43723 3.15127 3.15127C4.43723 1.8653 6.18137 1.14286 8 1.14286ZM8 0C6.41775 0 4.87103 0.469192 3.55544 1.34824C2.23985 2.22729 1.21447 3.47672 0.608967 4.93853C0.00346626 6.40034 -0.15496 8.00887 0.153721 9.56072C0.462403 11.1126 1.22433 12.538 2.34315 13.6568C3.46197 14.7757 4.88743 15.5376 6.43928 15.8463C7.99113 16.155 9.59966 15.9965 11.0615 15.391C12.5233 14.7855 13.7727 13.7601 14.6518 12.4446C15.5308 11.129 16 9.58225 16 8C16 5.87827 15.1571 3.84344 13.6569 2.34314C12.1566 0.842854 10.1217 0 8 0Z" fill="#949494"/>
|
||||
<path d="M10.2857 5.71439V10.2858H5.71427V5.71439H10.2857ZM10.2857 4.57153H5.71427C5.41116 4.57153 5.12047 4.69194 4.90615 4.90627C4.69182 5.1206 4.57141 5.41129 4.57141 5.71439V10.2858C4.57141 10.5889 4.69182 10.8796 4.90615 11.0939C5.12047 11.3083 5.41116 11.4287 5.71427 11.4287H10.2857C10.5888 11.4287 10.8795 11.3083 11.0938 11.0939C11.3081 10.8796 11.4286 10.5889 11.4286 10.2858V5.71439C11.4286 5.41129 11.3081 5.1206 11.0938 4.90627C10.8795 4.69194 10.5888 4.57153 10.2857 4.57153Z" fill="#949494"/>
|
||||
</svg>
|
||||
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
94
frontend/src/components/TextToSpeechButton.tsx
Normal file
94
frontend/src/components/TextToSpeechButton.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { useState, useRef } from 'react';
|
||||
import Speaker from '../assets/speaker.svg?react';
|
||||
import Stopspeech from '../assets/stopspeech.svg?react';
|
||||
import LoadingIcon from '../assets/Loading.svg?react'; // Add a loading icon SVG here
|
||||
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
|
||||
|
||||
export default function SpeakButton({
|
||||
text,
|
||||
colorLight,
|
||||
colorDark,
|
||||
}: {
|
||||
text: string;
|
||||
colorLight?: string;
|
||||
colorDark?: string;
|
||||
}) {
|
||||
const [isSpeaking, setIsSpeaking] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSpeakHovered, setIsSpeakHovered] = useState(false);
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
const handleSpeakClick = async () => {
|
||||
if (isSpeaking) {
|
||||
// Stop audio if it's currently playing
|
||||
audioRef.current?.pause();
|
||||
audioRef.current = null;
|
||||
setIsSpeaking(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Set loading state and initiate TTS request
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await fetch(apiHost + '/api/tts', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.audio_base64) {
|
||||
// Create and play the audio
|
||||
const audio = new Audio(`data:audio/mp3;base64,${data.audio_base64}`);
|
||||
audioRef.current = audio;
|
||||
|
||||
audio.play().then(() => {
|
||||
setIsSpeaking(true);
|
||||
setIsLoading(false);
|
||||
|
||||
// Reset when audio ends
|
||||
audio.onended = () => {
|
||||
setIsSpeaking(false);
|
||||
audioRef.current = null;
|
||||
};
|
||||
});
|
||||
} else {
|
||||
console.error('Failed to retrieve audio.');
|
||||
setIsLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching audio from TTS endpoint', error);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center justify-center rounded-full p-2 ${
|
||||
isSpeakHovered
|
||||
? `bg-[#EEEEEE] dark:bg-purple-taupe`
|
||||
: `bg-[${colorLight ? colorLight : '#FFFFFF'}] dark:bg-[${colorDark ? colorDark : 'transparent'}]`
|
||||
}`}
|
||||
>
|
||||
{isLoading ? (
|
||||
<LoadingIcon className="animate-spin" />
|
||||
) : isSpeaking ? (
|
||||
<Stopspeech
|
||||
className="cursor-pointer fill-none"
|
||||
onClick={handleSpeakClick}
|
||||
onMouseEnter={() => setIsSpeakHovered(true)}
|
||||
onMouseLeave={() => setIsSpeakHovered(false)}
|
||||
/>
|
||||
) : (
|
||||
<Speaker
|
||||
className="cursor-pointer fill-none"
|
||||
onClick={handleSpeakClick}
|
||||
onMouseEnter={() => setIsSpeakHovered(true)}
|
||||
onMouseLeave={() => setIsSpeakHovered(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import remarkGfm from 'remark-gfm';
|
||||
import remarkMath from 'remark-math';
|
||||
import rehypeKatex from 'rehype-katex';
|
||||
import 'katex/dist/katex.min.css';
|
||||
|
||||
import DocsGPT3 from '../assets/cute_docsgpt3.svg';
|
||||
import Dislike from '../assets/dislike.svg?react';
|
||||
import Document from '../assets/document.svg';
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
} from '../preferences/preferenceSlice';
|
||||
import classes from './ConversationBubble.module.css';
|
||||
import { FEEDBACK, MESSAGE_TYPE } from './conversationModels';
|
||||
import SpeakButton from '../components/TextToSpeechButton';
|
||||
|
||||
const DisableSourceFE = import.meta.env.VITE_DISABLE_SOURCE_FE || false;
|
||||
|
||||
@@ -336,6 +336,14 @@ const ConversationBubble = forwardRef<
|
||||
<CopyButton text={message} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`relative mr-5 block items-center justify-center lg:invisible
|
||||
${type !== 'ERROR' ? 'group-hover:lg:visible' : 'hidden'}`}
|
||||
>
|
||||
<div>
|
||||
<SpeakButton text={message} /> {/* Add SpeakButton here */}
|
||||
</div>
|
||||
</div>
|
||||
{type === 'ERROR' && (
|
||||
<div className="relative mr-5 block items-center justify-center">
|
||||
<div>{retryBtn}</div>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
:root {
|
||||
--viewport-height: 100vh;
|
||||
font-synthesis: none !important;
|
||||
}
|
||||
|
||||
@supports (height: 100dvh) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"cancel": "Cancel",
|
||||
"help": "Help",
|
||||
"emailUs": "Email us",
|
||||
"documentation": "documentation",
|
||||
"documentation": "Documentation",
|
||||
"demo": [
|
||||
{
|
||||
"header": "Learn about DocsGPT",
|
||||
@@ -86,7 +86,7 @@
|
||||
"start": "Start Chatting",
|
||||
"name": "Name",
|
||||
"choose": "Choose Files",
|
||||
"info": "Please upload .pdf, .txt, .rst, .csv, .xlsx, .docx, .md, .html, .epub, .json, .zip limited to 25mb",
|
||||
"info": "Please upload .pdf, .txt, .rst, .csv, .xlsx, .docx, .md, .html, .epub, .json, .pptx, .zip limited to 25mb",
|
||||
"uploadedFiles": "Uploaded Files",
|
||||
"cancel": "Cancel",
|
||||
"train": "Train",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"cancel": "Cancelar",
|
||||
"help": "Asistencia",
|
||||
"emailUs": "Envíanos un correo",
|
||||
"documentation": "documentación",
|
||||
"documentation": "Documentación",
|
||||
"demo": [
|
||||
{
|
||||
"header": "Aprende sobre DocsGPT",
|
||||
@@ -86,7 +86,7 @@
|
||||
"start": "Empezar a chatear",
|
||||
"name": "Nombre",
|
||||
"choose": "Seleccionar Archivos",
|
||||
"info": "Por favor, suba archivos .pdf, .txt, .rst, .csv, .xlsx, .docx, .md, .html, .epub, .json, .zip limitados a 25 MB",
|
||||
"info": "Por favor, suba archivos .pdf, .txt, .rst, .csv, .xlsx, .docx, .md, .html, .epub, .json, .pptx, .zip limitados a 25 MB",
|
||||
"uploadedFiles": "Archivos Subidos",
|
||||
"cancel": "Cancelar",
|
||||
"train": "Entrenar",
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
"start": "チャットを開始する",
|
||||
"name": "名前",
|
||||
"choose": "ファイルを選択",
|
||||
"info": ".pdf, .txt, .rst, .docx, .md, .json, .zipファイルを25MBまでアップロードしてください",
|
||||
"info": ".pdf, .txt, .rst, .docx, .md, .json, .pptx, .zipファイルを25MBまでアップロードしてください",
|
||||
"uploadedFiles": "アップロードされたファイル",
|
||||
"cancel": "キャンセル",
|
||||
"train": "トレーニング",
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
"remote": "遠端",
|
||||
"name": "名稱",
|
||||
"choose": "選擇檔案",
|
||||
"info": "請上傳 .pdf, .txt, .rst, .docx, .md, .json, .zip 檔案,大小限制為 25MB",
|
||||
"info": "請上傳 .pdf, .txt, .rst, .docx, .md, .json, .pptx, .zip 檔案,大小限制為 25MB",
|
||||
"uploadedFiles": "已上傳的檔案",
|
||||
"cancel": "取消",
|
||||
"train": "訓練",
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
"start": "开始聊天",
|
||||
"name": "名称",
|
||||
"choose": "选择文件",
|
||||
"info": "请上传 .pdf, .txt, .rst, .csv, .xlsx, .docx, .md, .html, .epub, .json, .zip 文件,限 25MB",
|
||||
"info": "请上传 .pdf, .txt, .rst, .csv, .xlsx, .docx, .md, .html, .epub, .json, .pptx, .zip 文件,限 25MB",
|
||||
"uploadedFiles": "已上传文件",
|
||||
"cancel": "取消",
|
||||
"train": "训练",
|
||||
|
||||
@@ -21,9 +21,10 @@ export async function getDocs(): Promise<Doc[] | null> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getConversations(): Promise<
|
||||
{ name: string; id: string }[] | null
|
||||
> {
|
||||
export async function getConversations(): Promise<{
|
||||
data: { name: string; id: string }[] | null;
|
||||
loading: boolean;
|
||||
}> {
|
||||
try {
|
||||
const response = await conversationService.getConversations();
|
||||
const data = await response.json();
|
||||
@@ -34,10 +35,10 @@ export async function getConversations(): Promise<
|
||||
conversations.push(conversation as { name: string; id: string });
|
||||
});
|
||||
|
||||
return conversations;
|
||||
return { data: conversations, loading: false };
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return null;
|
||||
return { data: null, loading: false };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@ export interface Preference {
|
||||
token_limit: number;
|
||||
selectedDocs: Doc | null;
|
||||
sourceDocs: Doc[] | null;
|
||||
conversations: { name: string; id: string }[] | null;
|
||||
conversations: {
|
||||
data: { name: string; id: string }[] | null;
|
||||
loading: boolean;
|
||||
};
|
||||
modalState: ActiveState;
|
||||
}
|
||||
|
||||
@@ -34,7 +37,10 @@ const initialState: Preference = {
|
||||
retriever: 'classic',
|
||||
} as Doc,
|
||||
sourceDocs: null,
|
||||
conversations: null,
|
||||
conversations: {
|
||||
data: null,
|
||||
loading: false,
|
||||
},
|
||||
modalState: 'INACTIVE',
|
||||
};
|
||||
|
||||
|
||||
@@ -23,7 +23,10 @@ const preloadedState: { preference: Preference } = {
|
||||
chunks: JSON.parse(chunks ?? '2').toString(),
|
||||
token_limit: token_limit ? parseInt(token_limit) : 2000,
|
||||
selectedDocs: doc !== null ? JSON.parse(doc) : null,
|
||||
conversations: null,
|
||||
conversations: {
|
||||
data: null,
|
||||
loading: false,
|
||||
},
|
||||
sourceDocs: [
|
||||
{
|
||||
name: 'default',
|
||||
|
||||
@@ -321,6 +321,8 @@ function Upload({
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': [
|
||||
'.xlsx',
|
||||
],
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation':
|
||||
['.pptx'],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ Welcome to the LLM Document Analysis by [LexEU](https://www.lexeu.ai/) competiti
|
||||
|
||||
### 📆 Timeline:
|
||||
- **Competition Announcement:** 1st October
|
||||
- **Deadline for Submissions:** 27th October
|
||||
- **Results Announcement:** Early November/ Late October
|
||||
- **Deadline for Submissions:** 8th November
|
||||
- **Results Announcement:** Early November
|
||||
|
||||
## 📜 How to Participate:
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ source .env
|
||||
|
||||
if [[ -n "$OPENAI_API_BASE" ]] && [[ -n "$OPENAI_API_VERSION" ]] && [[ -n "$AZURE_DEPLOYMENT_NAME" ]] && [[ -n "$AZURE_EMBEDDINGS_DEPLOYMENT_NAME" ]]; then
|
||||
echo "Running Azure Configuration"
|
||||
docker compose -f docker-compose-azure.yaml build && docker compose -f docker-compose-azure.yaml up
|
||||
docker compose -f docker-compose-azure.yaml up --build
|
||||
else
|
||||
echo "Running Plain Configuration"
|
||||
docker compose build && docker compose up
|
||||
docker compose up --build
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user