forked from open-webui/open-webui
Merge branch 'main' into rename-to-open-webui
This commit is contained in:
commit
c4bd17598a
26 changed files with 1265 additions and 399 deletions
41
README.md
41
README.md
|
@ -219,9 +219,48 @@ docker rm -f open-webui
|
|||
docker pull ghcr.io/open-webui/open-webui:main
|
||||
[insert command you used to install]
|
||||
```
|
||||
|
||||
In the last line, you need to use the very same command you used to install (local install, remote server, etc.)
|
||||
|
||||
### Moving from Ollama WebUI to Open WebUI
|
||||
|
||||
Given recent name changes, the docker image has been renamed. Additional steps are required to update for those people that used Ollama WebUI previously and want to start using the new images.
|
||||
|
||||
#### Updating to Open WebUI without keeping your data
|
||||
|
||||
If you want to update to the new image but don't want to keep any previous data like conversations, prompts, documents, etc. you can perform the following steps:
|
||||
|
||||
```bash
|
||||
docker rm -f ollama-webui
|
||||
docker pull ghcr.io/open-webui/open-webui:main
|
||||
[insert the equivalent command that you used to install with the new Docker image name]
|
||||
docker volume rm ollama-webui
|
||||
```
|
||||
|
||||
For example, for local installation it would be `docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main`. For other installation commands, check the relevant parts of this README document.
|
||||
|
||||
#### Migrating your contents from Ollama WebUI to Open WebUI
|
||||
|
||||
If you want to update to the new image migrating all your previous settings like conversations, prompts, documents, etc. you can perform the following steps:
|
||||
|
||||
```bash
|
||||
docker rm -f ollama-webui
|
||||
docker pull ghcr.io/open-webui/open-webui:main
|
||||
# Creates a new volume and uses a temporary container to copy from one volume to another as per https://github.com/moby/moby/issues/31154#issuecomment-360531460
|
||||
docker volume create --name open-webui
|
||||
docker run --rm -v ollama-webui:/from -v open-webui:/to alpine ash -c "cd /from ; cp -av . /to"
|
||||
[insert the equivalent command that you used to install with the new Docker image name]
|
||||
```
|
||||
|
||||
Once you verify that all the data has been migrated you can erase the old volumen using the following command:
|
||||
|
||||
```bash
|
||||
docker volume rm ollama-webui
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## How to Install Without Docker
|
||||
|
||||
While we strongly recommend using our convenient Docker container installation for optimal support, we understand that some situations may require a non-Docker setup, especially for development purposes. Please note that non-Docker installations are not officially supported, and you might need to troubleshoot on your own.
|
||||
|
|
|
@ -10,6 +10,8 @@ from fastapi import (
|
|||
)
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import os, shutil
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
# from chromadb.utils import embedding_functions
|
||||
|
@ -28,19 +30,40 @@ from langchain_community.document_loaders import (
|
|||
UnstructuredExcelLoader,
|
||||
)
|
||||
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
||||
from langchain_community.vectorstores import Chroma
|
||||
from langchain.chains import RetrievalQA
|
||||
from langchain_community.vectorstores import Chroma
|
||||
|
||||
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
import mimetypes
|
||||
import uuid
|
||||
import json
|
||||
import time
|
||||
|
||||
from utils.misc import calculate_sha256, calculate_sha256_string
|
||||
|
||||
from apps.web.models.documents import (
|
||||
Documents,
|
||||
DocumentForm,
|
||||
DocumentResponse,
|
||||
)
|
||||
|
||||
from utils.misc import (
|
||||
calculate_sha256,
|
||||
calculate_sha256_string,
|
||||
sanitize_filename,
|
||||
extract_folders_after_data_docs,
|
||||
)
|
||||
from utils.utils import get_current_user, get_admin_user
|
||||
from config import UPLOAD_DIR, EMBED_MODEL, CHROMA_CLIENT, CHUNK_SIZE, CHUNK_OVERLAP
|
||||
from config import (
|
||||
UPLOAD_DIR,
|
||||
DOCS_DIR,
|
||||
EMBED_MODEL,
|
||||
CHROMA_CLIENT,
|
||||
CHUNK_SIZE,
|
||||
CHUNK_OVERLAP,
|
||||
RAG_TEMPLATE,
|
||||
)
|
||||
from constants import ERROR_MESSAGES
|
||||
|
||||
# EMBEDDING_FUNC = embedding_functions.SentenceTransformerEmbeddingFunction(
|
||||
|
@ -49,6 +72,11 @@ from constants import ERROR_MESSAGES
|
|||
|
||||
app = FastAPI()
|
||||
|
||||
app.state.CHUNK_SIZE = CHUNK_SIZE
|
||||
app.state.CHUNK_OVERLAP = CHUNK_OVERLAP
|
||||
app.state.RAG_TEMPLATE = RAG_TEMPLATE
|
||||
|
||||
|
||||
origins = ["*"]
|
||||
|
||||
app.add_middleware(
|
||||
|
@ -70,7 +98,7 @@ class StoreWebForm(CollectionNameForm):
|
|||
|
||||
def store_data_in_vector_db(data, collection_name) -> bool:
|
||||
text_splitter = RecursiveCharacterTextSplitter(
|
||||
chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP
|
||||
chunk_size=app.state.CHUNK_SIZE, chunk_overlap=app.state.CHUNK_OVERLAP
|
||||
)
|
||||
docs = text_splitter.split_documents(data)
|
||||
|
||||
|
@ -94,7 +122,60 @@ def store_data_in_vector_db(data, collection_name) -> bool:
|
|||
|
||||
@app.get("/")
|
||||
async def get_status():
|
||||
return {"status": True}
|
||||
return {
|
||||
"status": True,
|
||||
"chunk_size": app.state.CHUNK_SIZE,
|
||||
"chunk_overlap": app.state.CHUNK_OVERLAP,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/chunk")
|
||||
async def get_chunk_params(user=Depends(get_admin_user)):
|
||||
return {
|
||||
"status": True,
|
||||
"chunk_size": app.state.CHUNK_SIZE,
|
||||
"chunk_overlap": app.state.CHUNK_OVERLAP,
|
||||
}
|
||||
|
||||
|
||||
class ChunkParamUpdateForm(BaseModel):
|
||||
chunk_size: int
|
||||
chunk_overlap: int
|
||||
|
||||
|
||||
@app.post("/chunk/update")
|
||||
async def update_chunk_params(
|
||||
form_data: ChunkParamUpdateForm, user=Depends(get_admin_user)
|
||||
):
|
||||
app.state.CHUNK_SIZE = form_data.chunk_size
|
||||
app.state.CHUNK_OVERLAP = form_data.chunk_overlap
|
||||
|
||||
return {
|
||||
"status": True,
|
||||
"chunk_size": app.state.CHUNK_SIZE,
|
||||
"chunk_overlap": app.state.CHUNK_OVERLAP,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/template")
|
||||
async def get_rag_template(user=Depends(get_current_user)):
|
||||
return {
|
||||
"status": True,
|
||||
"template": app.state.RAG_TEMPLATE,
|
||||
}
|
||||
|
||||
|
||||
class RAGTemplateForm(BaseModel):
|
||||
template: str
|
||||
|
||||
|
||||
@app.post("/template/update")
|
||||
async def update_rag_template(form_data: RAGTemplateForm, user=Depends(get_admin_user)):
|
||||
# TODO: check template requirements
|
||||
app.state.RAG_TEMPLATE = (
|
||||
form_data.template if form_data.template != "" else RAG_TEMPLATE
|
||||
)
|
||||
return {"status": True, "template": app.state.RAG_TEMPLATE}
|
||||
|
||||
|
||||
class QueryDocForm(BaseModel):
|
||||
|
@ -220,8 +301,8 @@ def store_web(form_data: StoreWebForm, user=Depends(get_current_user)):
|
|||
)
|
||||
|
||||
|
||||
def get_loader(file, file_path):
|
||||
file_ext = file.filename.split(".")[-1].lower()
|
||||
def get_loader(filename: str, file_content_type: str, file_path: str):
|
||||
file_ext = filename.split(".")[-1].lower()
|
||||
known_type = True
|
||||
|
||||
known_source_ext = [
|
||||
|
@ -279,20 +360,20 @@ def get_loader(file, file_path):
|
|||
loader = UnstructuredXMLLoader(file_path)
|
||||
elif file_ext == "md":
|
||||
loader = UnstructuredMarkdownLoader(file_path)
|
||||
elif file.content_type == "application/epub+zip":
|
||||
elif file_content_type == "application/epub+zip":
|
||||
loader = UnstructuredEPubLoader(file_path)
|
||||
elif (
|
||||
file.content_type
|
||||
file_content_type
|
||||
== "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
or file_ext in ["doc", "docx"]
|
||||
):
|
||||
loader = Docx2txtLoader(file_path)
|
||||
elif file.content_type in [
|
||||
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_ext in known_source_ext or file.content_type.find("text/") >= 0:
|
||||
elif file_ext in known_source_ext or file_content_type.find("text/") >= 0:
|
||||
loader = TextLoader(file_path)
|
||||
else:
|
||||
loader = TextLoader(file_path)
|
||||
|
@ -323,7 +404,7 @@ def store_doc(
|
|||
collection_name = calculate_sha256(f)[:63]
|
||||
f.close()
|
||||
|
||||
loader, known_type = get_loader(file, file_path)
|
||||
loader, known_type = get_loader(file.filename, file.content_type, file_path)
|
||||
data = loader.load()
|
||||
result = store_data_in_vector_db(data, collection_name)
|
||||
|
||||
|
@ -353,6 +434,63 @@ def store_doc(
|
|||
)
|
||||
|
||||
|
||||
@app.get("/scan")
|
||||
def scan_docs_dir(user=Depends(get_admin_user)):
|
||||
try:
|
||||
for path in Path(DOCS_DIR).rglob("./**/*"):
|
||||
if path.is_file() and not path.name.startswith("."):
|
||||
tags = extract_folders_after_data_docs(path)
|
||||
filename = path.name
|
||||
file_content_type = mimetypes.guess_type(path)
|
||||
|
||||
f = open(path, "rb")
|
||||
collection_name = calculate_sha256(f)[:63]
|
||||
f.close()
|
||||
|
||||
loader, known_type = get_loader(
|
||||
filename, file_content_type[0], str(path)
|
||||
)
|
||||
data = loader.load()
|
||||
|
||||
result = store_data_in_vector_db(data, collection_name)
|
||||
|
||||
if result:
|
||||
sanitized_filename = sanitize_filename(filename)
|
||||
doc = Documents.get_doc_by_name(sanitized_filename)
|
||||
|
||||
if doc == None:
|
||||
doc = Documents.insert_new_doc(
|
||||
user.id,
|
||||
DocumentForm(
|
||||
**{
|
||||
"name": sanitized_filename,
|
||||
"title": filename,
|
||||
"collection_name": collection_name,
|
||||
"filename": filename,
|
||||
"content": (
|
||||
json.dumps(
|
||||
{
|
||||
"tags": list(
|
||||
map(
|
||||
lambda name: {"name": name},
|
||||
tags,
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
if len(tags)
|
||||
else "{}"
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@app.get("/reset/db")
|
||||
def reset_vector_db(user=Depends(get_admin_user)):
|
||||
CHROMA_CLIENT.reset()
|
||||
|
|
|
@ -96,6 +96,10 @@ async def get_doc_by_name(name: str, user=Depends(get_current_user)):
|
|||
############################
|
||||
|
||||
|
||||
class TagItem(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
class TagDocumentForm(BaseModel):
|
||||
name: str
|
||||
tags: List[dict]
|
||||
|
|
|
@ -43,6 +43,14 @@ Path(UPLOAD_DIR).mkdir(parents=True, exist_ok=True)
|
|||
CACHE_DIR = f"{DATA_DIR}/cache"
|
||||
Path(CACHE_DIR).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
####################################
|
||||
# Docs DIR
|
||||
####################################
|
||||
|
||||
DOCS_DIR = f"{DATA_DIR}/docs"
|
||||
Path(DOCS_DIR).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
####################################
|
||||
# OLLAMA_API_BASE_URL
|
||||
####################################
|
||||
|
@ -136,6 +144,21 @@ CHROMA_CLIENT = chromadb.PersistentClient(
|
|||
CHUNK_SIZE = 1500
|
||||
CHUNK_OVERLAP = 100
|
||||
|
||||
|
||||
RAG_TEMPLATE = """Use the following context as your learned knowledge, inside <context></context> XML tags.
|
||||
<context>
|
||||
[context]
|
||||
</context>
|
||||
|
||||
When answer to user:
|
||||
- If you don't know, just say that you don't know.
|
||||
- If you don't know when you are not sure, ask for clarification.
|
||||
Avoid mentioning that you obtained the information from the context.
|
||||
And answer according to the language of the user's question.
|
||||
|
||||
Given the context information, answer the query.
|
||||
Query: [query]"""
|
||||
|
||||
####################################
|
||||
# Transcribe
|
||||
####################################
|
||||
|
|
32
backend/start_windows.bat
Normal file
32
backend/start_windows.bat
Normal file
|
@ -0,0 +1,32 @@
|
|||
:: This method is not recommended, and we recommend you use the `start.sh` file with WSL instead.
|
||||
@echo off
|
||||
SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
:: Get the directory of the current script
|
||||
SET "SCRIPT_DIR=%~dp0"
|
||||
cd /d "%SCRIPT_DIR%" || exit /b
|
||||
|
||||
SET "KEY_FILE=.webui_secret_key"
|
||||
SET "PORT=%PORT:8080%"
|
||||
SET "WEBUI_SECRET_KEY=%WEBUI_SECRET_KEY%"
|
||||
SET "WEBUI_JWT_SECRET_KEY=%WEBUI_JWT_SECRET_KEY%"
|
||||
|
||||
:: Check if WEBUI_SECRET_KEY and WEBUI_JWT_SECRET_KEY are not set
|
||||
IF "%WEBUI_SECRET_KEY%%WEBUI_JWT_SECRET_KEY%" == " " (
|
||||
echo No WEBUI_SECRET_KEY provided
|
||||
|
||||
IF NOT EXIST "%KEY_FILE%" (
|
||||
echo Generating WEBUI_SECRET_KEY
|
||||
:: Generate a random value to use as a WEBUI_SECRET_KEY in case the user didn't provide one
|
||||
SET /p WEBUI_SECRET_KEY=<nul
|
||||
FOR /L %%i IN (1,1,12) DO SET /p WEBUI_SECRET_KEY=<!random!>>%KEY_FILE%
|
||||
echo WEBUI_SECRET_KEY generated
|
||||
)
|
||||
|
||||
echo Loading WEBUI_SECRET_KEY from %KEY_FILE%
|
||||
SET /p WEBUI_SECRET_KEY=<%KEY_FILE%
|
||||
)
|
||||
|
||||
:: Execute uvicorn
|
||||
SET "WEBUI_SECRET_KEY=%WEBUI_SECRET_KEY%"
|
||||
uvicorn main:app --host 0.0.0.0 --port "%PORT%" --forwarded-allow-ips '*'
|
|
@ -1,3 +1,4 @@
|
|||
from pathlib import Path
|
||||
import hashlib
|
||||
import re
|
||||
|
||||
|
@ -38,3 +39,40 @@ def validate_email_format(email: str) -> bool:
|
|||
if not re.match(r"[^@]+@[^@]+\.[^@]+", email):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def sanitize_filename(file_name):
|
||||
# Convert to lowercase
|
||||
lower_case_file_name = file_name.lower()
|
||||
|
||||
# Remove special characters using regular expression
|
||||
sanitized_file_name = re.sub(r"[^\w\s]", "", lower_case_file_name)
|
||||
|
||||
# Replace spaces with dashes
|
||||
final_file_name = re.sub(r"\s+", "-", sanitized_file_name)
|
||||
|
||||
return final_file_name
|
||||
|
||||
|
||||
def extract_folders_after_data_docs(path):
|
||||
# Convert the path to a Path object if it's not already
|
||||
path = Path(path)
|
||||
|
||||
# Extract parts of the path
|
||||
parts = path.parts
|
||||
|
||||
# Find the index of '/data/docs' in the path
|
||||
try:
|
||||
index_data_docs = parts.index("data") + 1
|
||||
index_docs = parts.index("docs", index_data_docs) + 1
|
||||
except ValueError:
|
||||
return []
|
||||
|
||||
# Exclude the filename and accumulate folder names
|
||||
tags = []
|
||||
|
||||
folders = parts[index_docs:-1]
|
||||
for idx, part in enumerate(folders):
|
||||
tags.append("/".join(folders[: idx + 1]))
|
||||
|
||||
return tags
|
||||
|
|
|
@ -5,11 +5,14 @@ ollama:
|
|||
image: ollama/ollama:latest
|
||||
servicePort: 11434
|
||||
resources:
|
||||
limits:
|
||||
requests:
|
||||
cpu: "2000m"
|
||||
memory: "2Gi"
|
||||
limits:
|
||||
cpu: "4000m"
|
||||
memory: "4Gi"
|
||||
nvidia.com/gpu: "0"
|
||||
volumeSize: 1Gi
|
||||
volumeSize: 30Gi
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
service:
|
||||
|
@ -22,16 +25,19 @@ webui:
|
|||
image: ghcr.io/open-webui/open-webui:main
|
||||
servicePort: 8080
|
||||
resources:
|
||||
limits:
|
||||
requests:
|
||||
cpu: "500m"
|
||||
memory: "500Mi"
|
||||
limits:
|
||||
cpu: "1000m"
|
||||
memory: "1Gi"
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations:
|
||||
# Use appropriate annotations for your Ingress controller, e.g., for NGINX:
|
||||
# nginx.ingress.kubernetes.io/rewrite-target: /
|
||||
host: open-webui.minikube.local
|
||||
volumeSize: 1Gi
|
||||
volumeSize: 2Gi
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
service:
|
||||
|
|
|
@ -20,9 +20,13 @@ spec:
|
|||
ports:
|
||||
- containerPort: 11434
|
||||
resources:
|
||||
limits:
|
||||
requests:
|
||||
cpu: "2000m"
|
||||
memory: "2Gi"
|
||||
limits:
|
||||
cpu: "4000m"
|
||||
memory: "4Gi"
|
||||
nvidia.com/gpu: "0"
|
||||
volumeMounts:
|
||||
- name: ollama-volume
|
||||
mountPath: /root/.ollama
|
||||
|
@ -34,4 +38,4 @@ spec:
|
|||
accessModes: [ "ReadWriteOnce" ]
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
storage: 30Gi
|
|
@ -19,10 +19,20 @@ spec:
|
|||
ports:
|
||||
- containerPort: 8080
|
||||
resources:
|
||||
limits:
|
||||
requests:
|
||||
cpu: "500m"
|
||||
memory: "500Mi"
|
||||
limits:
|
||||
cpu: "1000m"
|
||||
memory: "1Gi"
|
||||
env:
|
||||
- name: OLLAMA_API_BASE_URL
|
||||
value: "http://ollama-service.open-webui.svc.cluster.local:11434/api"
|
||||
tty: true
|
||||
tty: true
|
||||
volumeMounts:
|
||||
- name: webui-volume
|
||||
mountPath: /app/backend/data
|
||||
volumes:
|
||||
- name: webui-volume
|
||||
persistentVolumeClaim:
|
||||
claimName: ollama-webui-pvc
|
12
kubernetes/manifest/base/webui-pvc.yaml
Normal file
12
kubernetes/manifest/base/webui-pvc.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
labels:
|
||||
app: ollama-webui
|
||||
name: ollama-webui-pvc
|
||||
namespace: ollama-namespace
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
|
@ -1,5 +1,120 @@
|
|||
import { RAG_API_BASE_URL } from '$lib/constants';
|
||||
|
||||
export const getChunkParams = async (token: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${RAG_API_BASE_URL}/chunk`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
error = err.detail;
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const updateChunkParams = async (token: string, size: number, overlap: number) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${RAG_API_BASE_URL}/chunk/update`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
chunk_size: size,
|
||||
chunk_overlap: overlap
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
error = err.detail;
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getRAGTemplate = async (token: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${RAG_API_BASE_URL}/template`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
error = err.detail;
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res?.template ?? '';
|
||||
};
|
||||
|
||||
export const updateRAGTemplate = async (token: string, template: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${RAG_API_BASE_URL}/template/update`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
template: template
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
error = err.detail;
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const uploadDocToVectorDB = async (token: string, collection_name: string, file: File) => {
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
|
@ -138,6 +253,32 @@ export const queryCollection = async (
|
|||
return res;
|
||||
};
|
||||
|
||||
export const scanDocs = async (token: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${RAG_API_BASE_URL}/scan`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const resetVectorDB = async (token: string) => {
|
||||
let error = null;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { prompts } from '$lib/stores';
|
||||
import { findWordIndices } from '$lib/utils';
|
||||
import { tick } from 'svelte';
|
||||
import toast from 'svelte-french-toast';
|
||||
|
||||
export let prompt = '';
|
||||
let selectedCommandIdx = 0;
|
||||
|
@ -24,7 +25,18 @@
|
|||
};
|
||||
|
||||
const confirmCommand = async (command) => {
|
||||
prompt = command.content;
|
||||
let text = command.content;
|
||||
|
||||
if (command.content.includes('{{CLIPBOARD}}')) {
|
||||
const clipboardText = await navigator.clipboard.readText().catch((err) => {
|
||||
toast.error('Failed to read clipboard contents');
|
||||
return '{{CLIPBOARD}}';
|
||||
});
|
||||
|
||||
text = command.content.replaceAll('{{CLIPBOARD}}', clipboardText);
|
||||
}
|
||||
|
||||
prompt = text;
|
||||
|
||||
const chatInputElement = document.getElementById('chat-textarea');
|
||||
|
||||
|
|
|
@ -72,13 +72,18 @@
|
|||
|
||||
if (message.info) {
|
||||
tooltipInstance = tippy(`#info-${message.id}`, {
|
||||
content: `<span class="text-xs" id="tooltip-${message.id}">token/s: ${
|
||||
content: `<span class="text-xs" id="tooltip-${message.id}">response_token/s: ${
|
||||
`${
|
||||
Math.round(
|
||||
((message.info.eval_count ?? 0) / (message.info.eval_duration / 1000000000)) * 100
|
||||
) / 100
|
||||
} tokens` ?? 'N/A'
|
||||
}<br/>
|
||||
prompt_token/s: ${
|
||||
Math.round(
|
||||
((message.info.prompt_eval_count ?? 0) / (message.info.prompt_eval_duration / 1000000000)) * 100
|
||||
) / 100 ?? 'N/A'
|
||||
} tokens<br/>
|
||||
total_duration: ${
|
||||
Math.round(((message.info.total_duration ?? 0) / 1000000) * 100) / 100 ??
|
||||
'N/A'
|
||||
|
@ -366,7 +371,7 @@
|
|||
|
||||
{#if message.done}
|
||||
<div
|
||||
class=" flex justify-start space-x-1 -mt-1 overflow-x-auto buttons text-gray-700 dark:text-gray-500"
|
||||
class=" flex justify-start space-x-1 overflow-x-auto buttons text-gray-700 dark:text-gray-500"
|
||||
>
|
||||
{#if siblings.length > 1}
|
||||
<div class="flex self-center min-w-fit">
|
||||
|
|
178
src/lib/components/documents/Settings/General.svelte
Normal file
178
src/lib/components/documents/Settings/General.svelte
Normal file
|
@ -0,0 +1,178 @@
|
|||
<script lang="ts">
|
||||
import { getDocs } from '$lib/apis/documents';
|
||||
import {
|
||||
getChunkParams,
|
||||
getRAGTemplate,
|
||||
scanDocs,
|
||||
updateChunkParams,
|
||||
updateRAGTemplate
|
||||
} from '$lib/apis/rag';
|
||||
import { documents } from '$lib/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import toast from 'svelte-french-toast';
|
||||
|
||||
export let saveHandler: Function;
|
||||
|
||||
let loading = false;
|
||||
|
||||
let chunkSize = 0;
|
||||
let chunkOverlap = 0;
|
||||
|
||||
let template = '';
|
||||
|
||||
const scanHandler = async () => {
|
||||
loading = true;
|
||||
const res = await scanDocs(localStorage.token);
|
||||
loading = false;
|
||||
|
||||
if (res) {
|
||||
await documents.set(await getDocs(localStorage.token));
|
||||
toast.success('Scan complete!');
|
||||
}
|
||||
};
|
||||
|
||||
const submitHandler = async () => {
|
||||
const res = await updateChunkParams(localStorage.token, chunkSize, chunkOverlap);
|
||||
await updateRAGTemplate(localStorage.token, template);
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
const res = await getChunkParams(localStorage.token);
|
||||
|
||||
if (res) {
|
||||
chunkSize = res.chunk_size;
|
||||
chunkOverlap = res.chunk_overlap;
|
||||
}
|
||||
|
||||
template = await getRAGTemplate(localStorage.token);
|
||||
});
|
||||
</script>
|
||||
|
||||
<form
|
||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||
on:submit|preventDefault={() => {
|
||||
submitHandler();
|
||||
saveHandler();
|
||||
}}
|
||||
>
|
||||
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
|
||||
<div>
|
||||
<div class=" mb-2 text-sm font-medium">General Settings</div>
|
||||
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">Scan for documents from '/data/docs'</div>
|
||||
|
||||
<button
|
||||
class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded flex flex-row space-x-1 items-center {loading
|
||||
? ' cursor-not-allowed'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
scanHandler();
|
||||
console.log('check');
|
||||
}}
|
||||
type="button"
|
||||
disabled={loading}
|
||||
>
|
||||
<div class="self-center font-medium">Scan</div>
|
||||
|
||||
<!-- <svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-3 h-3"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.932.75.75 0 0 1-1.3-.75 6 6 0 0 1 9.44-1.242l.842.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44 1.241l-.84-.84v1.371a.75.75 0 0 1-1.5 0V9.591a.75.75 0 0 1 .75-.75H5.35a.75.75 0 0 1 0 1.5H3.98l.841.841a4.5 4.5 0 0 0 7.08-.932.75.75 0 0 1 1.025-.273Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg> -->
|
||||
|
||||
{#if loading}
|
||||
<div class="ml-3 self-center">
|
||||
<svg
|
||||
class=" w-3 h-3"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><style>
|
||||
.spinner_ajPY {
|
||||
transform-origin: center;
|
||||
animation: spinner_AtaB 0.75s infinite linear;
|
||||
}
|
||||
@keyframes spinner_AtaB {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style><path
|
||||
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||
opacity=".25"
|
||||
/><path
|
||||
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
||||
class="spinner_ajPY"
|
||||
/></svg
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-700" />
|
||||
|
||||
<div class=" ">
|
||||
<div class=" text-sm font-medium">Chunk Params</div>
|
||||
|
||||
<div class=" flex">
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class="self-center text-xs font-medium min-w-fit">Chunk Size</div>
|
||||
|
||||
<div class="self-center p-3">
|
||||
<input
|
||||
class=" w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
|
||||
type="number"
|
||||
placeholder="Enter Chunk Size"
|
||||
bind:value={chunkSize}
|
||||
autocomplete="off"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div>
|
||||
|
||||
<div class="self-center p-3">
|
||||
<input
|
||||
class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
|
||||
type="number"
|
||||
placeholder="Enter Chunk Overlap"
|
||||
bind:value={chunkOverlap}
|
||||
autocomplete="off"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" mb-2.5 text-sm font-medium">RAG Template</div>
|
||||
<textarea
|
||||
bind:value={template}
|
||||
class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
|
||||
rows="4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-medium">
|
||||
<button
|
||||
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
86
src/lib/components/documents/SettingsModal.svelte
Normal file
86
src/lib/components/documents/SettingsModal.svelte
Normal file
|
@ -0,0 +1,86 @@
|
|||
<script>
|
||||
import Modal from '../common/Modal.svelte';
|
||||
import General from './Settings/General.svelte';
|
||||
|
||||
export let show = false;
|
||||
|
||||
let selectedTab = 'general';
|
||||
</script>
|
||||
|
||||
<Modal bind:show>
|
||||
<div>
|
||||
<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
|
||||
<div class=" text-lg font-medium self-center">Document Settings</div>
|
||||
<button
|
||||
class="self-center"
|
||||
on:click={() => {
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<hr class=" dark:border-gray-800" />
|
||||
|
||||
<div class="flex flex-col md:flex-row w-full p-4 md:space-x-4">
|
||||
<div
|
||||
class="tabs flex flex-row overflow-x-auto space-x-1 md:space-x-0 md:space-y-1 md:flex-col flex-1 md:flex-none md:w-40 dark:text-gray-200 text-xs text-left mb-3 md:mb-0"
|
||||
>
|
||||
<button
|
||||
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
|
||||
'general'
|
||||
? 'bg-gray-200 dark:bg-gray-700'
|
||||
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
|
||||
on:click={() => {
|
||||
selectedTab = 'general';
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class=" self-center">General</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1 md:min-h-[380px]">
|
||||
{#if selectedTab === 'general'}
|
||||
<General
|
||||
saveHandler={() => {
|
||||
show = false;
|
||||
}}
|
||||
/>
|
||||
<!-- <General
|
||||
saveHandler={() => {
|
||||
show = false;
|
||||
}}
|
||||
/> -->
|
||||
<!-- {:else if selectedTab === 'users'}
|
||||
<Users
|
||||
saveHandler={() => {
|
||||
show = false;
|
||||
}}
|
||||
/> -->
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
|
@ -1,17 +1,21 @@
|
|||
export const RAGTemplate = (context: string, query: string) => {
|
||||
let template = `Use the following context as your learned knowledge, inside <context></context> XML tags.
|
||||
<context>
|
||||
[context]
|
||||
</context>
|
||||
|
||||
When answer to user:
|
||||
- If you don't know, just say that you don't know.
|
||||
- If you don't know when you are not sure, ask for clarification.
|
||||
Avoid mentioning that you obtained the information from the context.
|
||||
And answer according to the language of the user's question.
|
||||
|
||||
Given the context information, answer the query.
|
||||
Query: [query]`;
|
||||
import { getRAGTemplate } from '$lib/apis/rag';
|
||||
|
||||
export const RAGTemplate = async (token: string, context: string, query: string) => {
|
||||
let template = await getRAGTemplate(token).catch(() => {
|
||||
return `Use the following context as your learned knowledge, inside <context></context> XML tags.
|
||||
<context>
|
||||
[context]
|
||||
</context>
|
||||
|
||||
When answer to user:
|
||||
- If you don't know, just say that you don't know.
|
||||
- If you don't know when you are not sure, ask for clarification.
|
||||
Avoid mentioning that you obtained the information from the context.
|
||||
And answer according to the language of the user's question.
|
||||
|
||||
Given the context information, answer the query.
|
||||
Query: [query]`;
|
||||
});
|
||||
|
||||
template = template.replace(/\[context\]/g, context);
|
||||
template = template.replace(/\[query\]/g, query);
|
||||
|
|
|
@ -266,7 +266,11 @@
|
|||
|
||||
console.log(contextString);
|
||||
|
||||
history.messages[parentId].raContent = RAGTemplate(contextString, query);
|
||||
history.messages[parentId].raContent = await RAGTemplate(
|
||||
localStorage.token,
|
||||
contextString,
|
||||
query
|
||||
);
|
||||
history.messages[parentId].contexts = relevantContexts;
|
||||
await tick();
|
||||
processing = '';
|
||||
|
|
|
@ -79,14 +79,20 @@
|
|||
>
|
||||
{#if loaded}
|
||||
<div class="overflow-y-auto w-full flex justify-center">
|
||||
<div class="w-full max-w-3xl px-10 md:px-16 flex flex-col">
|
||||
<div class="w-full max-w-3xl px-6 md:px-16 flex flex-col">
|
||||
<div class="py-10 w-full">
|
||||
<div class=" flex flex-col justify-center">
|
||||
<div class=" flex justify-between items-center">
|
||||
<div class=" text-2xl font-semibold">Users ({users.length})</div>
|
||||
<div class="flex items-center text-2xl font-semibold">
|
||||
All Users
|
||||
<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-200 dark:bg-gray-700" />
|
||||
<span class="text-lg font-medium text-gray-500 dark:text-gray-300"
|
||||
>{users.length}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="flex items-center space-x-1 border border-gray-200 dark:border-gray-600 px-3 py-1 rounded-lg"
|
||||
class="flex items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showSettingsModal = !showSettingsModal;
|
||||
|
@ -109,45 +115,34 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class=" text-gray-500 text-xs font-medium mt-1">
|
||||
Click on the user role cell in the table to change a user's role.
|
||||
<div class=" text-gray-500 text-xs mt-1">
|
||||
ⓘ Click on the user role button to change a user's role.
|
||||
</div>
|
||||
|
||||
<hr class=" my-3 dark:border-gray-600" />
|
||||
|
||||
<div class="scrollbar-hidden relative overflow-x-auto whitespace-nowrap">
|
||||
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
|
||||
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto">
|
||||
<thead
|
||||
class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"
|
||||
>
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3"> Name </th>
|
||||
<th scope="col" class="px-6 py-3"> Email </th>
|
||||
<th scope="col" class="px-6 py-3"> Role </th>
|
||||
<th scope="col" class="px-6 py-3"> Action </th>
|
||||
<th scope="col" class="px-3 py-2"> Role </th>
|
||||
<th scope="col" class="px-3 py-2"> Name </th>
|
||||
<th scope="col" class="px-3 py-2"> Email </th>
|
||||
<th scope="col" class="px-3 py-2"> Action </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each users as user}
|
||||
<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700">
|
||||
<th
|
||||
scope="row"
|
||||
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white w-fit"
|
||||
>
|
||||
<div class="flex flex-row">
|
||||
<img
|
||||
class=" rounded-full max-w-[30px] max-h-[30px] object-cover mr-4"
|
||||
src={user.profile_image_url}
|
||||
alt="user"
|
||||
/>
|
||||
|
||||
<div class=" font-semibold self-center">{user.name}</div>
|
||||
</div>
|
||||
</th>
|
||||
<td class="px-6 py-4"> {user.email} </td>
|
||||
<td class="px-6 py-4">
|
||||
<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700 text-xs">
|
||||
<td class="px-3 py-2 min-w-[7rem] w-28">
|
||||
<button
|
||||
class=" dark:text-white underline"
|
||||
class=" flex items-center gap-2 text-xs px-3 py-0.5 rounded-lg {user.role ===
|
||||
'admin' &&
|
||||
'text-sky-600 dark:text-sky-200 bg-sky-200/30'} {user.role === 'user' &&
|
||||
'text-green-600 dark:text-green-200 bg-green-200/30'} {user.role ===
|
||||
'pending' && 'text-gray-600 dark:text-gray-200 bg-gray-200/30'}"
|
||||
on:click={() => {
|
||||
if (user.role === 'user') {
|
||||
updateRoleHandler(user.id, 'admin');
|
||||
|
@ -156,52 +151,77 @@
|
|||
} else {
|
||||
updateRoleHandler(user.id, 'pending');
|
||||
}
|
||||
}}>{user.role}</button
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="w-1 h-1 rounded-full {user.role === 'admin' &&
|
||||
'bg-sky-600 dark:bg-sky-300'} {user.role === 'user' &&
|
||||
'bg-green-600 dark:bg-green-300'} {user.role === 'pending' &&
|
||||
'bg-gray-600 dark:bg-gray-300'}"
|
||||
/>
|
||||
{user.role}</button
|
||||
>
|
||||
</td>
|
||||
<td class="px-6 py-4 space-x-1 text-center flex justify-center">
|
||||
<button
|
||||
class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
|
||||
on:click={async () => {
|
||||
showEditUserModal = !showEditUserModal;
|
||||
selectedUser = user;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M11.013 2.513a1.75 1.75 0 0 1 2.475 2.474L6.226 12.25a2.751 2.751 0 0 1-.892.596l-2.047.848a.75.75 0 0 1-.98-.98l.848-2.047a2.75 2.75 0 0 1 .596-.892l7.262-7.261Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<td class="px-3 py-2 font-medium text-gray-900 dark:text-white w-max">
|
||||
<div class="flex flex-row w-max">
|
||||
<img
|
||||
class=" rounded-full w-6 h-6 object-cover mr-2.5"
|
||||
src={user.profile_image_url}
|
||||
alt="user"
|
||||
/>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
|
||||
on:click={async () => {
|
||||
deleteUserHandler(user.id);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
<div class=" font-medium self-center">{user.name}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class=" px-3 py-2"> {user.email} </td>
|
||||
|
||||
<td class="px-3 py-2">
|
||||
<div class="flex justify-start w-full">
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
on:click={async () => {
|
||||
showEditUserModal = !showEditUserModal;
|
||||
selectedUser = user;
|
||||
}}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
on:click={async () => {
|
||||
deleteUserHandler(user.id);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
|
|
@ -280,7 +280,11 @@
|
|||
|
||||
console.log(contextString);
|
||||
|
||||
history.messages[parentId].raContent = RAGTemplate(contextString, query);
|
||||
history.messages[parentId].raContent = await RAGTemplate(
|
||||
localStorage.token,
|
||||
contextString,
|
||||
query
|
||||
);
|
||||
history.messages[parentId].contexts = relevantContexts;
|
||||
await tick();
|
||||
processing = '';
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
import EditDocModal from '$lib/components/documents/EditDocModal.svelte';
|
||||
import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
|
||||
import SettingsModal from '$lib/components/documents/SettingsModal.svelte';
|
||||
let importFiles = '';
|
||||
|
||||
let inputFiles = '';
|
||||
|
@ -20,6 +21,7 @@
|
|||
|
||||
let tags = [];
|
||||
|
||||
let showSettingsModal = false;
|
||||
let showEditDocModal = false;
|
||||
let selectedDoc;
|
||||
let selectedTag = '';
|
||||
|
@ -179,11 +181,43 @@
|
|||
}}
|
||||
/>
|
||||
|
||||
<SettingsModal bind:show={showSettingsModal} />
|
||||
|
||||
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
|
||||
<div class=" py-2.5 flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class=" flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
|
||||
<div class="mb-6 flex justify-between items-center">
|
||||
<div class=" text-2xl font-semibold self-center">My Documents</div>
|
||||
<div class="mb-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class=" text-2xl font-semibold self-center">My Documents</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
class="flex items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showSettingsModal = !showSettingsModal;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div class=" text-xs">Document Settings</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class=" text-gray-500 text-xs mt-1">
|
||||
ⓘ Use '#' in the prompt input to load and select your documents.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" flex w-full space-x-2">
|
||||
|
@ -211,7 +245,7 @@
|
|||
|
||||
<div>
|
||||
<button
|
||||
class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
|
||||
class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
|
||||
on:click={() => {
|
||||
document.getElementById('upload-doc-input')?.click();
|
||||
}}
|
||||
|
@ -278,154 +312,154 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{#each $documents.filter((doc) => (selectedTag === '' || (doc?.content?.tags ?? [])
|
||||
.map((tag) => tag.name)
|
||||
.includes(selectedTag)) && (query === '' || doc.name.includes(query))) as doc}
|
||||
<div class=" flex space-x-4 cursor-pointer w-full mt-3 mb-3">
|
||||
<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
|
||||
<div class=" flex items-center space-x-3">
|
||||
<div class="p-2.5 bg-red-400 text-white rounded-lg">
|
||||
{#if doc}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-6 h-6"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg
|
||||
class=" w-6 h-6 translate-y-[0.5px]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><style>
|
||||
.spinner_qM83 {
|
||||
animation: spinner_8HQG 1.05s infinite;
|
||||
}
|
||||
.spinner_oXPr {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.spinner_ZTLf {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
@keyframes spinner_8HQG {
|
||||
0%,
|
||||
57.14% {
|
||||
animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
|
||||
transform: translate(0);
|
||||
<div class="my-3 mb-5">
|
||||
{#each $documents.filter((doc) => (selectedTag === '' || (doc?.content?.tags ?? [])
|
||||
.map((tag) => tag.name)
|
||||
.includes(selectedTag)) && (query === '' || doc.name.includes(query))) as doc}
|
||||
<div
|
||||
class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
|
||||
>
|
||||
<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
|
||||
<div class=" flex items-center space-x-3">
|
||||
<div class="p-2.5 bg-red-400 text-white rounded-lg">
|
||||
{#if doc}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="w-6 h-6"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg
|
||||
class=" w-6 h-6 translate-y-[0.5px]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><style>
|
||||
.spinner_qM83 {
|
||||
animation: spinner_8HQG 1.05s infinite;
|
||||
}
|
||||
28.57% {
|
||||
animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
|
||||
transform: translateY(-6px);
|
||||
.spinner_oXPr {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
100% {
|
||||
transform: translate(0);
|
||||
.spinner_ZTLf {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
}
|
||||
</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
|
||||
class="spinner_qM83 spinner_oXPr"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="2.5"
|
||||
/><circle class="spinner_qM83 spinner_ZTLf" cx="20" cy="12" r="2.5" /></svg
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div class=" flex-1 self-center flex-1">
|
||||
<div class=" font-bold line-clamp-1">#{doc.name} ({doc.filename})</div>
|
||||
<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
|
||||
{doc.title}
|
||||
@keyframes spinner_8HQG {
|
||||
0%,
|
||||
57.14% {
|
||||
animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
|
||||
transform: translate(0);
|
||||
}
|
||||
28.57% {
|
||||
animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
100% {
|
||||
transform: translate(0);
|
||||
}
|
||||
}
|
||||
</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
|
||||
class="spinner_qM83 spinner_oXPr"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="2.5"
|
||||
/><circle class="spinner_qM83 spinner_ZTLf" cx="20" cy="12" r="2.5" /></svg
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div class=" flex-1 self-center flex-1">
|
||||
<div class=" font-bold line-clamp-1">#{doc.name} ({doc.filename})</div>
|
||||
<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
|
||||
{doc.title}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row space-x-1 self-center">
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
showEditDocModal = !showEditDocModal;
|
||||
selectedDoc = doc;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
<div class="flex flex-row space-x-1 self-center">
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
showEditDocModal = !showEditDocModal;
|
||||
selectedDoc = doc;
|
||||
}}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- <button
|
||||
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
console.log('download file');
|
||||
}}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
|
||||
/>
|
||||
<path
|
||||
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
|
||||
/>
|
||||
</svg>
|
||||
</button> -->
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
deleteDoc(doc.name);
|
||||
}}
|
||||
<!-- <button
|
||||
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
console.log('download file');
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
<path
|
||||
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
|
||||
/>
|
||||
<path
|
||||
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
|
||||
/>
|
||||
</svg>
|
||||
</button> -->
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
deleteDoc(doc.name);
|
||||
}}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if $documents.length > 0}
|
||||
<hr class=" dark:border-gray-700 my-2.5" />
|
||||
{/if}
|
||||
|
||||
<div class=" flex justify-between w-full mb-3">
|
||||
<div class=" flex justify-end w-full mb-2">
|
||||
<div class="flex space-x-2">
|
||||
<input
|
||||
id="documents-import-input"
|
||||
|
@ -462,7 +496,7 @@
|
|||
/>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
document.getElementById('documents-import-input')?.click();
|
||||
}}
|
||||
|
@ -486,7 +520,7 @@
|
|||
</button>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
let blob = new Blob([JSON.stringify($documents)], {
|
||||
type: 'application/json'
|
||||
|
@ -513,29 +547,6 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-xs flex items-center space-x-1">
|
||||
<div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-3 h-3"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="line-clamp-1">
|
||||
Tip: Use '#' in the prompt input to swiftly load and select your documents.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
deleteModelfileByTagName,
|
||||
getModelfiles
|
||||
} from '$lib/apis/modelfiles';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
let localModelfiles = [];
|
||||
let importFiles;
|
||||
|
@ -69,11 +70,11 @@
|
|||
</script>
|
||||
|
||||
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
|
||||
<div class=" py-2.5 flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class="flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
|
||||
<div class=" text-2xl font-semibold mb-6">My Modelfiles</div>
|
||||
<div class=" text-2xl font-semibold mb-3">My Modelfiles</div>
|
||||
|
||||
<a class=" flex space-x-4 cursor-pointer w-full mb-3" href="/modelfiles/create">
|
||||
<a class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2" href="/modelfiles/create">
|
||||
<div class=" self-center w-10">
|
||||
<div
|
||||
class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
|
||||
|
@ -99,105 +100,132 @@
|
|||
</div>
|
||||
</a>
|
||||
|
||||
{#each $modelfiles as modelfile}
|
||||
<hr class=" dark:border-gray-700 my-2.5" />
|
||||
<div class=" flex space-x-4 cursor-pointer w-full mb-3">
|
||||
<a
|
||||
class=" flex flex-1 space-x-4 cursor-pointer w-full"
|
||||
href={`/?models=${encodeURIComponent(modelfile.tagName)}`}
|
||||
<hr class=" dark:border-gray-700" />
|
||||
|
||||
<div class=" my-2 mb-5">
|
||||
{#each $modelfiles as modelfile}
|
||||
<div
|
||||
class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
|
||||
>
|
||||
<div class=" self-center w-10">
|
||||
<div class=" rounded-full bg-stone-700">
|
||||
<img
|
||||
src={modelfile.imageUrl ?? '/user.png'}
|
||||
alt="modelfile profile"
|
||||
class=" rounded-full w-full h-auto object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" flex-1 self-center">
|
||||
<div class=" font-bold capitalize">{modelfile.title}</div>
|
||||
<div class=" text-sm overflow-hidden text-ellipsis line-clamp-2">
|
||||
{modelfile.desc}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="flex flex-row space-x-1 self-center">
|
||||
<a
|
||||
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
|
||||
type="button"
|
||||
href={`/modelfiles/edit?tag=${encodeURIComponent(modelfile.tagName)}`}
|
||||
class=" flex flex-1 space-x-4 cursor-pointer w-full"
|
||||
href={`/?models=${encodeURIComponent(modelfile.tagName)}`}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
|
||||
/>
|
||||
</svg>
|
||||
<div class=" self-center w-10">
|
||||
<div class=" rounded-full bg-stone-700">
|
||||
<img
|
||||
src={modelfile.imageUrl ?? '/user.png'}
|
||||
alt="modelfile profile"
|
||||
class=" rounded-full w-full h-auto object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" flex-1 self-center">
|
||||
<div class=" font-bold capitalize">{modelfile.title}</div>
|
||||
<div class=" text-sm overflow-hidden text-ellipsis line-clamp-1">
|
||||
{modelfile.desc}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
shareModelfile(modelfile);
|
||||
}}
|
||||
>
|
||||
<!-- TODO: update to share icon -->
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
<div class="flex flex-row space-x-1 self-center">
|
||||
<a
|
||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
type="button"
|
||||
href={`/modelfiles/edit?tag=${encodeURIComponent(modelfile.tagName)}`}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
deleteModelfile(modelfile.tagName);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
// console.log(modelfile);
|
||||
sessionStorage.modelfile = JSON.stringify(modelfile);
|
||||
goto('/modelfiles/create');
|
||||
}}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
shareModelfile(modelfile);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M7.217 10.907a2.25 2.25 0 1 0 0 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186 9.566-5.314m-9.566 7.5 9.566 5.314m0 0a2.25 2.25 0 1 0 3.935 2.186 2.25 2.25 0 0 0-3.935-2.186Zm0-12.814a2.25 2.25 0 1 0 3.933-2.185 2.25 2.25 0 0 0-3.933 2.185Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
deleteModelfile(modelfile.tagName);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-700 my-2.5" />
|
||||
|
||||
<div class=" flex justify-between w-full mb-3">
|
||||
<div class=" flex justify-end w-full mb-3">
|
||||
<div class="flex space-x-1">
|
||||
<input
|
||||
id="modelfiles-import-input"
|
||||
|
@ -227,7 +255,7 @@
|
|||
/>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
document.getElementById('modelfiles-import-input')?.click();
|
||||
}}
|
||||
|
@ -251,7 +279,7 @@
|
|||
</button>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
saveModelfiles($modelfiles);
|
||||
}}
|
||||
|
@ -348,10 +376,10 @@
|
|||
</div>
|
||||
|
||||
<div class=" my-16">
|
||||
<div class=" text-2xl font-semibold mb-6">Made by OpenWebUI Community</div>
|
||||
<div class=" text-2xl font-semibold mb-3">Made by OpenWebUI Community</div>
|
||||
|
||||
<a
|
||||
class=" flex space-x-4 cursor-pointer w-full mb-3"
|
||||
class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2"
|
||||
href="https://openwebui.com/"
|
||||
target="_blank"
|
||||
>
|
||||
|
@ -376,7 +404,7 @@
|
|||
|
||||
<div class=" self-center">
|
||||
<div class=" font-bold">Discover a modelfile</div>
|
||||
<div class=" text-sm">Discover, download, and explore Ollama Modelfiles</div>
|
||||
<div class=" text-sm">Discover, download, and explore model presets</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -209,7 +209,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
|
|||
success = false;
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
onMount(async () => {
|
||||
window.addEventListener('message', async (event) => {
|
||||
if (
|
||||
![
|
||||
|
@ -253,11 +253,36 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
|
|||
if (window.opener ?? false) {
|
||||
window.opener.postMessage('loaded', '*');
|
||||
}
|
||||
|
||||
if (sessionStorage.modelfile) {
|
||||
const modelfile = JSON.parse(sessionStorage.modelfile);
|
||||
console.log(modelfile);
|
||||
imageUrl = modelfile.imageUrl;
|
||||
title = modelfile.title;
|
||||
await tick();
|
||||
tagName = modelfile.tagName;
|
||||
desc = modelfile.desc;
|
||||
content = modelfile.content;
|
||||
suggestions =
|
||||
modelfile.suggestionPrompts.length != 0
|
||||
? modelfile.suggestionPrompts
|
||||
: [
|
||||
{
|
||||
content: ''
|
||||
}
|
||||
];
|
||||
|
||||
for (const category of modelfile.categories) {
|
||||
categories[category.toLowerCase()] = true;
|
||||
}
|
||||
|
||||
sessionStorage.removeItem('modelfile');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
|
||||
<div class=" py-2.5 flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class=" flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
|
||||
<input
|
||||
bind:this={filesInputElement}
|
||||
|
|
|
@ -181,7 +181,7 @@
|
|||
</script>
|
||||
|
||||
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
|
||||
<div class=" py-2.5 flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class="flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
|
||||
<input
|
||||
bind:this={filesInputElement}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { prompts } from '$lib/stores';
|
||||
import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
let importFiles = '';
|
||||
let query = '';
|
||||
|
@ -36,7 +37,7 @@
|
|||
</script>
|
||||
|
||||
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
|
||||
<div class=" py-2.5 flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class="flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
|
||||
<div class="mb-6 flex justify-between items-center">
|
||||
<div class=" text-2xl font-semibold self-center">My Prompts</div>
|
||||
|
@ -67,7 +68,7 @@
|
|||
|
||||
<div>
|
||||
<a
|
||||
class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
|
||||
class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
|
||||
href="/prompts/create"
|
||||
>
|
||||
<svg
|
||||
|
@ -83,13 +84,13 @@
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr class=" dark:border-gray-700 my-2.5" />
|
||||
|
||||
{#if $prompts.length === 0}
|
||||
<div />
|
||||
{:else}
|
||||
<div class="my-3 mb-5">
|
||||
{#each $prompts.filter((p) => query === '' || p.command.includes(query)) as prompt}
|
||||
<hr class=" dark:border-gray-700 my-2.5" />
|
||||
<div class=" flex space-x-4 cursor-pointer w-full mb-3">
|
||||
<div
|
||||
class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
|
||||
>
|
||||
<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
|
||||
<a href={`/prompts/edit?command=${encodeURIComponent(prompt.command)}`}>
|
||||
<div class=" flex-1 self-center pl-5">
|
||||
|
@ -102,7 +103,7 @@
|
|||
</div>
|
||||
<div class="flex flex-row space-x-1 self-center">
|
||||
<a
|
||||
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
|
||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
type="button"
|
||||
href={`/prompts/edit?command=${encodeURIComponent(prompt.command)}`}
|
||||
>
|
||||
|
@ -123,7 +124,32 @@
|
|||
</a>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
|
||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
// console.log(modelfile);
|
||||
sessionStorage.prompt = JSON.stringify(prompt);
|
||||
goto('/prompts/create');
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
sharePrompt(prompt);
|
||||
|
@ -146,7 +172,7 @@
|
|||
</button>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
|
||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
deletePrompt(prompt.command);
|
||||
|
@ -170,11 +196,9 @@
|
|||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-700 my-2.5" />
|
||||
|
||||
<div class=" flex justify-between w-full mb-3">
|
||||
<div class=" flex justify-end w-full mb-3">
|
||||
<div class="flex space-x-2">
|
||||
<input
|
||||
id="prompts-import-input"
|
||||
|
@ -210,7 +234,7 @@
|
|||
/>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
document.getElementById('prompts-import-input')?.click();
|
||||
}}
|
||||
|
@ -234,7 +258,7 @@
|
|||
</button>
|
||||
|
||||
<button
|
||||
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
// document.getElementById('modelfiles-import-input')?.click();
|
||||
let blob = new Blob([JSON.stringify($prompts)], {
|
||||
|
@ -275,7 +299,7 @@
|
|||
<div class=" text-2xl font-semibold mb-6">Made by OpenWebUI Community</div>
|
||||
|
||||
<a
|
||||
class=" flex space-x-4 cursor-pointer w-full mb-3"
|
||||
class=" flex space-x-4 cursor-pointer w-full mb-3 px-3 py-2"
|
||||
href="https://openwebui.com/?type=prompts"
|
||||
target="_blank"
|
||||
>
|
||||
|
@ -300,7 +324,7 @@
|
|||
|
||||
<div class=" self-center">
|
||||
<div class=" font-bold">Discover a prompt</div>
|
||||
<div class=" text-sm">Discover, download, and explore custom Prompts</div>
|
||||
<div class=" text-sm">Discover, download, and explore custom prompts</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
return regex.test(inputString);
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
onMount(async () => {
|
||||
window.addEventListener('message', async (event) => {
|
||||
if (
|
||||
![
|
||||
|
@ -74,11 +74,23 @@
|
|||
if (window.opener ?? false) {
|
||||
window.opener.postMessage('loaded', '*');
|
||||
}
|
||||
|
||||
if (sessionStorage.prompt) {
|
||||
const prompt = JSON.parse(sessionStorage.prompt);
|
||||
|
||||
console.log(prompt);
|
||||
title = prompt.title;
|
||||
await tick();
|
||||
content = prompt.content;
|
||||
command = prompt.command.at(0) === '/' ? prompt.command.slice(1) : prompt.command;
|
||||
|
||||
sessionStorage.removeItem('prompt');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
|
||||
<div class=" py-2.5 flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class=" flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
|
||||
<div class=" text-2xl font-semibold mb-6">My Prompts</div>
|
||||
|
||||
|
@ -171,12 +183,18 @@
|
|||
</div>
|
||||
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">
|
||||
Format your variables using square brackets like this: <span
|
||||
ⓘ Format your variables using square brackets like this: <span
|
||||
class=" text-gray-600 dark:text-gray-300 font-medium">[variable]</span
|
||||
>
|
||||
. Make sure to enclose them with
|
||||
<span class=" text-gray-600 dark:text-gray-300 font-medium">'['</span>
|
||||
and <span class=" text-gray-600 dark:text-gray-300 font-medium">']'</span> .
|
||||
and <span class=" text-gray-600 dark:text-gray-300 font-medium">']'</span>.
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">
|
||||
Utilize <span class=" text-gray-600 dark:text-gray-300 font-medium"
|
||||
>{`{{CLIPBOARD}}`}</span
|
||||
> variable to have them replaced with clipboard content.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
</script>
|
||||
|
||||
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
|
||||
<div class=" py-2.5 flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class="flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
|
||||
<div class=" text-2xl font-semibold mb-6">My Prompts</div>
|
||||
|
||||
|
|
Loading…
Reference in a new issue