Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0399a69b73 | ||
|
|
9e726a32e6 | ||
|
|
78284e49d7 | ||
|
|
64a6db4b55 | ||
|
|
16c9042318 | ||
|
|
7a47ba14f7 | ||
|
|
988123a2c8 | ||
|
|
3a661fda1a | ||
|
|
a85e93f1f9 | ||
|
|
7050d53718 | ||
|
|
cd91d8a987 | ||
|
|
eddaa7fa89 | ||
|
|
550fc63ebd | ||
|
|
57a81d70c1 | ||
|
|
295472fca1 | ||
|
|
a3e41db8d7 | ||
|
|
546efe0d7b | ||
|
|
27f01b0bc8 | ||
|
|
62ec2651ba | ||
|
|
b9cadff16b | ||
|
|
c0d273f162 | ||
|
|
098ba6ea4e | ||
|
|
582d11f191 | ||
|
|
cb2158a794 | ||
|
|
abfcceecef | ||
|
|
f4b87ecb23 | ||
|
|
48aad65514 | ||
|
|
cbb21a148d | ||
|
|
2f6e683163 | ||
|
|
1448c32fdf | ||
|
|
0a3b99b94a | ||
|
|
0bae789d39 | ||
|
|
304bf9d9b1 | ||
|
|
839efa4443 | ||
|
|
0a488c783f | ||
|
|
d6275ee941 | ||
|
|
2da7dd67ea | ||
|
|
506a061387 | ||
|
|
1a2971ae7b | ||
|
|
da9a54288e | ||
|
|
28c8b5841c | ||
|
|
e48cdf63d9 | ||
|
|
a649dc80c0 | ||
|
|
733d69425e | ||
|
|
e844e7f708 | ||
|
|
3b3d0cce1e | ||
|
|
b07641e58a | ||
|
|
72110c293a | ||
|
|
f64ac3269f | ||
|
|
eb5ed905eb | ||
|
|
4207f80ce9 | ||
|
|
073aecd427 | ||
|
|
3e1c679f2d | ||
|
|
47aeab81d8 | ||
|
|
f4e165d028 | ||
|
|
02d3fb427b | ||
|
|
117e07b0d4 | ||
|
|
efa6aa4b4c | ||
|
|
c73e8916ed | ||
|
|
df2cb16086 | ||
|
|
f267f3f7b0 | ||
|
|
8d8075e81f | ||
|
|
0a403eb129 | ||
|
|
f74642df1b | ||
|
|
782cce7c44 | ||
|
|
395ca82175 | ||
|
|
663b5adaf2 | ||
|
|
924ebf035b | ||
|
|
fafb8cd263 | ||
|
|
8110a872d5 | ||
|
|
331fe04df7 | ||
|
|
aed4763bfe | ||
|
|
8d0f3fbde9 | ||
|
|
318d4d1ce1 | ||
|
|
ec530ac9f8 | ||
|
|
11741ea7f0 | ||
|
|
bee0338763 | ||
|
|
073f06d449 | ||
|
|
5a787ab2c5 | ||
|
|
81dbc65853 | ||
|
|
8a7075c3bf | ||
|
|
78565e554b | ||
|
|
7081755e19 | ||
|
|
cf54adf5c4 | ||
|
|
ae9922a2cd | ||
|
|
bad7dca51e | ||
|
|
69716a5cec | ||
|
|
1af62a70f0 | ||
|
|
5694f16624 | ||
|
|
a68b95c95f | ||
|
|
f34b9733e3 | ||
|
|
8db03f3ab2 | ||
|
|
d001d7afb1 | ||
|
|
183b84beae | ||
|
|
075dddf3d3 | ||
|
|
7d64ada43e | ||
|
|
802eb3beec | ||
|
|
ac9308dbed | ||
|
|
b7ced4e4f1 | ||
|
|
536fd347b9 | ||
|
|
c8f7bb990c | ||
|
|
b8790200e9 | ||
|
|
4200ad111c | ||
|
|
0f90332e61 | ||
|
|
f87805b46f | ||
|
|
7a7d1931d9 | ||
|
|
e61e1b079f | ||
|
|
7813c3316f | ||
|
|
ac470e64e0 | ||
|
|
c52cc46d2c | ||
|
|
a2bd9b8639 | ||
|
|
0c43897f3d | ||
|
|
4195af4942 | ||
|
|
9f82f5abba | ||
|
|
075fbedb02 | ||
|
|
bcf79c8366 | ||
|
|
3b66aa55c0 | ||
|
|
3b06096c52 | ||
|
|
a602089b64 | ||
|
|
8d1db9a1c0 | ||
|
|
ca8fd8af1d | ||
|
|
d0d01c95f9 | ||
|
|
f669c0e78e | ||
|
|
33ad2381aa | ||
|
|
9bcb37ea10 | ||
|
|
099b1d066b | ||
|
|
9763d885be | ||
|
|
6bb299ae25 | ||
|
|
a0a064f4c8 | ||
|
|
40e1e212d4 | ||
|
|
c9f17ecb3a | ||
|
|
fdef2abdfb | ||
|
|
fc4e762b05 | ||
|
|
c08631d6ff | ||
|
|
3f973fe77f | ||
|
|
953d05428e | ||
|
|
6dc4b748fb | ||
|
|
fde0139bf7 | ||
|
|
afa591afb2 | ||
|
|
2588da0e27 | ||
|
|
8ce48dc7d1 | ||
|
|
9bea40bd40 | ||
|
|
9a8a48b879 | ||
|
|
1f6739337b | ||
|
|
132d741c55 | ||
|
|
5abe0089cb | ||
|
|
c5948d3e2c | ||
|
|
c004ecdccc | ||
|
|
e3b1cbbb86 | ||
|
|
f6cef312f2 | ||
|
|
75a40dead6 | ||
|
|
29e48b1c1f | ||
|
|
62ab163316 | ||
|
|
384b7e8462 | ||
|
|
50bec32153 |
+5
-1
@@ -9,4 +9,8 @@ OPENAI_API_KEY=''
|
||||
|
||||
# DO NOT TRACK
|
||||
SCARF_NO_ANALYTICS=true
|
||||
DO_NOT_TRACK=true
|
||||
DO_NOT_TRACK=true
|
||||
|
||||
# Use locally bundled version of the LiteLLM cost map json
|
||||
# to avoid repetitive startup connections
|
||||
LITELLM_LOCAL_MODEL_COST_MAP="True"
|
||||
|
||||
@@ -57,3 +57,14 @@ jobs:
|
||||
path: .
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Trigger Docker build workflow
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
workflow_id: 'docker-build.yaml',
|
||||
ref: 'v${{ steps.get_version.outputs.version }}',
|
||||
})
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#
|
||||
name: Create and publish a Docker image
|
||||
name: Create and publish Docker images with specific build args
|
||||
|
||||
# Configures this workflow to run every time a change is pushed to the branch called `release`.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -10,30 +9,39 @@ on:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
FULL_IMAGE_NAME: ghcr.io/${{ github.repository }}
|
||||
|
||||
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
build-main-image:
|
||||
runs-on: ubuntu-latest
|
||||
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
#
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
# Required for multi architecture build
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
# Required for multi architecture build
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -41,12 +49,11 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata for Docker images
|
||||
- name: Extract metadata for Docker images (default latest tag)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# This configuration dynamically generates tags based on the branch, tag, commit, and custom suffix for lite version.
|
||||
images: ${{ env.FULL_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
@@ -56,11 +63,322 @@ jobs:
|
||||
flavor: |
|
||||
latest=${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
- name: Build Docker image (latest)
|
||||
uses: docker/build-push-action@v5
|
||||
id: build
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-main-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
build-cuda-image:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata for Docker images (default latest tag)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.FULL_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha,prefix=git-
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=cuda
|
||||
flavor: |
|
||||
latest=${{ github.ref == 'refs/heads/main' }}
|
||||
suffix=-cuda,onlatest=true
|
||||
|
||||
- name: Build Docker image (cuda)
|
||||
uses: docker/build-push-action@v5
|
||||
id: build
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: USE_CUDA=true
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-cuda-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
build-ollama-image:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata for Docker images (ollama tag)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.FULL_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha,prefix=git-
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=ollama
|
||||
flavor: |
|
||||
latest=${{ github.ref == 'refs/heads/main' }}
|
||||
suffix=-ollama,onlatest=true
|
||||
|
||||
- name: Build Docker image (ollama)
|
||||
uses: docker/build-push-action@v5
|
||||
id: build
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: USE_OLLAMA=true
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-ollama-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge-main-images:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ build-main-image ]
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: digests-main-*
|
||||
path: /tmp/digests
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata for Docker images (default latest tag)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.FULL_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha,prefix=git-
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
flavor: |
|
||||
latest=${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *)
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }}
|
||||
|
||||
|
||||
merge-cuda-images:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ build-cuda-image ]
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: digests-cuda-*
|
||||
path: /tmp/digests
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata for Docker images (default latest tag)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.FULL_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha,prefix=git-
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=cuda
|
||||
flavor: |
|
||||
latest=${{ github.ref == 'refs/heads/main' }}
|
||||
suffix=-cuda,onlatest=true
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *)
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }}
|
||||
|
||||
merge-ollama-images:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ build-ollama-image ]
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: digests-ollama-*
|
||||
path: /tmp/digests
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata for Docker images (default ollama tag)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.FULL_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha,prefix=git-
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=ollama
|
||||
flavor: |
|
||||
latest=${{ github.ref == 'refs/heads/main' }}
|
||||
suffix=-ollama,onlatest=true
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *)
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }}
|
||||
|
||||
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.1.118] - 2024-04-10
|
||||
|
||||
### Added
|
||||
|
||||
- **🦙 Ollama and CUDA Images**: Added support for ':ollama' and ':cuda' tagged images.
|
||||
- **👍 Enhanced Response Rating**: Now you can annotate your ratings for better feedback.
|
||||
- **👤 User Initials Profile Photo**: User initials are now the default profile photo.
|
||||
- **🔍 Update RAG Embedding Model**: Customize RAG embedding model directly in document settings.
|
||||
- **🌍 Additional Language Support**: Added Turkish language support.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **🔒 Share Chat Permission**: Resolved issue with chat sharing permissions.
|
||||
- **🛠 Modal Close**: Modals can now be closed using the Esc key.
|
||||
|
||||
### Changed
|
||||
|
||||
- **🎨 Admin Panel Styling**: Refreshed styling for the admin panel.
|
||||
- **🐳 Docker Image Build**: Updated docker image build process for improved efficiency.
|
||||
|
||||
## [0.1.117] - 2024-04-03
|
||||
|
||||
### Added
|
||||
|
||||
+86
-46
@@ -1,78 +1,116 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Initialize device type args
|
||||
# use build args in the docker build commmand with --build-arg="BUILDARG=true"
|
||||
ARG USE_CUDA=false
|
||||
ARG USE_OLLAMA=false
|
||||
# Tested with cu117 for CUDA 11 and cu121 for CUDA 12 (default)
|
||||
ARG USE_CUDA_VER=cu121
|
||||
# any sentence transformer model; models to use can be found at https://huggingface.co/models?library=sentence-transformers
|
||||
# Leaderboard: https://huggingface.co/spaces/mteb/leaderboard
|
||||
# for better performance and multilangauge support use "intfloat/multilingual-e5-large" (~2.5GB) or "intfloat/multilingual-e5-base" (~1.5GB)
|
||||
# IMPORTANT: If you change the default model (all-MiniLM-L6-v2) and vice versa, you aren't able to use RAG Chat with your previous documents loaded in the WebUI! You need to re-embed them.
|
||||
ARG USE_EMBEDDING_MODEL=all-MiniLM-L6-v2
|
||||
|
||||
FROM node:alpine as build
|
||||
######## WebUI frontend ########
|
||||
FROM --platform=$BUILDPLATFORM node:21-alpine3.19 as build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# wget embedding model weight from alpine (does not exist from slim-buster)
|
||||
RUN wget "https://chroma-onnx-models.s3.amazonaws.com/all-MiniLM-L6-v2/onnx.tar.gz" -O - | \
|
||||
tar -xzf - -C /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
|
||||
######## WebUI backend ########
|
||||
FROM python:3.11-slim-bookworm as base
|
||||
|
||||
ENV ENV=prod
|
||||
ENV PORT ""
|
||||
# Use args
|
||||
ARG USE_CUDA
|
||||
ARG USE_OLLAMA
|
||||
ARG USE_CUDA_VER
|
||||
ARG USE_EMBEDDING_MODEL
|
||||
|
||||
ENV OLLAMA_BASE_URL "/ollama"
|
||||
## Basis ##
|
||||
ENV ENV=prod \
|
||||
PORT=8080 \
|
||||
# pass build args to the build
|
||||
USE_OLLAMA_DOCKER=${USE_OLLAMA} \
|
||||
USE_CUDA_DOCKER=${USE_CUDA} \
|
||||
USE_CUDA_DOCKER_VER=${USE_CUDA_VER} \
|
||||
USE_EMBEDDING_MODEL_DOCKER=${USE_EMBEDDING_MODEL}
|
||||
|
||||
ENV OPENAI_API_BASE_URL ""
|
||||
ENV OPENAI_API_KEY ""
|
||||
## Basis URL Config ##
|
||||
ENV OLLAMA_BASE_URL="/ollama" \
|
||||
OPENAI_API_BASE_URL=""
|
||||
|
||||
ENV WEBUI_SECRET_KEY ""
|
||||
ENV WEBUI_AUTH_TRUSTED_EMAIL_HEADER ""
|
||||
## API Key and Security Config ##
|
||||
ENV OPENAI_API_KEY="" \
|
||||
WEBUI_SECRET_KEY="" \
|
||||
SCARF_NO_ANALYTICS=true \
|
||||
DO_NOT_TRACK=true
|
||||
|
||||
ENV SCARF_NO_ANALYTICS true
|
||||
ENV DO_NOT_TRACK true
|
||||
# Use locally bundled version of the LiteLLM cost map json
|
||||
# to avoid repetitive startup connections
|
||||
ENV LITELLM_LOCAL_MODEL_COST_MAP="True"
|
||||
|
||||
######## Preloaded models ########
|
||||
# whisper TTS Settings
|
||||
ENV WHISPER_MODEL="base"
|
||||
ENV WHISPER_MODEL_DIR="/app/backend/data/cache/whisper/models"
|
||||
|
||||
# RAG Embedding Model Settings
|
||||
# any sentence transformer model; models to use can be found at https://huggingface.co/models?library=sentence-transformers
|
||||
# Leaderboard: https://huggingface.co/spaces/mteb/leaderboard
|
||||
# for better persormance and multilangauge support use "intfloat/multilingual-e5-large" (~2.5GB) or "intfloat/multilingual-e5-base" (~1.5GB)
|
||||
# IMPORTANT: If you change the default model (all-MiniLM-L6-v2) and vice versa, you aren't able to use RAG Chat with your previous documents loaded in the WebUI! You need to re-embed them.
|
||||
ENV RAG_EMBEDDING_MODEL="all-MiniLM-L6-v2"
|
||||
# device type for whisper tts and embbeding models - "cpu" (default), "cuda" (nvidia gpu and CUDA required) or "mps" (apple silicon) - choosing this right can lead to better performance
|
||||
ENV RAG_EMBEDDING_MODEL_DEVICE_TYPE="cpu"
|
||||
ENV RAG_EMBEDDING_MODEL_DIR="/app/backend/data/cache/embedding/models"
|
||||
ENV SENTENCE_TRANSFORMERS_HOME $RAG_EMBEDDING_MODEL_DIR
|
||||
#### Other models #########################################################
|
||||
## whisper TTS model settings ##
|
||||
ENV WHISPER_MODEL="base" \
|
||||
WHISPER_MODEL_DIR="/app/backend/data/cache/whisper/models"
|
||||
|
||||
######## Preloaded models ########
|
||||
## RAG Embedding model settings ##
|
||||
ENV RAG_EMBEDDING_MODEL="$USE_EMBEDDING_MODEL_DOCKER" \
|
||||
RAG_EMBEDDING_MODEL_DIR="/app/backend/data/cache/embedding/models" \
|
||||
SENTENCE_TRANSFORMERS_HOME="/app/backend/data/cache/embedding/models"
|
||||
#### Other models ##########################################################
|
||||
|
||||
WORKDIR /app/backend
|
||||
|
||||
RUN if [ "$USE_OLLAMA" = "true" ]; then \
|
||||
apt-get update && \
|
||||
# Install pandoc and netcat
|
||||
apt-get install -y --no-install-recommends pandoc netcat-openbsd && \
|
||||
# for RAG OCR
|
||||
apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
|
||||
# install helper tools
|
||||
apt-get install -y --no-install-recommends curl && \
|
||||
# install ollama
|
||||
curl -fsSL https://ollama.com/install.sh | sh && \
|
||||
# cleanup
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
else \
|
||||
apt-get update && \
|
||||
# Install pandoc and netcat
|
||||
apt-get install -y --no-install-recommends pandoc netcat-openbsd && \
|
||||
# for RAG OCR
|
||||
apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
|
||||
# cleanup
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
fi
|
||||
|
||||
# install python dependencies
|
||||
COPY ./backend/requirements.txt ./requirements.txt
|
||||
|
||||
RUN apt-get update && apt-get install ffmpeg libsm6 libxext6 -y
|
||||
RUN if [ "$USE_CUDA" = "true" ]; then \
|
||||
# If you use CUDA the whisper and embedding model will be downloaded on first use
|
||||
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir && \
|
||||
pip3 install -r requirements.txt --no-cache-dir && \
|
||||
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])" && \
|
||||
python -c "import os; from chromadb.utils import embedding_functions; sentence_transformer_ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name=os.environ['RAG_EMBEDDING_MODEL'], device='cpu')"; \
|
||||
else \
|
||||
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
|
||||
pip3 install -r requirements.txt --no-cache-dir && \
|
||||
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])" && \
|
||||
python -c "import os; from chromadb.utils import embedding_functions; sentence_transformer_ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name=os.environ['RAG_EMBEDDING_MODEL'], device='cpu')"; \
|
||||
fi
|
||||
|
||||
RUN pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir
|
||||
RUN pip3 install -r requirements.txt --no-cache-dir
|
||||
|
||||
# Install pandoc and netcat
|
||||
# RUN python -c "import pypandoc; pypandoc.download_pandoc()"
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y pandoc netcat-openbsd \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# preload embedding model
|
||||
RUN python -c "import os; from chromadb.utils import embedding_functions; sentence_transformer_ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name=os.environ['RAG_EMBEDDING_MODEL'], device=os.environ['RAG_EMBEDDING_MODEL_DEVICE_TYPE'])"
|
||||
# preload tts model
|
||||
RUN python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='auto', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"
|
||||
|
||||
# copy embedding weight from build
|
||||
RUN mkdir -p /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2
|
||||
COPY --from=build /app/onnx /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2/onnx
|
||||
# RUN mkdir -p /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2
|
||||
# COPY --from=build /app/onnx /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2/onnx
|
||||
|
||||
# copy built frontend files
|
||||
COPY --from=build /app/build /app/build
|
||||
@@ -82,4 +120,6 @@ COPY --from=build /app/package.json /app/package.json
|
||||
# copy backend files
|
||||
COPY ./backend .
|
||||
|
||||
CMD [ "bash", "start.sh"]
|
||||
EXPOSE 8080
|
||||
|
||||
CMD [ "bash", "start.sh"]
|
||||
@@ -94,24 +94,27 @@ Don't forget to explore our sibling project, [Open WebUI Community](https://open
|
||||
|
||||
### Quick Start with Docker 🐳
|
||||
|
||||
> [!IMPORTANT]
|
||||
> [!WARNING]
|
||||
> When using Docker to install Open WebUI, make sure to include the `-v open-webui:/app/backend/data` in your Docker command. This step is crucial as it ensures your database is properly mounted and prevents any loss of data.
|
||||
|
||||
- **If Ollama is on your computer**, use this command:
|
||||
> [!TIP]
|
||||
> If you wish to utilize Open WebUI with Ollama included or CUDA acceleration, we recommend utilizing our official images tagged with either `:cuda` or `:ollama`. To enable CUDA, you must install the [Nvidia CUDA container toolkit](https://docs.nvidia.com/dgx/nvidia-container-runtime-upgrade/) on your Linux/WSL system.
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
**If Ollama is on your computer**, use this command:
|
||||
|
||||
- **If Ollama is on a Different Server**, use this command:
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
- To connect to Ollama on another server, change the `OLLAMA_BASE_URL` to the server's URL:
|
||||
**If Ollama is on a Different Server**, use this command:
|
||||
|
||||
```bash
|
||||
docker run -d -p 3000:8080 -e OLLAMA_BASE_URL=https://example.com -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
|
||||
```
|
||||
To connect to Ollama on another server, change the `OLLAMA_BASE_URL` to the server's URL:
|
||||
|
||||
- After installation, you can access Open WebUI at [http://localhost:3000](http://localhost:3000). Enjoy! 😄
|
||||
```bash
|
||||
docker run -d -p 3000:8080 -e OLLAMA_BASE_URL=https://example.com -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
|
||||
```
|
||||
|
||||
After installation, you can access Open WebUI at [http://localhost:3000](http://localhost:3000). Enjoy! 😄
|
||||
|
||||
#### Open WebUI: Server Connection Error
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ from config import (
|
||||
UPLOAD_DIR,
|
||||
WHISPER_MODEL,
|
||||
WHISPER_MODEL_DIR,
|
||||
DEVICE_TYPE,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -42,6 +43,10 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# setting device type for whisper model
|
||||
whisper_device_type = DEVICE_TYPE if DEVICE_TYPE and DEVICE_TYPE == "cuda" else "cpu"
|
||||
log.info(f"whisper_device_type: {whisper_device_type}")
|
||||
|
||||
|
||||
@app.post("/transcribe")
|
||||
def transcribe(
|
||||
@@ -66,7 +71,7 @@ def transcribe(
|
||||
|
||||
model = WhisperModel(
|
||||
WHISPER_MODEL,
|
||||
device="auto",
|
||||
device=whisper_device_type,
|
||||
compute_type="int8",
|
||||
download_root=WHISPER_MODEL_DIR,
|
||||
)
|
||||
|
||||
@@ -215,7 +215,8 @@ async def get_ollama_versions(url_idx: Optional[int] = None):
|
||||
|
||||
if len(responses) > 0:
|
||||
lowest_version = min(
|
||||
responses, key=lambda x: tuple(map(int, x["version"].split(".")))
|
||||
responses,
|
||||
key=lambda x: tuple(map(int, x["version"].split("-")[0].split("."))),
|
||||
)
|
||||
|
||||
return {"version": lowest_version["version"]}
|
||||
|
||||
+48
-44
@@ -13,8 +13,8 @@ import os, shutil, logging, re
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from sentence_transformers import SentenceTransformer
|
||||
from chromadb.utils import embedding_functions
|
||||
from chromadb.utils.batch_utils import create_batches
|
||||
|
||||
from langchain_community.document_loaders import (
|
||||
WebBaseLoader,
|
||||
@@ -45,7 +45,7 @@ from apps.web.models.documents import (
|
||||
DocumentResponse,
|
||||
)
|
||||
|
||||
from apps.rag.utils import query_doc, query_collection
|
||||
from apps.rag.utils import query_doc, query_collection, get_embedding_model_path
|
||||
|
||||
from utils.misc import (
|
||||
calculate_sha256,
|
||||
@@ -59,7 +59,8 @@ from config import (
|
||||
UPLOAD_DIR,
|
||||
DOCS_DIR,
|
||||
RAG_EMBEDDING_MODEL,
|
||||
RAG_EMBEDDING_MODEL_DEVICE_TYPE,
|
||||
RAG_EMBEDDING_MODEL_AUTO_UPDATE,
|
||||
DEVICE_TYPE,
|
||||
CHROMA_CLIENT,
|
||||
CHUNK_SIZE,
|
||||
CHUNK_OVERLAP,
|
||||
@@ -71,28 +72,25 @@ from constants import ERROR_MESSAGES
|
||||
log = logging.getLogger(__name__)
|
||||
log.setLevel(SRC_LOG_LEVELS["RAG"])
|
||||
|
||||
#
|
||||
# if RAG_EMBEDDING_MODEL:
|
||||
# sentence_transformer_ef = SentenceTransformer(
|
||||
# model_name_or_path=RAG_EMBEDDING_MODEL,
|
||||
# cache_folder=RAG_EMBEDDING_MODEL_DIR,
|
||||
# device=RAG_EMBEDDING_MODEL_DEVICE_TYPE,
|
||||
# )
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.state.PDF_EXTRACT_IMAGES = False
|
||||
app.state.CHUNK_SIZE = CHUNK_SIZE
|
||||
app.state.CHUNK_OVERLAP = CHUNK_OVERLAP
|
||||
app.state.RAG_TEMPLATE = RAG_TEMPLATE
|
||||
|
||||
|
||||
app.state.RAG_EMBEDDING_MODEL = RAG_EMBEDDING_MODEL
|
||||
|
||||
|
||||
app.state.TOP_K = 4
|
||||
|
||||
app.state.sentence_transformer_ef = (
|
||||
embedding_functions.SentenceTransformerEmbeddingFunction(
|
||||
model_name=app.state.RAG_EMBEDDING_MODEL,
|
||||
device=RAG_EMBEDDING_MODEL_DEVICE_TYPE,
|
||||
model_name=get_embedding_model_path(
|
||||
app.state.RAG_EMBEDDING_MODEL, RAG_EMBEDDING_MODEL_AUTO_UPDATE
|
||||
),
|
||||
device=DEVICE_TYPE,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -143,18 +141,33 @@ class EmbeddingModelUpdateForm(BaseModel):
|
||||
async def update_embedding_model(
|
||||
form_data: EmbeddingModelUpdateForm, user=Depends(get_admin_user)
|
||||
):
|
||||
app.state.RAG_EMBEDDING_MODEL = form_data.embedding_model
|
||||
app.state.sentence_transformer_ef = (
|
||||
embedding_functions.SentenceTransformerEmbeddingFunction(
|
||||
model_name=app.state.RAG_EMBEDDING_MODEL,
|
||||
device=RAG_EMBEDDING_MODEL_DEVICE_TYPE,
|
||||
)
|
||||
|
||||
log.info(
|
||||
f"Updating embedding model: {app.state.RAG_EMBEDDING_MODEL} to {form_data.embedding_model}"
|
||||
)
|
||||
|
||||
return {
|
||||
"status": True,
|
||||
"embedding_model": app.state.RAG_EMBEDDING_MODEL,
|
||||
}
|
||||
try:
|
||||
sentence_transformer_ef = (
|
||||
embedding_functions.SentenceTransformerEmbeddingFunction(
|
||||
model_name=get_embedding_model_path(form_data.embedding_model, True),
|
||||
device=DEVICE_TYPE,
|
||||
)
|
||||
)
|
||||
|
||||
app.state.RAG_EMBEDDING_MODEL = form_data.embedding_model
|
||||
app.state.sentence_transformer_ef = sentence_transformer_ef
|
||||
|
||||
return {
|
||||
"status": True,
|
||||
"embedding_model": app.state.RAG_EMBEDDING_MODEL,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
log.exception(f"Problem updating embedding model: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=ERROR_MESSAGES.DEFAULT(e),
|
||||
)
|
||||
|
||||
|
||||
@app.get("/config")
|
||||
@@ -341,9 +354,14 @@ def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> b
|
||||
embedding_function=app.state.sentence_transformer_ef,
|
||||
)
|
||||
|
||||
collection.add(
|
||||
documents=texts, metadatas=metadatas, ids=[str(uuid.uuid1()) for _ in texts]
|
||||
)
|
||||
for batch in create_batches(
|
||||
api=CHROMA_CLIENT,
|
||||
ids=[str(uuid.uuid1()) for _ in texts],
|
||||
metadatas=metadatas,
|
||||
documents=texts,
|
||||
):
|
||||
collection.add(*batch)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@@ -448,25 +466,11 @@ def store_doc(
|
||||
|
||||
log.info(f"file.content_type: {file.content_type}")
|
||||
try:
|
||||
is_valid_filename = True
|
||||
unsanitized_filename = file.filename
|
||||
if re.search(r'[\\/:"\*\?<>|\n\t ]', unsanitized_filename) is not None:
|
||||
is_valid_filename = False
|
||||
filename = os.path.basename(unsanitized_filename)
|
||||
|
||||
unvalidated_file_path = f"{UPLOAD_DIR}/{unsanitized_filename}"
|
||||
dereferenced_file_path = str(Path(unvalidated_file_path).resolve(strict=False))
|
||||
if not dereferenced_file_path.startswith(UPLOAD_DIR):
|
||||
is_valid_filename = False
|
||||
file_path = f"{UPLOAD_DIR}/{filename}"
|
||||
|
||||
if is_valid_filename:
|
||||
file_path = dereferenced_file_path
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT(),
|
||||
)
|
||||
|
||||
filename = file.filename
|
||||
contents = file.file.read()
|
||||
with open(file_path, "wb") as f:
|
||||
f.write(contents)
|
||||
@@ -477,7 +481,7 @@ def store_doc(
|
||||
collection_name = calculate_sha256(f)[:63]
|
||||
f.close()
|
||||
|
||||
loader, known_type = get_loader(file.filename, file.content_type, file_path)
|
||||
loader, known_type = get_loader(filename, file.content_type, file_path)
|
||||
data = loader.load()
|
||||
|
||||
try:
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from typing import List
|
||||
from huggingface_hub import snapshot_download
|
||||
|
||||
from config import SRC_LOG_LEVELS, CHROMA_CLIENT
|
||||
|
||||
@@ -188,3 +190,43 @@ def rag_messages(docs, messages, template, k, embedding_function):
|
||||
messages[last_user_message_idx] = new_user_message
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
def get_embedding_model_path(
|
||||
embedding_model: str, update_embedding_model: bool = False
|
||||
):
|
||||
# Construct huggingface_hub kwargs with local_files_only to return the snapshot path
|
||||
cache_dir = os.getenv("SENTENCE_TRANSFORMERS_HOME")
|
||||
|
||||
local_files_only = not update_embedding_model
|
||||
|
||||
snapshot_kwargs = {
|
||||
"cache_dir": cache_dir,
|
||||
"local_files_only": local_files_only,
|
||||
}
|
||||
|
||||
log.debug(f"embedding_model: {embedding_model}")
|
||||
log.debug(f"snapshot_kwargs: {snapshot_kwargs}")
|
||||
|
||||
# Inspiration from upstream sentence_transformers
|
||||
if (
|
||||
os.path.exists(embedding_model)
|
||||
or ("\\" in embedding_model or embedding_model.count("/") > 1)
|
||||
and local_files_only
|
||||
):
|
||||
# If fully qualified path exists, return input, else set repo_id
|
||||
return embedding_model
|
||||
elif "/" not in embedding_model:
|
||||
# Set valid repo_id for model short-name
|
||||
embedding_model = "sentence-transformers" + "/" + embedding_model
|
||||
|
||||
snapshot_kwargs["repo_id"] = embedding_model
|
||||
|
||||
# Attempt to query the huggingface_hub library to determine the local path and/or to update
|
||||
try:
|
||||
embedding_model_repo_path = snapshot_download(**snapshot_kwargs)
|
||||
log.debug(f"embedding_model_repo_path: {embedding_model_repo_path}")
|
||||
return embedding_model_repo_path
|
||||
except Exception as e:
|
||||
log.exception(f"Cannot determine embedding model snapshot path: {e}")
|
||||
return embedding_model
|
||||
|
||||
@@ -86,6 +86,7 @@ class SignupForm(BaseModel):
|
||||
name: str
|
||||
email: str
|
||||
password: str
|
||||
profile_image_url: Optional[str] = "/user.png"
|
||||
|
||||
|
||||
class AuthsTable:
|
||||
@@ -94,7 +95,12 @@ class AuthsTable:
|
||||
self.db.create_tables([Auth])
|
||||
|
||||
def insert_new_auth(
|
||||
self, email: str, password: str, name: str, role: str = "pending"
|
||||
self,
|
||||
email: str,
|
||||
password: str,
|
||||
name: str,
|
||||
profile_image_url: str = "/user.png",
|
||||
role: str = "pending",
|
||||
) -> Optional[UserModel]:
|
||||
log.info("insert_new_auth")
|
||||
|
||||
@@ -105,7 +111,7 @@ class AuthsTable:
|
||||
)
|
||||
result = Auth.create(**auth.model_dump())
|
||||
|
||||
user = Users.insert_new_user(id, name, email, role)
|
||||
user = Users.insert_new_user(id, name, email, profile_image_url, role)
|
||||
|
||||
if result and user:
|
||||
return user
|
||||
|
||||
@@ -206,6 +206,18 @@ class ChatTable:
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_chat_by_share_id(self, id: str) -> Optional[ChatModel]:
|
||||
try:
|
||||
chat = Chat.get(Chat.share_id == id)
|
||||
|
||||
if chat:
|
||||
chat = Chat.get(Chat.id == id)
|
||||
return ChatModel(**model_to_dict(chat))
|
||||
else:
|
||||
return None
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_chat_by_id_and_user_id(self, id: str, user_id: str) -> Optional[ChatModel]:
|
||||
try:
|
||||
chat = Chat.get(Chat.id == id, Chat.user_id == user_id)
|
||||
|
||||
@@ -31,7 +31,7 @@ class UserModel(BaseModel):
|
||||
name: str
|
||||
email: str
|
||||
role: str = "pending"
|
||||
profile_image_url: str = "/user.png"
|
||||
profile_image_url: str
|
||||
timestamp: int # timestamp in epoch
|
||||
api_key: Optional[str] = None
|
||||
|
||||
@@ -59,7 +59,12 @@ class UsersTable:
|
||||
self.db.create_tables([User])
|
||||
|
||||
def insert_new_user(
|
||||
self, id: str, name: str, email: str, role: str = "pending"
|
||||
self,
|
||||
id: str,
|
||||
name: str,
|
||||
email: str,
|
||||
profile_image_url: str = "/user.png",
|
||||
role: str = "pending",
|
||||
) -> Optional[UserModel]:
|
||||
user = UserModel(
|
||||
**{
|
||||
@@ -67,7 +72,7 @@ class UsersTable:
|
||||
"name": name,
|
||||
"email": email,
|
||||
"role": role,
|
||||
"profile_image_url": "/user.png",
|
||||
"profile_image_url": profile_image_url,
|
||||
"timestamp": int(time.time()),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -163,7 +163,11 @@ async def signup(request: Request, form_data: SignupForm):
|
||||
)
|
||||
hashed = get_password_hash(form_data.password)
|
||||
user = Auths.insert_new_auth(
|
||||
form_data.email.lower(), hashed, form_data.name, role
|
||||
form_data.email.lower(),
|
||||
hashed,
|
||||
form_data.name,
|
||||
form_data.profile_image_url,
|
||||
role,
|
||||
)
|
||||
|
||||
if user:
|
||||
|
||||
@@ -251,7 +251,15 @@ async def delete_shared_chat_by_id(id: str, user=Depends(get_current_user)):
|
||||
|
||||
@router.get("/share/{share_id}", response_model=Optional[ChatResponse])
|
||||
async def get_shared_chat_by_id(share_id: str, user=Depends(get_current_user)):
|
||||
chat = Chats.get_chat_by_id(share_id)
|
||||
if user.role == "pending":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
|
||||
)
|
||||
|
||||
if user.role == "user":
|
||||
chat = Chats.get_chat_by_share_id(share_id)
|
||||
elif user.role == "admin":
|
||||
chat = Chats.get_chat_by_id(share_id)
|
||||
|
||||
if chat:
|
||||
return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
from fastapi import APIRouter, UploadFile, File, BackgroundTasks
|
||||
from fastapi import APIRouter, UploadFile, File, Response
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from starlette.responses import StreamingResponse, FileResponse
|
||||
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
from fpdf import FPDF
|
||||
import markdown
|
||||
import requests
|
||||
import os
|
||||
import aiohttp
|
||||
import json
|
||||
|
||||
|
||||
from utils.utils import get_admin_user
|
||||
@@ -18,7 +13,7 @@ from utils.misc import calculate_sha256, get_gravatar_url
|
||||
|
||||
from config import OLLAMA_BASE_URLS, DATA_DIR, UPLOAD_DIR
|
||||
from constants import ERROR_MESSAGES
|
||||
|
||||
from typing import List
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -41,6 +36,59 @@ async def get_html_from_markdown(
|
||||
return {"html": markdown.markdown(form_data.md)}
|
||||
|
||||
|
||||
class ChatForm(BaseModel):
|
||||
title: str
|
||||
messages: List[dict]
|
||||
|
||||
|
||||
@router.post("/pdf")
|
||||
async def download_chat_as_pdf(
|
||||
form_data: ChatForm,
|
||||
):
|
||||
pdf = FPDF()
|
||||
pdf.add_page()
|
||||
|
||||
STATIC_DIR = "./static"
|
||||
FONTS_DIR = f"{STATIC_DIR}/fonts"
|
||||
|
||||
pdf.add_font("NotoSans", "", f"{FONTS_DIR}/NotoSans-Regular.ttf")
|
||||
pdf.add_font("NotoSans", "b", f"{FONTS_DIR}/NotoSans-Bold.ttf")
|
||||
pdf.add_font("NotoSans", "i", f"{FONTS_DIR}/NotoSans-Italic.ttf")
|
||||
pdf.add_font("NotoSansKR", "", f"{FONTS_DIR}/NotoSansKR-Regular.ttf")
|
||||
pdf.add_font("NotoSansJP", "", f"{FONTS_DIR}/NotoSansJP-Regular.ttf")
|
||||
|
||||
pdf.set_font("NotoSans", size=12)
|
||||
pdf.set_fallback_fonts(["NotoSansKR", "NotoSansJP"])
|
||||
|
||||
pdf.set_auto_page_break(auto=True, margin=15)
|
||||
|
||||
# Adjust the effective page width for multi_cell
|
||||
effective_page_width = (
|
||||
pdf.w - 2 * pdf.l_margin - 10
|
||||
) # Subtracted an additional 10 for extra padding
|
||||
|
||||
# Add chat messages
|
||||
for message in form_data.messages:
|
||||
role = message["role"]
|
||||
content = message["content"]
|
||||
pdf.set_font("NotoSans", "B", size=14) # Bold for the role
|
||||
pdf.multi_cell(effective_page_width, 10, f"{role.upper()}", 0, "L")
|
||||
pdf.ln(1) # Extra space between messages
|
||||
|
||||
pdf.set_font("NotoSans", size=10) # Regular for content
|
||||
pdf.multi_cell(effective_page_width, 6, content, 0, "L")
|
||||
pdf.ln(1.5) # Extra space between messages
|
||||
|
||||
# Save the pdf with name .pdf
|
||||
pdf_bytes = pdf.output()
|
||||
|
||||
return Response(
|
||||
content=bytes(pdf_bytes),
|
||||
media_type="application/pdf",
|
||||
headers={"Content-Disposition": f"attachment;filename=chat.pdf"},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/db/download")
|
||||
async def download_db(user=Depends(get_admin_user)):
|
||||
|
||||
|
||||
+44
-21
@@ -28,8 +28,6 @@ except ImportError:
|
||||
WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
|
||||
WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
|
||||
|
||||
shutil.copyfile("../build/favicon.png", "./static/favicon.png")
|
||||
|
||||
####################################
|
||||
# ENV (dev,test,prod)
|
||||
####################################
|
||||
@@ -103,6 +101,26 @@ for version in soup.find_all("h2"):
|
||||
|
||||
CHANGELOG = changelog_json
|
||||
|
||||
####################################
|
||||
# DATA/FRONTEND BUILD DIR
|
||||
####################################
|
||||
|
||||
DATA_DIR = str(Path(os.getenv("DATA_DIR", "./data")).resolve())
|
||||
FRONTEND_BUILD_DIR = str(Path(os.getenv("FRONTEND_BUILD_DIR", "../build")))
|
||||
|
||||
try:
|
||||
with open(f"{DATA_DIR}/config.json", "r") as f:
|
||||
CONFIG_DATA = json.load(f)
|
||||
except:
|
||||
CONFIG_DATA = {}
|
||||
|
||||
####################################
|
||||
# Static DIR
|
||||
####################################
|
||||
|
||||
STATIC_DIR = str(Path(os.getenv("STATIC_DIR", "./static")).resolve())
|
||||
|
||||
shutil.copyfile(f"{FRONTEND_BUILD_DIR}/favicon.png", f"{STATIC_DIR}/favicon.png")
|
||||
|
||||
####################################
|
||||
# LOGGING
|
||||
@@ -165,7 +183,7 @@ if CUSTOM_NAME:
|
||||
|
||||
r = requests.get(url, stream=True)
|
||||
if r.status_code == 200:
|
||||
with open("./static/favicon.png", "wb") as f:
|
||||
with open(f"{STATIC_DIR}/favicon.png", "wb") as f:
|
||||
r.raw.decode_content = True
|
||||
shutil.copyfileobj(r.raw, f)
|
||||
|
||||
@@ -177,18 +195,6 @@ else:
|
||||
if WEBUI_NAME != "Open WebUI":
|
||||
WEBUI_NAME += " (Open WebUI)"
|
||||
|
||||
####################################
|
||||
# DATA/FRONTEND BUILD DIR
|
||||
####################################
|
||||
|
||||
DATA_DIR = str(Path(os.getenv("DATA_DIR", "./data")).resolve())
|
||||
FRONTEND_BUILD_DIR = str(Path(os.getenv("FRONTEND_BUILD_DIR", "../build")))
|
||||
|
||||
try:
|
||||
with open(f"{DATA_DIR}/config.json", "r") as f:
|
||||
CONFIG_DATA = json.load(f)
|
||||
except:
|
||||
CONFIG_DATA = {}
|
||||
|
||||
####################################
|
||||
# File Upload DIR
|
||||
@@ -257,6 +263,7 @@ OLLAMA_API_BASE_URL = os.environ.get(
|
||||
|
||||
OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "")
|
||||
K8S_FLAG = os.environ.get("K8S_FLAG", "")
|
||||
USE_OLLAMA_DOCKER = os.environ.get("USE_OLLAMA_DOCKER", "false")
|
||||
|
||||
if OLLAMA_BASE_URL == "" and OLLAMA_API_BASE_URL != "":
|
||||
OLLAMA_BASE_URL = (
|
||||
@@ -266,9 +273,13 @@ if OLLAMA_BASE_URL == "" and OLLAMA_API_BASE_URL != "":
|
||||
)
|
||||
|
||||
if ENV == "prod":
|
||||
if OLLAMA_BASE_URL == "/ollama":
|
||||
OLLAMA_BASE_URL = "http://host.docker.internal:11434"
|
||||
|
||||
if OLLAMA_BASE_URL == "/ollama" and not K8S_FLAG:
|
||||
if USE_OLLAMA_DOCKER.lower() == "true":
|
||||
# if you use all-in-one docker container (Open WebUI + Ollama)
|
||||
# with the docker build arg USE_OLLAMA=true (--build-arg="USE_OLLAMA=true") this only works with http://localhost:11434
|
||||
OLLAMA_BASE_URL = "http://localhost:11434"
|
||||
else:
|
||||
OLLAMA_BASE_URL = "http://host.docker.internal:11434"
|
||||
elif K8S_FLAG:
|
||||
OLLAMA_BASE_URL = "http://ollama-service.open-webui.svc.cluster.local:11434"
|
||||
|
||||
@@ -391,10 +402,22 @@ if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
|
||||
CHROMA_DATA_PATH = f"{DATA_DIR}/vector_db"
|
||||
# this uses the model defined in the Dockerfile ENV variable. If you dont use docker or docker based deployments such as k8s, the default embedding model will be used (all-MiniLM-L6-v2)
|
||||
RAG_EMBEDDING_MODEL = os.environ.get("RAG_EMBEDDING_MODEL", "all-MiniLM-L6-v2")
|
||||
# device type ebbeding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance
|
||||
RAG_EMBEDDING_MODEL_DEVICE_TYPE = os.environ.get(
|
||||
"RAG_EMBEDDING_MODEL_DEVICE_TYPE", "cpu"
|
||||
log.info(f"Embedding model set: {RAG_EMBEDDING_MODEL}"),
|
||||
|
||||
RAG_EMBEDDING_MODEL_AUTO_UPDATE = (
|
||||
os.environ.get("RAG_EMBEDDING_MODEL_AUTO_UPDATE", "").lower() == "true"
|
||||
)
|
||||
|
||||
|
||||
# device type ebbeding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance
|
||||
USE_CUDA = os.environ.get("USE_CUDA_DOCKER", "false")
|
||||
|
||||
if USE_CUDA.lower() == "true":
|
||||
DEVICE_TYPE = "cuda"
|
||||
else:
|
||||
DEVICE_TYPE = "cpu"
|
||||
|
||||
|
||||
CHROMA_CLIENT = chromadb.PersistentClient(
|
||||
path=CHROMA_DATA_PATH,
|
||||
settings=Settings(allow_reset=True, anonymized_telemetry=False),
|
||||
|
||||
+15
-10
@@ -5,6 +5,7 @@ import time
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import aiohttp
|
||||
import requests
|
||||
|
||||
from fastapi import FastAPI, Request, Depends, status
|
||||
@@ -18,6 +19,7 @@ from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
from apps.ollama.main import app as ollama_app
|
||||
from apps.openai.main import app as openai_app
|
||||
|
||||
from apps.litellm.main import app as litellm_app, startup as litellm_app_startup
|
||||
from apps.audio.main import app as audio_app
|
||||
from apps.images.main import app as images_app
|
||||
@@ -38,6 +40,8 @@ from config import (
|
||||
VERSION,
|
||||
CHANGELOG,
|
||||
FRONTEND_BUILD_DIR,
|
||||
CACHE_DIR,
|
||||
STATIC_DIR,
|
||||
MODEL_FILTER_ENABLED,
|
||||
MODEL_FILTER_LIST,
|
||||
GLOBAL_LOG_LEVEL,
|
||||
@@ -269,14 +273,16 @@ async def get_app_changelog():
|
||||
@app.get("/api/version/updates")
|
||||
async def get_app_latest_release_version():
|
||||
try:
|
||||
response = requests.get(
|
||||
f"https://api.github.com/repos/open-webui/open-webui/releases/latest"
|
||||
)
|
||||
response.raise_for_status()
|
||||
latest_version = response.json()["tag_name"]
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
"https://api.github.com/repos/open-webui/open-webui/releases/latest"
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
data = await response.json()
|
||||
latest_version = data["tag_name"]
|
||||
|
||||
return {"current": VERSION, "latest": latest_version[1:]}
|
||||
except Exception as e:
|
||||
return {"current": VERSION, "latest": latest_version[1:]}
|
||||
except aiohttp.ClientError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=ERROR_MESSAGES.RATE_LIMIT_EXCEEDED,
|
||||
@@ -297,9 +303,8 @@ async def get_manifest_json():
|
||||
}
|
||||
|
||||
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
app.mount("/cache", StaticFiles(directory="data/cache"), name="cache")
|
||||
|
||||
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
|
||||
app.mount("/cache", StaticFiles(directory=CACHE_DIR), name="cache")
|
||||
|
||||
app.mount(
|
||||
"/",
|
||||
|
||||
@@ -42,6 +42,8 @@ xlrd
|
||||
opencv-python-headless
|
||||
rapidocr-onnxruntime
|
||||
|
||||
fpdf2
|
||||
|
||||
faster-whisper
|
||||
|
||||
PyJWT
|
||||
|
||||
+16
-6
@@ -7,16 +7,26 @@ KEY_FILE=.webui_secret_key
|
||||
|
||||
PORT="${PORT:-8080}"
|
||||
if test "$WEBUI_SECRET_KEY $WEBUI_JWT_SECRET_KEY" = " "; then
|
||||
echo No WEBUI_SECRET_KEY provided
|
||||
echo "No WEBUI_SECRET_KEY provided"
|
||||
|
||||
if ! [ -e "$KEY_FILE" ]; then
|
||||
echo Generating WEBUI_SECRET_KEY
|
||||
echo "Generating WEBUI_SECRET_KEY"
|
||||
# Generate a random value to use as a WEBUI_SECRET_KEY in case the user didn't provide one.
|
||||
echo $(head -c 12 /dev/random | base64) > $KEY_FILE
|
||||
echo $(head -c 12 /dev/random | base64) > "$KEY_FILE"
|
||||
fi
|
||||
|
||||
echo Loading WEBUI_SECRET_KEY from $KEY_FILE
|
||||
WEBUI_SECRET_KEY=`cat $KEY_FILE`
|
||||
echo "Loading WEBUI_SECRET_KEY from $KEY_FILE"
|
||||
WEBUI_SECRET_KEY=$(cat "$KEY_FILE")
|
||||
fi
|
||||
|
||||
WEBUI_SECRET_KEY="$WEBUI_SECRET_KEY" exec uvicorn main:app --host 0.0.0.0 --port "$PORT" --forwarded-allow-ips '*'
|
||||
if [ "$USE_OLLAMA_DOCKER" = "true" ]; then
|
||||
echo "USE_OLLAMA is set to true, starting ollama serve."
|
||||
ollama serve &
|
||||
fi
|
||||
|
||||
if [ "$USE_CUDA_DOCKER" = "true" ]; then
|
||||
echo "CUDA is enabled, appending LD_LIBRARY_PATH to include torch/cudnn & cublas libraries."
|
||||
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/lib/python3.11/site-packages/torch/lib:/usr/local/lib/python3.11/site-packages/nvidia/cudnn/lib"
|
||||
fi
|
||||
|
||||
WEBUI_SECRET_KEY="$WEBUI_SECRET_KEY" exec uvicorn main:app --host 0.0.0.0 --port "$PORT" --forwarded-allow-ips '*'
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
services:
|
||||
ollama:
|
||||
devices:
|
||||
- /dev/kfd:/dev/kfd
|
||||
- /dev/dri:/dev/dri
|
||||
image: ollama/ollama:${OLLAMA_DOCKER_TAG-rocm}
|
||||
environment:
|
||||
- 'HSA_OVERRIDE_GFX_VERSION=${HSA_OVERRIDE_GFX_VERSION-11.0.0}'
|
||||
+2
-2
@@ -8,7 +8,7 @@ services:
|
||||
pull_policy: always
|
||||
tty: true
|
||||
restart: unless-stopped
|
||||
image: ollama/ollama:latest
|
||||
image: ollama/ollama:${OLLAMA_DOCKER_TAG-latest}
|
||||
|
||||
open-webui:
|
||||
build:
|
||||
@@ -16,7 +16,7 @@ services:
|
||||
args:
|
||||
OLLAMA_BASE_URL: '/ollama'
|
||||
dockerfile: Dockerfile
|
||||
image: ghcr.io/open-webui/open-webui:main
|
||||
image: ghcr.io/open-webui/open-webui:${WEBUI_DOCKER_TAG-main}
|
||||
container_name: open-webui
|
||||
volumes:
|
||||
- open-webui:/app/backend/data
|
||||
|
||||
@@ -7,7 +7,11 @@ ollama
|
||||
{{- end -}}
|
||||
|
||||
{{- define "ollama.url" -}}
|
||||
{{- printf "http://%s.%s.svc.cluster.local:%d/api" (include "ollama.name" .) (.Release.Namespace) (.Values.ollama.service.port | int) }}
|
||||
{{- if .Values.ollama.externalHost }}
|
||||
{{- printf .Values.ollama.externalHost }}
|
||||
{{- else }}
|
||||
{{- printf "http://%s.%s.svc.cluster.local:%d/" (include "ollama.name" .) (.Release.Namespace) (.Values.ollama.service.port | int) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "chart.name" -}}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{{- if not .Values.ollama.externalHost }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
@@ -19,3 +20,4 @@ spec:
|
||||
port: {{ .port }}
|
||||
targetPort: http
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{{- if not .Values.ollama.externalHost }}
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
@@ -94,3 +95,4 @@ spec:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -17,7 +17,9 @@ spec:
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.webui.persistence.size }}
|
||||
{{- if .Values.webui.persistence.storageClass }}
|
||||
storageClassName: {{ .Values.webui.persistence.storageClass }}
|
||||
{{- end }}
|
||||
{{- with .Values.webui.persistence.selector }}
|
||||
selector:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
nameOverride: ""
|
||||
|
||||
ollama:
|
||||
externalHost: ""
|
||||
annotations: {}
|
||||
podAnnotations: {}
|
||||
replicaCount: 1
|
||||
|
||||
Generated
+5
-5
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "open-webui",
|
||||
"version": "0.1.117",
|
||||
"version": "0.1.118",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "open-webui",
|
||||
"version": "0.1.117",
|
||||
"version": "0.1.118",
|
||||
"dependencies": {
|
||||
"@sveltejs/adapter-node": "^1.3.1",
|
||||
"async": "^3.2.5",
|
||||
@@ -5688,9 +5688,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "5.28.3",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz",
|
||||
"integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==",
|
||||
"version": "5.28.4",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
|
||||
"integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "open-webui",
|
||||
"version": "0.1.117",
|
||||
"version": "0.1.118",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev --host",
|
||||
|
||||
@@ -58,7 +58,12 @@ export const userSignIn = async (email: string, password: string) => {
|
||||
return res;
|
||||
};
|
||||
|
||||
export const userSignUp = async (name: string, email: string, password: string) => {
|
||||
export const userSignUp = async (
|
||||
name: string,
|
||||
email: string,
|
||||
password: string,
|
||||
profile_image_url: string
|
||||
) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signup`, {
|
||||
@@ -69,7 +74,8 @@ export const userSignUp = async (name: string, email: string, password: string)
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
email: email,
|
||||
password: password
|
||||
password: password,
|
||||
profile_image_url: profile_image_url
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
|
||||
@@ -345,3 +345,64 @@ export const resetVectorDB = async (token: string) => {
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getEmbeddingModel = async (token: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${RAG_API_BASE_URL}/embedding/model`, {
|
||||
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;
|
||||
};
|
||||
|
||||
type EmbeddingModelUpdateForm = {
|
||||
embedding_model: string;
|
||||
};
|
||||
|
||||
export const updateEmbeddingModel = async (token: string, payload: EmbeddingModelUpdateForm) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${RAG_API_BASE_URL}/embedding/model/update`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...payload
|
||||
})
|
||||
})
|
||||
.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;
|
||||
};
|
||||
|
||||
@@ -22,6 +22,32 @@ export const getGravatarUrl = async (email: string) => {
|
||||
return res;
|
||||
};
|
||||
|
||||
export const downloadChatAsPDF = async (chat: object) => {
|
||||
let error = null;
|
||||
|
||||
const blob = await fetch(`${WEBUI_API_BASE_URL}/utils/pdf`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: chat.title,
|
||||
messages: chat.messages
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.blob();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
error = err;
|
||||
return null;
|
||||
});
|
||||
|
||||
return blob;
|
||||
};
|
||||
|
||||
export const getHTMLFromMarkdown = async (md: string) => {
|
||||
let error = null;
|
||||
|
||||
|
||||
@@ -295,6 +295,13 @@
|
||||
|
||||
const dropZone = document.querySelector('body');
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
console.log('Escape');
|
||||
dragged = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
dragged = true;
|
||||
@@ -350,11 +357,15 @@
|
||||
dragged = false;
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
dropZone?.addEventListener('dragover', onDragOver);
|
||||
dropZone?.addEventListener('drop', onDrop);
|
||||
dropZone?.addEventListener('dragleave', onDragLeave);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
|
||||
dropZone?.removeEventListener('dragover', onDragOver);
|
||||
dropZone?.removeEventListener('drop', onDrop);
|
||||
dropZone?.removeEventListener('dragleave', onDragLeave);
|
||||
|
||||
@@ -107,12 +107,8 @@
|
||||
await sendPrompt(userPrompt, userMessageId, chatId);
|
||||
};
|
||||
|
||||
const confirmEditResponseMessage = async (messageId, content) => {
|
||||
history.messages[messageId].originalContent = history.messages[messageId].content;
|
||||
history.messages[messageId].content = content;
|
||||
|
||||
const updateChatMessages = async () => {
|
||||
await tick();
|
||||
|
||||
await updateChatById(localStorage.token, chatId, {
|
||||
messages: messages,
|
||||
history: history
|
||||
@@ -121,15 +117,20 @@
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
};
|
||||
|
||||
const rateMessage = async (messageId, rating) => {
|
||||
history.messages[messageId].rating = rating;
|
||||
await tick();
|
||||
await updateChatById(localStorage.token, chatId, {
|
||||
messages: messages,
|
||||
history: history
|
||||
});
|
||||
const confirmEditResponseMessage = async (messageId, content) => {
|
||||
history.messages[messageId].originalContent = history.messages[messageId].content;
|
||||
history.messages[messageId].content = content;
|
||||
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
await updateChatMessages();
|
||||
};
|
||||
|
||||
const rateMessage = async (messageId, rating) => {
|
||||
history.messages[messageId].annotation = {
|
||||
...history.messages[messageId].annotation,
|
||||
rating: rating
|
||||
};
|
||||
|
||||
await updateChatMessages();
|
||||
};
|
||||
|
||||
const showPreviousMessage = async (message) => {
|
||||
@@ -338,6 +339,7 @@
|
||||
siblings={history.messages[message.parentId]?.childrenIds ?? []}
|
||||
isLastMessage={messageIdx + 1 === messages.length}
|
||||
{readOnly}
|
||||
{updateChatMessages}
|
||||
{confirmEditResponseMessage}
|
||||
{showPreviousMessage}
|
||||
{showNextMessage}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let show = false;
|
||||
export let message;
|
||||
|
||||
const LIKE_REASONS = [
|
||||
`Accurate information`,
|
||||
`Followed instructions perfectly`,
|
||||
`Showcased creativity`,
|
||||
`Positive attitude`,
|
||||
`Attention to detail`,
|
||||
`Thorough explanation`,
|
||||
`Other`
|
||||
];
|
||||
|
||||
const DISLIKE_REASONS = [
|
||||
`Don't like the style`,
|
||||
`Not factually correct`,
|
||||
`Didn't fully follow instructions`,
|
||||
`Refused when it shouldn't have`,
|
||||
`Being Lazy`,
|
||||
`Other`
|
||||
];
|
||||
|
||||
let reasons = [];
|
||||
let selectedReason = null;
|
||||
let comment = '';
|
||||
|
||||
$: if (message.annotation.rating === 1) {
|
||||
reasons = LIKE_REASONS;
|
||||
} else if (message.annotation.rating === -1) {
|
||||
reasons = DISLIKE_REASONS;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
selectedReason = message.annotation.reason;
|
||||
comment = message.annotation.comment;
|
||||
});
|
||||
|
||||
const submitHandler = () => {
|
||||
console.log('submitHandler');
|
||||
|
||||
message.annotation.reason = selectedReason;
|
||||
message.annotation.comment = comment;
|
||||
|
||||
dispatch('submit');
|
||||
|
||||
toast.success('Thanks for your feedback!');
|
||||
show = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class=" my-2.5 rounded-xl px-4 py-3 border dark:border-gray-850">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class=" text-sm">Tell us more:</div>
|
||||
|
||||
<button
|
||||
on:click={() => {
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-4"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if reasons.length > 0}
|
||||
<div class="flex flex-wrap gap-2 text-sm mt-2.5">
|
||||
{#each reasons as reason}
|
||||
<button
|
||||
class="px-3.5 py-1 border dark:border-gray-850 dark:hover:bg-gray-850 {selectedReason ===
|
||||
reason
|
||||
? 'dark:bg-gray-800'
|
||||
: ''} transition rounded-lg"
|
||||
on:click={() => {
|
||||
selectedReason = reason;
|
||||
}}
|
||||
>
|
||||
{reason}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-2">
|
||||
<textarea
|
||||
bind:value={comment}
|
||||
class="w-full text-sm px-1 py-2 bg-transparent outline-none resize-none rounded-xl"
|
||||
placeholder="Feel free to add specific details"
|
||||
rows="2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 flex justify-end">
|
||||
<button
|
||||
class=" bg-emerald-700 text-white text-sm font-medium rounded-lg px-3.5 py-1.5"
|
||||
on:click={() => {
|
||||
submitHandler();
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,6 +30,7 @@
|
||||
import Image from '$lib/components/common/Image.svelte';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import RateComment from './RateComment.svelte';
|
||||
|
||||
export let modelfiles = [];
|
||||
export let message;
|
||||
@@ -39,6 +40,7 @@
|
||||
|
||||
export let readOnly = false;
|
||||
|
||||
export let updateChatMessages: Function;
|
||||
export let confirmEditResponseMessage: Function;
|
||||
export let showPreviousMessage: Function;
|
||||
export let showNextMessage: Function;
|
||||
@@ -60,6 +62,8 @@
|
||||
let loadingSpeech = false;
|
||||
let generatingImage = false;
|
||||
|
||||
let showRateComment = false;
|
||||
|
||||
$: tokens = marked.lexer(sanitizeResponseContent(message.content));
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
@@ -536,11 +540,13 @@
|
||||
<button
|
||||
class="{isLastMessage
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1 rounded {message.rating === 1
|
||||
: 'invisible group-hover:visible'} p-1 rounded {message?.annotation
|
||||
?.rating === 1
|
||||
? 'bg-gray-100 dark:bg-gray-800'
|
||||
: ''} dark:hover:text-white hover:text-black transition"
|
||||
on:click={() => {
|
||||
rateMessage(message.id, 1);
|
||||
showRateComment = true;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
@@ -563,11 +569,13 @@
|
||||
<button
|
||||
class="{isLastMessage
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1 rounded {message.rating === -1
|
||||
: 'invisible group-hover:visible'} p-1 rounded {message?.annotation
|
||||
?.rating === -1
|
||||
? 'bg-gray-100 dark:bg-gray-800'
|
||||
: ''} dark:hover:text-white hover:text-black transition"
|
||||
on:click={() => {
|
||||
rateMessage(message.id, -1);
|
||||
showRateComment = true;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
@@ -824,6 +832,16 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showRateComment}
|
||||
<RateComment
|
||||
bind:show={showRateComment}
|
||||
bind:message
|
||||
on:submit={() => {
|
||||
updateChatMessages();
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import UpdatePassword from './Account/UpdatePassword.svelte';
|
||||
import { getGravatarUrl } from '$lib/apis/utils';
|
||||
import { generateInitialsImage, canvasPixelTest } from '$lib/utils';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
import Plus from '$lib/components/icons/Plus.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
@@ -18,6 +19,8 @@
|
||||
let profileImageUrl = '';
|
||||
let name = '';
|
||||
|
||||
let showAPIKeys = false;
|
||||
|
||||
let showJWTToken = false;
|
||||
let JWTTokenCopied = false;
|
||||
|
||||
@@ -28,6 +31,12 @@
|
||||
let profileImageInputElement: HTMLInputElement;
|
||||
|
||||
const submitHandler = async () => {
|
||||
if (name !== $user.name) {
|
||||
if (profileImageUrl === generateInitialsImage($user.name) || profileImageUrl === '') {
|
||||
profileImageUrl = generateInitialsImage(name);
|
||||
}
|
||||
}
|
||||
|
||||
const updatedUser = await updateUserProfile(localStorage.token, name, profileImageUrl).catch(
|
||||
(error) => {
|
||||
toast.error(error);
|
||||
@@ -125,59 +134,93 @@
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Profile')}</div>
|
||||
<div class="space-y-1">
|
||||
<!-- <div class=" text-sm font-medium">{$i18n.t('Account')}</div> -->
|
||||
|
||||
<div class="flex space-x-5">
|
||||
<div class="flex flex-col">
|
||||
<div class="self-center">
|
||||
<button
|
||||
class="relative rounded-full dark:bg-gray-700"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
profileImageInputElement.click();
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={profileImageUrl !== '' ? profileImageUrl : '/user.png'}
|
||||
alt="profile"
|
||||
class=" rounded-full w-16 h-16 object-cover"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="absolute flex justify-center rounded-full bottom-0 left-0 right-0 top-0 h-full w-full overflow-hidden bg-gray-700 bg-fixed opacity-0 transition duration-300 ease-in-out hover:opacity-50"
|
||||
<div class="flex space-x-5">
|
||||
<div class="flex flex-col">
|
||||
<div class="self-center mt-2">
|
||||
<button
|
||||
class="relative rounded-full dark:bg-gray-700"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
profileImageInputElement.click();
|
||||
}}
|
||||
>
|
||||
<div class="my-auto text-gray-100">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class=" text-xs text-gray-600"
|
||||
on:click={async () => {
|
||||
const url = await getGravatarUrl($user.email);
|
||||
<img
|
||||
src={profileImageUrl !== '' ? profileImageUrl : generateInitialsImage(name)}
|
||||
alt="profile"
|
||||
class=" rounded-full size-16 object-cover"
|
||||
/>
|
||||
|
||||
profileImageUrl = url;
|
||||
}}>{$i18n.t('Use Gravatar')}</button
|
||||
>
|
||||
<div
|
||||
class="absolute flex justify-center rounded-full bottom-0 left-0 right-0 top-0 h-full w-full overflow-hidden bg-gray-700 bg-fixed opacity-0 transition duration-300 ease-in-out hover:opacity-50"
|
||||
>
|
||||
<div class="my-auto text-gray-100">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex flex-col self-center gap-0.5">
|
||||
<div class=" mb-0.5 text-sm font-medium">{$i18n.t('Profile Image')}</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-full px-4 py-0.5 bg-gray-100 dark:bg-gray-850"
|
||||
on:click={async () => {
|
||||
if (canvasPixelTest()) {
|
||||
profileImageUrl = generateInitialsImage(name);
|
||||
} else {
|
||||
toast.info(
|
||||
$i18n.t(
|
||||
'Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.'
|
||||
),
|
||||
{
|
||||
duration: 1000 * 10
|
||||
}
|
||||
);
|
||||
}
|
||||
}}>{$i18n.t('Use Initials')}</button
|
||||
>
|
||||
|
||||
<button
|
||||
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-full px-4 py-0.5 bg-gray-100 dark:bg-gray-850"
|
||||
on:click={async () => {
|
||||
const url = await getGravatarUrl($user.email);
|
||||
|
||||
profileImageUrl = url;
|
||||
}}>{$i18n.t('Use Gravatar')}</button
|
||||
>
|
||||
|
||||
<button
|
||||
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-lg px-2 py-1"
|
||||
on:click={async () => {
|
||||
profileImageUrl = '/user.png';
|
||||
}}>{$i18n.t('Remove')}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<div class="pt-0.5">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name')}</div>
|
||||
<div class=" mb-1 text-xs font-medium">{$i18n.t('Name')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
type="text"
|
||||
bind:value={name}
|
||||
required
|
||||
@@ -187,133 +230,46 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-700 my-4" />
|
||||
<UpdatePassword />
|
||||
<div class="py-0.5">
|
||||
<UpdatePassword />
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-700 my-4" />
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="justify-between w-full">
|
||||
<div class="flex justify-between w-full">
|
||||
<div class="self-center text-xs font-medium">{$i18n.t('JWT Token')}</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<div class=" font-medium">{$i18n.t('API keys')}</div>
|
||||
<button
|
||||
class=" text-xs font-medium text-gray-500"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showAPIKeys = !showAPIKeys;
|
||||
}}>{showAPIKeys ? $i18n.t('Hide') : $i18n.t('Show')}</button
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex mt-2">
|
||||
<div class="flex w-full">
|
||||
<input
|
||||
class="w-full rounded-l-lg py-1.5 pl-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||
type={showJWTToken ? 'text' : 'password'}
|
||||
value={localStorage.token}
|
||||
disabled
|
||||
/>
|
||||
|
||||
<button
|
||||
class="px-2 transition rounded-r-lg bg-white dark:bg-gray-800"
|
||||
on:click={() => {
|
||||
showJWTToken = !showJWTToken;
|
||||
}}
|
||||
>
|
||||
{#if showJWTToken}
|
||||
<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="M3.28 2.22a.75.75 0 0 0-1.06 1.06l10.5 10.5a.75.75 0 1 0 1.06-1.06l-1.322-1.323a7.012 7.012 0 0 0 2.16-3.11.87.87 0 0 0 0-.567A7.003 7.003 0 0 0 4.82 3.76l-1.54-1.54Zm3.196 3.195 1.135 1.136A1.502 1.502 0 0 1 9.45 8.389l1.136 1.135a3 3 0 0 0-4.109-4.109Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="m7.812 10.994 1.816 1.816A7.003 7.003 0 0 1 1.38 8.28a.87.87 0 0 1 0-.566 6.985 6.985 0 0 1 1.113-2.039l2.513 2.513a3 3 0 0 0 2.806 2.806Z"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M1.38 8.28a.87.87 0 0 1 0-.566 7.003 7.003 0 0 1 13.238.006.87.87 0 0 1 0 .566A7.003 7.003 0 0 1 1.379 8.28ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
{#if showAPIKeys}
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="justify-between w-full">
|
||||
<div class="flex justify-between w-full">
|
||||
<div class="self-center text-xs font-medium">{$i18n.t('JWT Token')}</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-800 transition rounded-lg"
|
||||
on:click={() => {
|
||||
copyToClipboard(localStorage.token);
|
||||
JWTTokenCopied = true;
|
||||
setTimeout(() => {
|
||||
JWTTokenCopied = false;
|
||||
}, 2000);
|
||||
}}
|
||||
>
|
||||
{#if JWTTokenCopied}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<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.986 3H12a2 2 0 0 1 2 2v6a2 2 0 0 1-1.5 1.937V7A2.5 2.5 0 0 0 10 4.5H4.063A2 2 0 0 1 6 3h.014A2.25 2.25 0 0 1 8.25 1h1.5a2.25 2.25 0 0 1 2.236 2ZM10.5 4v-.75a.75.75 0 0 0-.75-.75h-1.5a.75.75 0 0 0-.75.75V4h3Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M3 6a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1H3Zm1.75 2.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5ZM4 11.75a.75.75 0 0 1 .75-.75h3.5a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1-.75-.75Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="justify-between w-full">
|
||||
<div class="flex justify-between w-full">
|
||||
<div class="self-center text-xs font-medium">{$i18n.t('API Key')}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex mt-2">
|
||||
{#if APIKey}
|
||||
<div class="flex mt-2">
|
||||
<div class="flex w-full">
|
||||
<input
|
||||
class="w-full rounded-l-lg py-1.5 pl-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||
type={showAPIKey ? 'text' : 'password'}
|
||||
value={APIKey}
|
||||
class="w-full rounded-l-lg py-1.5 pl-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
type={showJWTToken ? 'text' : 'password'}
|
||||
value={localStorage.token}
|
||||
disabled
|
||||
/>
|
||||
|
||||
<button
|
||||
class="px-2 transition rounded-r-lg bg-white dark:bg-gray-800"
|
||||
class="px-2 transition rounded-r-lg bg-white dark:bg-gray-850"
|
||||
on:click={() => {
|
||||
showAPIKey = !showAPIKey;
|
||||
showJWTToken = !showJWTToken;
|
||||
}}
|
||||
>
|
||||
{#if showAPIKey}
|
||||
{#if showJWTToken}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
@@ -348,16 +304,16 @@
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-800 transition rounded-lg"
|
||||
class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-850 transition rounded-lg"
|
||||
on:click={() => {
|
||||
copyToClipboard(APIKey);
|
||||
APIKeyCopied = true;
|
||||
copyToClipboard(localStorage.token);
|
||||
JWTTokenCopied = true;
|
||||
setTimeout(() => {
|
||||
APIKeyCopied = false;
|
||||
JWTTokenCopied = false;
|
||||
}, 2000);
|
||||
}}
|
||||
>
|
||||
{#if APIKeyCopied}
|
||||
{#if JWTTokenCopied}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -390,45 +346,146 @@
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="justify-between w-full">
|
||||
<div class="flex justify-between w-full">
|
||||
<div class="self-center text-xs font-medium">{$i18n.t('API Key')}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex mt-2">
|
||||
{#if APIKey}
|
||||
<div class="flex w-full">
|
||||
<input
|
||||
class="w-full rounded-l-lg py-1.5 pl-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
type={showAPIKey ? 'text' : 'password'}
|
||||
value={APIKey}
|
||||
disabled
|
||||
/>
|
||||
|
||||
<button
|
||||
class="px-2 transition rounded-r-lg bg-white dark:bg-gray-850"
|
||||
on:click={() => {
|
||||
showAPIKey = !showAPIKey;
|
||||
}}
|
||||
>
|
||||
{#if showAPIKey}
|
||||
<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="M3.28 2.22a.75.75 0 0 0-1.06 1.06l10.5 10.5a.75.75 0 1 0 1.06-1.06l-1.322-1.323a7.012 7.012 0 0 0 2.16-3.11.87.87 0 0 0 0-.567A7.003 7.003 0 0 0 4.82 3.76l-1.54-1.54Zm3.196 3.195 1.135 1.136A1.502 1.502 0 0 1 9.45 8.389l1.136 1.135a3 3 0 0 0-4.109-4.109Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="m7.812 10.994 1.816 1.816A7.003 7.003 0 0 1 1.38 8.28a.87.87 0 0 1 0-.566 6.985 6.985 0 0 1 1.113-2.039l2.513 2.513a3 3 0 0 0 2.806 2.806Z"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M1.38 8.28a.87.87 0 0 1 0-.566 7.003 7.003 0 0 1 13.238.006.87.87 0 0 1 0 .566A7.003 7.003 0 0 1 1.379 8.28ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Tooltip content="Create new key">
|
||||
<button
|
||||
class=" px-1.5 py-1 dark:hover:bg-gray-800transition rounded-lg"
|
||||
class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-850 transition rounded-lg"
|
||||
on:click={() => {
|
||||
copyToClipboard(APIKey);
|
||||
APIKeyCopied = true;
|
||||
setTimeout(() => {
|
||||
APIKeyCopied = false;
|
||||
}, 2000);
|
||||
}}
|
||||
>
|
||||
{#if APIKeyCopied}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<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.986 3H12a2 2 0 0 1 2 2v6a2 2 0 0 1-1.5 1.937V7A2.5 2.5 0 0 0 10 4.5H4.063A2 2 0 0 1 6 3h.014A2.25 2.25 0 0 1 8.25 1h1.5a2.25 2.25 0 0 1 2.236 2ZM10.5 4v-.75a.75.75 0 0 0-.75-.75h-1.5a.75.75 0 0 0-.75.75V4h3Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M3 6a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1H3Zm1.75 2.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5ZM4 11.75a.75.75 0 0 1 .75-.75h3.5a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1-.75-.75Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<Tooltip content="Create new key">
|
||||
<button
|
||||
class=" px-1.5 py-1 dark:hover:bg-gray-850transition rounded-lg"
|
||||
on:click={() => {
|
||||
createAPIKeyHandler();
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{:else}
|
||||
<button
|
||||
class="flex gap-1.5 items-center font-medium px-3.5 py-1.5 rounded-lg bg-gray-100/70 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-850 transition"
|
||||
on:click={() => {
|
||||
createAPIKeyHandler();
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{:else}
|
||||
<button
|
||||
class="flex gap-1.5 items-center font-medium px-3.5 py-1.5 rounded-lg bg-gray-100/70 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
createAPIKeyHandler();
|
||||
}}
|
||||
>
|
||||
<Plus strokeWidth="2" className=" size-3.5" />
|
||||
<Plus strokeWidth="2" className=" size-3.5" />
|
||||
|
||||
Create new secret key</button
|
||||
>
|
||||
{/if}
|
||||
Create new secret key</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-medium">
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Desktop Notifications')}</div>
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Notifications')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
export let show = true;
|
||||
export let size = 'md';
|
||||
|
||||
let modalElement = null;
|
||||
let mounted = false;
|
||||
|
||||
const sizeToWidth = (size) => {
|
||||
@@ -19,14 +20,23 @@
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
console.log('Escape');
|
||||
show = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
mounted = true;
|
||||
});
|
||||
|
||||
$: if (mounted) {
|
||||
if (show) {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
document.body.style.overflow = 'unset';
|
||||
}
|
||||
}
|
||||
@@ -36,6 +46,7 @@
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
bind:this={modalElement}
|
||||
class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-50 overflow-hidden overscroll-contain"
|
||||
in:fade={{ duration: 10 }}
|
||||
on:click={() => {
|
||||
|
||||
@@ -6,18 +6,23 @@
|
||||
getQuerySettings,
|
||||
scanDocs,
|
||||
updateQuerySettings,
|
||||
resetVectorDB
|
||||
resetVectorDB,
|
||||
getEmbeddingModel,
|
||||
updateEmbeddingModel
|
||||
} from '$lib/apis/rag';
|
||||
|
||||
import { documents } from '$lib/stores';
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let saveHandler: Function;
|
||||
|
||||
let loading = false;
|
||||
let scanDirLoading = false;
|
||||
let updateEmbeddingModelLoading = false;
|
||||
|
||||
let showResetConfirm = false;
|
||||
|
||||
@@ -30,10 +35,12 @@
|
||||
k: 4
|
||||
};
|
||||
|
||||
let embeddingModel = '';
|
||||
|
||||
const scanHandler = async () => {
|
||||
loading = true;
|
||||
scanDirLoading = true;
|
||||
const res = await scanDocs(localStorage.token);
|
||||
loading = false;
|
||||
scanDirLoading = false;
|
||||
|
||||
if (res) {
|
||||
await documents.set(await getDocs(localStorage.token));
|
||||
@@ -41,6 +48,38 @@
|
||||
}
|
||||
};
|
||||
|
||||
const embeddingModelUpdateHandler = async () => {
|
||||
if (embeddingModel.split('/').length - 1 > 1) {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'Model filesystem path detected. Model shortname is required for update, cannot continue.'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Update embedding model attempt:', embeddingModel);
|
||||
|
||||
updateEmbeddingModelLoading = true;
|
||||
const res = await updateEmbeddingModel(localStorage.token, {
|
||||
embedding_model: embeddingModel
|
||||
}).catch(async (error) => {
|
||||
toast.error(error);
|
||||
embeddingModel = (await getEmbeddingModel(localStorage.token)).embedding_model;
|
||||
return null;
|
||||
});
|
||||
updateEmbeddingModelLoading = false;
|
||||
|
||||
if (res) {
|
||||
console.log('embeddingModelUpdateHandler:', res);
|
||||
if (res.status === true) {
|
||||
toast.success($i18n.t('Model {{embedding_model}} update complete!', res), {
|
||||
duration: 1000 * 10
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const submitHandler = async () => {
|
||||
const res = await updateRAGConfig(localStorage.token, {
|
||||
pdf_extract_images: pdfExtractImages,
|
||||
@@ -62,6 +101,8 @@
|
||||
chunkOverlap = res.chunk.chunk_overlap;
|
||||
}
|
||||
|
||||
embeddingModel = (await getEmbeddingModel(localStorage.token)).embedding_model;
|
||||
|
||||
querySettings = await getQuerySettings(localStorage.token);
|
||||
});
|
||||
</script>
|
||||
@@ -73,7 +114,7 @@
|
||||
saveHandler();
|
||||
}}
|
||||
>
|
||||
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
|
||||
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[22rem]">
|
||||
<div>
|
||||
<div class=" mb-2 text-sm font-medium">{$i18n.t('General Settings')}</div>
|
||||
|
||||
@@ -83,7 +124,7 @@
|
||||
</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
|
||||
class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg flex flex-row space-x-1 items-center {scanDirLoading
|
||||
? ' cursor-not-allowed'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
@@ -91,24 +132,11 @@
|
||||
console.log('check');
|
||||
}}
|
||||
type="button"
|
||||
disabled={loading}
|
||||
disabled={scanDirLoading}
|
||||
>
|
||||
<div class="self-center font-medium">{$i18n.t('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}
|
||||
{#if scanDirLoading}
|
||||
<div class="ml-3 self-center">
|
||||
<svg
|
||||
class=" w-3 h-3"
|
||||
@@ -141,196 +169,258 @@
|
||||
|
||||
<hr class=" dark:border-gray-700" />
|
||||
|
||||
<div class=" ">
|
||||
<div class=" text-sm font-medium">{$i18n.t('Chunk Params')}</div>
|
||||
|
||||
<div class=" flex">
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class="self-center text-xs font-medium min-w-fit">{$i18n.t('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={$i18n.t('Enter Chunk Size')}
|
||||
bind:value={chunkSize}
|
||||
autocomplete="off"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div>
|
||||
<div class=" mb-2 text-sm font-medium">{$i18n.t('Update Embedding Model')}</div>
|
||||
<div class="flex w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit">{$i18n.t('Chunk Overlap')}</div>
|
||||
|
||||
<div class="self-center p-3">
|
||||
<div class="flex-1 mr-2">
|
||||
<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={$i18n.t('Enter Chunk Overlap')}
|
||||
bind:value={chunkOverlap}
|
||||
autocomplete="off"
|
||||
min="0"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
placeholder={$i18n.t('Update embedding model (e.g. {{model}})', {
|
||||
model: embeddingModel.slice(-40)
|
||||
})}
|
||||
bind:value={embeddingModel}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between items-center text-xs">
|
||||
<div class=" text-xs font-medium">{$i18n.t('PDF Extract Images (OCR)')}</div>
|
||||
|
||||
<button
|
||||
class=" text-xs font-medium text-gray-500"
|
||||
type="button"
|
||||
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
|
||||
on:click={() => {
|
||||
pdfExtractImages = !pdfExtractImages;
|
||||
}}>{pdfExtractImages ? $i18n.t('On') : $i18n.t('Off')}</button
|
||||
embeddingModelUpdateHandler();
|
||||
}}
|
||||
disabled={updateEmbeddingModelLoading}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" text-sm font-medium">{$i18n.t('Query Params')}</div>
|
||||
|
||||
<div class=" flex">
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class="self-center text-xs font-medium flex-1">{$i18n.t('Top K')}</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={$i18n.t('Enter Top K')}
|
||||
bind:value={querySettings.k}
|
||||
autocomplete="off"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
{#if updateEmbeddingModelLoading}
|
||||
<div class="self-center">
|
||||
<svg
|
||||
class=" w-4 h-4"
|
||||
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>
|
||||
{:else}
|
||||
<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>
|
||||
{/if}
|
||||
</button>
|
||||
</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 class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
|
||||
{$i18n.t(
|
||||
'Warning: If you update or change your embedding model, you will need to re-import all documents.'
|
||||
)}
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-700 my-3" />
|
||||
|
||||
<div class=" ">
|
||||
<div class=" text-sm font-medium">{$i18n.t('Chunk Params')}</div>
|
||||
|
||||
<div class=" flex">
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class="self-center text-xs font-medium min-w-fit">{$i18n.t('Chunk Size')}</div>
|
||||
|
||||
<div class="self-center p-3">
|
||||
<input
|
||||
class=" w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Enter Chunk Size')}
|
||||
bind:value={chunkSize}
|
||||
autocomplete="off"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('RAG Template')}</div>
|
||||
<textarea
|
||||
bind:value={querySettings.template}
|
||||
class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
|
||||
rows="4"
|
||||
/>
|
||||
<div class="flex w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit">
|
||||
{$i18n.t('Chunk Overlap')}
|
||||
</div>
|
||||
|
||||
<div class="self-center p-3">
|
||||
<input
|
||||
class="w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Enter Chunk Overlap')}
|
||||
bind:value={chunkOverlap}
|
||||
autocomplete="off"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pr-2">
|
||||
<div class="flex justify-between items-center text-xs">
|
||||
<div class=" text-xs font-medium">{$i18n.t('PDF Extract Images (OCR)')}</div>
|
||||
|
||||
<button
|
||||
class=" text-xs font-medium text-gray-500"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
pdfExtractImages = !pdfExtractImages;
|
||||
}}>{pdfExtractImages ? $i18n.t('On') : $i18n.t('Off')}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-700 my-3" />
|
||||
|
||||
<div>
|
||||
<div class=" text-sm font-medium">{$i18n.t('Query Params')}</div>
|
||||
|
||||
<div class=" flex">
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class="self-center text-xs font-medium flex-1">{$i18n.t('Top K')}</div>
|
||||
|
||||
<div class="self-center p-3">
|
||||
<input
|
||||
class=" w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Enter Top K')}
|
||||
bind:value={querySettings.k}
|
||||
autocomplete="off"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('RAG Template')}</div>
|
||||
<textarea
|
||||
bind:value={querySettings.template}
|
||||
class="w-full rounded-lg px-4 py-3 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
|
||||
rows="4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-700 my-3" />
|
||||
|
||||
{#if showResetConfirm}
|
||||
<div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
|
||||
<div class="flex items-center space-x-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM5.72 7.47a.75.75 0 0 1 1.06 0L8 8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06L9.06 9.75l1.22 1.22a.75.75 0 1 1-1.06 1.06L8 10.81l-1.22 1.22a.75.75 0 0 1-1.06-1.06l1.22-1.22-1.22-1.22a.75.75 0 0 1 0-1.06Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>{$i18n.t('Are you sure?')}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-1.5 items-center">
|
||||
<button
|
||||
class="hover:text-white transition"
|
||||
on:click={() => {
|
||||
const res = resetVectorDB(localStorage.token).catch((error) => {
|
||||
toast.error(error);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res) {
|
||||
toast.success($i18n.t('Success'));
|
||||
}
|
||||
|
||||
showResetConfirm = false;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="hover:text-white transition"
|
||||
on:click={() => {
|
||||
showResetConfirm = false;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
showResetConfirm = true;
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-3">
|
||||
<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="M3.5 2A1.5 1.5 0 0 0 2 3.5v9A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 12.5 4H9.621a1.5 1.5 0 0 1-1.06-.44L7.439 2.44A1.5 1.5 0 0 0 6.38 2H3.5Zm6.75 7.75a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0 0 1.5h4.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Reset Vector Storage')}</div>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-700" />
|
||||
|
||||
{#if showResetConfirm}
|
||||
<div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
|
||||
<div class="flex items-center space-x-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM5.72 7.47a.75.75 0 0 1 1.06 0L8 8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06L9.06 9.75l1.22 1.22a.75.75 0 1 1-1.06 1.06L8 10.81l-1.22 1.22a.75.75 0 0 1-1.06-1.06l1.22-1.22-1.22-1.22a.75.75 0 0 1 0-1.06Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>{$i18n.t('Are you sure?')}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-1.5 items-center">
|
||||
<button
|
||||
class="hover:text-white transition"
|
||||
on:click={() => {
|
||||
const res = resetVectorDB(localStorage.token).catch((error) => {
|
||||
toast.error(error);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res) {
|
||||
toast.success($i18n.t('Success'));
|
||||
}
|
||||
|
||||
showResetConfirm = false;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="hover:text-white transition"
|
||||
on:click={() => {
|
||||
showResetConfirm = false;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
showResetConfirm = true;
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-3">
|
||||
<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="M3.5 2A1.5 1.5 0 0 0 2 3.5v9A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 12.5 4H9.621a1.5 1.5 0 0 1-1.06-.44L7.439 2.44A1.5 1.5 0 0 0 6.38 2H3.5Zm6.75 7.75a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0 0 1.5h4.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Reset Vector Storage')}</div>
|
||||
</button>
|
||||
{/if}
|
||||
</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"
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||
import Tags from '$lib/components/common/Tags.svelte';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import { downloadChatAsPDF } from '$lib/apis/utils';
|
||||
|
||||
export let shareEnabled: boolean = false;
|
||||
export let shareHandler: Function;
|
||||
@@ -25,7 +27,7 @@
|
||||
|
||||
export let onClose: Function = () => {};
|
||||
|
||||
const downloadChatAsTxt = async () => {
|
||||
const downloadTxt = async () => {
|
||||
const _chat = chat.chat;
|
||||
console.log('download', chat);
|
||||
|
||||
@@ -40,54 +42,29 @@
|
||||
saveAs(blob, `chat-${_chat.title}.txt`);
|
||||
};
|
||||
|
||||
const downloadChatAsPdf = async () => {
|
||||
const downloadPdf = async () => {
|
||||
const _chat = chat.chat;
|
||||
console.log('download', chat);
|
||||
|
||||
const doc = new jsPDF();
|
||||
const blob = await downloadChatAsPDF(_chat);
|
||||
|
||||
// Initialize y-coordinate for text placement
|
||||
let yPos = 10;
|
||||
const pageHeight = doc.internal.pageSize.height;
|
||||
// Create a URL for the blob
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
// Function to check if new text exceeds the current page height
|
||||
function checkAndAddNewPage() {
|
||||
if (yPos > pageHeight - 10) {
|
||||
doc.addPage();
|
||||
yPos = 10; // Reset yPos for the new page
|
||||
}
|
||||
}
|
||||
// Create a link element to trigger the download
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `chat-${_chat.title}.pdf`;
|
||||
|
||||
// Function to add text with specific style
|
||||
function addStyledText(text, isTitle = false) {
|
||||
// Set font style and size based on the parameters
|
||||
doc.setFont('helvetica', isTitle ? 'bold' : 'normal');
|
||||
doc.setFontSize(isTitle ? 12 : 10);
|
||||
// Append the link to the body and click it programmatically
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
const textMargin = 7;
|
||||
// Remove the link from the body
|
||||
document.body.removeChild(a);
|
||||
|
||||
// Split text into lines to ensure it fits within the page width
|
||||
const lines = doc.splitTextToSize(text, 180); // Adjust the width as needed
|
||||
|
||||
lines.forEach((line) => {
|
||||
checkAndAddNewPage(); // Check if we need a new page before adding more text
|
||||
doc.text(line, 10, yPos);
|
||||
yPos += textMargin; // Increment yPos for the next line
|
||||
});
|
||||
|
||||
// Add extra space after a block of text
|
||||
yPos += 2;
|
||||
}
|
||||
|
||||
_chat.messages.forEach((message, i) => {
|
||||
// Add user text in bold
|
||||
doc.setFont('helvetica', 'normal', 'bold');
|
||||
|
||||
addStyledText(message.role.toUpperCase(), { isTitle: true });
|
||||
addStyledText(message.content);
|
||||
});
|
||||
|
||||
doc.save(`chat-${_chat.title}.pdf`);
|
||||
// Revoke the URL to release memory
|
||||
window.URL.revokeObjectURL(url);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -193,7 +170,7 @@
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer dark:hover:bg-gray-850 rounded-md"
|
||||
on:click={() => {
|
||||
downloadChatAsTxt();
|
||||
downloadTxt();
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center line-clamp-1">Plain text (.txt)</div>
|
||||
@@ -202,7 +179,7 @@
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer dark:hover:bg-gray-850 rounded-md"
|
||||
on:click={() => {
|
||||
downloadChatAsPdf();
|
||||
downloadPdf();
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center line-clamp-1">PDF document (.pdf)</div>
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "Изтрито {{deleteModelTag}}",
|
||||
"Deleted {tagName}": "Изтрито {tagName}",
|
||||
"Description": "Описание",
|
||||
"Desktop Notifications": "Десктоп Известия",
|
||||
"Notifications": "Десктоп Известия",
|
||||
"Disabled": "Деактивиран",
|
||||
"Discover a modelfile": "Откриване на модфайл",
|
||||
"Discover a prompt": "Откриване на промпт",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "Esborrat {{deleteModelTag}}",
|
||||
"Deleted {tagName}": "Esborrat {tagName}",
|
||||
"Description": "Descripció",
|
||||
"Desktop Notifications": "Notificacions d'Escriptori",
|
||||
"Notifications": "Notificacions d'Escriptori",
|
||||
"Disabled": "Desactivat",
|
||||
"Discover a modelfile": "Descobreix un fitxer de model",
|
||||
"Discover a prompt": "Descobreix un prompt",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} gelöscht",
|
||||
"Deleted {tagName}": "{tagName} gelöscht",
|
||||
"Description": "Beschreibung",
|
||||
"Desktop Notifications": "Desktop-Benachrichtigungen",
|
||||
"Notifications": "Desktop-Benachrichtigungen",
|
||||
"Disabled": "Deaktiviert",
|
||||
"Discover a modelfile": "Eine Modelfiles entdecken",
|
||||
"Discover a prompt": "Einen Prompt entdecken",
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"analyze": "analyse",
|
||||
"analyzed": "analysed",
|
||||
"analyzes": "analyses",
|
||||
"apologize": "apologise",
|
||||
"apologized": "apologised",
|
||||
"apologizes": "apologises",
|
||||
"apologizing": "apologising",
|
||||
"canceled": "cancelled",
|
||||
"canceling": "cancelling",
|
||||
"capitalize": "capitalise",
|
||||
"capitalized": "capitalised",
|
||||
"capitalizes": "capitalises",
|
||||
"center": "centre",
|
||||
"centered": "centred",
|
||||
"color": "colour",
|
||||
"colorize": "colourise",
|
||||
"customize": "customise",
|
||||
"customizes": "customises",
|
||||
"defense": "defence",
|
||||
"dialog": "dialogue",
|
||||
"emphasize": "emphasise",
|
||||
"emphasized": "emphasised",
|
||||
"emphasizes": "emphasises",
|
||||
"favor": "favour",
|
||||
"favorable": "favourable",
|
||||
"favorite": "favourite",
|
||||
"favoritism": "favouritism",
|
||||
"labor": "labour",
|
||||
"labored": "laboured",
|
||||
"laboring": "labouring",
|
||||
"maximize": "maximise",
|
||||
"maximizes": "maximises",
|
||||
"minimize": "minimise",
|
||||
"minimizes": "minimises",
|
||||
"neighbor": "neighbour",
|
||||
"neighborhood": "neighbourhood",
|
||||
"offense": "offence",
|
||||
"organize": "organise",
|
||||
"organizes": "organises",
|
||||
"personalize": "personalise",
|
||||
"personalizes": "personalises",
|
||||
"program": "programme",
|
||||
"programmed": "programmed",
|
||||
"programs": "programmes",
|
||||
"quantization": "quantisation",
|
||||
"quantize": "quantise",
|
||||
"randomize": "randomise",
|
||||
"randomizes": "randomises",
|
||||
"realize": "realise",
|
||||
"realizes": "realises",
|
||||
"recognize": "recognise",
|
||||
"recognizes": "recognises",
|
||||
"summarize": "summarise",
|
||||
"summarizes": "summarises",
|
||||
"theater": "theatre",
|
||||
"theaters": "theatres",
|
||||
"toward": "towards",
|
||||
"traveled": "travelled",
|
||||
"traveler": "traveller",
|
||||
"traveling": "travelling",
|
||||
"utilize": "utilise",
|
||||
"utilizes": "utilises"
|
||||
}
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "",
|
||||
"Deleted {tagName}": "",
|
||||
"Description": "",
|
||||
"Desktop Notifications": "",
|
||||
"Notifications": "",
|
||||
"Disabled": "",
|
||||
"Discover a modelfile": "",
|
||||
"Discover a prompt": "",
|
||||
@@ -120,6 +120,7 @@
|
||||
"Edit Doc": "",
|
||||
"Edit User": "",
|
||||
"Email": "",
|
||||
"Embedding model: {{embedding_model}}": "",
|
||||
"Enable Chat History": "",
|
||||
"Enable New Sign Ups": "",
|
||||
"Enabled": "",
|
||||
@@ -150,6 +151,7 @@
|
||||
"Failed to read clipboard contents": "",
|
||||
"File Mode": "",
|
||||
"File not found.": "",
|
||||
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "",
|
||||
"Focus chat input": "",
|
||||
"Format your variables using square brackets like this:": "",
|
||||
"From (Base Model)": "",
|
||||
@@ -193,8 +195,11 @@
|
||||
"MMMM DD, YYYY": "",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "",
|
||||
"Model {{embedding_model}} update complete!": "",
|
||||
"Model {{embedding_model}} update failed or not required!": "",
|
||||
"Model {{modelId}} not found": "",
|
||||
"Model {{modelName}} already exists.": "",
|
||||
"Model filesystem path detected. Model shortname is required for update, cannot continue.": "",
|
||||
"Model Name": "",
|
||||
"Model not selected": "",
|
||||
"Model Tag Name": "",
|
||||
@@ -332,7 +337,10 @@
|
||||
"TTS Settings": "",
|
||||
"Type Hugging Face Resolve (Download) URL": "",
|
||||
"Uh-oh! There was an issue connecting to {{provider}}.": "",
|
||||
"Understand that updating or changing your embedding model requires reset of the vector database and re-import of all documents. You have been warned!": "",
|
||||
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "",
|
||||
"Update": "",
|
||||
"Update embedding model {{embedding_model}}": "",
|
||||
"Update password": "",
|
||||
"Upload a GGUF model": "",
|
||||
"Upload files": "",
|
||||
@@ -340,6 +348,7 @@
|
||||
"URL Mode": "",
|
||||
"Use '#' in the prompt input to load and select your documents.": "",
|
||||
"Use Gravatar": "",
|
||||
"Use Initials": "",
|
||||
"user": "",
|
||||
"User Permissions": "",
|
||||
"Users": "",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "Se borró {{deleteModelTag}}",
|
||||
"Deleted {tagName}": "Se borró {tagName}",
|
||||
"Description": "Descripción",
|
||||
"Desktop Notifications": "Notificaciones",
|
||||
"Notifications": "Notificaciones",
|
||||
"Disabled": "Desactivado",
|
||||
"Discover a modelfile": "Descubre un modelfile",
|
||||
"Discover a prompt": "Descubre un Prompt",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} پاک شد",
|
||||
"Deleted {tagName}": "{tagName} حذف شد",
|
||||
"Description": "توضیحات",
|
||||
"Desktop Notifications": "اعلان",
|
||||
"Notifications": "اعلان",
|
||||
"Disabled": "غیرفعال",
|
||||
"Discover a modelfile": "فایل مدل را کشف کنید",
|
||||
"Discover a prompt": "یک اعلان را کشف کنید",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} supprimé",
|
||||
"Deleted {tagName}": "{tagName} supprimé",
|
||||
"Description": "Description",
|
||||
"Desktop Notifications": "Notifications de bureau",
|
||||
"Notifications": "Notifications de bureau",
|
||||
"Disabled": "Désactivé",
|
||||
"Discover a modelfile": "Découvrir un fichier de modèle",
|
||||
"Discover a prompt": "Découvrir un prompt",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} supprimé",
|
||||
"Deleted {tagName}": "{tagName} supprimé",
|
||||
"Description": "Description",
|
||||
"Desktop Notifications": "Notifications de bureau",
|
||||
"Notifications": "Notifications de bureau",
|
||||
"Disabled": "Désactivé",
|
||||
"Discover a modelfile": "Découvrir un fichier de modèle",
|
||||
"Discover a prompt": "Découvrir un prompt",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "Eliminato {{deleteModelTag}}",
|
||||
"Deleted {tagName}": "Eliminato {tagName}",
|
||||
"Description": "Descrizione",
|
||||
"Desktop Notifications": "Notifiche desktop",
|
||||
"Notifications": "Notifiche desktop",
|
||||
"Disabled": "Disabilitato",
|
||||
"Discover a modelfile": "Scopri un file modello",
|
||||
"Discover a prompt": "Scopri un prompt",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} を削除しました",
|
||||
"Deleted {tagName}": "{tagName} を削除しました",
|
||||
"Description": "説明",
|
||||
"Desktop Notifications": "デスクトップ通知",
|
||||
"Notifications": "デスクトップ通知",
|
||||
"Disabled": "無効",
|
||||
"Discover a modelfile": "モデルファイルを見つける",
|
||||
"Discover a prompt": "プロンプトを見つける",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} 삭제됨",
|
||||
"Deleted {tagName}": "{tagName} 삭제됨",
|
||||
"Description": "설명",
|
||||
"Desktop Notifications": "알림",
|
||||
"Notifications": "알림",
|
||||
"Disabled": "비활성화",
|
||||
"Discover a modelfile": "모델파일 검색",
|
||||
"Discover a prompt": "프롬프트 검색",
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
"code": "de-DE",
|
||||
"title": "Deutsch"
|
||||
},
|
||||
{
|
||||
"code": "en-GB",
|
||||
"title": "English (GB)"
|
||||
},
|
||||
{
|
||||
"code": "es-ES",
|
||||
"title": "Spanish"
|
||||
@@ -51,10 +55,18 @@
|
||||
"code": "pt-PT",
|
||||
"title": "Portuguese (Portugal)"
|
||||
},
|
||||
{
|
||||
"code": "pt-BR",
|
||||
"title": "Portuguese (Brazil)"
|
||||
},
|
||||
{
|
||||
"code": "ru-RU",
|
||||
"title": "Russian (Russia)"
|
||||
},
|
||||
{
|
||||
"code": "tr-TR",
|
||||
"title": "Turkish"
|
||||
},
|
||||
{
|
||||
"code": "uk-UA",
|
||||
"title": "Ukrainian"
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} is verwijderd",
|
||||
"Deleted {tagName}": "{tagName} is verwijderd",
|
||||
"Description": "Beschrijving",
|
||||
"Desktop Notifications": "Desktop Notificaties",
|
||||
"Notifications": "Desktop Notificaties",
|
||||
"Disabled": "Uitgeschakeld",
|
||||
"Discover a modelfile": "Ontdek een modelfile",
|
||||
"Discover a prompt": "Ontdek een prompt",
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
{
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 's' ou '-1' para não expirar.",
|
||||
"(Beta)": "(Beta)",
|
||||
"(e.g. `sh webui.sh --api`)": "(por exemplo, `sh webui.sh --api`)",
|
||||
"(latest)": "(mais recente)",
|
||||
"{{modelName}} is thinking...": "{{modelName}} está pensando...",
|
||||
"{{webUIName}} Backend Required": "{{webUIName}} Backend Necessário",
|
||||
"a user": "um usuário",
|
||||
"About": "Sobre",
|
||||
"Account": "Conta",
|
||||
"Action": "Ação",
|
||||
"Add a model": "Adicionar um modelo",
|
||||
"Add a model tag name": "Adicionar um nome de tag de modelo",
|
||||
"Add a short description about what this modelfile does": "Adicione uma breve descrição sobre o que este arquivo de modelo faz",
|
||||
"Add a short title for this prompt": "Adicione um título curto para este prompt",
|
||||
"Add a tag": "Adicionar uma tag",
|
||||
"Add Docs": "Adicionar Documentos",
|
||||
"Add Files": "Adicionar Arquivos",
|
||||
"Add message": "Adicionar mensagem",
|
||||
"add tags": "adicionar tags",
|
||||
"Adjusting these settings will apply changes universally to all users.": "Ajustar essas configurações aplicará alterações universalmente a todos os usuários.",
|
||||
"admin": "administrador",
|
||||
"Admin Panel": "Painel do Administrador",
|
||||
"Admin Settings": "Configurações do Administrador",
|
||||
"Advanced Parameters": "Parâmetros Avançados",
|
||||
"all": "todos",
|
||||
"All Users": "Todos os Usuários",
|
||||
"Allow": "Permitir",
|
||||
"Allow Chat Deletion": "Permitir Exclusão de Bate-papo",
|
||||
"alphanumeric characters and hyphens": "caracteres alfanuméricos e hífens",
|
||||
"Already have an account?": "Já tem uma conta?",
|
||||
"an assistant": "um assistente",
|
||||
"and": "e",
|
||||
"API Base URL": "URL Base da API",
|
||||
"API Key": "Chave da API",
|
||||
"API RPM": "API RPM",
|
||||
"are allowed - Activate this command by typing": "são permitidos - Ative este comando digitando",
|
||||
"Are you sure?": "Tem certeza?",
|
||||
"Audio": "Áudio",
|
||||
"Auto-playback response": "Reprodução automática da resposta",
|
||||
"Auto-send input after 3 sec.": "Enviar entrada automaticamente após 3 segundos.",
|
||||
"AUTOMATIC1111 Base URL": "URL Base do AUTOMATIC1111",
|
||||
"AUTOMATIC1111 Base URL is required.": "A URL Base do AUTOMATIC1111 é obrigatória.",
|
||||
"available!": "disponível!",
|
||||
"Back": "Voltar",
|
||||
"Builder Mode": "Modo de Construtor",
|
||||
"Cancel": "Cancelar",
|
||||
"Categories": "Categorias",
|
||||
"Change Password": "Alterar Senha",
|
||||
"Chat": "Bate-papo",
|
||||
"Chat History": "Histórico de Bate-papo",
|
||||
"Chat History is off for this browser.": "O histórico de bate-papo está desativado para este navegador.",
|
||||
"Chats": "Bate-papos",
|
||||
"Check Again": "Verifique novamente",
|
||||
"Check for updates": "Verificar atualizações",
|
||||
"Checking for updates...": "Verificando atualizações...",
|
||||
"Choose a model before saving...": "Escolha um modelo antes de salvar...",
|
||||
"Chunk Overlap": "Sobreposição de Fragmento",
|
||||
"Chunk Params": "Parâmetros de Fragmento",
|
||||
"Chunk Size": "Tamanho do Fragmento",
|
||||
"Click here for help.": "Clique aqui para obter ajuda.",
|
||||
"Click here to check other modelfiles.": "Clique aqui para verificar outros arquivos de modelo.",
|
||||
"Click here to select": "Clique aqui para selecionar",
|
||||
"Click here to select documents.": "Clique aqui para selecionar documentos.",
|
||||
"click here.": "clique aqui.",
|
||||
"Click on the user role button to change a user's role.": "Clique no botão de função do usuário para alterar a função de um usuário.",
|
||||
"Close": "Fechar",
|
||||
"Collection": "Coleção",
|
||||
"Command": "Comando",
|
||||
"Confirm Password": "Confirmar Senha",
|
||||
"Connections": "Conexões",
|
||||
"Content": "Conteúdo",
|
||||
"Context Length": "Comprimento do Contexto",
|
||||
"Conversation Mode": "Modo de Conversa",
|
||||
"Copy last code block": "Copiar último bloco de código",
|
||||
"Copy last response": "Copiar última resposta",
|
||||
"Copying to clipboard was successful!": "Cópia para a área de transferência bem-sucedida!",
|
||||
"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Crie uma frase concisa de 3 a 5 palavras como cabeçalho para a seguinte consulta, aderindo estritamente ao limite de 3 a 5 palavras e evitando o uso da palavra 'título':",
|
||||
"Create a modelfile": "Criar um arquivo de modelo",
|
||||
"Create Account": "Criar Conta",
|
||||
"Created at": "Criado em",
|
||||
"Created by": "Criado por",
|
||||
"Current Model": "Modelo Atual",
|
||||
"Current Password": "Senha Atual",
|
||||
"Custom": "Personalizado",
|
||||
"Customize Ollama models for a specific purpose": "Personalize os modelos Ollama para um propósito específico",
|
||||
"Dark": "Escuro",
|
||||
"Database": "Banco de dados",
|
||||
"DD/MM/YYYY HH:mm": "DD/MM/AAAA HH:mm",
|
||||
"Default": "Padrão",
|
||||
"Default (Automatic1111)": "Padrão (Automatic1111)",
|
||||
"Default (Web API)": "Padrão (API Web)",
|
||||
"Default model updated": "Modelo padrão atualizado",
|
||||
"Default Prompt Suggestions": "Sugestões de Prompt Padrão",
|
||||
"Default User Role": "Função de Usuário Padrão",
|
||||
"delete": "excluir",
|
||||
"Delete a model": "Excluir um modelo",
|
||||
"Delete chat": "Excluir bate-papo",
|
||||
"Delete Chats": "Excluir Bate-papos",
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} excluído",
|
||||
"Deleted {tagName}": "{tagName} excluído",
|
||||
"Description": "Descrição",
|
||||
"Notifications": "Notificações da Área de Trabalho",
|
||||
"Disabled": "Desativado",
|
||||
"Discover a modelfile": "Descobrir um arquivo de modelo",
|
||||
"Discover a prompt": "Descobrir um prompt",
|
||||
"Discover, download, and explore custom prompts": "Descubra, baixe e explore prompts personalizados",
|
||||
"Discover, download, and explore model presets": "Descubra, baixe e explore predefinições de modelo",
|
||||
"Display the username instead of You in the Chat": "Exibir o nome de usuário em vez de Você no Bate-papo",
|
||||
"Document": "Documento",
|
||||
"Document Settings": "Configurações de Documento",
|
||||
"Documents": "Documentos",
|
||||
"does not make any external connections, and your data stays securely on your locally hosted server.": "não faz conexões externas e seus dados permanecem seguros em seu servidor hospedado localmente.",
|
||||
"Don't Allow": "Não Permitir",
|
||||
"Don't have an account?": "Não tem uma conta?",
|
||||
"Download as a File": "Baixar como Arquivo",
|
||||
"Download Database": "Baixar Banco de Dados",
|
||||
"Drop any files here to add to the conversation": "Solte os arquivos aqui para adicionar à conversa",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "por exemplo, '30s', '10m'. Unidades de tempo válidas são 's', 'm', 'h'.",
|
||||
"Edit Doc": "Editar Documento",
|
||||
"Edit User": "Editar Usuário",
|
||||
"Email": "E-mail",
|
||||
"Enable Chat History": "Ativar Histórico de Bate-papo",
|
||||
"Enable New Sign Ups": "Ativar Novas Inscrições",
|
||||
"Enabled": "Ativado",
|
||||
"Enter {{role}} message here": "Digite a mensagem de {{role}} aqui",
|
||||
"Enter API Key": "Digite a Chave da API",
|
||||
"Enter Chunk Overlap": "Digite a Sobreposição de Fragmento",
|
||||
"Enter Chunk Size": "Digite o Tamanho do Fragmento",
|
||||
"Enter Image Size (e.g. 512x512)": "Digite o Tamanho da Imagem (por exemplo, 512x512)",
|
||||
"Enter LiteLLM API Base URL (litellm_params.api_base)": "Digite a URL Base da API LiteLLM (litellm_params.api_base)",
|
||||
"Enter LiteLLM API Key (litellm_params.api_key)": "Digite a Chave da API LiteLLM (litellm_params.api_key)",
|
||||
"Enter LiteLLM API RPM (litellm_params.rpm)": "Digite o RPM da API LiteLLM (litellm_params.rpm)",
|
||||
"Enter LiteLLM Model (litellm_params.model)": "Digite o Modelo LiteLLM (litellm_params.model)",
|
||||
"Enter Max Tokens (litellm_params.max_tokens)": "Digite o Máximo de Tokens (litellm_params.max_tokens)",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Digite a tag do modelo (por exemplo, {{modelTag}})",
|
||||
"Enter Number of Steps (e.g. 50)": "Digite o Número de Etapas (por exemplo, 50)",
|
||||
"Enter stop sequence": "Digite a sequência de parada",
|
||||
"Enter Top K": "Digite o Top K",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "Digite a URL (por exemplo, http://127.0.0.1:7860/)",
|
||||
"Enter Your Email": "Digite seu E-mail",
|
||||
"Enter Your Full Name": "Digite seu Nome Completo",
|
||||
"Enter Your Password": "Digite sua Senha",
|
||||
"Experimental": "Experimental",
|
||||
"Export All Chats (All Users)": "Exportar Todos os Bate-papos (Todos os Usuários)",
|
||||
"Export Chats": "Exportar Bate-papos",
|
||||
"Export Documents Mapping": "Exportar Mapeamento de Documentos",
|
||||
"Export Modelfiles": "Exportar Arquivos de Modelo",
|
||||
"Export Prompts": "Exportar Prompts",
|
||||
"Failed to read clipboard contents": "Falha ao ler o conteúdo da área de transferência",
|
||||
"File Mode": "Modo de Arquivo",
|
||||
"File not found.": "Arquivo não encontrado.",
|
||||
"Focus chat input": "Focar entrada de bate-papo",
|
||||
"Format your variables using square brackets like this:": "Formate suas variáveis usando colchetes como este:",
|
||||
"From (Base Model)": "De (Modelo Base)",
|
||||
"Full Screen Mode": "Modo de Tela Cheia",
|
||||
"General": "Geral",
|
||||
"General Settings": "Configurações Gerais",
|
||||
"Hello, {{name}}": "Olá, {{name}}",
|
||||
"Hide": "Ocultar",
|
||||
"Hide Additional Params": "Ocultar Parâmetros Adicionais",
|
||||
"How can I help you today?": "Como posso ajudá-lo hoje?",
|
||||
"Image Generation (Experimental)": "Geração de Imagens (Experimental)",
|
||||
"Image Generation Engine": "Mecanismo de Geração de Imagens",
|
||||
"Image Settings": "Configurações de Imagem",
|
||||
"Images": "Imagens",
|
||||
"Import Chats": "Importar Bate-papos",
|
||||
"Import Documents Mapping": "Importar Mapeamento de Documentos",
|
||||
"Import Modelfiles": "Importar Arquivos de Modelo",
|
||||
"Import Prompts": "Importar Prompts",
|
||||
"Include `--api` flag when running stable-diffusion-webui": "Inclua a flag `--api` ao executar stable-diffusion-webui",
|
||||
"Interface": "Interface",
|
||||
"join our Discord for help.": "junte-se ao nosso Discord para obter ajuda.",
|
||||
"JSON": "JSON",
|
||||
"JWT Expiration": "Expiração JWT",
|
||||
"JWT Token": "Token JWT",
|
||||
"Keep Alive": "Manter Vivo",
|
||||
"Keyboard shortcuts": "Atalhos de teclado",
|
||||
"Language": "Idioma",
|
||||
"Light": "Claro",
|
||||
"Listening...": "Ouvindo...",
|
||||
"LLMs can make mistakes. Verify important information.": "LLMs podem cometer erros. Verifique informações importantes.",
|
||||
"Made by OpenWebUI Community": "Feito pela Comunidade OpenWebUI",
|
||||
"Make sure to enclose them with": "Certifique-se de colocá-los entre",
|
||||
"Manage LiteLLM Models": "Gerenciar Modelos LiteLLM",
|
||||
"Manage Models": "Gerenciar Modelos",
|
||||
"Manage Ollama Models": "Gerenciar Modelos Ollama",
|
||||
"Max Tokens": "Máximo de Tokens",
|
||||
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Máximo de 3 modelos podem ser baixados simultaneamente. Tente novamente mais tarde.",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"MMMM DD, YYYY": "MMMM DD, AAAA",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "O modelo '{{modelName}}' foi baixado com sucesso.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "O modelo '{{modelTag}}' já está na fila para download.",
|
||||
"Model {{modelId}} not found": "Modelo {{modelId}} não encontrado",
|
||||
"Model {{modelName}} already exists.": "O modelo {{modelName}} já existe.",
|
||||
"Model Name": "Nome do Modelo",
|
||||
"Model not selected": "Modelo não selecionado",
|
||||
"Model Tag Name": "Nome da Tag do Modelo",
|
||||
"Model Whitelisting": "Lista de Permissões de Modelo",
|
||||
"Model(s) Whitelisted": "Modelo(s) na Lista de Permissões",
|
||||
"Modelfile": "Arquivo de Modelo",
|
||||
"Modelfile Advanced Settings": "Configurações Avançadas do Arquivo de Modelo",
|
||||
"Modelfile Content": "Conteúdo do Arquivo de Modelo",
|
||||
"Modelfiles": "Arquivos de Modelo",
|
||||
"Models": "Modelos",
|
||||
"My Documents": "Meus Documentos",
|
||||
"My Modelfiles": "Meus Arquivos de Modelo",
|
||||
"My Prompts": "Meus Prompts",
|
||||
"Name": "Nome",
|
||||
"Name Tag": "Nome da Tag",
|
||||
"Name your modelfile": "Nomeie seu arquivo de modelo",
|
||||
"New Chat": "Novo Bate-papo",
|
||||
"New Password": "Nova Senha",
|
||||
"Not sure what to add?": "Não tem certeza do que adicionar?",
|
||||
"Not sure what to write? Switch to": "Não tem certeza do que escrever? Mude para",
|
||||
"Off": "Desligado",
|
||||
"Okay, Let's Go!": "Ok, Vamos Lá!",
|
||||
"Ollama Base URL": "URL Base do Ollama",
|
||||
"Ollama Version": "Versão do Ollama",
|
||||
"On": "Ligado",
|
||||
"Only": "Somente",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "Somente caracteres alfanuméricos e hífens são permitidos na string de comando.",
|
||||
"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "Opa! Aguente firme! Seus arquivos ainda estão no forno de processamento. Estamos cozinhando-os com perfeição. Por favor, seja paciente e avisaremos quando estiverem prontos.",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Opa! Parece que a URL é inválida. Verifique novamente e tente outra vez.",
|
||||
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Opa! Você está usando um método não suportado (somente frontend). Por favor, sirva o WebUI a partir do backend.",
|
||||
"Open": "Abrir",
|
||||
"Open AI": "OpenAI",
|
||||
"Open AI (Dall-E)": "OpenAI (Dall-E)",
|
||||
"Open new chat": "Abrir novo bate-papo",
|
||||
"OpenAI API": "API OpenAI",
|
||||
"OpenAI API Key": "Chave da API OpenAI",
|
||||
"OpenAI API Key is required.": "A Chave da API OpenAI é obrigatória.",
|
||||
"or": "ou",
|
||||
"Parameters": "Parâmetros",
|
||||
"Password": "Senha",
|
||||
"PDF Extract Images (OCR)": "Extrair Imagens de PDF (OCR)",
|
||||
"pending": "pendente",
|
||||
"Permission denied when accessing microphone: {{error}}": "Permissão negada ao acessar o microfone: {{error}}",
|
||||
"Playground": "Playground",
|
||||
"Profile": "Perfil",
|
||||
"Prompt Content": "Conteúdo do Prompt",
|
||||
"Prompt suggestions": "Sugestões de Prompt",
|
||||
"Prompts": "Prompts",
|
||||
"Pull a model from Ollama.com": "Extrair um modelo do Ollama.com",
|
||||
"Pull Progress": "Progresso da Extração",
|
||||
"Query Params": "Parâmetros de Consulta",
|
||||
"RAG Template": "Modelo RAG",
|
||||
"Raw Format": "Formato Bruto",
|
||||
"Record voice": "Gravar voz",
|
||||
"Redirecting you to OpenWebUI Community": "Redirecionando você para a Comunidade OpenWebUI",
|
||||
"Release Notes": "Notas de Lançamento",
|
||||
"Repeat Last N": "Repetir Últimos N",
|
||||
"Repeat Penalty": "Penalidade de Repetição",
|
||||
"Request Mode": "Modo de Solicitação",
|
||||
"Reset Vector Storage": "Redefinir Armazenamento de Vetor",
|
||||
"Response AutoCopy to Clipboard": "Cópia Automática da Resposta para a Área de Transferência",
|
||||
"Role": "Função",
|
||||
"Rosé Pine": "Rosé Pine",
|
||||
"Rosé Pine Dawn": "Rosé Pine Dawn",
|
||||
"Save": "Salvar",
|
||||
"Save & Create": "Salvar e Criar",
|
||||
"Save & Submit": "Salvar e Enviar",
|
||||
"Save & Update": "Salvar e Atualizar",
|
||||
"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Salvar logs de bate-papo diretamente no armazenamento do seu navegador não é mais suportado. Reserve um momento para baixar e excluir seus logs de bate-papo clicando no botão abaixo. Não se preocupe, você pode facilmente reimportar seus logs de bate-papo para o backend através de",
|
||||
"Scan": "Digitalizar",
|
||||
"Scan complete!": "Digitalização concluída!",
|
||||
"Scan for documents from {{path}}": "Digitalizar documentos de {{path}}",
|
||||
"Search": "Pesquisar",
|
||||
"Search Documents": "Pesquisar Documentos",
|
||||
"Search Prompts": "Pesquisar Prompts",
|
||||
"See readme.md for instructions": "Consulte readme.md para obter instruções",
|
||||
"See what's new": "Veja o que há de novo",
|
||||
"Seed": "Semente",
|
||||
"Select a mode": "Selecione um modo",
|
||||
"Select a model": "Selecione um modelo",
|
||||
"Select an Ollama instance": "Selecione uma instância Ollama",
|
||||
"Send a Message": "Enviar uma Mensagem",
|
||||
"Send message": "Enviar mensagem",
|
||||
"Server connection verified": "Conexão com o servidor verificada",
|
||||
"Set as default": "Definir como padrão",
|
||||
"Set Default Model": "Definir Modelo Padrão",
|
||||
"Set Image Size": "Definir Tamanho da Imagem",
|
||||
"Set Steps": "Definir Etapas",
|
||||
"Set Title Auto-Generation Model": "Definir Modelo de Geração Automática de Título",
|
||||
"Set Voice": "Definir Voz",
|
||||
"Settings": "Configurações",
|
||||
"Settings saved successfully!": "Configurações salvas com sucesso!",
|
||||
"Share to OpenWebUI Community": "Compartilhar com a Comunidade OpenWebUI",
|
||||
"short-summary": "resumo-curto",
|
||||
"Show": "Mostrar",
|
||||
"Show Additional Params": "Mostrar Parâmetros Adicionais",
|
||||
"Show shortcuts": "Mostrar",
|
||||
"sidebar": "barra lateral",
|
||||
"Sign in": "Entrar",
|
||||
"Sign Out": "Sair",
|
||||
"Sign up": "Inscrever-se",
|
||||
"Speech recognition error: {{error}}": "Erro de reconhecimento de fala: {{error}}",
|
||||
"Speech-to-Text Engine": "Mecanismo de Fala para Texto",
|
||||
"SpeechRecognition API is not supported in this browser.": "A API SpeechRecognition não é suportada neste navegador.",
|
||||
"Stop Sequence": "Sequência de Parada",
|
||||
"STT Settings": "Configurações STT",
|
||||
"Submit": "Enviar",
|
||||
"Success": "Sucesso",
|
||||
"Successfully updated.": "Atualizado com sucesso.",
|
||||
"Sync All": "Sincronizar Tudo",
|
||||
"System": "Sistema",
|
||||
"System Prompt": "Prompt do Sistema",
|
||||
"Tags": "Tags",
|
||||
"Temperature": "Temperatura",
|
||||
"Template": "Modelo",
|
||||
"Text Completion": "Complemento de Texto",
|
||||
"Text-to-Speech Engine": "Mecanismo de Texto para Fala",
|
||||
"Tfs Z": "Tfs Z",
|
||||
"Theme": "Tema",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Isso garante que suas conversas valiosas sejam salvas com segurança em seu banco de dados de backend. Obrigado!",
|
||||
"This setting does not sync across browsers or devices.": "Esta configuração não sincroniza entre navegadores ou dispositivos.",
|
||||
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Dica: Atualize vários slots de variáveis consecutivamente pressionando a tecla Tab na entrada de bate-papo após cada substituição.",
|
||||
"Title": "Título",
|
||||
"Title Auto-Generation": "Geração Automática de Título",
|
||||
"Title Generation Prompt": "Prompt de Geração de Título",
|
||||
"to": "para",
|
||||
"To access the available model names for downloading,": "Para acessar os nomes de modelo disponíveis para download,",
|
||||
"To access the GGUF models available for downloading,": "Para acessar os modelos GGUF disponíveis para download,",
|
||||
"to chat input.": "para a entrada de bate-papo.",
|
||||
"Toggle settings": "Alternar configurações",
|
||||
"Toggle sidebar": "Alternar barra lateral",
|
||||
"Top K": "Top K",
|
||||
"Top P": "Top P",
|
||||
"Trouble accessing Ollama?": "Problemas para acessar o Ollama?",
|
||||
"TTS Settings": "Configurações TTS",
|
||||
"Type Hugging Face Resolve (Download) URL": "Digite a URL do Hugging Face Resolve (Download)",
|
||||
"Uh-oh! There was an issue connecting to {{provider}}.": "Opa! Houve um problema ao conectar-se a {{provider}}.",
|
||||
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Tipo de arquivo desconhecido '{{file_type}}', mas aceitando e tratando como texto simples",
|
||||
"Update password": "Atualizar senha",
|
||||
"Upload a GGUF model": "Carregar um modelo GGUF",
|
||||
"Upload files": "Carregar arquivos",
|
||||
"Upload Progress": "Progresso do Carregamento",
|
||||
"URL Mode": "Modo de URL",
|
||||
"Use '#' in the prompt input to load and select your documents.": "Use '#' na entrada do prompt para carregar e selecionar seus documentos.",
|
||||
"Use Gravatar": "Usar Gravatar",
|
||||
"user": "usuário",
|
||||
"User Permissions": "Permissões do Usuário",
|
||||
"Users": "Usuários",
|
||||
"Utilize": "Utilizar",
|
||||
"Valid time units:": "Unidades de tempo válidas:",
|
||||
"variable": "variável",
|
||||
"variable to have them replaced with clipboard content.": "variável para que sejam substituídos pelo conteúdo da área de transferência.",
|
||||
"Version": "Versão",
|
||||
"Web": "Web",
|
||||
"WebUI Add-ons": "Complementos WebUI",
|
||||
"WebUI Settings": "Configurações WebUI",
|
||||
"WebUI will make requests to": "WebUI fará solicitações para",
|
||||
"What’s New in": "O que há de novo em",
|
||||
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quando o histórico está desativado, novos bate-papos neste navegador não aparecerão em seu histórico em nenhum dos seus dispositivos.",
|
||||
"Whisper (Local)": "Whisper (Local)",
|
||||
"Write a prompt suggestion (e.g. Who are you?)": "Escreva uma sugestão de prompt (por exemplo, Quem é você?)",
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "Escreva um resumo em 50 palavras que resuma [tópico ou palavra-chave].",
|
||||
"You": "Você",
|
||||
"You're a helpful assistant.": "Você é um assistente útil.",
|
||||
"You're now logged in.": "Você está conectado agora."
|
||||
}
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} excluído",
|
||||
"Deleted {tagName}": "{tagName} excluído",
|
||||
"Description": "Descrição",
|
||||
"Desktop Notifications": "Notificações da Área de Trabalho",
|
||||
"Notifications": "Notificações da Área de Trabalho",
|
||||
"Disabled": "Desativado",
|
||||
"Discover a modelfile": "Descobrir um arquivo de modelo",
|
||||
"Discover a prompt": "Descobrir um prompt",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "Удалено {{deleteModelTag}}",
|
||||
"Deleted {tagName}": "Удалено {tagName}",
|
||||
"Description": "Описание",
|
||||
"Desktop Notifications": "Уведомления на рабочем столе",
|
||||
"Notifications": "Уведомления на рабочем столе",
|
||||
"Disabled": "Отключено",
|
||||
"Discover a modelfile": "Найти файл модели",
|
||||
"Discover a prompt": "Найти промт",
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
{
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' veya süresiz için '-1'.",
|
||||
"(Beta)": "(Beta)",
|
||||
"(e.g. `sh webui.sh --api`)": "(örn. `sh webui.sh --api`)",
|
||||
"(latest)": "(en son)",
|
||||
"{{modelName}} is thinking...": "{{modelName}} düşünüyor...",
|
||||
"{{webUIName}} Backend Required": "{{webUIName}} Arkayüz Gerekli",
|
||||
"a user": "bir kullanıcı",
|
||||
"About": "Hakkında",
|
||||
"Account": "Hesap",
|
||||
"Action": "Eylem",
|
||||
"Add a model": "Bir model ekleyin",
|
||||
"Add a model tag name": "Bir model etiket adı ekleyin",
|
||||
"Add a short description about what this modelfile does": "Bu model dosyasının ne yaptığı hakkında kısa bir açıklama ekleyin",
|
||||
"Add a short title for this prompt": "Bu prompt için kısa bir başlık ekleyin",
|
||||
"Add a tag": "Bir etiket ekleyin",
|
||||
"Add Docs": "Dökümanlar Ekle",
|
||||
"Add Files": "Dosyalar Ekle",
|
||||
"Add message": "Mesaj ekle",
|
||||
"add tags": "etiketler ekle",
|
||||
"Adjusting these settings will apply changes universally to all users.": "Bu ayarları ayarlamak değişiklikleri tüm kullanıcılara evrensel olarak uygular.",
|
||||
"admin": "yönetici",
|
||||
"Admin Panel": "Yönetici Paneli",
|
||||
"Admin Settings": "Yönetici Ayarları",
|
||||
"Advanced Parameters": "Gelişmiş Parametreler",
|
||||
"all": "tümü",
|
||||
"All Users": "Tüm Kullanıcılar",
|
||||
"Allow": "İzin ver",
|
||||
"Allow Chat Deletion": "Sohbet Silmeye İzin Ver",
|
||||
"alphanumeric characters and hyphens": "alfanumerik karakterler ve tireler",
|
||||
"Already have an account?": "Zaten bir hesabınız mı var?",
|
||||
"an assistant": "bir asistan",
|
||||
"and": "ve",
|
||||
"API Base URL": "API Temel URL",
|
||||
"API Key": "API Anahtarı",
|
||||
"API RPM": "API RPM",
|
||||
"are allowed - Activate this command by typing": "izin verilir - Bu komutu yazarak etkinleştirin",
|
||||
"Are you sure?": "Emin misiniz?",
|
||||
"Audio": "Ses",
|
||||
"Auto-playback response": "Yanıtı otomatik oynatma",
|
||||
"Auto-send input after 3 sec.": "3 saniye sonra otomatik olarak gönder",
|
||||
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Temel URL",
|
||||
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Temel URL gereklidir.",
|
||||
"available!": "mevcut!",
|
||||
"Back": "Geri",
|
||||
"Builder Mode": "Oluşturucu Modu",
|
||||
"Cancel": "İptal",
|
||||
"Categories": "Kategoriler",
|
||||
"Change Password": "Parola Değiştir",
|
||||
"Chat": "Sohbet",
|
||||
"Chat History": "Sohbet Geçmişi",
|
||||
"Chat History is off for this browser.": "Bu tarayıcı için sohbet geçmişi kapalı.",
|
||||
"Chats": "Sohbetler",
|
||||
"Check Again": "Tekrar Kontrol Et",
|
||||
"Check for updates": "Güncellemeleri kontrol et",
|
||||
"Checking for updates...": "Güncellemeler kontrol ediliyor...",
|
||||
"Choose a model before saving...": "Kaydetmeden önce bir model seçin...",
|
||||
"Chunk Overlap": "Chunk Çakışması",
|
||||
"Chunk Params": "Chunk Parametreleri",
|
||||
"Chunk Size": "Chunk Boyutu",
|
||||
"Click here for help.": "Yardım için buraya tıklayın.",
|
||||
"Click here to check other modelfiles.": "Diğer model dosyalarını kontrol etmek için buraya tıklayın.",
|
||||
"Click here to select": "Seçmek için buraya tıklayın",
|
||||
"Click here to select documents.": "Belgeleri seçmek için buraya tıklayın.",
|
||||
"click here.": "buraya tıklayın.",
|
||||
"Click on the user role button to change a user's role.": "Bir kullanıcının rolünü değiştirmek için kullanıcı rolü düğmesine tıklayın.",
|
||||
"Close": "Kapat",
|
||||
"Collection": "Koleksiyon",
|
||||
"Command": "Komut",
|
||||
"Confirm Password": "Parolayı Onayla",
|
||||
"Connections": "Bağlantılar",
|
||||
"Content": "İçerik",
|
||||
"Context Length": "Bağlam Uzunluğu",
|
||||
"Conversation Mode": "Sohbet Modu",
|
||||
"Copy last code block": "Son kod bloğunu kopyala",
|
||||
"Copy last response": "Son yanıtı kopyala",
|
||||
"Copying to clipboard was successful!": "Panoya kopyalama başarılı!",
|
||||
"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Aşağıdaki sorgu için başlık olarak 3-5 kelimelik kısa ve öz bir ifade oluşturun, 3-5 kelime sınırına kesinlikle uyun ve 'başlık' kelimesini kullanmaktan kaçının:",
|
||||
"Create a modelfile": "Bir model dosyası oluştur",
|
||||
"Create Account": "Hesap Oluştur",
|
||||
"Created at": "Oluşturulma tarihi",
|
||||
"Created by": "Oluşturan",
|
||||
"Current Model": "Mevcut Model",
|
||||
"Current Password": "Mevcut Parola",
|
||||
"Custom": "Özel",
|
||||
"Customize Ollama models for a specific purpose": "Ollama modellerini belirli bir amaç için özelleştirin",
|
||||
"Dark": "Koyu",
|
||||
"Database": "Veritabanı",
|
||||
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
|
||||
"Default": "Varsayılan",
|
||||
"Default (Automatic1111)": "Varsayılan (Automatic1111)",
|
||||
"Default (Web API)": "Varsayılan (Web API)",
|
||||
"Default model updated": "Varsayılan model güncellendi",
|
||||
"Default Prompt Suggestions": "Varsayılan Prompt Önerileri",
|
||||
"Default User Role": "Varsayılan Kullanıcı Rolü",
|
||||
"delete": "sil",
|
||||
"Delete a model": "Bir modeli sil",
|
||||
"Delete chat": "Sohbeti sil",
|
||||
"Delete Chats": "Sohbetleri Sil",
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} silindi",
|
||||
"Deleted {tagName}": "{tagName} silindi",
|
||||
"Description": "Açıklama",
|
||||
"Notifications": "Bildirimler",
|
||||
"Disabled": "Devre Dışı",
|
||||
"Discover a modelfile": "Bir model dosyası keşfedin",
|
||||
"Discover a prompt": "Bir prompt keşfedin",
|
||||
"Discover, download, and explore custom prompts": "Özel promptları keşfedin, indirin ve inceleyin",
|
||||
"Discover, download, and explore model presets": "Model ön ayarlarını keşfedin, indirin ve inceleyin",
|
||||
"Display the username instead of You in the Chat": "Sohbet'te Siz yerine kullanıcı adını göster",
|
||||
"Document": "Belge",
|
||||
"Document Settings": "Belge Ayarları",
|
||||
"Documents": "Belgeler",
|
||||
"does not make any external connections, and your data stays securely on your locally hosted server.": "herhangi bir harici bağlantı yapmaz ve verileriniz güvenli bir şekilde yerel olarak barındırılan sunucunuzda kalır.",
|
||||
"Don't Allow": "İzin Verme",
|
||||
"Don't have an account?": "Hesabınız yok mu?",
|
||||
"Download as a File": "Dosya olarak indir",
|
||||
"Download Database": "Veritabanını İndir",
|
||||
"Drop any files here to add to the conversation": "Sohbete eklemek istediğiniz dosyaları buraya bırakın",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "örn. '30s', '10m'. Geçerli zaman birimleri 's', 'm', 'h'.",
|
||||
"Edit Doc": "Belgeyi Düzenle",
|
||||
"Edit User": "Kullanıcıyı Düzenle",
|
||||
"Email": "E-posta",
|
||||
"Enable Chat History": "Sohbet Geçmişini Etkinleştir",
|
||||
"Enable New Sign Ups": "Yeni Kayıtları Etkinleştir",
|
||||
"Enabled": "Etkin",
|
||||
"Enter {{role}} message here": "Buraya {{role}} mesajını girin",
|
||||
"Enter API Key": "API Anahtarını Girin",
|
||||
"Enter Chunk Overlap": "Chunk Örtüşmesini Girin",
|
||||
"Enter Chunk Size": "Chunk Boyutunu Girin",
|
||||
"Enter Image Size (e.g. 512x512)": "Görüntü Boyutunu Girin (örn. 512x512)",
|
||||
"Enter LiteLLM API Base URL (litellm_params.api_base)": "LiteLLM API Ana URL'sini Girin (litellm_params.api_base)",
|
||||
"Enter LiteLLM API Key (litellm_params.api_key)": "LiteLLM API Anahtarını Girin (litellm_params.api_key)",
|
||||
"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM API RPM'ini Girin (litellm_params.rpm)",
|
||||
"Enter LiteLLM Model (litellm_params.model)": "LiteLLM Modelini Girin (litellm_params.model)",
|
||||
"Enter Max Tokens (litellm_params.max_tokens)": "Maksimum Token Sayısını Girin (litellm_params.max_tokens)",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Model etiketini girin (örn. {{modelTag}})",
|
||||
"Enter Number of Steps (e.g. 50)": "Adım Sayısını Girin (örn. 50)",
|
||||
"Enter stop sequence": "Durdurma dizisini girin",
|
||||
"Enter Top K": "Top K'yı girin",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "URL'yi Girin (örn. http://127.0.0.1:7860/)",
|
||||
"Enter Your Email": "E-postanızı Girin",
|
||||
"Enter Your Full Name": "Tam Adınızı Girin",
|
||||
"Enter Your Password": "Parolanızı Girin",
|
||||
"Experimental": "Deneysel",
|
||||
"Export All Chats (All Users)": "Tüm Sohbetleri Dışa Aktar (Tüm Kullanıcılar)",
|
||||
"Export Chats": "Sohbetleri Dışa Aktar",
|
||||
"Export Documents Mapping": "Belge Eşlemesini Dışa Aktar",
|
||||
"Export Modelfiles": "Model Dosyalarını Dışa Aktar",
|
||||
"Export Prompts": "Promptları Dışa Aktar",
|
||||
"Failed to read clipboard contents": "Pano içeriği okunamadı",
|
||||
"File Mode": "Dosya Modu",
|
||||
"File not found.": "Dosya bulunamadı.",
|
||||
"Focus chat input": "Sohbet girişine odaklan",
|
||||
"Format your variables using square brackets like this:": "Değişkenlerinizi şu şekilde kare parantezlerle biçimlendirin:",
|
||||
"From (Base Model)": "(Temel Model)'den",
|
||||
"Full Screen Mode": "Tam Ekran Modu",
|
||||
"General": "Genel",
|
||||
"General Settings": "Genel Ayarlar",
|
||||
"Hello, {{name}}": "Merhaba, {{name}}",
|
||||
"Hide": "Gizle",
|
||||
"Hide Additional Params": "Ek Parametreleri Gizle",
|
||||
"How can I help you today?": "Bugün size nasıl yardımcı olabilirim?",
|
||||
"Image Generation (Experimental)": "Görüntü Oluşturma (Deneysel)",
|
||||
"Image Generation Engine": "Görüntü Oluşturma Motoru",
|
||||
"Image Settings": "Görüntü Ayarları",
|
||||
"Images": "Görüntüler",
|
||||
"Import Chats": "Sohbetleri İçe Aktar",
|
||||
"Import Documents Mapping": "Belge Eşlemesini İçe Aktar",
|
||||
"Import Modelfiles": "Model Dosyalarını İçe Aktar",
|
||||
"Import Prompts": "Promptları İçe Aktar",
|
||||
"Include `--api` flag when running stable-diffusion-webui": "stable-diffusion-webui çalıştırılırken `--api` bayrağını dahil edin",
|
||||
"Interface": "Arayüz",
|
||||
"join our Discord for help.": "yardım için Discord'umuza katılın.",
|
||||
"JSON": "JSON",
|
||||
"JWT Expiration": "JWT Bitişi",
|
||||
"JWT Token": "JWT Token",
|
||||
"Keep Alive": "Canlı Tut",
|
||||
"Keyboard shortcuts": "Klavye kısayolları",
|
||||
"Language": "Dil",
|
||||
"Light": "Açık",
|
||||
"Listening...": "Dinleniyor...",
|
||||
"LLMs can make mistakes. Verify important information.": "LLM'ler hata yapabilir. Önemli bilgileri doğrulayın.",
|
||||
"Made by OpenWebUI Community": "OpenWebUI Topluluğu tarafından yapılmıştır",
|
||||
"Make sure to enclose them with": "Değişkenlerinizi şu şekilde biçimlendirin:",
|
||||
"Manage LiteLLM Models": "LiteLLM Modellerini Yönet",
|
||||
"Manage Models": "Modelleri Yönet",
|
||||
"Manage Ollama Models": "Ollama Modellerini Yönet",
|
||||
"Max Tokens": "Maksimum Token",
|
||||
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Aynı anda en fazla 3 model indirilebilir. Lütfen daha sonra tekrar deneyin.",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"MMMM DD, YYYY": "DD MMMM YYYY",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' başarıyla indirildi.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "'{{modelTag}}' zaten indirme sırasında.",
|
||||
"Model {{modelId}} not found": "{{modelId}} bulunamadı",
|
||||
"Model {{modelName}} already exists.": "{{modelName}} zaten mevcut.",
|
||||
"Model Name": "Model Adı",
|
||||
"Model not selected": "Model seçilmedi",
|
||||
"Model Tag Name": "Model Etiket Adı",
|
||||
"Model Whitelisting": "Model Beyaz Listeye Alma",
|
||||
"Model(s) Whitelisted": "Model(ler) Beyaz Listeye Alındı",
|
||||
"Modelfile": "Model Dosyası",
|
||||
"Modelfile Advanced Settings": "Model Dosyası Gelişmiş Ayarları",
|
||||
"Modelfile Content": "Model Dosyası İçeriği",
|
||||
"Modelfiles": "Model Dosyaları",
|
||||
"Models": "Modeller",
|
||||
"My Documents": "Belgelerim",
|
||||
"My Modelfiles": "Model Dosyalarım",
|
||||
"My Prompts": "Promptlarım",
|
||||
"Name": "Ad",
|
||||
"Name Tag": "Ad Etiketi",
|
||||
"Name your modelfile": "Model dosyanıza ad verin",
|
||||
"New Chat": "Yeni Sohbet",
|
||||
"New Password": "Yeni Parola",
|
||||
"Not sure what to add?": "Ne ekleyeceğinizden emin değil misiniz?",
|
||||
"Not sure what to write? Switch to": "Ne yazacağınızdan emin değil misiniz? Şuraya geçin",
|
||||
"Off": "Kapalı",
|
||||
"Okay, Let's Go!": "Tamam, Hadi Başlayalım!",
|
||||
"Ollama Base URL": "Ollama Temel URL",
|
||||
"Ollama Version": "Ollama Sürümü",
|
||||
"On": "Açık",
|
||||
"Only": "Yalnızca",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "Komut dizisinde yalnızca alfasayısal karakterler ve tireler kabul edilir.",
|
||||
"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "Hop! Biraz sabırlı ol! Dosyaların hala hazırlama fırınında. Onları ağzınıza layık olana kadar pişiriyoruz :) Lütfen sabırlı olun; hazır olduklarında size haber vereceğiz.",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Hop! URL geçersiz gibi görünüyor. Lütfen tekrar kontrol edin ve yeniden deneyin.",
|
||||
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hop! Desteklenmeyen bir yöntem kullanıyorsunuz (yalnızca önyüz). Lütfen WebUI'yi arkayüzden sunun.",
|
||||
"Open": "Aç",
|
||||
"Open AI": "Open AI",
|
||||
"Open AI (Dall-E)": "Open AI (Dall-E)",
|
||||
"Open new chat": "Yeni sohbet aç",
|
||||
"OpenAI API": "OpenAI API",
|
||||
"OpenAI API Key": "OpenAI API Anahtarı",
|
||||
"OpenAI API Key is required.": "OpenAI API Anahtarı gereklidir.",
|
||||
"or": "veya",
|
||||
"Parameters": "Parametreler",
|
||||
"Password": "Parola",
|
||||
"PDF Extract Images (OCR)": "PDF Görüntülerini Çıkart (OCR)",
|
||||
"pending": "beklemede",
|
||||
"Permission denied when accessing microphone: {{error}}": "Mikrofona erişim izni reddedildi: {{error}}",
|
||||
"Playground": "Oyun Alanı",
|
||||
"Profile": "Profil",
|
||||
"Prompt Content": "Prompt İçeriği",
|
||||
"Prompt suggestions": "Prompt önerileri",
|
||||
"Prompts": "Promptlar",
|
||||
"Pull a model from Ollama.com": "Ollama.com'dan bir model çekin",
|
||||
"Pull Progress": "Çekme İlerlemesi",
|
||||
"Query Params": "Sorgu Parametreleri",
|
||||
"RAG Template": "RAG Şablonu",
|
||||
"Raw Format": "Ham Format",
|
||||
"Record voice": "Ses kaydı yap",
|
||||
"Redirecting you to OpenWebUI Community": "OpenWebUI Topluluğuna yönlendiriliyorsunuz",
|
||||
"Release Notes": "Sürüm Notları",
|
||||
"Repeat Last N": "Son N'yi Tekrar Et",
|
||||
"Repeat Penalty": "Tekrar Cezası",
|
||||
"Request Mode": "İstek Modu",
|
||||
"Reset Vector Storage": "Vektör Depolamayı Sıfırla",
|
||||
"Response AutoCopy to Clipboard": "Yanıtı Panoya Otomatik Kopyala",
|
||||
"Role": "Rol",
|
||||
"Rosé Pine": "Rosé Pine",
|
||||
"Rosé Pine Dawn": "Rosé Pine Dawn",
|
||||
"Save": "Kaydet",
|
||||
"Save & Create": "Kaydet ve Oluştur",
|
||||
"Save & Submit": "Kaydet ve Gönder",
|
||||
"Save & Update": "Kaydet ve Güncelle",
|
||||
"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Sohbet kayıtlarının doğrudan tarayıcınızın depolama alanına kaydedilmesi artık desteklenmemektedir. Lütfen aşağıdaki butona tıklayarak sohbet kayıtlarınızı indirmek ve silmek için bir dakikanızı ayırın. Endişelenmeyin, sohbet günlüklerinizi arkayüze kolayca yeniden aktarabilirsiniz:",
|
||||
"Scan": "Tarama",
|
||||
"Scan complete!": "Tarama tamamlandı!",
|
||||
"Scan for documents from {{path}}": "{{path}} dizininden belgeleri tarayın",
|
||||
"Search": "Ara",
|
||||
"Search Documents": "Belgeleri Ara",
|
||||
"Search Prompts": "Prompt Ara",
|
||||
"See readme.md for instructions": "Yönergeler için readme.md dosyasına bakın",
|
||||
"See what's new": "Yeniliklere göz atın",
|
||||
"Seed": "Seed",
|
||||
"Select a mode": "Bir mod seç",
|
||||
"Select a model": "Bir model seç",
|
||||
"Select an Ollama instance": "Bir Ollama örneği seçin",
|
||||
"Send a Message": "Bir Mesaj Gönder",
|
||||
"Send message": "Mesaj gönder",
|
||||
"Server connection verified": "Sunucu bağlantısı doğrulandı",
|
||||
"Set as default": "Varsayılan olarak ayarla",
|
||||
"Set Default Model": "Varsayılan Modeli Ayarla",
|
||||
"Set Image Size": "Görüntü Boyutunu Ayarla",
|
||||
"Set Steps": "Adımları Ayarla",
|
||||
"Set Title Auto-Generation Model": "Otomatik Başlık Oluşturma Modelini Ayarla",
|
||||
"Set Voice": "Ses Ayarla",
|
||||
"Settings": "Ayarlar",
|
||||
"Settings saved successfully!": "Ayarlar başarıyla kaydedildi!",
|
||||
"Share to OpenWebUI Community": "OpenWebUI Topluluğu ile Paylaş",
|
||||
"short-summary": "kısa-özet",
|
||||
"Show": "Göster",
|
||||
"Show Additional Params": "Ek Parametreleri Göster",
|
||||
"Show shortcuts": "Kısayolları göster",
|
||||
"sidebar": "kenar çubuğu",
|
||||
"Sign in": "Oturum aç",
|
||||
"Sign Out": "Çıkış Yap",
|
||||
"Sign up": "Kaydol",
|
||||
"Speech recognition error: {{error}}": "Konuşma tanıma hatası: {{error}}",
|
||||
"Speech-to-Text Engine": "Konuşmadan Metne Motoru",
|
||||
"SpeechRecognition API is not supported in this browser.": "SpeechRecognition API bu tarayıcıda desteklenmiyor.",
|
||||
"Stop Sequence": "Diziyi Durdur",
|
||||
"STT Settings": "STT Ayarları",
|
||||
"Submit": "Gönder",
|
||||
"Success": "Başarılı",
|
||||
"Successfully updated.": "Başarıyla güncellendi.",
|
||||
"Sync All": "Tümünü Senkronize Et",
|
||||
"System": "Sistem",
|
||||
"System Prompt": "Sistem Promptu",
|
||||
"Tags": "Etiketler",
|
||||
"Temperature": "Temperature",
|
||||
"Template": "Şablon",
|
||||
"Text Completion": "Metin Tamamlama",
|
||||
"Text-to-Speech Engine": "Metinden Sese Motoru",
|
||||
"Tfs Z": "Tfs Z",
|
||||
"Theme": "Tema",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Bu, önemli konuşmalarınızın güvenli bir şekilde arkayüz veritabanınıza kaydedildiğini garantiler. Teşekkür ederiz!",
|
||||
"This setting does not sync across browsers or devices.": "Bu ayar tarayıcılar veya cihazlar arasında senkronize edilmez.",
|
||||
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "İpucu: Her değiştirmeden sonra sohbet girişinde tab tuşuna basarak birden fazla değişken yuvasını art arda güncelleyin.",
|
||||
"Title": "Başlık",
|
||||
"Title Auto-Generation": "Otomatik Başlık Oluşturma",
|
||||
"Title Generation Prompt": "Başlık Oluşturma Promptu",
|
||||
"to": "için",
|
||||
"To access the available model names for downloading,": "İndirilebilir mevcut model adlarına erişmek için,",
|
||||
"To access the GGUF models available for downloading,": "İndirilebilir mevcut GGUF modellerine erişmek için,",
|
||||
"to chat input.": "sohbet girişine.",
|
||||
"Toggle settings": "Ayarları Aç/Kapat",
|
||||
"Toggle sidebar": "Kenar Çubuğunu Aç/Kapat",
|
||||
"Top K": "Top K",
|
||||
"Top P": "Top P",
|
||||
"Trouble accessing Ollama?": "Ollama'ya erişmede sorun mu yaşıyorsunuz?",
|
||||
"TTS Settings": "TTS Ayarları",
|
||||
"Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (Download) URL'sini Yazın",
|
||||
"Uh-oh! There was an issue connecting to {{provider}}.": "Ah! {{provider}}'a bağlanırken bir sorun oluştu.",
|
||||
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Bilinmeyen Dosya Türü '{{file_type}}', ancak düz metin olarak kabul ediliyor ve işleniyor",
|
||||
"Update password": "Parolayı Güncelle",
|
||||
"Upload a GGUF model": "Bir GGUF modeli yükle",
|
||||
"Upload files": "Dosyaları Yükle",
|
||||
"Upload Progress": "Yükleme İlerlemesi",
|
||||
"URL Mode": "URL Modu",
|
||||
"Use '#' in the prompt input to load and select your documents.": "Belgelerinizi yüklemek ve seçmek için promptda '#' kullanın.",
|
||||
"Use Gravatar": "Gravatar Kullan",
|
||||
"user": "kullanıcı",
|
||||
"User Permissions": "Kullanıcı İzinleri",
|
||||
"Users": "Kullanıcılar",
|
||||
"Utilize": "Kullan",
|
||||
"Valid time units:": "Geçerli zaman birimleri:",
|
||||
"variable": "değişken",
|
||||
"variable to have them replaced with clipboard content.": "panodaki içerikle değiştirilmesi için değişken.",
|
||||
"Version": "Sürüm",
|
||||
"Web": "Web",
|
||||
"WebUI Add-ons": "WebUI Eklentileri",
|
||||
"WebUI Settings": "WebUI Ayarları",
|
||||
"WebUI will make requests to": "WebUI, isteklerde bulunacak:",
|
||||
"What’s New in": "Yenilikler:",
|
||||
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Geçmiş kapatıldığında, bu tarayıcıdaki yeni sohbetler hiçbir cihazınızdaki geçmişinizde görünmez.",
|
||||
"Whisper (Local)": "Whisper (Yerel)",
|
||||
"Write a prompt suggestion (e.g. Who are you?)": "Bir prompt önerisi yazın (örn. Sen kimsin?)",
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "[Konuyu veya anahtar kelimeyi] özetleyen 50 kelimelik bir özet yazın.",
|
||||
"You": "Siz",
|
||||
"You're a helpful assistant.": "Sen yardımcı bir asistansın.",
|
||||
"You're now logged in.": "Şimdi oturum açtınız."
|
||||
}
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "Видалено {{deleteModelTag}}",
|
||||
"Deleted {tagName}": "Видалено {tagName}",
|
||||
"Description": "Опис",
|
||||
"Desktop Notifications": "Сповіщення",
|
||||
"Notifications": "Сповіщення",
|
||||
"Disabled": "Вимкнено",
|
||||
"Discover a modelfile": "Знайти файл моделі",
|
||||
"Discover a prompt": "Знайти промт",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "Đã xóa {{deleteModelTag}}",
|
||||
"Deleted {tagName}": "Đã xóa {tagName}",
|
||||
"Description": "Mô tả",
|
||||
"Desktop Notifications": "Thông báo trên máy tính (Notification)",
|
||||
"Notifications": "Thông báo trên máy tính (Notification)",
|
||||
"Disabled": "Đã vô hiệu hóa",
|
||||
"Discover a modelfile": "Khám phá thêm các mô hình mới",
|
||||
"Discover a prompt": "Khám phá thêm prompt mới",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "已删除{{deleteModelTag}}",
|
||||
"Deleted {tagName}": "已删除{tagName}",
|
||||
"Description": "描述",
|
||||
"Desktop Notifications": "桌面通知",
|
||||
"Notifications": "桌面通知",
|
||||
"Disabled": "禁用",
|
||||
"Discover a modelfile": "探索模型文件",
|
||||
"Discover a prompt": "探索提示词",
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
"Deleted {{deleteModelTag}}": "已刪除 {{deleteModelTag}}",
|
||||
"Deleted {tagName}": "已刪除 {tagName}",
|
||||
"Description": "描述",
|
||||
"Desktop Notifications": "桌面通知",
|
||||
"Notifications": "桌面通知",
|
||||
"Disabled": "已停用",
|
||||
"Discover a modelfile": "發現新 Modelfile",
|
||||
"Discover a prompt": "發現新提示詞",
|
||||
|
||||
@@ -111,6 +111,82 @@ export const getGravatarURL = (email) => {
|
||||
return `https://www.gravatar.com/avatar/${hash}`;
|
||||
};
|
||||
|
||||
export const canvasPixelTest = () => {
|
||||
// Test a 1x1 pixel to potentially identify browser/plugin fingerprint blocking or spoofing
|
||||
// Inspiration: https://github.com/kkapsner/CanvasBlocker/blob/master/test/detectionTest.js
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.height = 1;
|
||||
canvas.width = 1;
|
||||
const imageData = new ImageData(canvas.width, canvas.height);
|
||||
const pixelValues = imageData.data;
|
||||
|
||||
// Generate RGB test data
|
||||
for (let i = 0; i < imageData.data.length; i += 1) {
|
||||
if (i % 4 !== 3) {
|
||||
pixelValues[i] = Math.floor(256 * Math.random());
|
||||
} else {
|
||||
pixelValues[i] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
const p = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
||||
|
||||
// Read RGB data and fail if unmatched
|
||||
for (let i = 0; i < p.length; i += 1) {
|
||||
if (p[i] !== pixelValues[i]) {
|
||||
console.log(
|
||||
'canvasPixelTest: Wrong canvas pixel RGB value detected:',
|
||||
p[i],
|
||||
'at:',
|
||||
i,
|
||||
'expected:',
|
||||
pixelValues[i]
|
||||
);
|
||||
console.log('canvasPixelTest: Canvas blocking or spoofing is likely');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const generateInitialsImage = (name) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = 100;
|
||||
canvas.height = 100;
|
||||
|
||||
if (!canvasPixelTest()) {
|
||||
console.log(
|
||||
'generateInitialsImage: failed pixel test, fingerprint evasion is likely. Using default image.'
|
||||
);
|
||||
return '/user.png';
|
||||
}
|
||||
|
||||
ctx.fillStyle = '#F39C12';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
ctx.fillStyle = '#FFFFFF';
|
||||
ctx.font = '40px Helvetica';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
const sanitizedName = name.trim();
|
||||
const initials =
|
||||
sanitizedName.length > 0
|
||||
? sanitizedName[0] +
|
||||
(sanitizedName.split(' ').length > 1
|
||||
? sanitizedName[sanitizedName.lastIndexOf(' ') + 1]
|
||||
: '')
|
||||
: '';
|
||||
|
||||
ctx.fillText(initials.toUpperCase(), canvas.width / 2, canvas.height / 2);
|
||||
|
||||
return canvas.toDataURL();
|
||||
};
|
||||
|
||||
export const copyToClipboard = (text) => {
|
||||
if (!navigator.clipboard) {
|
||||
const textArea = document.createElement('textarea');
|
||||
|
||||
+176
-137
@@ -4,6 +4,8 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount, getContext } from 'svelte';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users';
|
||||
@@ -16,6 +18,7 @@
|
||||
let loaded = false;
|
||||
let users = [];
|
||||
|
||||
let search = '';
|
||||
let selectedUser = null;
|
||||
|
||||
let showSettingsModal = false;
|
||||
@@ -80,157 +83,193 @@
|
||||
|
||||
<SettingsModal bind:show={showSettingsModal} />
|
||||
|
||||
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white font-mona">
|
||||
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
|
||||
{#if loaded}
|
||||
<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=" mx-auto w-full">
|
||||
<div class="w-full">
|
||||
<div class=" flex flex-col justify-center">
|
||||
<div class=" flex justify-between items-center">
|
||||
<div class="flex items-center text-2xl font-semibold">
|
||||
{$i18n.t('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 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"
|
||||
<div class=" px-5 pt-3">
|
||||
<div class=" flex justify-between items-center">
|
||||
<div class="flex items-center text-2xl font-semibold">Dashboard</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;
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<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">{$i18n.t('Admin Settings')}</div>
|
||||
</button>
|
||||
<div class=" text-xs">{$i18n.t('Admin Settings')}</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class=" text-gray-500 text-xs mt-1">
|
||||
ⓘ {$i18n.t("Click on the user role button to change a user's role.")}
|
||||
|
||||
<div class="px-5 flex text-sm gap-2.5">
|
||||
<div class="py-3 border-b font-medium text-gray-100 cursor-pointer">Overview</div>
|
||||
<!-- <div class="py-3 text-gray-300 cursor-pointer">Users</div> -->
|
||||
</div>
|
||||
|
||||
<hr class=" my-3 dark:border-gray-600" />
|
||||
<hr class=" mb-3 dark:border-gray-800" />
|
||||
|
||||
<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-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-3 py-2"> {$i18n.t('Role')} </th>
|
||||
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
|
||||
<th scope="col" class="px-3 py-2"> {$i18n.t('Email')} </th>
|
||||
<th scope="col" class="px-3 py-2"> {$i18n.t('Action')} </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each users as user}
|
||||
<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=" 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');
|
||||
} else if (user.role === 'pending') {
|
||||
updateRoleHandler(user.id, 'user');
|
||||
} else {
|
||||
updateRoleHandler(user.id, 'pending');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<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'}"
|
||||
/>
|
||||
{$i18n.t(user.role)}</button
|
||||
>
|
||||
</td>
|
||||
<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"
|
||||
/>
|
||||
<div class="px-5">
|
||||
<div class="mt-0.5 mb-3 flex justify-between">
|
||||
<div class="flex text-lg font-medium px-0.5">
|
||||
{$i18n.t('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 class=" font-medium self-center">{user.name}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class=" px-3 py-2"> {user.email} </td>
|
||||
<div class="">
|
||||
<input
|
||||
class=" w-60 rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
placeholder={$i18n.t('Search')}
|
||||
bind:value={search}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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;
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<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-auto">
|
||||
<thead
|
||||
class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400"
|
||||
>
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-2"> {$i18n.t('Role')} </th>
|
||||
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
|
||||
<th scope="col" class="px-3 py-2"> {$i18n.t('Email')} </th>
|
||||
<th scope="col" class="px-3 py-2"> {$i18n.t('Created at')} </th>
|
||||
<th scope="col" class="px-3 py-2 text-right" />
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each users.filter((user) => {
|
||||
if (search === '') {
|
||||
return true;
|
||||
} else {
|
||||
let name = user.name.toLowerCase();
|
||||
const query = search.toLowerCase();
|
||||
return name.includes(query);
|
||||
}
|
||||
}) as user}
|
||||
<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=" 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');
|
||||
} else if (user.role === 'pending') {
|
||||
updateRoleHandler(user.id, 'user');
|
||||
} else {
|
||||
updateRoleHandler(user.id, 'pending');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<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'}"
|
||||
/>
|
||||
{$i18n.t(user.role)}</button
|
||||
>
|
||||
</td>
|
||||
<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"
|
||||
/>
|
||||
|
||||
<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">
|
||||
{dayjs(user.timestamp * 1000).format($i18n.t('MMMM DD, YYYY'))}
|
||||
</td>
|
||||
|
||||
<td class="px-3 py-2 text-right">
|
||||
<div class="flex justify-end 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;
|
||||
}}
|
||||
>
|
||||
<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}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class=" text-gray-500 text-xs mt-2 text-right">
|
||||
ⓘ {$i18n.t("Click on the user role button to change a user's role.")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { WEBUI_NAME, config, user } from '$lib/stores';
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { generateInitialsImage, canvasPixelTest } from '$lib/utils';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
@@ -36,10 +37,12 @@
|
||||
};
|
||||
|
||||
const signUpHandler = async () => {
|
||||
const sessionUser = await userSignUp(name, email, password).catch((error) => {
|
||||
toast.error(error);
|
||||
return null;
|
||||
});
|
||||
const sessionUser = await userSignUp(name, email, password, generateInitialsImage(name)).catch(
|
||||
(error) => {
|
||||
toast.error(error);
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
await setSessionUser(sessionUser);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user