Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22c50f62cb | ||
|
|
eefe01454f | ||
|
|
74dd9b561a | ||
|
|
2fd2f792d6 | ||
|
|
93bd20b854 | ||
|
|
291c7595c4 | ||
|
|
68de49e533 | ||
|
|
d6a0805966 | ||
|
|
a5f8e87f3f | ||
|
|
45ecaaf392 | ||
|
|
4c221eabef | ||
|
|
b12edb4a7a | ||
|
|
50e8979c00 | ||
|
|
459a41774e | ||
|
|
fbd520bf07 | ||
|
|
00b01c973e | ||
|
|
f04164378a | ||
|
|
c468df2f71 | ||
|
|
fa9593b4e8 | ||
|
|
cbd18ec63c | ||
|
|
7e5bda6016 | ||
|
|
713934edb6 | ||
|
|
710850e442 | ||
|
|
2a10438b4d | ||
|
|
7eb14437ff | ||
|
|
9451726ee6 | ||
|
|
97d68a6a05 | ||
|
|
cd79afb425 | ||
|
|
b79596332a | ||
|
|
21cc6ebcb7 | ||
|
|
80ecea980e | ||
|
|
4c8932d71c | ||
|
|
7a5a3c45e0 | ||
|
|
3df03fa3fe | ||
|
|
77ec9dd1f2 | ||
|
|
22b694e0fd | ||
|
|
5e9ace1c6e | ||
|
|
76bd77bc56 | ||
|
|
a4083f43cb | ||
|
|
b67f80f7a4 | ||
|
|
a53c1cfc77 | ||
|
|
b788514a10 | ||
|
|
5f16ec077a | ||
|
|
941dc41c3d | ||
|
|
44f9e930d2 | ||
|
|
c235a3d539 | ||
|
|
e720afacfa | ||
|
|
b352732b43 | ||
|
|
0caf04617e | ||
|
|
e0ebd7aeaf | ||
|
|
2ffe55f128 | ||
|
|
9d73f22aff | ||
|
|
9dad7e7c9a | ||
|
|
b5372bf715 | ||
|
|
20a3db975d | ||
|
|
851754700a | ||
|
|
375056f8dc | ||
|
|
236e2c040c | ||
|
|
cf811edefb | ||
|
|
f6e839611b | ||
|
|
2a79c30657 | ||
|
|
d882cb41ac | ||
|
|
323b9adc63 | ||
|
|
3488b7f006 | ||
|
|
daed66f7c6 | ||
|
|
b219906fa3 | ||
|
|
a556e39651 | ||
|
|
b35316177b | ||
|
|
3ed509c5d3 | ||
|
|
6699f0c339 | ||
|
|
9dbd223b4e | ||
|
|
11e0aaa32f | ||
|
|
9aea0fb03d | ||
|
|
b51922809b | ||
|
|
314b5b5f6c | ||
|
|
7c0c11b776 | ||
|
|
44ad7c4132 | ||
|
|
748a930a5f | ||
|
|
dba45acbda | ||
|
|
ac7bb03cb3 | ||
|
|
1be60c9729 | ||
|
|
54a4b7db14 | ||
|
|
741ed5dc4c | ||
|
|
b1b72441bb | ||
|
|
b48e73fa43 | ||
|
|
2e0def73eb | ||
|
|
36ce157907 | ||
|
|
9cdb5bf9fe | ||
|
|
2952e61167 | ||
|
|
8b10b058e5 | ||
|
|
0c441b588c | ||
|
|
a938ffb586 | ||
|
|
b5d882606a | ||
|
|
d9ce1d3ea3 | ||
|
|
9091513c39 | ||
|
|
0f5ecafc57 | ||
|
|
98c16776f8 | ||
|
|
91dda2401d | ||
|
|
6238495d61 | ||
|
|
0708a3d75e | ||
|
|
b25e3ed364 | ||
|
|
1fa3bf6793 | ||
|
|
b93337e62f | ||
|
|
d2d255228c | ||
|
|
23b674ddda | ||
|
|
0d3ce18a61 | ||
|
|
067c94810a | ||
|
|
916ce9bc4f | ||
|
|
0981fae161 | ||
|
|
2649d29a3e | ||
|
|
838778aa3e | ||
|
|
8c8388ea9f | ||
|
|
b40054c54d | ||
|
|
fffd42e4d7 | ||
|
|
db817fcf29 | ||
|
|
faa5884150 | ||
|
|
922628c1ee | ||
|
|
8e30948de9 | ||
|
|
e08c144f0b | ||
|
|
51afb1e378 | ||
|
|
c49cc3fa86 | ||
|
|
e17d66eb61 | ||
|
|
d6260fd20c | ||
|
|
79a4abc3ec | ||
|
|
6623ecb302 | ||
|
|
18a3f06a62 | ||
|
|
1d8496eabb | ||
|
|
8a9cf44dbc | ||
|
|
872ea83c50 | ||
|
|
ff01398812 | ||
|
|
3b8ea55bc8 | ||
|
|
81c8717d75 | ||
|
|
429242b4d3 | ||
|
|
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"
|
||||
|
||||
@@ -24,6 +24,9 @@ assignees: ''
|
||||
|
||||
## Environment
|
||||
|
||||
- **Open WebUI Version:** [e.g., 0.1.120]
|
||||
- **Ollama (if applicable):** [e.g., 0.1.30, 0.1.32-rc1]
|
||||
|
||||
- **Operating System:** [e.g., Windows 10, macOS Big Sur, Ubuntu 20.04]
|
||||
- **Browser (if applicable):** [e.g., Chrome 100.0, Firefox 98.0]
|
||||
|
||||
|
||||
@@ -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,62 @@ 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.120] - 2024-04-20
|
||||
|
||||
### Added
|
||||
|
||||
- **📦 Archive Chat Feature**: Easily archive chats with a new sidebar button, and access archived chats via the profile button > archived chats.
|
||||
- **🔊 Configurable Text-to-Speech Endpoint**: Customize your Text-to-Speech experience with configurable OpenAI endpoints.
|
||||
- **🛠️ Improved Error Handling**: Enhanced error message handling for connection failures.
|
||||
- **⌨️ Enhanced Shortcut**: When editing messages, use ctrl/cmd+enter to save and submit, and esc to close.
|
||||
- **🌐 Language Support**: Added support for Georgian and enhanced translations for Portuguese and Vietnamese.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **🔧 Model Selector**: Resolved issue where default model selection was not saving.
|
||||
- **🔗 Share Link Copy Button**: Fixed bug where the copy button wasn't copying links in Safari.
|
||||
- **🎨 Light Theme Styling**: Addressed styling issue with the light theme.
|
||||
|
||||
## [0.1.119] - 2024-04-16
|
||||
|
||||
### Added
|
||||
|
||||
- **🌟 Enhanced RAG Embedding Support**: Ollama, and OpenAI models can now be used for RAG embedding model.
|
||||
- **🔄 Seamless Integration**: Copy 'ollama run <model name>' directly from Ollama page to easily select and pull models.
|
||||
- **🏷️ Tagging Feature**: Add tags to chats directly via the sidebar chat menu.
|
||||
- **📱 Mobile Accessibility**: Swipe left and right on mobile to effortlessly open and close the sidebar.
|
||||
- **🔍 Improved Navigation**: Admin panel now supports pagination for user list.
|
||||
- **🌍 Additional Language Support**: Added Polish language support.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **🌍 Language Enhancements**: Vietnamese and Spanish translations have been improved.
|
||||
- **🔧 Helm Fixes**: Resolved issues with Helm trailing slash and manifest.json.
|
||||
|
||||
### Changed
|
||||
|
||||
- **🐳 Docker Optimization**: Updated docker image build process to utilize 'uv' for significantly faster builds compared to 'pip3'.
|
||||
|
||||
## [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
|
||||
|
||||
+87
-46
@@ -1,78 +1,117 @@
|
||||
# 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 pip3 install uv && \
|
||||
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 && \
|
||||
uv pip install --system -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 && \
|
||||
uv pip install --system -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 +121,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
|
||||
|
||||
@@ -182,4 +185,4 @@ If you have any questions, suggestions, or need assistance, please open an issue
|
||||
|
||||
---
|
||||
|
||||
Created by [Timothy J. Baek](https://github.com/tjbck) - Let's make Open Web UI even more amazing together! 💪
|
||||
Created by [Timothy J. Baek](https://github.com/tjbck) - Let's make Open WebUI even more amazing together! 💪
|
||||
|
||||
+130
-7
@@ -10,8 +10,19 @@ from fastapi import (
|
||||
File,
|
||||
Form,
|
||||
)
|
||||
|
||||
from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
|
||||
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from faster_whisper import WhisperModel
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
import requests
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
|
||||
from constants import ERROR_MESSAGES
|
||||
from utils.utils import (
|
||||
@@ -28,6 +39,10 @@ from config import (
|
||||
UPLOAD_DIR,
|
||||
WHISPER_MODEL,
|
||||
WHISPER_MODEL_DIR,
|
||||
WHISPER_MODEL_AUTO_UPDATE,
|
||||
DEVICE_TYPE,
|
||||
AUDIO_OPENAI_API_BASE_URL,
|
||||
AUDIO_OPENAI_API_KEY,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -43,7 +58,103 @@ app.add_middleware(
|
||||
)
|
||||
|
||||
|
||||
@app.post("/transcribe")
|
||||
app.state.OPENAI_API_BASE_URL = AUDIO_OPENAI_API_BASE_URL
|
||||
app.state.OPENAI_API_KEY = AUDIO_OPENAI_API_KEY
|
||||
|
||||
# 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}")
|
||||
|
||||
SPEECH_CACHE_DIR = Path(CACHE_DIR).joinpath("./audio/speech/")
|
||||
SPEECH_CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
class OpenAIConfigUpdateForm(BaseModel):
|
||||
url: str
|
||||
key: str
|
||||
|
||||
|
||||
@app.get("/config")
|
||||
async def get_openai_config(user=Depends(get_admin_user)):
|
||||
return {
|
||||
"OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL,
|
||||
"OPENAI_API_KEY": app.state.OPENAI_API_KEY,
|
||||
}
|
||||
|
||||
|
||||
@app.post("/config/update")
|
||||
async def update_openai_config(
|
||||
form_data: OpenAIConfigUpdateForm, user=Depends(get_admin_user)
|
||||
):
|
||||
if form_data.key == "":
|
||||
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.API_KEY_NOT_FOUND)
|
||||
|
||||
app.state.OPENAI_API_BASE_URL = form_data.url
|
||||
app.state.OPENAI_API_KEY = form_data.key
|
||||
|
||||
return {
|
||||
"status": True,
|
||||
"OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL,
|
||||
"OPENAI_API_KEY": app.state.OPENAI_API_KEY,
|
||||
}
|
||||
|
||||
|
||||
@app.post("/speech")
|
||||
async def speech(request: Request, user=Depends(get_verified_user)):
|
||||
body = await request.body()
|
||||
name = hashlib.sha256(body).hexdigest()
|
||||
|
||||
file_path = SPEECH_CACHE_DIR.joinpath(f"{name}.mp3")
|
||||
file_body_path = SPEECH_CACHE_DIR.joinpath(f"{name}.json")
|
||||
|
||||
# Check if the file already exists in the cache
|
||||
if file_path.is_file():
|
||||
return FileResponse(file_path)
|
||||
|
||||
headers = {}
|
||||
headers["Authorization"] = f"Bearer {app.state.OPENAI_API_KEY}"
|
||||
headers["Content-Type"] = "application/json"
|
||||
|
||||
r = None
|
||||
try:
|
||||
r = requests.post(
|
||||
url=f"{app.state.OPENAI_API_BASE_URL}/audio/speech",
|
||||
data=body,
|
||||
headers=headers,
|
||||
stream=True,
|
||||
)
|
||||
|
||||
r.raise_for_status()
|
||||
|
||||
# Save the streaming content to a file
|
||||
with open(file_path, "wb") as f:
|
||||
for chunk in r.iter_content(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
|
||||
with open(file_body_path, "w") as f:
|
||||
json.dump(json.loads(body.decode("utf-8")), f)
|
||||
|
||||
# Return the saved file
|
||||
return FileResponse(file_path)
|
||||
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
error_detail = "Open WebUI: Server Connection Error"
|
||||
if r is not None:
|
||||
try:
|
||||
res = r.json()
|
||||
if "error" in res:
|
||||
error_detail = f"External: {res['error']['message']}"
|
||||
except:
|
||||
error_detail = f"External: {e}"
|
||||
|
||||
raise HTTPException(
|
||||
status_code=r.status_code if r != None else 500,
|
||||
detail=error_detail,
|
||||
)
|
||||
|
||||
|
||||
@app.post("/transcriptions")
|
||||
def transcribe(
|
||||
file: UploadFile = File(...),
|
||||
user=Depends(get_current_user),
|
||||
@@ -64,12 +175,24 @@ def transcribe(
|
||||
f.write(contents)
|
||||
f.close()
|
||||
|
||||
model = WhisperModel(
|
||||
WHISPER_MODEL,
|
||||
device="auto",
|
||||
compute_type="int8",
|
||||
download_root=WHISPER_MODEL_DIR,
|
||||
)
|
||||
whisper_kwargs = {
|
||||
"model_size_or_path": WHISPER_MODEL,
|
||||
"device": whisper_device_type,
|
||||
"compute_type": "int8",
|
||||
"download_root": WHISPER_MODEL_DIR,
|
||||
"local_files_only": not WHISPER_MODEL_AUTO_UPDATE,
|
||||
}
|
||||
|
||||
log.debug(f"whisper_kwargs: {whisper_kwargs}")
|
||||
|
||||
try:
|
||||
model = WhisperModel(**whisper_kwargs)
|
||||
except:
|
||||
log.warning(
|
||||
"WhisperModel initialization failed, attempting download with local_files_only=False"
|
||||
)
|
||||
whisper_kwargs["local_files_only"] = False
|
||||
model = WhisperModel(**whisper_kwargs)
|
||||
|
||||
segments, info = model.transcribe(file_path, beam_size=5)
|
||||
log.info(
|
||||
|
||||
@@ -29,7 +29,15 @@ import base64
|
||||
import json
|
||||
import logging
|
||||
|
||||
from config import SRC_LOG_LEVELS, CACHE_DIR, AUTOMATIC1111_BASE_URL, COMFYUI_BASE_URL
|
||||
from config import (
|
||||
SRC_LOG_LEVELS,
|
||||
CACHE_DIR,
|
||||
ENABLE_IMAGE_GENERATION,
|
||||
AUTOMATIC1111_BASE_URL,
|
||||
COMFYUI_BASE_URL,
|
||||
OPENAI_API_BASE_URL,
|
||||
OPENAI_API_KEY,
|
||||
)
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -48,9 +56,11 @@ app.add_middleware(
|
||||
)
|
||||
|
||||
app.state.ENGINE = ""
|
||||
app.state.ENABLED = False
|
||||
app.state.ENABLED = ENABLE_IMAGE_GENERATION
|
||||
|
||||
app.state.OPENAI_API_BASE_URL = OPENAI_API_BASE_URL
|
||||
app.state.OPENAI_API_KEY = OPENAI_API_KEY
|
||||
|
||||
app.state.OPENAI_API_KEY = ""
|
||||
app.state.MODEL = ""
|
||||
|
||||
|
||||
@@ -354,7 +364,7 @@ def generate_image(
|
||||
}
|
||||
|
||||
r = requests.post(
|
||||
url=f"https://api.openai.com/v1/images/generations",
|
||||
url=f"{app.state.OPENAI_API_BASE_URL}/images/generations",
|
||||
json=data,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
@@ -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"]}
|
||||
@@ -611,8 +612,13 @@ async def generate_embeddings(
|
||||
user=Depends(get_current_user),
|
||||
):
|
||||
if url_idx == None:
|
||||
if form_data.model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[form_data.model]["urls"])
|
||||
model = form_data.model
|
||||
|
||||
if ":" not in model:
|
||||
model = f"{model}:latest"
|
||||
|
||||
if model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[model]["urls"])
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
@@ -648,6 +654,60 @@ async def generate_embeddings(
|
||||
)
|
||||
|
||||
|
||||
def generate_ollama_embeddings(
|
||||
form_data: GenerateEmbeddingsForm,
|
||||
url_idx: Optional[int] = None,
|
||||
):
|
||||
|
||||
log.info(f"generate_ollama_embeddings {form_data}")
|
||||
|
||||
if url_idx == None:
|
||||
model = form_data.model
|
||||
|
||||
if ":" not in model:
|
||||
model = f"{model}:latest"
|
||||
|
||||
if model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[model]["urls"])
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
|
||||
)
|
||||
|
||||
url = app.state.OLLAMA_BASE_URLS[url_idx]
|
||||
log.info(f"url: {url}")
|
||||
|
||||
try:
|
||||
r = requests.request(
|
||||
method="POST",
|
||||
url=f"{url}/api/embeddings",
|
||||
data=form_data.model_dump_json(exclude_none=True).encode(),
|
||||
)
|
||||
r.raise_for_status()
|
||||
|
||||
data = r.json()
|
||||
|
||||
log.info(f"generate_ollama_embeddings {data}")
|
||||
|
||||
if "embedding" in data:
|
||||
return data["embedding"]
|
||||
else:
|
||||
raise "Something went wrong :/"
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
error_detail = "Open WebUI: Server Connection Error"
|
||||
if r is not None:
|
||||
try:
|
||||
res = r.json()
|
||||
if "error" in res:
|
||||
error_detail = f"Ollama: {res['error']}"
|
||||
except:
|
||||
error_detail = f"Ollama: {e}"
|
||||
|
||||
raise error_detail
|
||||
|
||||
|
||||
class GenerateCompletionForm(BaseModel):
|
||||
model: str
|
||||
prompt: str
|
||||
@@ -671,8 +731,13 @@ async def generate_completion(
|
||||
):
|
||||
|
||||
if url_idx == None:
|
||||
if form_data.model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[form_data.model]["urls"])
|
||||
model = form_data.model
|
||||
|
||||
if ":" not in model:
|
||||
model = f"{model}:latest"
|
||||
|
||||
if model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[model]["urls"])
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
@@ -769,8 +834,13 @@ async def generate_chat_completion(
|
||||
):
|
||||
|
||||
if url_idx == None:
|
||||
if form_data.model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[form_data.model]["urls"])
|
||||
model = form_data.model
|
||||
|
||||
if ":" not in model:
|
||||
model = f"{model}:latest"
|
||||
|
||||
if model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[model]["urls"])
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
@@ -873,8 +943,13 @@ async def generate_openai_chat_completion(
|
||||
):
|
||||
|
||||
if url_idx == None:
|
||||
if form_data.model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[form_data.model]["urls"])
|
||||
model = form_data.model
|
||||
|
||||
if ":" not in model:
|
||||
model = f"{model}:latest"
|
||||
|
||||
if model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[model]["urls"])
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
|
||||
@@ -341,7 +341,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
|
||||
try:
|
||||
res = r.json()
|
||||
if "error" in res:
|
||||
error_detail = f"External: {res['error']}"
|
||||
error_detail = f"External: {res['error']['message'] if 'message' in res['error'] else res['error']}"
|
||||
except:
|
||||
error_detail = f"External: {e}"
|
||||
|
||||
|
||||
+210
-67
@@ -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,
|
||||
@@ -39,13 +39,22 @@ import uuid
|
||||
import json
|
||||
|
||||
|
||||
from apps.ollama.main import generate_ollama_embeddings, GenerateEmbeddingsForm
|
||||
|
||||
from apps.web.models.documents import (
|
||||
Documents,
|
||||
DocumentForm,
|
||||
DocumentResponse,
|
||||
)
|
||||
|
||||
from apps.rag.utils import query_doc, query_collection
|
||||
from apps.rag.utils import (
|
||||
query_doc,
|
||||
query_embeddings_doc,
|
||||
query_collection,
|
||||
query_embeddings_collection,
|
||||
get_embedding_model_path,
|
||||
generate_openai_embeddings,
|
||||
)
|
||||
|
||||
from utils.misc import (
|
||||
calculate_sha256,
|
||||
@@ -58,8 +67,12 @@ from config import (
|
||||
SRC_LOG_LEVELS,
|
||||
UPLOAD_DIR,
|
||||
DOCS_DIR,
|
||||
RAG_EMBEDDING_ENGINE,
|
||||
RAG_EMBEDDING_MODEL,
|
||||
RAG_EMBEDDING_MODEL_DEVICE_TYPE,
|
||||
RAG_EMBEDDING_MODEL_AUTO_UPDATE,
|
||||
RAG_OPENAI_API_BASE_URL,
|
||||
RAG_OPENAI_API_KEY,
|
||||
DEVICE_TYPE,
|
||||
CHROMA_CLIENT,
|
||||
CHUNK_SIZE,
|
||||
CHUNK_OVERLAP,
|
||||
@@ -71,28 +84,30 @@ 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.TOP_K = 4
|
||||
app.state.CHUNK_SIZE = CHUNK_SIZE
|
||||
app.state.CHUNK_OVERLAP = CHUNK_OVERLAP
|
||||
app.state.RAG_TEMPLATE = RAG_TEMPLATE
|
||||
|
||||
|
||||
app.state.RAG_EMBEDDING_ENGINE = RAG_EMBEDDING_ENGINE
|
||||
app.state.RAG_EMBEDDING_MODEL = RAG_EMBEDDING_MODEL
|
||||
app.state.TOP_K = 4
|
||||
app.state.RAG_TEMPLATE = RAG_TEMPLATE
|
||||
|
||||
app.state.OPENAI_API_BASE_URL = RAG_OPENAI_API_BASE_URL
|
||||
app.state.OPENAI_API_KEY = RAG_OPENAI_API_KEY
|
||||
|
||||
app.state.PDF_EXTRACT_IMAGES = False
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -123,38 +138,80 @@ async def get_status():
|
||||
"chunk_size": app.state.CHUNK_SIZE,
|
||||
"chunk_overlap": app.state.CHUNK_OVERLAP,
|
||||
"template": app.state.RAG_TEMPLATE,
|
||||
"embedding_engine": app.state.RAG_EMBEDDING_ENGINE,
|
||||
"embedding_model": app.state.RAG_EMBEDDING_MODEL,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/embedding/model")
|
||||
async def get_embedding_model(user=Depends(get_admin_user)):
|
||||
@app.get("/embedding")
|
||||
async def get_embedding_config(user=Depends(get_admin_user)):
|
||||
return {
|
||||
"status": True,
|
||||
"embedding_engine": app.state.RAG_EMBEDDING_ENGINE,
|
||||
"embedding_model": app.state.RAG_EMBEDDING_MODEL,
|
||||
"openai_config": {
|
||||
"url": app.state.OPENAI_API_BASE_URL,
|
||||
"key": app.state.OPENAI_API_KEY,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class OpenAIConfigForm(BaseModel):
|
||||
url: str
|
||||
key: str
|
||||
|
||||
|
||||
class EmbeddingModelUpdateForm(BaseModel):
|
||||
openai_config: Optional[OpenAIConfigForm] = None
|
||||
embedding_engine: str
|
||||
embedding_model: str
|
||||
|
||||
|
||||
@app.post("/embedding/model/update")
|
||||
async def update_embedding_model(
|
||||
@app.post("/embedding/update")
|
||||
async def update_embedding_config(
|
||||
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}"
|
||||
)
|
||||
try:
|
||||
app.state.RAG_EMBEDDING_ENGINE = form_data.embedding_engine
|
||||
|
||||
return {
|
||||
"status": True,
|
||||
"embedding_model": app.state.RAG_EMBEDDING_MODEL,
|
||||
}
|
||||
if app.state.RAG_EMBEDDING_ENGINE in ["ollama", "openai"]:
|
||||
app.state.RAG_EMBEDDING_MODEL = form_data.embedding_model
|
||||
app.state.sentence_transformer_ef = None
|
||||
|
||||
if form_data.openai_config != None:
|
||||
app.state.OPENAI_API_BASE_URL = form_data.openai_config.url
|
||||
app.state.OPENAI_API_KEY = form_data.openai_config.key
|
||||
else:
|
||||
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_engine": app.state.RAG_EMBEDDING_ENGINE,
|
||||
"embedding_model": app.state.RAG_EMBEDDING_MODEL,
|
||||
"openai_config": {
|
||||
"url": app.state.OPENAI_API_BASE_URL,
|
||||
"key": app.state.OPENAI_API_KEY,
|
||||
},
|
||||
}
|
||||
|
||||
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")
|
||||
@@ -239,12 +296,37 @@ def query_doc_handler(
|
||||
):
|
||||
|
||||
try:
|
||||
return query_doc(
|
||||
collection_name=form_data.collection_name,
|
||||
query=form_data.query,
|
||||
k=form_data.k if form_data.k else app.state.TOP_K,
|
||||
embedding_function=app.state.sentence_transformer_ef,
|
||||
)
|
||||
if app.state.RAG_EMBEDDING_ENGINE == "":
|
||||
return query_doc(
|
||||
collection_name=form_data.collection_name,
|
||||
query=form_data.query,
|
||||
k=form_data.k if form_data.k else app.state.TOP_K,
|
||||
embedding_function=app.state.sentence_transformer_ef,
|
||||
)
|
||||
else:
|
||||
if app.state.RAG_EMBEDDING_ENGINE == "ollama":
|
||||
query_embeddings = generate_ollama_embeddings(
|
||||
GenerateEmbeddingsForm(
|
||||
**{
|
||||
"model": app.state.RAG_EMBEDDING_MODEL,
|
||||
"prompt": form_data.query,
|
||||
}
|
||||
)
|
||||
)
|
||||
elif app.state.RAG_EMBEDDING_ENGINE == "openai":
|
||||
query_embeddings = generate_openai_embeddings(
|
||||
model=app.state.RAG_EMBEDDING_MODEL,
|
||||
text=form_data.query,
|
||||
key=app.state.OPENAI_API_KEY,
|
||||
url=app.state.OPENAI_API_BASE_URL,
|
||||
)
|
||||
|
||||
return query_embeddings_doc(
|
||||
collection_name=form_data.collection_name,
|
||||
query_embeddings=query_embeddings,
|
||||
k=form_data.k if form_data.k else app.state.TOP_K,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
raise HTTPException(
|
||||
@@ -264,12 +346,45 @@ def query_collection_handler(
|
||||
form_data: QueryCollectionsForm,
|
||||
user=Depends(get_current_user),
|
||||
):
|
||||
return query_collection(
|
||||
collection_names=form_data.collection_names,
|
||||
query=form_data.query,
|
||||
k=form_data.k if form_data.k else app.state.TOP_K,
|
||||
embedding_function=app.state.sentence_transformer_ef,
|
||||
)
|
||||
try:
|
||||
if app.state.RAG_EMBEDDING_ENGINE == "":
|
||||
return query_collection(
|
||||
collection_names=form_data.collection_names,
|
||||
query=form_data.query,
|
||||
k=form_data.k if form_data.k else app.state.TOP_K,
|
||||
embedding_function=app.state.sentence_transformer_ef,
|
||||
)
|
||||
else:
|
||||
|
||||
if app.state.RAG_EMBEDDING_ENGINE == "ollama":
|
||||
query_embeddings = generate_ollama_embeddings(
|
||||
GenerateEmbeddingsForm(
|
||||
**{
|
||||
"model": app.state.RAG_EMBEDDING_MODEL,
|
||||
"prompt": form_data.query,
|
||||
}
|
||||
)
|
||||
)
|
||||
elif app.state.RAG_EMBEDDING_ENGINE == "openai":
|
||||
query_embeddings = generate_openai_embeddings(
|
||||
model=app.state.RAG_EMBEDDING_MODEL,
|
||||
text=form_data.query,
|
||||
key=app.state.OPENAI_API_KEY,
|
||||
url=app.state.OPENAI_API_BASE_URL,
|
||||
)
|
||||
|
||||
return query_embeddings_collection(
|
||||
collection_names=form_data.collection_names,
|
||||
query_embeddings=query_embeddings,
|
||||
k=form_data.k if form_data.k else app.state.TOP_K,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT(e),
|
||||
)
|
||||
|
||||
|
||||
@app.post("/web")
|
||||
@@ -304,9 +419,11 @@ def store_data_in_vector_db(data, collection_name, overwrite: bool = False) -> b
|
||||
chunk_overlap=app.state.CHUNK_OVERLAP,
|
||||
add_start_index=True,
|
||||
)
|
||||
|
||||
docs = text_splitter.split_documents(data)
|
||||
|
||||
if len(docs) > 0:
|
||||
log.info(f"store_data_in_vector_db {docs}")
|
||||
return store_docs_in_vector_db(docs, collection_name, overwrite), None
|
||||
else:
|
||||
raise ValueError(ERROR_MESSAGES.EMPTY_CONTENT)
|
||||
@@ -325,6 +442,7 @@ def store_text_in_vector_db(
|
||||
|
||||
|
||||
def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> bool:
|
||||
log.info(f"store_docs_in_vector_db {docs} {collection_name}")
|
||||
|
||||
texts = [doc.page_content for doc in docs]
|
||||
metadatas = [doc.metadata for doc in docs]
|
||||
@@ -336,14 +454,53 @@ def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> b
|
||||
log.info(f"deleting existing collection {collection_name}")
|
||||
CHROMA_CLIENT.delete_collection(name=collection_name)
|
||||
|
||||
collection = CHROMA_CLIENT.create_collection(
|
||||
name=collection_name,
|
||||
embedding_function=app.state.sentence_transformer_ef,
|
||||
)
|
||||
if app.state.RAG_EMBEDDING_ENGINE == "":
|
||||
|
||||
collection = CHROMA_CLIENT.create_collection(
|
||||
name=collection_name,
|
||||
embedding_function=app.state.sentence_transformer_ef,
|
||||
)
|
||||
|
||||
for batch in create_batches(
|
||||
api=CHROMA_CLIENT,
|
||||
ids=[str(uuid.uuid1()) for _ in texts],
|
||||
metadatas=metadatas,
|
||||
documents=texts,
|
||||
):
|
||||
collection.add(*batch)
|
||||
|
||||
else:
|
||||
collection = CHROMA_CLIENT.create_collection(name=collection_name)
|
||||
|
||||
if app.state.RAG_EMBEDDING_ENGINE == "ollama":
|
||||
embeddings = [
|
||||
generate_ollama_embeddings(
|
||||
GenerateEmbeddingsForm(
|
||||
**{"model": app.state.RAG_EMBEDDING_MODEL, "prompt": text}
|
||||
)
|
||||
)
|
||||
for text in texts
|
||||
]
|
||||
elif app.state.RAG_EMBEDDING_ENGINE == "openai":
|
||||
embeddings = [
|
||||
generate_openai_embeddings(
|
||||
model=app.state.RAG_EMBEDDING_MODEL,
|
||||
text=text,
|
||||
key=app.state.OPENAI_API_KEY,
|
||||
url=app.state.OPENAI_API_BASE_URL,
|
||||
)
|
||||
for text in texts
|
||||
]
|
||||
|
||||
for batch in create_batches(
|
||||
api=CHROMA_CLIENT,
|
||||
ids=[str(uuid.uuid1()) for _ in texts],
|
||||
metadatas=metadatas,
|
||||
embeddings=embeddings,
|
||||
documents=texts,
|
||||
):
|
||||
collection.add(*batch)
|
||||
|
||||
collection.add(
|
||||
documents=texts, metadatas=metadatas, ids=[str(uuid.uuid1()) for _ in texts]
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@@ -448,25 +605,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 +620,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:
|
||||
|
||||
+172
-16
@@ -1,9 +1,17 @@
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from typing import List
|
||||
import requests
|
||||
|
||||
|
||||
from huggingface_hub import snapshot_download
|
||||
from apps.ollama.main import generate_ollama_embeddings, GenerateEmbeddingsForm
|
||||
|
||||
|
||||
from config import SRC_LOG_LEVELS, CHROMA_CLIENT
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.setLevel(SRC_LOG_LEVELS["RAG"])
|
||||
|
||||
@@ -24,6 +32,24 @@ def query_doc(collection_name: str, query: str, k: int, embedding_function):
|
||||
raise e
|
||||
|
||||
|
||||
def query_embeddings_doc(collection_name: str, query_embeddings, k: int):
|
||||
try:
|
||||
# if you use docker use the model from the environment variable
|
||||
log.info(f"query_embeddings_doc {query_embeddings}")
|
||||
collection = CHROMA_CLIENT.get_collection(
|
||||
name=collection_name,
|
||||
)
|
||||
result = collection.query(
|
||||
query_embeddings=[query_embeddings],
|
||||
n_results=k,
|
||||
)
|
||||
|
||||
log.info(f"query_embeddings_doc:result {result}")
|
||||
return result
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
|
||||
def merge_and_sort_query_results(query_results, k):
|
||||
# Initialize lists to store combined data
|
||||
combined_ids = []
|
||||
@@ -94,14 +120,46 @@ def query_collection(
|
||||
return merge_and_sort_query_results(results, k)
|
||||
|
||||
|
||||
def query_embeddings_collection(collection_names: List[str], query_embeddings, k: int):
|
||||
|
||||
results = []
|
||||
log.info(f"query_embeddings_collection {query_embeddings}")
|
||||
|
||||
for collection_name in collection_names:
|
||||
try:
|
||||
collection = CHROMA_CLIENT.get_collection(name=collection_name)
|
||||
|
||||
result = collection.query(
|
||||
query_embeddings=[query_embeddings],
|
||||
n_results=k,
|
||||
)
|
||||
results.append(result)
|
||||
except:
|
||||
pass
|
||||
|
||||
return merge_and_sort_query_results(results, k)
|
||||
|
||||
|
||||
def rag_template(template: str, context: str, query: str):
|
||||
template = template.replace("[context]", context)
|
||||
template = template.replace("[query]", query)
|
||||
return template
|
||||
|
||||
|
||||
def rag_messages(docs, messages, template, k, embedding_function):
|
||||
log.debug(f"docs: {docs}")
|
||||
def rag_messages(
|
||||
docs,
|
||||
messages,
|
||||
template,
|
||||
k,
|
||||
embedding_engine,
|
||||
embedding_model,
|
||||
embedding_function,
|
||||
openai_key,
|
||||
openai_url,
|
||||
):
|
||||
log.debug(
|
||||
f"docs: {docs} {messages} {embedding_engine} {embedding_model} {embedding_function} {openai_key} {openai_url}"
|
||||
)
|
||||
|
||||
last_user_message_idx = None
|
||||
for i in range(len(messages) - 1, -1, -1):
|
||||
@@ -134,22 +192,57 @@ def rag_messages(docs, messages, template, k, embedding_function):
|
||||
context = None
|
||||
|
||||
try:
|
||||
if doc["type"] == "collection":
|
||||
context = query_collection(
|
||||
collection_names=doc["collection_names"],
|
||||
query=query,
|
||||
k=k,
|
||||
embedding_function=embedding_function,
|
||||
)
|
||||
elif doc["type"] == "text":
|
||||
|
||||
if doc["type"] == "text":
|
||||
context = doc["content"]
|
||||
else:
|
||||
context = query_doc(
|
||||
collection_name=doc["collection_name"],
|
||||
query=query,
|
||||
k=k,
|
||||
embedding_function=embedding_function,
|
||||
)
|
||||
if embedding_engine == "":
|
||||
if doc["type"] == "collection":
|
||||
context = query_collection(
|
||||
collection_names=doc["collection_names"],
|
||||
query=query,
|
||||
k=k,
|
||||
embedding_function=embedding_function,
|
||||
)
|
||||
else:
|
||||
context = query_doc(
|
||||
collection_name=doc["collection_name"],
|
||||
query=query,
|
||||
k=k,
|
||||
embedding_function=embedding_function,
|
||||
)
|
||||
|
||||
else:
|
||||
if embedding_engine == "ollama":
|
||||
query_embeddings = generate_ollama_embeddings(
|
||||
GenerateEmbeddingsForm(
|
||||
**{
|
||||
"model": embedding_model,
|
||||
"prompt": query,
|
||||
}
|
||||
)
|
||||
)
|
||||
elif embedding_engine == "openai":
|
||||
query_embeddings = generate_openai_embeddings(
|
||||
model=embedding_model,
|
||||
text=query,
|
||||
key=openai_key,
|
||||
url=openai_url,
|
||||
)
|
||||
|
||||
if doc["type"] == "collection":
|
||||
context = query_embeddings_collection(
|
||||
collection_names=doc["collection_names"],
|
||||
query_embeddings=query_embeddings,
|
||||
k=k,
|
||||
)
|
||||
else:
|
||||
context = query_embeddings_doc(
|
||||
collection_name=doc["collection_name"],
|
||||
query_embeddings=query_embeddings,
|
||||
k=k,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
context = None
|
||||
@@ -188,3 +281,66 @@ 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
|
||||
|
||||
|
||||
def generate_openai_embeddings(
|
||||
model: str, text: str, key: str, url: str = "https://api.openai.com/v1"
|
||||
):
|
||||
try:
|
||||
r = requests.post(
|
||||
f"{url}/embeddings",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {key}",
|
||||
},
|
||||
json={"input": text, "model": model},
|
||||
)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
if "data" in data:
|
||||
return data["data"][0]["embedding"]
|
||||
else:
|
||||
raise "Something went wrong :/"
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Peewee migrations -- 002_add_local_sharing.py.
|
||||
|
||||
Some examples (model - class or model name)::
|
||||
|
||||
> Model = migrator.orm['table_name'] # Return model in current state by name
|
||||
> Model = migrator.ModelClass # Return model in current state by name
|
||||
|
||||
> migrator.sql(sql) # Run custom SQL
|
||||
> migrator.run(func, *args, **kwargs) # Run python function with the given args
|
||||
> migrator.create_model(Model) # Create a model (could be used as decorator)
|
||||
> migrator.remove_model(model, cascade=True) # Remove a model
|
||||
> migrator.add_fields(model, **fields) # Add fields to a model
|
||||
> migrator.change_fields(model, **fields) # Change fields
|
||||
> migrator.remove_fields(model, *field_names, cascade=True)
|
||||
> migrator.rename_field(model, old_field_name, new_field_name)
|
||||
> migrator.rename_table(model, new_table_name)
|
||||
> migrator.add_index(model, *col_names, unique=False)
|
||||
> migrator.add_not_null(model, *field_names)
|
||||
> migrator.add_default(model, field_name, default)
|
||||
> migrator.add_constraint(model, name, sql)
|
||||
> migrator.drop_index(model, *col_names)
|
||||
> migrator.drop_not_null(model, *field_names)
|
||||
> migrator.drop_constraints(model, *constraints)
|
||||
|
||||
"""
|
||||
|
||||
from contextlib import suppress
|
||||
|
||||
import peewee as pw
|
||||
from peewee_migrate import Migrator
|
||||
|
||||
|
||||
with suppress(ImportError):
|
||||
import playhouse.postgres_ext as pw_pext
|
||||
|
||||
|
||||
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your migrations here."""
|
||||
|
||||
migrator.add_fields("chat", archived=pw.BooleanField(default=False))
|
||||
|
||||
|
||||
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your rollback migrations here."""
|
||||
|
||||
migrator.remove_fields("chat", "archived")
|
||||
@@ -0,0 +1,77 @@
|
||||
"""Peewee migrations -- 002_add_local_sharing.py.
|
||||
|
||||
Some examples (model - class or model name)::
|
||||
|
||||
> Model = migrator.orm['table_name'] # Return model in current state by name
|
||||
> Model = migrator.ModelClass # Return model in current state by name
|
||||
|
||||
> migrator.sql(sql) # Run custom SQL
|
||||
> migrator.run(func, *args, **kwargs) # Run python function with the given args
|
||||
> migrator.create_model(Model) # Create a model (could be used as decorator)
|
||||
> migrator.remove_model(model, cascade=True) # Remove a model
|
||||
> migrator.add_fields(model, **fields) # Add fields to a model
|
||||
> migrator.change_fields(model, **fields) # Change fields
|
||||
> migrator.remove_fields(model, *field_names, cascade=True)
|
||||
> migrator.rename_field(model, old_field_name, new_field_name)
|
||||
> migrator.rename_table(model, new_table_name)
|
||||
> migrator.add_index(model, *col_names, unique=False)
|
||||
> migrator.add_not_null(model, *field_names)
|
||||
> migrator.add_default(model, field_name, default)
|
||||
> migrator.add_constraint(model, name, sql)
|
||||
> migrator.drop_index(model, *col_names)
|
||||
> migrator.drop_not_null(model, *field_names)
|
||||
> migrator.drop_constraints(model, *constraints)
|
||||
|
||||
"""
|
||||
|
||||
from contextlib import suppress
|
||||
|
||||
import peewee as pw
|
||||
from peewee_migrate import Migrator
|
||||
|
||||
|
||||
with suppress(ImportError):
|
||||
import playhouse.postgres_ext as pw_pext
|
||||
|
||||
|
||||
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your migrations here."""
|
||||
|
||||
# Adding fields created_at and updated_at to the 'chat' table
|
||||
migrator.add_fields(
|
||||
"chat",
|
||||
created_at=pw.DateTimeField(null=True), # Allow null for transition
|
||||
updated_at=pw.DateTimeField(null=True), # Allow null for transition
|
||||
)
|
||||
|
||||
# Populate the new fields from an existing 'timestamp' field
|
||||
migrator.sql(
|
||||
"UPDATE chat SET created_at = timestamp, updated_at = timestamp WHERE timestamp IS NOT NULL"
|
||||
)
|
||||
|
||||
# Now that the data has been copied, remove the original 'timestamp' field
|
||||
migrator.remove_fields("chat", "timestamp")
|
||||
|
||||
# Update the fields to be not null now that they are populated
|
||||
migrator.change_fields(
|
||||
"chat",
|
||||
created_at=pw.DateTimeField(null=False),
|
||||
updated_at=pw.DateTimeField(null=False),
|
||||
)
|
||||
|
||||
|
||||
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your rollback migrations here."""
|
||||
|
||||
# Recreate the timestamp field initially allowing null values for safe transition
|
||||
migrator.add_fields("chat", timestamp=pw.DateTimeField(null=True))
|
||||
|
||||
# Copy the earliest created_at date back into the new timestamp field
|
||||
# This assumes created_at was originally a copy of timestamp
|
||||
migrator.sql("UPDATE chat SET timestamp = created_at")
|
||||
|
||||
# Remove the created_at and updated_at fields
|
||||
migrator.remove_fields("chat", "created_at", "updated_at")
|
||||
|
||||
# Finally, alter the timestamp field to not allow nulls if that was the original setting
|
||||
migrator.change_fields("chat", timestamp=pw.DateTimeField(null=False))
|
||||
@@ -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
|
||||
|
||||
@@ -19,8 +19,12 @@ class Chat(Model):
|
||||
user_id = CharField()
|
||||
title = CharField()
|
||||
chat = TextField() # Save Chat JSON as Text
|
||||
timestamp = DateField()
|
||||
|
||||
created_at = DateTimeField()
|
||||
updated_at = DateTimeField()
|
||||
|
||||
share_id = CharField(null=True, unique=True)
|
||||
archived = BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
database = DB
|
||||
@@ -31,8 +35,12 @@ class ChatModel(BaseModel):
|
||||
user_id: str
|
||||
title: str
|
||||
chat: str
|
||||
timestamp: int # timestamp in epoch
|
||||
|
||||
created_at: int # timestamp in epoch
|
||||
updated_at: int # timestamp in epoch
|
||||
|
||||
share_id: Optional[str] = None
|
||||
archived: bool = False
|
||||
|
||||
|
||||
####################
|
||||
@@ -53,13 +61,17 @@ class ChatResponse(BaseModel):
|
||||
user_id: str
|
||||
title: str
|
||||
chat: dict
|
||||
timestamp: int # timestamp in epoch
|
||||
updated_at: int # timestamp in epoch
|
||||
created_at: int # timestamp in epoch
|
||||
share_id: Optional[str] = None # id of the chat to be shared
|
||||
archived: bool
|
||||
|
||||
|
||||
class ChatTitleIdResponse(BaseModel):
|
||||
id: str
|
||||
title: str
|
||||
updated_at: int
|
||||
created_at: int
|
||||
|
||||
|
||||
class ChatTable:
|
||||
@@ -77,7 +89,8 @@ class ChatTable:
|
||||
form_data.chat["title"] if "title" in form_data.chat else "New Chat"
|
||||
),
|
||||
"chat": json.dumps(form_data.chat),
|
||||
"timestamp": int(time.time()),
|
||||
"created_at": int(time.time()),
|
||||
"updated_at": int(time.time()),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -89,7 +102,7 @@ class ChatTable:
|
||||
query = Chat.update(
|
||||
chat=json.dumps(chat),
|
||||
title=chat["title"] if "title" in chat else "New Chat",
|
||||
timestamp=int(time.time()),
|
||||
updated_at=int(time.time()),
|
||||
).where(Chat.id == id)
|
||||
query.execute()
|
||||
|
||||
@@ -111,7 +124,8 @@ class ChatTable:
|
||||
"user_id": f"shared-{chat_id}",
|
||||
"title": chat.title,
|
||||
"chat": chat.chat,
|
||||
"timestamp": int(time.time()),
|
||||
"created_at": chat.created_at,
|
||||
"updated_at": int(time.time()),
|
||||
}
|
||||
)
|
||||
shared_result = Chat.create(**shared_chat.model_dump())
|
||||
@@ -163,14 +177,42 @@ class ChatTable:
|
||||
except:
|
||||
return None
|
||||
|
||||
def toggle_chat_archive_by_id(self, id: str) -> Optional[ChatModel]:
|
||||
try:
|
||||
chat = self.get_chat_by_id(id)
|
||||
query = Chat.update(
|
||||
archived=(not chat.archived),
|
||||
).where(Chat.id == id)
|
||||
|
||||
query.execute()
|
||||
|
||||
chat = Chat.get(Chat.id == id)
|
||||
return ChatModel(**model_to_dict(chat))
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_archived_chat_lists_by_user_id(
|
||||
self, user_id: str, skip: int = 0, limit: int = 50
|
||||
) -> List[ChatModel]:
|
||||
return [
|
||||
ChatModel(**model_to_dict(chat))
|
||||
for chat in Chat.select()
|
||||
.where(Chat.archived == True)
|
||||
.where(Chat.user_id == user_id)
|
||||
.order_by(Chat.updated_at.desc())
|
||||
# .limit(limit)
|
||||
# .offset(skip)
|
||||
]
|
||||
|
||||
def get_chat_lists_by_user_id(
|
||||
self, user_id: str, skip: int = 0, limit: int = 50
|
||||
) -> List[ChatModel]:
|
||||
return [
|
||||
ChatModel(**model_to_dict(chat))
|
||||
for chat in Chat.select()
|
||||
.where(Chat.archived == False)
|
||||
.where(Chat.user_id == user_id)
|
||||
.order_by(Chat.timestamp.desc())
|
||||
.order_by(Chat.updated_at.desc())
|
||||
# .limit(limit)
|
||||
# .offset(skip)
|
||||
]
|
||||
@@ -181,14 +223,15 @@ class ChatTable:
|
||||
return [
|
||||
ChatModel(**model_to_dict(chat))
|
||||
for chat in Chat.select()
|
||||
.where(Chat.archived == False)
|
||||
.where(Chat.id.in_(chat_ids))
|
||||
.order_by(Chat.timestamp.desc())
|
||||
.order_by(Chat.updated_at.desc())
|
||||
]
|
||||
|
||||
def get_all_chats(self) -> List[ChatModel]:
|
||||
return [
|
||||
ChatModel(**model_to_dict(chat))
|
||||
for chat in Chat.select().order_by(Chat.timestamp.desc())
|
||||
for chat in Chat.select().order_by(Chat.updated_at.desc())
|
||||
]
|
||||
|
||||
def get_all_chats_by_user_id(self, user_id: str) -> List[ChatModel]:
|
||||
@@ -196,7 +239,7 @@ class ChatTable:
|
||||
ChatModel(**model_to_dict(chat))
|
||||
for chat in Chat.select()
|
||||
.where(Chat.user_id == user_id)
|
||||
.order_by(Chat.timestamp.desc())
|
||||
.order_by(Chat.updated_at.desc())
|
||||
]
|
||||
|
||||
def get_chat_by_id(self, id: str) -> Optional[ChatModel]:
|
||||
@@ -206,6 +249,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:
|
||||
|
||||
@@ -47,6 +47,18 @@ async def get_user_chats(
|
||||
return Chats.get_chat_lists_by_user_id(user.id, skip, limit)
|
||||
|
||||
|
||||
############################
|
||||
# GetArchivedChats
|
||||
############################
|
||||
|
||||
|
||||
@router.get("/archived", response_model=List[ChatTitleIdResponse])
|
||||
async def get_archived_user_chats(
|
||||
user=Depends(get_current_user), skip: int = 0, limit: int = 50
|
||||
):
|
||||
return Chats.get_archived_chat_lists_by_user_id(user.id, skip, limit)
|
||||
|
||||
|
||||
############################
|
||||
# GetAllChats
|
||||
############################
|
||||
@@ -189,6 +201,23 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_
|
||||
return result
|
||||
|
||||
|
||||
############################
|
||||
# ArchiveChat
|
||||
############################
|
||||
|
||||
|
||||
@router.get("/{id}/archive", response_model=Optional[ChatResponse])
|
||||
async def archive_chat_by_id(id: str, user=Depends(get_current_user)):
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
|
||||
if chat:
|
||||
chat = Chats.toggle_chat_archive_by_id(id)
|
||||
return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()
|
||||
)
|
||||
|
||||
|
||||
############################
|
||||
# ShareChatById
|
||||
############################
|
||||
@@ -251,7 +280,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)):
|
||||
|
||||
|
||||
+111
-57
@@ -18,6 +18,51 @@ from secrets import token_bytes
|
||||
from constants import ERROR_MESSAGES
|
||||
|
||||
|
||||
####################################
|
||||
# LOGGING
|
||||
####################################
|
||||
|
||||
log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
|
||||
|
||||
GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
|
||||
if GLOBAL_LOG_LEVEL in log_levels:
|
||||
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
|
||||
else:
|
||||
GLOBAL_LOG_LEVEL = "INFO"
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
|
||||
|
||||
log_sources = [
|
||||
"AUDIO",
|
||||
"COMFYUI",
|
||||
"CONFIG",
|
||||
"DB",
|
||||
"IMAGES",
|
||||
"LITELLM",
|
||||
"MAIN",
|
||||
"MODELS",
|
||||
"OLLAMA",
|
||||
"OPENAI",
|
||||
"RAG",
|
||||
"WEBHOOK",
|
||||
]
|
||||
|
||||
SRC_LOG_LEVELS = {}
|
||||
|
||||
for source in log_sources:
|
||||
log_env_var = source + "_LOG_LEVEL"
|
||||
SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
|
||||
if SRC_LOG_LEVELS[source] not in log_levels:
|
||||
SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
|
||||
log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
|
||||
|
||||
log.setLevel(SRC_LOG_LEVELS["CONFIG"])
|
||||
|
||||
####################################
|
||||
# Load .env file
|
||||
####################################
|
||||
|
||||
try:
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
|
||||
@@ -28,8 +73,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,47 +146,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 = {}
|
||||
|
||||
####################################
|
||||
# LOGGING
|
||||
# Static DIR
|
||||
####################################
|
||||
log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
|
||||
|
||||
GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
|
||||
if GLOBAL_LOG_LEVEL in log_levels:
|
||||
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
|
||||
else:
|
||||
GLOBAL_LOG_LEVEL = "INFO"
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
|
||||
|
||||
log_sources = [
|
||||
"AUDIO",
|
||||
"COMFYUI",
|
||||
"CONFIG",
|
||||
"DB",
|
||||
"IMAGES",
|
||||
"LITELLM",
|
||||
"MAIN",
|
||||
"MODELS",
|
||||
"OLLAMA",
|
||||
"OPENAI",
|
||||
"RAG",
|
||||
"WEBHOOK",
|
||||
]
|
||||
|
||||
SRC_LOG_LEVELS = {}
|
||||
|
||||
for source in log_sources:
|
||||
log_env_var = source + "_LOG_LEVEL"
|
||||
SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
|
||||
if SRC_LOG_LEVELS[source] not in log_levels:
|
||||
SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
|
||||
log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
|
||||
|
||||
log.setLevel(SRC_LOG_LEVELS["CONFIG"])
|
||||
STATIC_DIR = str(Path(os.getenv("STATIC_DIR", "./static")).resolve())
|
||||
|
||||
shutil.copyfile(f"{FRONTEND_BUILD_DIR}/favicon.png", f"{STATIC_DIR}/favicon.png")
|
||||
|
||||
####################################
|
||||
# CUSTOM_NAME
|
||||
@@ -165,7 +187,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 +199,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 +267,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 +277,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"
|
||||
|
||||
@@ -306,6 +321,13 @@ OPENAI_API_BASE_URLS = [
|
||||
for url in OPENAI_API_BASE_URLS.split(";")
|
||||
]
|
||||
|
||||
OPENAI_API_KEY = ""
|
||||
OPENAI_API_KEY = OPENAI_API_KEYS[
|
||||
OPENAI_API_BASE_URLS.index("https://api.openai.com/v1")
|
||||
]
|
||||
OPENAI_API_BASE_URL = "https://api.openai.com/v1"
|
||||
|
||||
|
||||
####################################
|
||||
# WEBUI
|
||||
####################################
|
||||
@@ -390,11 +412,26 @@ 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_ENGINE = os.environ.get("RAG_EMBEDDING_ENGINE", "")
|
||||
|
||||
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 embedding 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),
|
||||
@@ -417,17 +454,34 @@ And answer according to the language of the user's question.
|
||||
Given the context information, answer the query.
|
||||
Query: [query]"""
|
||||
|
||||
RAG_OPENAI_API_BASE_URL = os.getenv("RAG_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL)
|
||||
RAG_OPENAI_API_KEY = os.getenv("RAG_OPENAI_API_KEY", OPENAI_API_KEY)
|
||||
|
||||
####################################
|
||||
# Transcribe
|
||||
####################################
|
||||
|
||||
WHISPER_MODEL = os.getenv("WHISPER_MODEL", "base")
|
||||
WHISPER_MODEL_DIR = os.getenv("WHISPER_MODEL_DIR", f"{CACHE_DIR}/whisper/models")
|
||||
WHISPER_MODEL_AUTO_UPDATE = (
|
||||
os.environ.get("WHISPER_MODEL_AUTO_UPDATE", "").lower() == "true"
|
||||
)
|
||||
|
||||
|
||||
####################################
|
||||
# Images
|
||||
####################################
|
||||
|
||||
ENABLE_IMAGE_GENERATION = (
|
||||
os.environ.get("ENABLE_IMAGE_GENERATION", "").lower() == "true"
|
||||
)
|
||||
AUTOMATIC1111_BASE_URL = os.getenv("AUTOMATIC1111_BASE_URL", "")
|
||||
COMFYUI_BASE_URL = os.getenv("COMFYUI_BASE_URL", "")
|
||||
|
||||
|
||||
####################################
|
||||
# Audio
|
||||
####################################
|
||||
|
||||
AUDIO_OPENAI_API_BASE_URL = os.getenv("AUDIO_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL)
|
||||
AUDIO_OPENAI_API_KEY = os.getenv("AUDIO_OPENAI_API_KEY", OPENAI_API_KEY)
|
||||
|
||||
+19
-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,
|
||||
@@ -110,7 +114,11 @@ class RAGMiddleware(BaseHTTPMiddleware):
|
||||
data["messages"],
|
||||
rag_app.state.RAG_TEMPLATE,
|
||||
rag_app.state.TOP_K,
|
||||
rag_app.state.RAG_EMBEDDING_ENGINE,
|
||||
rag_app.state.RAG_EMBEDDING_MODEL,
|
||||
rag_app.state.sentence_transformer_ef,
|
||||
rag_app.state.RAG_OPENAI_API_KEY,
|
||||
rag_app.state.RAG_OPENAI_API_BASE_URL,
|
||||
)
|
||||
del data["docs"]
|
||||
|
||||
@@ -269,14 +277,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 +307,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.
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 11 KiB |
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.120",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "open-webui",
|
||||
"version": "0.1.117",
|
||||
"version": "0.1.120",
|
||||
"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.120",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev --host",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<link rel="manifest" href="%sveltekit.assets%/manifest.json" />
|
||||
<link rel="manifest" href="%sveltekit.assets%/manifest.json" crossorigin="use-credentials" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<script>
|
||||
|
||||
+100
-1
@@ -1,11 +1,73 @@
|
||||
import { AUDIO_API_BASE_URL } from '$lib/constants';
|
||||
|
||||
export const getAudioConfig = async (token: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${AUDIO_API_BASE_URL}/config`, {
|
||||
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 OpenAIConfigForm = {
|
||||
url: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
export const updateAudioConfig = async (token: string, payload: OpenAIConfigForm) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${AUDIO_API_BASE_URL}/config/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;
|
||||
};
|
||||
|
||||
export const transcribeAudio = async (token: string, file: File) => {
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
|
||||
let error = null;
|
||||
const res = await fetch(`${AUDIO_API_BASE_URL}/transcribe`, {
|
||||
const res = await fetch(`${AUDIO_API_BASE_URL}/transcriptions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
@@ -29,3 +91,40 @@ export const transcribeAudio = async (token: string, file: File) => {
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const synthesizeOpenAISpeech = async (
|
||||
token: string = '',
|
||||
speaker: string = 'alloy',
|
||||
text: string = ''
|
||||
) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${AUDIO_API_BASE_URL}/speech`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'tts-1',
|
||||
input: text,
|
||||
voice: speaker
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.log(err);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -62,6 +62,37 @@ export const getChatList = async (token: string = '') => {
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getArchivedChatList = async (token: string = '') => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/archived`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
...(token && { authorization: `Bearer ${token}` })
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.then((json) => {
|
||||
return json;
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err;
|
||||
console.log(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getAllChats = async (token: string) => {
|
||||
let error = null;
|
||||
|
||||
@@ -282,6 +313,38 @@ export const shareChatById = async (token: string, id: string) => {
|
||||
return res;
|
||||
};
|
||||
|
||||
export const archiveChatById = async (token: string, id: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/archive`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
...(token && { authorization: `Bearer ${token}` })
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.then((json) => {
|
||||
return json;
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err;
|
||||
|
||||
console.log(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const deleteSharedChatById = async (token: string, id: string) => {
|
||||
let error = null;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { OLLAMA_API_BASE_URL } from '$lib/constants';
|
||||
import { promptTemplate } from '$lib/utils';
|
||||
|
||||
export const getOllamaUrls = async (token: string = '') => {
|
||||
let error = null;
|
||||
@@ -144,7 +145,7 @@ export const generateTitle = async (
|
||||
) => {
|
||||
let error = null;
|
||||
|
||||
template = template.replace(/{{prompt}}/g, prompt);
|
||||
template = promptTemplate(template, prompt);
|
||||
|
||||
console.log(template);
|
||||
|
||||
@@ -219,6 +220,32 @@ export const generatePrompt = async (token: string = '', model: string, conversa
|
||||
return res;
|
||||
};
|
||||
|
||||
export const generateEmbeddings = async (token: string = '', model: string, text: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${OLLAMA_API_BASE_URL}/api/embeddings`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: model,
|
||||
prompt: text
|
||||
})
|
||||
}).catch((err) => {
|
||||
error = err;
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const generateTextCompletion = async (token: string = '', model: string, text: string) => {
|
||||
let error = null;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { OPENAI_API_BASE_URL } from '$lib/constants';
|
||||
import { promptTemplate } from '$lib/utils';
|
||||
|
||||
export const getOpenAIUrls = async (token: string = '') => {
|
||||
let error = null;
|
||||
@@ -273,7 +274,7 @@ export const generateTitle = async (
|
||||
) => {
|
||||
let error = null;
|
||||
|
||||
template = template.replace(/{{prompt}}/g, prompt);
|
||||
template = promptTemplate(template, prompt);
|
||||
|
||||
console.log(template);
|
||||
|
||||
|
||||
@@ -345,3 +345,71 @@ export const resetVectorDB = async (token: string) => {
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getEmbeddingConfig = async (token: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${RAG_API_BASE_URL}/embedding`, {
|
||||
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 OpenAIConfigForm = {
|
||||
key: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
type EmbeddingModelUpdateForm = {
|
||||
openai_config?: OpenAIConfigForm;
|
||||
embedding_engine: string;
|
||||
embedding_model: string;
|
||||
};
|
||||
|
||||
export const updateEmbeddingConfig = async (token: string, payload: EmbeddingModelUpdateForm) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${RAG_API_BASE_URL}/embedding/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;
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<button
|
||||
class="self-center"
|
||||
on:click={() => {
|
||||
localStorage.version = $config.version;
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
@@ -321,27 +328,28 @@
|
||||
];
|
||||
};
|
||||
|
||||
const inputFiles = e.dataTransfer?.files;
|
||||
const inputFiles = Array.from(e.dataTransfer?.files);
|
||||
|
||||
if (inputFiles && inputFiles.length > 0) {
|
||||
const file = inputFiles[0];
|
||||
console.log(file, file.name.split('.').at(-1));
|
||||
if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
|
||||
reader.readAsDataURL(file);
|
||||
} else if (
|
||||
SUPPORTED_FILE_TYPE.includes(file['type']) ||
|
||||
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
|
||||
) {
|
||||
uploadDoc(file);
|
||||
} else {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
|
||||
{ file_type: file['type'] }
|
||||
)
|
||||
);
|
||||
uploadDoc(file);
|
||||
}
|
||||
inputFiles.forEach((file) => {
|
||||
console.log(file, file.name.split('.').at(-1));
|
||||
if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
|
||||
reader.readAsDataURL(file);
|
||||
} else if (
|
||||
SUPPORTED_FILE_TYPE.includes(file['type']) ||
|
||||
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
|
||||
) {
|
||||
uploadDoc(file);
|
||||
} else {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
|
||||
{ file_type: file['type'] }
|
||||
)
|
||||
);
|
||||
uploadDoc(file);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
toast.error($i18n.t(`File not found.`));
|
||||
}
|
||||
@@ -350,11 +358,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);
|
||||
@@ -456,6 +468,7 @@
|
||||
bind:files={inputFiles}
|
||||
type="file"
|
||||
hidden
|
||||
multiple
|
||||
on:change={async () => {
|
||||
let reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
@@ -471,25 +484,27 @@
|
||||
};
|
||||
|
||||
if (inputFiles && inputFiles.length > 0) {
|
||||
const file = inputFiles[0];
|
||||
if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
|
||||
reader.readAsDataURL(file);
|
||||
} else if (
|
||||
SUPPORTED_FILE_TYPE.includes(file['type']) ||
|
||||
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
|
||||
) {
|
||||
uploadDoc(file);
|
||||
filesInputElement.value = '';
|
||||
} else {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
|
||||
{ file_type: file['type'] }
|
||||
)
|
||||
);
|
||||
uploadDoc(file);
|
||||
filesInputElement.value = '';
|
||||
}
|
||||
const _inputFiles = Array.from(inputFiles);
|
||||
_inputFiles.forEach((file) => {
|
||||
if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
|
||||
reader.readAsDataURL(file);
|
||||
} else if (
|
||||
SUPPORTED_FILE_TYPE.includes(file['type']) ||
|
||||
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
|
||||
) {
|
||||
uploadDoc(file);
|
||||
filesInputElement.value = '';
|
||||
} else {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
|
||||
{ file_type: file['type'] }
|
||||
)
|
||||
);
|
||||
uploadDoc(file);
|
||||
filesInputElement.value = '';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
toast.error($i18n.t(`File not found.`));
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import Placeholder from './Messages/Placeholder.svelte';
|
||||
import Spinner from '../common/Spinner.svelte';
|
||||
import { imageGenerations } from '$lib/apis/images';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
@@ -42,40 +43,11 @@
|
||||
element.scrollTop = element.scrollHeight;
|
||||
};
|
||||
|
||||
const copyToClipboard = (text) => {
|
||||
if (!navigator.clipboard) {
|
||||
var textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
|
||||
// Avoid scrolling to bottom
|
||||
textArea.style.top = '0';
|
||||
textArea.style.left = '0';
|
||||
textArea.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
var successful = document.execCommand('copy');
|
||||
var msg = successful ? 'successful' : 'unsuccessful';
|
||||
console.log('Fallback: Copying text command was ' + msg);
|
||||
} catch (err) {
|
||||
console.error('Fallback: Oops, unable to copy', err);
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
return;
|
||||
const copyToClipboardWithToast = async (text) => {
|
||||
const res = await copyToClipboard(text);
|
||||
if (res) {
|
||||
toast.success($i18n.t('Copying to clipboard was successful!'));
|
||||
}
|
||||
navigator.clipboard.writeText(text).then(
|
||||
function () {
|
||||
console.log('Async: Copying to clipboard was successful!');
|
||||
toast.success($i18n.t('Copying to clipboard was successful!'));
|
||||
},
|
||||
function (err) {
|
||||
console.error('Async: Could not copy text: ', err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const confirmEditMessage = async (messageId, content) => {
|
||||
@@ -107,12 +79,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 +89,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) => {
|
||||
@@ -329,7 +302,7 @@
|
||||
{confirmEditMessage}
|
||||
{showPreviousMessage}
|
||||
{showNextMessage}
|
||||
{copyToClipboard}
|
||||
copyToClipboard={copyToClipboardWithToast}
|
||||
/>
|
||||
{:else}
|
||||
<ResponseMessage
|
||||
@@ -338,11 +311,12 @@
|
||||
siblings={history.messages[message.parentId]?.childrenIds ?? []}
|
||||
isLastMessage={messageIdx + 1 === messages.length}
|
||||
{readOnly}
|
||||
{updateChatMessages}
|
||||
{confirmEditResponseMessage}
|
||||
{showPreviousMessage}
|
||||
{showNextMessage}
|
||||
{rateMessage}
|
||||
{copyToClipboard}
|
||||
copyToClipboard={copyToClipboardWithToast}
|
||||
{continueGeneration}
|
||||
{regenerateResponse}
|
||||
on:save={async (e) => {
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { createEventDispatcher, onMount, getContext } from 'svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let messageId = null;
|
||||
export let show = false;
|
||||
export let message;
|
||||
|
||||
let LIKE_REASONS = [];
|
||||
let DISLIKE_REASONS = [];
|
||||
|
||||
function loadReasons() {
|
||||
LIKE_REASONS = [
|
||||
$i18n.t('Accurate information'),
|
||||
$i18n.t('Followed instructions perfectly'),
|
||||
$i18n.t('Showcased creativity'),
|
||||
$i18n.t('Positive attitude'),
|
||||
$i18n.t('Attention to detail'),
|
||||
$i18n.t('Thorough explanation'),
|
||||
$i18n.t('Other')
|
||||
];
|
||||
|
||||
DISLIKE_REASONS = [
|
||||
$i18n.t("Don't like the style"),
|
||||
$i18n.t('Not factually correct'),
|
||||
$i18n.t("Didn't fully follow instructions"),
|
||||
$i18n.t("Refused when it shouldn't have"),
|
||||
$i18n.t('Being lazy'),
|
||||
$i18n.t('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;
|
||||
loadReasons();
|
||||
});
|
||||
|
||||
const submitHandler = () => {
|
||||
console.log('submitHandler');
|
||||
|
||||
message.annotation.reason = selectedReason;
|
||||
message.annotation.comment = comment;
|
||||
|
||||
dispatch('submit');
|
||||
|
||||
toast.success($i18n.t('Thanks for your feedback!'));
|
||||
show = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<div
|
||||
class=" my-2.5 rounded-xl px-4 py-3 border dark:border-gray-850"
|
||||
id="message-feedback-{messageId}"
|
||||
>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class=" text-sm">{$i18n.t('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 hover:bg-gray-100 dark:hover:bg-gray-850 {selectedReason ===
|
||||
reason
|
||||
? 'bg-gray-200 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={$i18n.t('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>
|
||||
@@ -15,9 +15,10 @@
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
import { config, settings } from '$lib/stores';
|
||||
import { synthesizeOpenAISpeech } from '$lib/apis/openai';
|
||||
import { synthesizeOpenAISpeech } from '$lib/apis/audio';
|
||||
import { imageGenerations } from '$lib/apis/images';
|
||||
import {
|
||||
approximateToHumanReadable,
|
||||
extractSentences,
|
||||
revertSanitizedResponseContent,
|
||||
sanitizeResponseContent
|
||||
@@ -30,6 +31,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 +41,7 @@
|
||||
|
||||
export let readOnly = false;
|
||||
|
||||
export let updateChatMessages: Function;
|
||||
export let confirmEditResponseMessage: Function;
|
||||
export let showPreviousMessage: Function;
|
||||
export let showNextMessage: Function;
|
||||
@@ -60,6 +63,8 @@
|
||||
let loadingSpeech = false;
|
||||
let generatingImage = false;
|
||||
|
||||
let showRateComment = false;
|
||||
|
||||
$: tokens = marked.lexer(sanitizeResponseContent(message.content));
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
@@ -118,7 +123,10 @@
|
||||
eval_count: ${message.info.eval_count ?? 'N/A'}<br/>
|
||||
eval_duration: ${
|
||||
Math.round(((message.info.eval_duration ?? 0) / 1000000) * 100) / 100 ?? 'N/A'
|
||||
}ms</span>`,
|
||||
}ms<br/>
|
||||
approximate_total: ${approximateToHumanReadable(
|
||||
message.info.total_duration
|
||||
)}</span>`,
|
||||
allowHTML: true
|
||||
});
|
||||
}
|
||||
@@ -168,10 +176,12 @@
|
||||
|
||||
const toggleSpeakMessage = async () => {
|
||||
if (speaking) {
|
||||
speechSynthesis.cancel();
|
||||
try {
|
||||
speechSynthesis.cancel();
|
||||
|
||||
sentencesAudio[speakingIdx].pause();
|
||||
sentencesAudio[speakingIdx].currentTime = 0;
|
||||
sentencesAudio[speakingIdx].pause();
|
||||
sentencesAudio[speakingIdx].currentTime = 0;
|
||||
} catch {}
|
||||
|
||||
speaking = null;
|
||||
speakingIdx = null;
|
||||
@@ -213,6 +223,10 @@
|
||||
sentence
|
||||
).catch((error) => {
|
||||
toast.error(error);
|
||||
|
||||
speaking = null;
|
||||
loadingSpeech = false;
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -222,7 +236,6 @@
|
||||
const audio = new Audio(blobUrl);
|
||||
sentencesAudio[idx] = audio;
|
||||
loadingSpeech = false;
|
||||
|
||||
lastPlayedAudioPromise = lastPlayedAudioPromise.then(() => playAudio(idx));
|
||||
}
|
||||
}
|
||||
@@ -536,11 +549,19 @@
|
||||
<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;
|
||||
|
||||
window.setTimeout(() => {
|
||||
document
|
||||
.getElementById(`message-feedback-${message.id}`)
|
||||
?.scrollIntoView();
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
@@ -563,11 +584,18 @@
|
||||
<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;
|
||||
window.setTimeout(() => {
|
||||
document
|
||||
.getElementById(`message-feedback-${message.id}`)
|
||||
?.scrollIntoView();
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
@@ -824,6 +852,17 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showRateComment}
|
||||
<RateComment
|
||||
messageId={message.id}
|
||||
bind:show={showRateComment}
|
||||
bind:message
|
||||
on:submit={() => {
|
||||
updateChatMessages();
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -176,10 +176,23 @@
|
||||
e.target.style.height = '';
|
||||
e.target.style.height = `${e.target.scrollHeight}px`;
|
||||
}}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
document.getElementById('close-edit-message-button')?.click();
|
||||
}
|
||||
|
||||
const isCmdOrCtrlPressed = e.metaKey || e.ctrlKey;
|
||||
const isEnterPressed = e.key === 'Enter';
|
||||
|
||||
if (isCmdOrCtrlPressed && isEnterPressed) {
|
||||
document.getElementById('save-edit-message-button')?.click();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class=" mt-2 mb-1 flex justify-center space-x-2 text-sm font-medium">
|
||||
<button
|
||||
id="save-edit-message-button"
|
||||
class="px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded-lg"
|
||||
on:click={() => {
|
||||
editMessageConfirmHandler();
|
||||
@@ -189,6 +202,7 @@
|
||||
</button>
|
||||
|
||||
<button
|
||||
id="close-edit-message-button"
|
||||
class=" px-4 py-2 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-100 transition outline outline-1 outline-gray-200 dark:outline-gray-600 rounded-lg"
|
||||
on:click={() => {
|
||||
cancelEditMessage();
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
: items;
|
||||
|
||||
const pullModelHandler = async () => {
|
||||
const sanitizedModelTag = searchValue.trim();
|
||||
const sanitizedModelTag = searchValue.trim().replace(/^ollama\s+(run|pull)\s+/, '');
|
||||
|
||||
console.log($MODEL_DOWNLOAD_POOL);
|
||||
if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]) {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { getAudioConfig, updateAudioConfig } from '$lib/apis/audio';
|
||||
import { user } from '$lib/stores';
|
||||
import { createEventDispatcher, onMount, getContext } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
const dispatch = createEventDispatcher();
|
||||
@@ -9,6 +11,9 @@
|
||||
|
||||
// Audio
|
||||
|
||||
let OpenAIUrl = '';
|
||||
let OpenAIKey = '';
|
||||
|
||||
let STTEngines = ['', 'openai'];
|
||||
let STTEngine = '';
|
||||
|
||||
@@ -69,6 +74,18 @@
|
||||
saveSettings({ speechAutoSend: speechAutoSend });
|
||||
};
|
||||
|
||||
const updateConfigHandler = async () => {
|
||||
const res = await updateAudioConfig(localStorage.token, {
|
||||
url: OpenAIUrl,
|
||||
key: OpenAIKey
|
||||
});
|
||||
|
||||
if (res) {
|
||||
OpenAIUrl = res.OPENAI_API_BASE_URL;
|
||||
OpenAIKey = res.OPENAI_API_KEY;
|
||||
}
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
||||
|
||||
@@ -85,12 +102,24 @@
|
||||
} else {
|
||||
getWebAPIVoices();
|
||||
}
|
||||
|
||||
if ($user.role === 'admin') {
|
||||
const res = await getAudioConfig(localStorage.token);
|
||||
|
||||
if (res) {
|
||||
OpenAIUrl = res.OPENAI_API_BASE_URL;
|
||||
OpenAIKey = res.OPENAI_API_KEY;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<form
|
||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||
on:submit|preventDefault={() => {
|
||||
on:submit|preventDefault={async () => {
|
||||
if ($user.role === 'admin') {
|
||||
await updateConfigHandler();
|
||||
}
|
||||
saveSettings({
|
||||
audio: {
|
||||
STTEngine: STTEngine !== '' ? STTEngine : undefined,
|
||||
@@ -101,7 +130,7 @@
|
||||
dispatch('save');
|
||||
}}
|
||||
>
|
||||
<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-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
|
||||
|
||||
@@ -196,6 +225,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $user.role === 'admin'}
|
||||
{#if TTSEngine === 'openai'}
|
||||
<div class="mt-1 flex gap-2 mb-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={OpenAIUrl}
|
||||
required
|
||||
/>
|
||||
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
placeholder={$i18n.t('API Key')}
|
||||
bind:value={OpenAIKey}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Auto-playback response')}</div>
|
||||
|
||||
@@ -223,7 +272,7 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<select
|
||||
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"
|
||||
bind:value={speaker}
|
||||
placeholder="Select a voice"
|
||||
>
|
||||
@@ -241,16 +290,18 @@
|
||||
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<select
|
||||
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||
<input
|
||||
list="voice-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
bind:value={speaker}
|
||||
placeholder="Select a voice"
|
||||
>
|
||||
/>
|
||||
|
||||
<datalist id="voice-list">
|
||||
{#each voices as voice}
|
||||
<option value={voice.name} class="bg-gray-100 dark:bg-gray-700">{voice.name}</option
|
||||
>
|
||||
<option value={voice.name} />
|
||||
{/each}
|
||||
</select>
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -288,20 +288,20 @@
|
||||
<div class="flex border-b dark:border-gray-600 w-full">
|
||||
<input
|
||||
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
|
||||
placeholder="Title (e.g. Tell me a fun fact)"
|
||||
placeholder={$i18n.t('Title (e.g. Tell me a fun fact)')}
|
||||
bind:value={prompt.title[0]}
|
||||
/>
|
||||
|
||||
<input
|
||||
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
|
||||
placeholder="Subtitle (e.g. about the Roman Empire)"
|
||||
placeholder={$i18n.t('Subtitle (e.g. about the Roman Empire)')}
|
||||
bind:value={prompt.title[1]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
|
||||
placeholder="Prompt (e.g. Tell me a fun fact about the Roman Empire)"
|
||||
placeholder={$i18n.t('Prompt (e.g. Tell me a fun fact about the Roman Empire)')}
|
||||
bind:value={prompt.content}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
};
|
||||
|
||||
const pullModelHandler = async () => {
|
||||
const sanitizedModelTag = modelTag.trim();
|
||||
const sanitizedModelTag = modelTag.trim().replace(/^ollama\s+(run|pull)\s+/, '');
|
||||
if (modelDownloadStatus[sanitizedModelTag]) {
|
||||
toast.error(
|
||||
$i18n.t(`Model '{{modelTag}}' is already in queue for downloading.`, {
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { models, settings, user } from '$lib/stores';
|
||||
|
||||
import { getOllamaModels } from '$lib/apis/ollama';
|
||||
import { getOpenAIModels } from '$lib/apis/openai';
|
||||
import { getLiteLLMModels } from '$lib/apis/litellm';
|
||||
import { getModels } from '$lib/utils';
|
||||
|
||||
import Modal from '../common/Modal.svelte';
|
||||
import Account from './Settings/Account.svelte';
|
||||
@@ -25,37 +23,11 @@
|
||||
const saveSettings = async (updated) => {
|
||||
console.log(updated);
|
||||
await settings.set({ ...$settings, ...updated });
|
||||
await models.set(await getModels());
|
||||
await models.set(await getModels(localStorage.token));
|
||||
localStorage.setItem('settings', JSON.stringify($settings));
|
||||
};
|
||||
|
||||
let selectedTab = 'general';
|
||||
|
||||
const getModels = async () => {
|
||||
let models = await Promise.all([
|
||||
await getOllamaModels(localStorage.token).catch((error) => {
|
||||
console.log(error);
|
||||
return null;
|
||||
}),
|
||||
await getOpenAIModels(localStorage.token).catch((error) => {
|
||||
console.log(error);
|
||||
return null;
|
||||
}),
|
||||
await getLiteLLMModels(localStorage.token).catch((error) => {
|
||||
console.log(error);
|
||||
return null;
|
||||
})
|
||||
]);
|
||||
|
||||
models = models
|
||||
.filter((models) => models)
|
||||
.reduce((a, e, i, arr) => a.concat(e, ...(i < arr.length - 1 ? [{ name: 'hr' }] : [])), []);
|
||||
|
||||
// models.push(...(ollamaModels ? [{ name: 'hr' }, ...ollamaModels] : []));
|
||||
// models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : []));
|
||||
// models.push(...(liteLLMModels ? [{ name: 'hr' }, ...liteLLMModels] : []));
|
||||
return models;
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal bind:show>
|
||||
|
||||
@@ -3,24 +3,27 @@
|
||||
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats';
|
||||
import { chatId, modelfiles } from '$lib/stores';
|
||||
import { modelfiles } from '$lib/stores';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
|
||||
import Modal from '../common/Modal.svelte';
|
||||
import Link from '../icons/Link.svelte';
|
||||
|
||||
export let chatId;
|
||||
|
||||
let chat = null;
|
||||
let shareUrl = null;
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
const shareLocalChat = async () => {
|
||||
const _chat = chat;
|
||||
|
||||
const sharedChat = await shareChatById(localStorage.token, $chatId);
|
||||
const chatShareUrl = `${window.location.origin}/s/${sharedChat.id}`;
|
||||
const sharedChat = await shareChatById(localStorage.token, chatId);
|
||||
shareUrl = `${window.location.origin}/s/${sharedChat.id}`;
|
||||
console.log(shareUrl);
|
||||
chat = await getChatById(localStorage.token, chatId);
|
||||
|
||||
toast.success($i18n.t('Copied shared chat URL to clipboard!'));
|
||||
copyToClipboard(chatShareUrl);
|
||||
chat = await getChatById(localStorage.token, $chatId);
|
||||
return shareUrl;
|
||||
};
|
||||
|
||||
const shareChat = async () => {
|
||||
@@ -56,8 +59,8 @@
|
||||
|
||||
$: if (show) {
|
||||
(async () => {
|
||||
if ($chatId) {
|
||||
chat = await getChatById(localStorage.token, $chatId);
|
||||
if (chatId) {
|
||||
chat = await getChatById(localStorage.token, chatId);
|
||||
} else {
|
||||
chat = null;
|
||||
console.log(chat);
|
||||
@@ -101,10 +104,10 @@
|
||||
<button
|
||||
class="underline"
|
||||
on:click={async () => {
|
||||
const res = await deleteSharedChatById(localStorage.token, $chatId);
|
||||
const res = await deleteSharedChatById(localStorage.token, chatId);
|
||||
|
||||
if (res) {
|
||||
chat = await getChatById(localStorage.token, $chatId);
|
||||
chat = await getChatById(localStorage.token, chatId);
|
||||
}
|
||||
}}>delete this link</button
|
||||
> and create a new shared link.
|
||||
@@ -131,8 +134,12 @@
|
||||
<button
|
||||
class=" self-center flex items-center gap-1 px-3.5 py-2 rounded-xl text-sm font-medium bg-emerald-600 hover:bg-emerald-500 text-white"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
on:pointerdown={() => {
|
||||
shareLocalChat();
|
||||
}}
|
||||
on:click={async () => {
|
||||
copyToClipboard(shareUrl);
|
||||
toast.success($i18n.t('Copied shared chat URL to clipboard!'));
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<script>
|
||||
import {
|
||||
addTagById,
|
||||
deleteTagById,
|
||||
getAllChatTags,
|
||||
getTagsById,
|
||||
updateChatById
|
||||
} from '$lib/apis/chats';
|
||||
import { tags as _tags } from '$lib/stores';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import Tags from '../common/Tags.svelte';
|
||||
|
||||
export let chatId = '';
|
||||
let tags = [];
|
||||
|
||||
const getTags = async () => {
|
||||
return await getTagsById(localStorage.token, chatId).catch(async (error) => {
|
||||
return [];
|
||||
});
|
||||
};
|
||||
|
||||
const addTag = async (tagName) => {
|
||||
const res = await addTagById(localStorage.token, chatId, tagName);
|
||||
tags = await getTags();
|
||||
|
||||
await updateChatById(localStorage.token, chatId, {
|
||||
tags: tags
|
||||
});
|
||||
|
||||
_tags.set(await getAllChatTags(localStorage.token));
|
||||
};
|
||||
|
||||
const deleteTag = async (tagName) => {
|
||||
const res = await deleteTagById(localStorage.token, chatId, tagName);
|
||||
tags = await getTags();
|
||||
|
||||
await updateChatById(localStorage.token, chatId, {
|
||||
tags: tags
|
||||
});
|
||||
|
||||
_tags.set(await getAllChatTags(localStorage.token));
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
if (chatId) {
|
||||
tags = await getTags();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Tags {tags} {deleteTag} {addTag} />
|
||||
@@ -4,10 +4,12 @@
|
||||
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
|
||||
export let show = false;
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root
|
||||
bind:open={show}
|
||||
onOpenChange={(state) => {
|
||||
dispatch('change', state);
|
||||
}}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
export let show = true;
|
||||
export let size = 'md';
|
||||
|
||||
let modalElement = null;
|
||||
let mounted = false;
|
||||
|
||||
const sizeToWidth = (size) => {
|
||||
@@ -14,8 +15,17 @@
|
||||
return 'w-[16rem]';
|
||||
} else if (size === 'sm') {
|
||||
return 'w-[30rem]';
|
||||
} else {
|
||||
} else if (size === 'md') {
|
||||
return 'w-[44rem]';
|
||||
} else {
|
||||
return 'w-[48rem]';
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
console.log('Escape');
|
||||
show = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -25,8 +35,10 @@
|
||||
|
||||
$: if (mounted) {
|
||||
if (show) {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
document.body.style.overflow = 'unset';
|
||||
}
|
||||
}
|
||||
@@ -36,7 +48,8 @@
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
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"
|
||||
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-[9999] overflow-hidden overscroll-contain"
|
||||
in:fade={{ duration: 10 }}
|
||||
on:click={() => {
|
||||
show = false;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
import { Pagination } from 'bits-ui';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
import ChevronLeft from '../icons/ChevronLeft.svelte';
|
||||
import ChevronRight from '../icons/ChevronRight.svelte';
|
||||
|
||||
export let page = 0;
|
||||
export let count = 0;
|
||||
export let perPage = 20;
|
||||
</script>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<Pagination.Root bind:page {count} {perPage} let:pages>
|
||||
<div class="my-2 flex items-center">
|
||||
<Pagination.PrevButton
|
||||
class="mr-[25px] inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 active:scale-98 disabled:cursor-not-allowed disabled:text-gray-400 dark:disabled:text-gray-700 hover:disabled:bg-transparent dark:hover:disabled:bg-transparent"
|
||||
>
|
||||
<ChevronLeft className="size-4" strokeWidth="2" />
|
||||
</Pagination.PrevButton>
|
||||
<div class="flex items-center gap-2.5">
|
||||
{#each pages as page (page.key)}
|
||||
{#if page.type === 'ellipsis'}
|
||||
<div class="text-sm font-medium text-foreground-alt">...</div>
|
||||
{:else}
|
||||
<Pagination.Page
|
||||
{page}
|
||||
class="inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 text-sm font-medium hover:bg-dark-10 active:scale-98 disabled:cursor-not-allowed disabled:opacity-50 hover:disabled:bg-transparent data-[selected]:bg-black data-[selected]:text-gray-100 data-[selected]:hover:bg-black dark:data-[selected]:bg-white dark:data-[selected]:text-gray-900 dark:data-[selected]:hover:bg-white"
|
||||
>
|
||||
{page.value}
|
||||
</Pagination.Page>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<Pagination.NextButton
|
||||
class="ml-[25px] inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 active:scale-98 disabled:cursor-not-allowed disabled:text-gray-400 dark:disabled:text-gray-700 hover:disabled:bg-transparent dark:hover:disabled:bg-transparent"
|
||||
>
|
||||
<ChevronRight className="size-4" strokeWidth="2" />
|
||||
</Pagination.NextButton>
|
||||
</div>
|
||||
</Pagination.Root>
|
||||
</div>
|
||||
@@ -16,7 +16,6 @@
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let show = false;
|
||||
export let selectedDoc;
|
||||
let uploadDocInputElement: HTMLInputElement;
|
||||
let inputFiles;
|
||||
let tags = [];
|
||||
|
||||
@@ -6,21 +6,32 @@
|
||||
getQuerySettings,
|
||||
scanDocs,
|
||||
updateQuerySettings,
|
||||
resetVectorDB
|
||||
resetVectorDB,
|
||||
getEmbeddingConfig,
|
||||
updateEmbeddingConfig
|
||||
} from '$lib/apis/rag';
|
||||
|
||||
import { documents } from '$lib/stores';
|
||||
import { documents, models } 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;
|
||||
|
||||
let embeddingEngine = '';
|
||||
let embeddingModel = '';
|
||||
|
||||
let OpenAIKey = '';
|
||||
let OpenAIUrl = '';
|
||||
|
||||
let chunkSize = 0;
|
||||
let chunkOverlap = 0;
|
||||
let pdfExtractImages = true;
|
||||
@@ -31,9 +42,9 @@
|
||||
};
|
||||
|
||||
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 +52,69 @@
|
||||
}
|
||||
};
|
||||
|
||||
const embeddingModelUpdateHandler = async () => {
|
||||
if (embeddingEngine === '' && embeddingModel.split('/').length - 1 > 1) {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'Model filesystem path detected. Model shortname is required for update, cannot continue.'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (embeddingEngine === 'ollama' && embeddingModel === '') {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'Model filesystem path detected. Model shortname is required for update, cannot continue.'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (embeddingEngine === 'openai' && embeddingModel === '') {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'Model filesystem path detected. Model shortname is required for update, cannot continue.'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((embeddingEngine === 'openai' && OpenAIKey === '') || OpenAIUrl === '') {
|
||||
toast.error($i18n.t('OpenAI URL/Key required.'));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Update embedding model attempt:', embeddingModel);
|
||||
|
||||
updateEmbeddingModelLoading = true;
|
||||
const res = await updateEmbeddingConfig(localStorage.token, {
|
||||
embedding_engine: embeddingEngine,
|
||||
embedding_model: embeddingModel,
|
||||
...(embeddingEngine === 'openai'
|
||||
? {
|
||||
openai_config: {
|
||||
key: OpenAIKey,
|
||||
url: OpenAIUrl
|
||||
}
|
||||
}
|
||||
: {})
|
||||
}).catch(async (error) => {
|
||||
toast.error(error);
|
||||
await setEmbeddingConfig();
|
||||
return null;
|
||||
});
|
||||
updateEmbeddingModelLoading = false;
|
||||
|
||||
if (res) {
|
||||
console.log('embeddingModelUpdateHandler:', res);
|
||||
if (res.status === true) {
|
||||
toast.success($i18n.t('Embedding model set to "{{embedding_model}}"', res), {
|
||||
duration: 1000 * 10
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const submitHandler = async () => {
|
||||
const res = await updateRAGConfig(localStorage.token, {
|
||||
pdf_extract_images: pdfExtractImages,
|
||||
@@ -52,6 +126,18 @@
|
||||
querySettings = await updateQuerySettings(localStorage.token, querySettings);
|
||||
};
|
||||
|
||||
const setEmbeddingConfig = async () => {
|
||||
const embeddingConfig = await getEmbeddingConfig(localStorage.token);
|
||||
|
||||
if (embeddingConfig) {
|
||||
embeddingEngine = embeddingConfig.embedding_engine;
|
||||
embeddingModel = embeddingConfig.embedding_model;
|
||||
|
||||
OpenAIKey = embeddingConfig.openai_config.key;
|
||||
OpenAIUrl = embeddingConfig.openai_config.url;
|
||||
}
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
const res = await getRAGConfig(localStorage.token);
|
||||
|
||||
@@ -62,6 +148,8 @@
|
||||
chunkOverlap = res.chunk.chunk_overlap;
|
||||
}
|
||||
|
||||
await setEmbeddingConfig();
|
||||
|
||||
querySettings = await getQuerySettings(localStorage.token);
|
||||
});
|
||||
</script>
|
||||
@@ -73,264 +161,424 @@
|
||||
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>
|
||||
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Scan for documents from {{path}}', { path: '/data/docs' })}
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Embedding Model Engine')}</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
|
||||
bind:value={embeddingEngine}
|
||||
placeholder="Select an embedding model engine"
|
||||
on:change={(e) => {
|
||||
if (e.target.value === 'ollama') {
|
||||
embeddingModel = '';
|
||||
} else if (e.target.value === 'openai') {
|
||||
embeddingModel = 'text-embedding-3-small';
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="">{$i18n.t('Default (SentenceTransformer)')}</option>
|
||||
<option value="ollama">{$i18n.t('Ollama')}</option>
|
||||
<option value="openai">{$i18n.t('OpenAI')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if embeddingEngine === 'openai'}
|
||||
<div class="mt-1 flex gap-2">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={OpenAIUrl}
|
||||
required
|
||||
/>
|
||||
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
placeholder={$i18n.t('API Key')}
|
||||
bind:value={OpenAIKey}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div>
|
||||
<div class=" mb-2 text-sm font-medium">{$i18n.t('Update Embedding Model')}</div>
|
||||
|
||||
{#if embeddingEngine === 'ollama'}
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<select
|
||||
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
bind:value={embeddingModel}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
required
|
||||
>
|
||||
{#if !embeddingModel}
|
||||
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
|
||||
{/if}
|
||||
{#each $models.filter((m) => m.id && !m.external) as model}
|
||||
<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
|
||||
>{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<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={() => {
|
||||
embeddingModelUpdateHandler();
|
||||
}}
|
||||
disabled={updateEmbeddingModelLoading}
|
||||
>
|
||||
{#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
|
||||
fill-rule="evenodd"
|
||||
d="M12.416 3.376a.75.75 0 0 1 .208 1.04l-5 7.5a.75.75 0 0 1-1.154.114l-3-3a.75.75 0 0 1 1.06-1.06l2.353 2.353 4.493-6.74a.75.75 0 0 1 1.04-.207Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<input
|
||||
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>
|
||||
<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={() => {
|
||||
embeddingModelUpdateHandler();
|
||||
}}
|
||||
disabled={updateEmbeddingModelLoading}
|
||||
>
|
||||
{#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>
|
||||
{/if}
|
||||
|
||||
<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>
|
||||
|
||||
<button
|
||||
class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded flex flex-row space-x-1 items-center {loading
|
||||
? ' cursor-not-allowed'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
scanHandler();
|
||||
console.log('check');
|
||||
}}
|
||||
type="button"
|
||||
disabled={loading}
|
||||
>
|
||||
<div class="self-center font-medium">{$i18n.t('Scan')}</div>
|
||||
<hr class=" dark:border-gray-700 my-3" />
|
||||
|
||||
<!-- <svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-3 h-3"
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Scan for documents from {{path}}', { path: '/data/docs' })}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg flex flex-row space-x-1 items-center {scanDirLoading
|
||||
? ' cursor-not-allowed'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
scanHandler();
|
||||
console.log('check');
|
||||
}}
|
||||
type="button"
|
||||
disabled={scanDirLoading}
|
||||
>
|
||||
<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> -->
|
||||
<div class="self-center font-medium">{$i18n.t('Scan')}</div>
|
||||
|
||||
{#if loading}
|
||||
<div class="ml-3 self-center">
|
||||
<svg
|
||||
class=" w-3 h-3"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><style>
|
||||
.spinner_ajPY {
|
||||
transform-origin: center;
|
||||
animation: spinner_AtaB 0.75s infinite linear;
|
||||
}
|
||||
@keyframes spinner_AtaB {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
{#if scanDirLoading}
|
||||
<div class="ml-3 self-center">
|
||||
<svg
|
||||
class=" w-3 h-3"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><style>
|
||||
.spinner_ajPY {
|
||||
transform-origin: center;
|
||||
animation: spinner_AtaB 0.75s infinite linear;
|
||||
}
|
||||
}
|
||||
</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
|
||||
@keyframes spinner_AtaB {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style><path
|
||||
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||
opacity=".25"
|
||||
/><path
|
||||
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
||||
class="spinner_ajPY"
|
||||
/></svg
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<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 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>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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="flex w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit">{$i18n.t('Chunk Overlap')}</div>
|
||||
<hr class=" dark:border-gray-700 my-3" />
|
||||
|
||||
<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 Overlap')}
|
||||
bind:value={chunkOverlap}
|
||||
autocomplete="off"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class=" text-sm font-medium">{$i18n.t('Query Params')}</div>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between items-center text-xs">
|
||||
<div class=" text-xs font-medium">{$i18n.t('PDF Extract Images (OCR)')}</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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</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="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>
|
||||
|
||||
<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>
|
||||
<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"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-3.5';
|
||||
export let strokeWidth = '2.5';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={strokeWidth}
|
||||
stroke="currentColor"
|
||||
class={className}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m20.25 7.5-.625 10.632a2.25 2.25 0 0 1-2.247 2.118H6.622a2.25 2.25 0 0 1-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
export let className = 'w-4 h-4';
|
||||
export let strokeWidth = '1.5';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={strokeWidth}
|
||||
stroke="currentColor"
|
||||
class={className}
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
export let className = 'w-4 h-4';
|
||||
export let strokeWidth = '1.5';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={strokeWidth}
|
||||
stroke="currentColor"
|
||||
class={className}
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
|
||||
</svg>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
export let className = 'w-4 h-4';
|
||||
</script>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M15.75 4.5a3 3 0 1 1 .825 2.066l-8.421 4.679a3.002 3.002 0 0 1 0 1.51l8.421 4.679a3 3 0 1 1-.729 1.31l-8.421-4.678a3 3 0 1 1 0-4.132l8.421-4.679a3 3 0 0 1-.096-.755Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
@@ -19,17 +19,13 @@
|
||||
export let chat;
|
||||
export let selectedModels;
|
||||
|
||||
export let tags = [];
|
||||
export let addTag: Function;
|
||||
export let deleteTag: Function;
|
||||
|
||||
export let showModelSelector = true;
|
||||
|
||||
let showShareChatModal = false;
|
||||
let showDownloadChatModal = false;
|
||||
</script>
|
||||
|
||||
<ShareChatModal bind:show={showShareChatModal} />
|
||||
<ShareChatModal bind:show={showShareChatModal} chatId={$chatId} />
|
||||
<nav id="nav" class=" sticky py-2.5 top-0 flex flex-row justify-center z-30">
|
||||
<div
|
||||
class=" flex {$settings?.fullScreenMode ?? null ? 'max-w-full' : 'max-w-3xl'}
|
||||
@@ -85,9 +81,6 @@
|
||||
downloadHandler={() => {
|
||||
showDownloadChatModal = !showDownloadChatModal;
|
||||
}}
|
||||
{tags}
|
||||
{deleteTag}
|
||||
{addTag}
|
||||
>
|
||||
<button
|
||||
class="cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
|
||||
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||
import Tags from '$lib/components/common/Tags.svelte';
|
||||
import Tags from '$lib/components/chat/Tags.svelte';
|
||||
|
||||
import { downloadChatAsPDF } from '$lib/apis/utils';
|
||||
|
||||
export let shareEnabled: boolean = false;
|
||||
export let shareHandler: Function;
|
||||
@@ -19,13 +21,9 @@
|
||||
// export let tagHandler: Function;
|
||||
|
||||
export let chat;
|
||||
export let tags;
|
||||
export let deleteTag: Function;
|
||||
export let addTag: Function;
|
||||
|
||||
export let onClose: Function = () => {};
|
||||
|
||||
const downloadChatAsTxt = async () => {
|
||||
const downloadTxt = async () => {
|
||||
const _chat = chat.chat;
|
||||
console.log('download', chat);
|
||||
|
||||
@@ -40,54 +38,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 +166,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 +175,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>
|
||||
@@ -213,7 +186,7 @@
|
||||
<hr class="border-gray-100 dark:border-gray-800 mt-2.5 mb-1.5" />
|
||||
|
||||
<div class="flex p-1">
|
||||
<Tags {tags} {deleteTag} {addTag} />
|
||||
<Tags chatId={chat.id} />
|
||||
</div>
|
||||
|
||||
<!-- <DropdownMenu.Item
|
||||
|
||||
@@ -17,13 +17,17 @@
|
||||
getChatById,
|
||||
getChatListByTagName,
|
||||
updateChatById,
|
||||
getAllChatTags
|
||||
getAllChatTags,
|
||||
archiveChatById
|
||||
} from '$lib/apis/chats';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { fade, slide } from 'svelte/transition';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import Tooltip from '../common/Tooltip.svelte';
|
||||
import ChatMenu from './Sidebar/ChatMenu.svelte';
|
||||
import ShareChatModal from '../chat/ShareChatModal.svelte';
|
||||
import ArchiveBox from '../icons/ArchiveBox.svelte';
|
||||
import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
|
||||
|
||||
let show = false;
|
||||
let navElement;
|
||||
@@ -31,12 +35,16 @@
|
||||
let title: string = 'UI';
|
||||
let search = '';
|
||||
|
||||
let shareChatId = null;
|
||||
|
||||
let selectedChatId = null;
|
||||
|
||||
let chatDeleteId = null;
|
||||
let chatTitleEditId = null;
|
||||
let chatTitle = '';
|
||||
|
||||
let showArchivedChatsModal = false;
|
||||
let showShareChatModal = false;
|
||||
let showDropdown = false;
|
||||
let isEditing = false;
|
||||
|
||||
@@ -45,6 +53,39 @@
|
||||
show = true;
|
||||
}
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
|
||||
let touchstartX = 0;
|
||||
let touchendX = 0;
|
||||
|
||||
function checkDirection() {
|
||||
const screenWidth = window.innerWidth;
|
||||
const swipeDistance = Math.abs(touchendX - touchstartX);
|
||||
if (swipeDistance >= screenWidth / 4) {
|
||||
if (touchendX < touchstartX) {
|
||||
show = false;
|
||||
}
|
||||
if (touchendX > touchstartX) {
|
||||
show = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onTouchStart = (e) => {
|
||||
touchstartX = e.changedTouches[0].screenX;
|
||||
};
|
||||
|
||||
const onTouchEnd = (e) => {
|
||||
touchendX = e.changedTouches[0].screenX;
|
||||
checkDirection();
|
||||
};
|
||||
|
||||
document.addEventListener('touchstart', onTouchStart);
|
||||
document.addEventListener('touchend', onTouchEnd);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('touchstart', onTouchStart);
|
||||
document.removeEventListener('touchend', onTouchEnd);
|
||||
};
|
||||
});
|
||||
|
||||
// Helper function to fetch and add chat content to each chat
|
||||
@@ -101,8 +142,21 @@
|
||||
localStorage.setItem('settings', JSON.stringify($settings));
|
||||
location.href = '/';
|
||||
};
|
||||
|
||||
const archiveChatHandler = async (id) => {
|
||||
await archiveChatById(localStorage.token, id);
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
};
|
||||
</script>
|
||||
|
||||
<ShareChatModal bind:show={showShareChatModal} chatId={shareChatId} />
|
||||
<ArchivedChatsModal
|
||||
bind:show={showArchivedChatsModal}
|
||||
on:change={async () => {
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
bind:this={navElement}
|
||||
class="h-screen max-h-[100dvh] min-h-screen {show
|
||||
@@ -511,8 +565,13 @@
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex self-center space-x-1.5 z-10">
|
||||
<div class="flex self-center space-x-1 z-10">
|
||||
<ChatMenu
|
||||
chatId={chat.id}
|
||||
shareHandler={() => {
|
||||
shareChatId = selectedChatId;
|
||||
showShareChatModal = true;
|
||||
}}
|
||||
renameHandler={() => {
|
||||
chatTitle = chat.title;
|
||||
chatTitleEditId = chat.id;
|
||||
@@ -543,6 +602,18 @@
|
||||
</svg>
|
||||
</button>
|
||||
</ChatMenu>
|
||||
|
||||
<Tooltip content="Archive">
|
||||
<button
|
||||
aria-label="Archive"
|
||||
class=" self-center dark:hover:text-white transition"
|
||||
on:click={() => {
|
||||
archiveChatHandler(chat.id);
|
||||
}}
|
||||
>
|
||||
<ArchiveBox />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -575,13 +646,13 @@
|
||||
{#if showDropdown}
|
||||
<div
|
||||
id="dropdownDots"
|
||||
class="absolute z-40 bottom-[70px] 4.5rem rounded-xl shadow w-[240px] bg-white dark:bg-gray-900"
|
||||
class="absolute z-40 bottom-[70px] rounded-lg shadow w-[240px] bg-white dark:bg-gray-900"
|
||||
transition:fade|slide={{ duration: 100 }}
|
||||
>
|
||||
<div class="py-2 w-full">
|
||||
<div class="p-1 py-2 w-full">
|
||||
{#if $user.role === 'admin'}
|
||||
<button
|
||||
class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
goto('/admin');
|
||||
showDropdown = false;
|
||||
@@ -607,7 +678,7 @@
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
goto('/playground');
|
||||
showDropdown = false;
|
||||
@@ -634,7 +705,20 @@
|
||||
{/if}
|
||||
|
||||
<button
|
||||
class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
showArchivedChatsModal = true;
|
||||
showDropdown = false;
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-3">
|
||||
<ArchiveBox className="size-5" strokeWidth="1.5" />
|
||||
</div>
|
||||
<div class=" self-center font-medium">{$i18n.t('Archived Chats')}</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
on:click={async () => {
|
||||
await showSettings.set(true);
|
||||
showDropdown = false;
|
||||
@@ -665,11 +749,11 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-700 m-0 p-0" />
|
||||
<hr class=" dark:border-gray-800 m-0 p-0" />
|
||||
|
||||
<div class="py-2 w-full">
|
||||
<div class="p-1 py-2 w-full">
|
||||
<button
|
||||
class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
class="flex rounded-md py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
localStorage.removeItem('token');
|
||||
location.href = '/auth';
|
||||
@@ -706,6 +790,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="sidebar-handle"
|
||||
class="fixed left-0 top-[50dvh] -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
|
||||
>
|
||||
<Tooltip
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import dayjs from 'dayjs';
|
||||
import { getContext, createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
import Modal from '$lib/components/common/Modal.svelte';
|
||||
import { archiveChatById, deleteChatById, getArchivedChatList } from '$lib/apis/chats';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let show = false;
|
||||
|
||||
let chats = [];
|
||||
|
||||
const unarchiveChatHandler = async (chatId) => {
|
||||
const res = await archiveChatById(localStorage.token, chatId).catch((error) => {
|
||||
toast.error(error);
|
||||
});
|
||||
|
||||
chats = await getArchivedChatList(localStorage.token);
|
||||
|
||||
dispatch('change');
|
||||
};
|
||||
|
||||
const deleteChatHandler = async (chatId) => {
|
||||
const res = await deleteChatById(localStorage.token, chatId).catch((error) => {
|
||||
toast.error(error);
|
||||
});
|
||||
|
||||
chats = await getArchivedChatList(localStorage.token);
|
||||
};
|
||||
|
||||
$: if (show) {
|
||||
(async () => {
|
||||
chats = await getArchivedChatList(localStorage.token);
|
||||
})();
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal size="lg" bind:show>
|
||||
<div>
|
||||
<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
|
||||
<div class=" text-lg font-medium self-center">{$i18n.t('Archived Chats')}</div>
|
||||
<button
|
||||
class="self-center"
|
||||
on:click={() => {
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<hr class=" dark:border-gray-850" />
|
||||
|
||||
<div class="flex flex-col md:flex-row w-full px-5 py-4 md:space-x-4 dark:text-gray-200">
|
||||
<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
|
||||
{#if chats.length > 0}
|
||||
<div class="text-left text-sm w-full mb-4">
|
||||
<div class="relative overflow-x-auto">
|
||||
<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-transparent dark:text-gray-200 border-b-2 border-gray-800"
|
||||
>
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </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>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each chats as chat, idx}
|
||||
<tr
|
||||
class="bg-white {idx !== chats.length - 1 &&
|
||||
'border-b'} dark:bg-gray-900 dark:border-gray-850 text-xs"
|
||||
>
|
||||
<td class="px-3 py-1 w-2/3">
|
||||
<a href="/c/{chat.id}" target="_blank">
|
||||
<div class=" underline line-clamp-1">
|
||||
{chat.title}
|
||||
</div>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td class=" px-3 py-1">
|
||||
{dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
|
||||
</td>
|
||||
|
||||
<td class="px-3 py-1 text-right">
|
||||
<div class="flex justify-end w-full">
|
||||
<Tooltip content="Unarchive Chat">
|
||||
<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 () => {
|
||||
unarchiveChatHandler(chat.id);
|
||||
}}
|
||||
>
|
||||
<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="M9 8.25H7.5a2.25 2.25 0 0 0-2.25 2.25v9a2.25 2.25 0 0 0 2.25 2.25h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25H15m0-3-3-3m0 0-3 3m3-3V15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="Delete Chat">
|
||||
<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 () => {
|
||||
deleteChatHandler(chat.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>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- {#each chats as chat}
|
||||
<div>
|
||||
{JSON.stringify(chat)}
|
||||
</div>
|
||||
{/each} -->
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-left text-sm w-full mb-8">You have no archived conversations.</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -6,14 +6,21 @@
|
||||
import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
|
||||
import Pencil from '$lib/components/icons/Pencil.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Tags from '$lib/components/chat/Tags.svelte';
|
||||
import Share from '$lib/components/icons/Share.svelte';
|
||||
|
||||
export let shareHandler: Function;
|
||||
export let renameHandler: Function;
|
||||
export let deleteHandler: Function;
|
||||
|
||||
export let onClose: Function;
|
||||
|
||||
export let chatId = '';
|
||||
|
||||
let show = false;
|
||||
</script>
|
||||
|
||||
<Dropdown
|
||||
bind:show
|
||||
on:change={(e) => {
|
||||
if (e.detail === false) {
|
||||
onClose();
|
||||
@@ -26,14 +33,24 @@
|
||||
|
||||
<div slot="content">
|
||||
<DropdownMenu.Content
|
||||
class="w-full max-w-[130px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
|
||||
class="w-full max-w-[180px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-900 dark:text-white shadow"
|
||||
sideOffset={-2}
|
||||
side="bottom"
|
||||
align="start"
|
||||
transition={flyAndScale}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer"
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer dark:hover:bg-gray-850 rounded-md"
|
||||
on:click={() => {
|
||||
shareHandler();
|
||||
}}
|
||||
>
|
||||
<Share />
|
||||
<div class="flex items-center">Share</div>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer dark:hover:bg-gray-850 rounded-md"
|
||||
on:click={() => {
|
||||
renameHandler();
|
||||
}}
|
||||
@@ -43,7 +60,7 @@
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer"
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer dark:hover:bg-gray-850 rounded-md"
|
||||
on:click={() => {
|
||||
deleteHandler();
|
||||
}}
|
||||
@@ -51,6 +68,12 @@
|
||||
<GarbageBin strokeWidth="2" />
|
||||
<div class="flex items-center">Delete</div>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<hr class="border-gray-100 dark:border-gray-800 mt-2.5 mb-1.5" />
|
||||
|
||||
<div class="flex p-1">
|
||||
<Tags {chatId} />
|
||||
</div>
|
||||
</DropdownMenu.Content>
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'с', 'м', 'ч', 'д', 'с' или '-1' за неограничен срок.",
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' или '-1' за неограничен срок.",
|
||||
"(Beta)": "(Бета)",
|
||||
"(e.g. `sh webui.sh --api`)": "(например `sh webui.sh --api`)",
|
||||
"(latest)": "(последна)",
|
||||
@@ -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": "",
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"Click here to select": "Presiona aquí para seleccionar",
|
||||
"Click here to select documents.": "Presiona aquí para seleccionar documentos",
|
||||
"click here.": "Presiona aquí.",
|
||||
"Click on the user role button to change a user's role.": "Presiona en el botón de roles del usuario para cambiar el rol de un usuario.",
|
||||
"Click on the user role button to change a user's role.": "Presiona en el botón de roles del usuario para cambiar su rol.",
|
||||
"Close": "Cerrar",
|
||||
"Collection": "Colección",
|
||||
"Command": "Comando",
|
||||
@@ -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",
|
||||
@@ -120,9 +120,10 @@
|
||||
"Edit Doc": "Editar Documento",
|
||||
"Edit User": "Editar Usuario",
|
||||
"Email": "Email",
|
||||
"Embedding model: {{embedding_model}}": "Modelo de Embedding: {{embedding_model}}",
|
||||
"Enable Chat History": "Activa el Historial de Chat",
|
||||
"Enable New Sign Ups": "Habilitar Nuevos Registros",
|
||||
"Enabled": "Habilitado",
|
||||
"Enabled": "Activado",
|
||||
"Enter {{role}} message here": "Introduzca el mensaje {{role}} aquí",
|
||||
"Enter API Key": "Ingrese la clave API",
|
||||
"Enter Chunk Overlap": "Ingresar superposición de fragmentos",
|
||||
@@ -145,11 +146,12 @@
|
||||
"Export All Chats (All Users)": "Exportar todos los chats (Todos los usuarios)",
|
||||
"Export Chats": "Exportar Chats",
|
||||
"Export Documents Mapping": "Exportar el mapeo de documentos",
|
||||
"Export Modelfiles": "Exportal Modelfiles",
|
||||
"Export Modelfiles": "Exportar Modelfiles",
|
||||
"Export Prompts": "Exportar Prompts",
|
||||
"Failed to read clipboard contents": "No se pudo leer el contenido del portapapeles",
|
||||
"File Mode": "Modo de archivo",
|
||||
"File not found.": "Archivo no encontrado.",
|
||||
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Se detectó suplantación de huellas: No se pueden usar las iniciales como avatar. Por defecto se utiliza la imagen de perfil predeterminada.",
|
||||
"Focus chat input": "Enfoca la entrada del chat",
|
||||
"Format your variables using square brackets like this:": "Formatee sus variables usando corchetes así:",
|
||||
"From (Base Model)": "Desde (Modelo Base)",
|
||||
@@ -193,8 +195,11 @@
|
||||
"MMMM DD, YYYY": "MMMM DD, YYYY",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "El modelo '{{modelName}}' se ha descargado correctamente.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "El modelo '{{modelTag}}' ya está en cola para descargar.",
|
||||
"Model {{embedding_model}} update complete!": "¡La actualizacón del modelo {{embedding_model}} fué completada!",
|
||||
"Model {{embedding_model}} update failed or not required!": "¡La actualización del modelo {{embedding_model}} falló o no es requerida!",
|
||||
"Model {{modelId}} not found": "El modelo {{modelId}} no fue encontrado",
|
||||
"Model {{modelName}} already exists.": "El modelo {{modelName}} ya existe.",
|
||||
"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Se detectó la ruta del sistema de archivos del modelo. Se requiere el nombre corto del modelo para la actualización, no se puede continuar.",
|
||||
"Model Name": "Nombre del modelo",
|
||||
"Model not selected": "Modelo no seleccionado",
|
||||
"Model Tag Name": "Nombre de la etiqueta del modelo",
|
||||
@@ -215,11 +220,11 @@
|
||||
"New Password": "Nueva Contraseña",
|
||||
"Not sure what to add?": "¿No estás seguro de qué añadir?",
|
||||
"Not sure what to write? Switch to": "¿No estás seguro de qué escribir? Cambia a",
|
||||
"Off": "Apagado",
|
||||
"Off": "Desactivado",
|
||||
"Okay, Let's Go!": "Okay, Let's Go!",
|
||||
"Ollama Base URL": "URL base de Ollama",
|
||||
"Ollama Version": "Version de Ollama",
|
||||
"On": "Encendido",
|
||||
"On": "Activado",
|
||||
"Only": "Solamente",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "Sólo se permiten caracteres alfanuméricos y guiones en la cadena 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.": "¡Ups! ¡Agárrate fuerte! Tus archivos todavía están en el horno de procesamiento. Los estamos cocinando a la perfección. Tenga paciencia y le avisaremos una vez que estén listos.",
|
||||
@@ -227,7 +232,7 @@
|
||||
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "¡Ups! Estás utilizando un método no compatible (solo frontend). Sirve la WebUI desde el backend.",
|
||||
"Open": "Abrir",
|
||||
"Open AI": "Open AI",
|
||||
"Open AI (Dall-E)": "",
|
||||
"Open AI (Dall-E)": "Open AI (Dall-E)",
|
||||
"Open new chat": "Abrir nuevo chat",
|
||||
"OpenAI API": "OpenAI API",
|
||||
"OpenAI API Key": "Clave de OpenAI API",
|
||||
@@ -243,7 +248,7 @@
|
||||
"Prompt Content": "Contenido del Prompt",
|
||||
"Prompt suggestions": "Sugerencias de Prompts",
|
||||
"Prompts": "Prompts",
|
||||
"Pull a model from Ollama.com": "Extraer un modelo de Ollama.com",
|
||||
"Pull a model from Ollama.com": "Halar un modelo de Ollama.com",
|
||||
"Pull Progress": "Progreso de extracción",
|
||||
"Query Params": "Parámetros de consulta",
|
||||
"RAG Template": "Plantilla de RAG",
|
||||
@@ -256,7 +261,7 @@
|
||||
"Request Mode": "Modo de petición",
|
||||
"Reset Vector Storage": "Restablecer almacenamiento vectorial",
|
||||
"Response AutoCopy to Clipboard": "Copiar respuesta automáticamente al portapapeles",
|
||||
"Role": "personalizados",
|
||||
"Role": "Rol",
|
||||
"Rosé Pine": "Rosé Pine",
|
||||
"Rosé Pine Dawn": "Rosé Pine Dawn",
|
||||
"Save": "Guardar",
|
||||
@@ -332,7 +337,10 @@
|
||||
"TTS Settings": "Configuración de TTS",
|
||||
"Type Hugging Face Resolve (Download) URL": "Type Hugging Face Resolve (Download) URL",
|
||||
"Uh-oh! There was an issue connecting to {{provider}}.": "¡UH oh! Hubo un problema al conectarse a {{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!": "Comprenda que actualizar o cambiar su modelo de embedding requiere restablecer la base de datos de vectores y volver a importar todos los documentos. ¡Usted ha sido advertido!",
|
||||
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Tipo de archivo desconocido '{{file_type}}', pero se acepta y se trata como texto sin formato",
|
||||
"Update": "Actualizar",
|
||||
"Update embedding model {{embedding_model}}": "Actualizar modelo de embedding {{embedding_model}}",
|
||||
"Update password": "Actualiza contraseña",
|
||||
"Upload a GGUF model": "Sube un modelo GGUF",
|
||||
"Upload files": "Subir archivos",
|
||||
@@ -340,6 +348,7 @@
|
||||
"URL Mode": "Modo de URL",
|
||||
"Use '#' in the prompt input to load and select your documents.": "Utilice '#' en el prompt para cargar y seleccionar sus documentos.",
|
||||
"Use Gravatar": "Usar Gravatar",
|
||||
"Use Initials": "Usar Iniciales",
|
||||
"user": "usuario",
|
||||
"User Permissions": "Permisos de usuario",
|
||||
"Users": "Usuarios",
|
||||
@@ -347,7 +356,7 @@
|
||||
"Valid time units:": "Unidades válidas de tiempo:",
|
||||
"variable": "variable",
|
||||
"variable to have them replaced with clipboard content.": "variable para reemplazarlos con el contenido del portapapeles.",
|
||||
"Version": "Version",
|
||||
"Version": "Versión",
|
||||
"Web": "Web",
|
||||
"WebUI Add-ons": "WebUI Add-ons",
|
||||
"WebUI Settings": "Configuración del WebUI",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'秒', '分', '時間', '日', '週' または '-1' で無期限。",
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' または '-1' で無期限。",
|
||||
"(Beta)": "(ベータ版)",
|
||||
"(e.g. sh webui.sh --api)": "(例: sh webui.sh --api)",
|
||||
"(latest)": "(最新)",
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} を削除しました",
|
||||
"Deleted {tagName}": "{tagName} を削除しました",
|
||||
"Description": "説明",
|
||||
"Desktop Notifications": "デスクトップ通知",
|
||||
"Notifications": "デスクトップ通知",
|
||||
"Disabled": "無効",
|
||||
"Discover a modelfile": "モデルファイルを見つける",
|
||||
"Discover a prompt": "プロンプトを見つける",
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
{
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' ან '-1' ვადის გასვლისთვის.",
|
||||
"(Beta)": "(ბეტა)",
|
||||
"(e.g. `sh webui.sh --api`)": "(მაგ. `sh webui.sh --api`)",
|
||||
"(latest)": "(უახლესი)",
|
||||
"{{modelName}} is thinking...": "{{modelName}} ფიქრობს...",
|
||||
"{{webUIName}} Backend Required": "{{webUIName}} საჭიროა ბექენდი",
|
||||
"a user": "მომხმარებელი",
|
||||
"About": "შესახებ",
|
||||
"Account": "ანგარიში",
|
||||
"Action": "ქმედება",
|
||||
"Add a model": "მოდელის დამატება",
|
||||
"Add a model tag name": "მოდელის ტეგის სახელის დამატება",
|
||||
"Add a short description about what this modelfile does": "დაამატე მოკლე აღწერა იმის შესახებ, თუ რას აკეთებს ეს მოდელური ფაილი",
|
||||
"Add a short title for this prompt": "დაამატე მოკლე სათაური ამ მოთხოვნისთვის",
|
||||
"Add a tag": "დაამატე ტეგი",
|
||||
"Add Docs": "დოკუმენტის დამატება",
|
||||
"Add Files": "ფაილების დამატება",
|
||||
"Add message": "შეტყობინების დამატება",
|
||||
"add tags": "ტეგების დამატება",
|
||||
"Adjusting these settings will apply changes universally to all users.": "ამ პარამეტრების რეგულირება ცვლილებებს უნივერსალურად გამოიყენებს ყველა მომხმარებლისთვის",
|
||||
"admin": "ადმინისტრატორი",
|
||||
"Admin Panel": "ადმინ პანელი",
|
||||
"Admin Settings": "ადმინისტრატორის ხელსაწყოები",
|
||||
"Advanced Parameters": "დამატებითი პარამეტრები",
|
||||
"all": "ყველა",
|
||||
"All Users": "ყველა მომხმარებელი",
|
||||
"Allow": "ნების დართვა",
|
||||
"Allow Chat Deletion": "მიმოწერის წაშლის დაშვება",
|
||||
"alphanumeric characters and hyphens": "ალფანუმერული სიმბოლოები და დეფისები",
|
||||
"Already have an account?": "უკვე გაქვს ანგარიში?",
|
||||
"an assistant": "ასისტენტი",
|
||||
"and": "და",
|
||||
"API Base URL": "API საბაზისო URL",
|
||||
"API Key": "API გასაღები",
|
||||
"API RPM": "API RPM",
|
||||
"are allowed - Activate this command by typing": "დაშვებულია - ბრძანების გასააქტიურებლად აკრიფეთ:",
|
||||
"Are you sure?": "დარწმუნებული ხარ?",
|
||||
"Audio": "ხმოვანი",
|
||||
"Auto-playback response": "ავტომატური დაკვრის პასუხი",
|
||||
"Auto-send input after 3 sec.": "შეყვანის ავტომატური გაგზავნა 3 წამის შემდეგ ",
|
||||
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 საბაზისო მისამართი",
|
||||
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 საბაზისო მისამართი აუცილებელია",
|
||||
"available!": "ხელმისაწვდომია!",
|
||||
"Back": "უკან",
|
||||
"Builder Mode": "მოდელის შექმნა",
|
||||
"Cancel": "გაუქმება",
|
||||
"Categories": "კატეგორიები",
|
||||
"Change Password": "პაროლის შეცვლა",
|
||||
"Chat": "მიმოწერა",
|
||||
"Chat History": "მიმოწერის ისტორია",
|
||||
"Chat History is off for this browser.": "მიმოწერის ისტორია ამ ბრაუზერისთვის გათიშულია",
|
||||
"Chats": "მიმოწერები",
|
||||
"Check Again": "თავიდან შემოწმება",
|
||||
"Check for updates": "განახლებების ძიება",
|
||||
"Checking for updates...": "მიმდინარეობს განახლებების ძიება...",
|
||||
"Choose a model before saving...": "აირჩიეთ მოდელი შენახვამდე...",
|
||||
"Chunk Overlap": "გადახურვა ფრაგმენტულია",
|
||||
"Chunk Params": "გადახურვის პარამეტრები",
|
||||
"Chunk Size": "გადახურვის ზომა",
|
||||
"Click here for help.": "დახმარებისთვის, დააკლიკე აქ",
|
||||
"Click here to check other modelfiles.": "სხვა მოდელური ფაილების სანახავად, დააკლიკე აქ",
|
||||
"Click here to select": "ასარჩევად, დააკლიკე აქ",
|
||||
"Click here to select documents.": "დოკუმენტების ასარჩევად, დააკლიკე აქ",
|
||||
"click here.": "დააკლიკე აქ",
|
||||
"Click on the user role button to change a user's role.": "დააკლიკეთ მომხმარებლის როლის ღილაკს რომ შეცვალოთ მომხმარების როლი",
|
||||
"Close": "დახურვა",
|
||||
"Collection": "ნაკრები",
|
||||
"Command": "ბრძანება",
|
||||
"Confirm Password": "პაროლის დამოწმება",
|
||||
"Connections": "კავშირები",
|
||||
"Content": "კონტენტი",
|
||||
"Context Length": "კონტექსტის სიგრძე",
|
||||
"Conversation Mode": "საუბრი რეჟიმი",
|
||||
"Copy last code block": "ბოლო ბლოკის კოპირება",
|
||||
"Copy last response": "ბოლო პასუხის კოპირება",
|
||||
"Copying to clipboard was successful!": "კლავიატურაზე კოპირება წარმატებით დასრულდა",
|
||||
"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':": "შექმენით მოკლე, 3-5 სიტყვიანი ფრაზა, როგორც სათაური თქვენი შემდეგი შეკითხვისთვის, მკაცრად დაიცავით 3-5 სიტყვის ლიმიტი და მოერიდეთ გამოიყენოთ სიტყვა „სათაური“.",
|
||||
"Create a modelfile": "მოდელური ფაილის შექმნა",
|
||||
"Create Account": "ანგარიშის შექმნა",
|
||||
"Created at": "შექმნილია",
|
||||
"Created by": "ავტორი",
|
||||
"Current Model": "მიმდინარე მოდელი",
|
||||
"Current Password": "მიმდინარე პაროლი",
|
||||
"Custom": "საკუთარი",
|
||||
"Customize Ollama models for a specific purpose": "Ollama მოდელების დამუშავება სპეციფიური დანიშნულებისთვის",
|
||||
"Dark": "მუქი",
|
||||
"Database": "მონაცემთა ბაზა",
|
||||
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
|
||||
"Default": "დეფოლტი",
|
||||
"Default (Automatic1111)": "დეფოლტ (Automatic1111)",
|
||||
"Default (Web API)": "დეფოლტ (Web API)",
|
||||
"Default model updated": "დეფოლტ მოდელი განახლებულია",
|
||||
"Default Prompt Suggestions": "",
|
||||
"Default User Role": "მომხმარებლის დეფოლტ როლი",
|
||||
"delete": "წაშლა",
|
||||
"Delete a model": "მოდელის წაშლა",
|
||||
"Delete chat": "შეტყობინების წაშლა",
|
||||
"Delete Chats": "შეტყობინებების წაშლა",
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} წაშლილია",
|
||||
"Deleted {tagName}": "{tagName} წაშლილია",
|
||||
"Description": "აღწერა",
|
||||
"Notifications": "შეტყობინება",
|
||||
"Disabled": "გაუქმებულია",
|
||||
"Discover a modelfile": "აღმოაჩინეთ მოდელური ფაილი",
|
||||
"Discover a prompt": "აღმოაჩინეთ მოთხოვნა",
|
||||
"Discover, download, and explore custom prompts": "აღმოაჩინეთ, ჩამოტვირთეთ და შეისწავლეთ მორგებული მოთხოვნები",
|
||||
"Discover, download, and explore model presets": "აღმოაჩინეთ, ჩამოტვირთეთ და შეისწავლეთ მოდელის წინასწარ პარამეტრები",
|
||||
"Display the username instead of You in the Chat": "ჩატში აჩვენე მომხმარებლის სახელი თქვენს ნაცვლად",
|
||||
"Document": "დოკუმენტი",
|
||||
"Document Settings": "დოკუმენტის პარამეტრები",
|
||||
"Documents": "დოკუმენტები",
|
||||
"does not make any external connections, and your data stays securely on your locally hosted server.": "არ ამყარებს გარე კავშირებს და თქვენი მონაცემები უსაფრთხოდ რჩება თქვენს ადგილობრივ სერვერზე.",
|
||||
"Don't Allow": "არ დაუშვა",
|
||||
"Don't have an account?": "არ გაქვს ანგარიში?",
|
||||
"Download as a File": "გადმოწერე როგორც ფაილი",
|
||||
"Download Database": "გადმოწერე მონაცემთა ბაზა",
|
||||
"Drop any files here to add to the conversation": "გადაიტანეთ ფაილები აქ, რათა დაამატოთ ისინი მიმოწერაში",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "მაგალითად, '30წ', '10მ'. მოქმედი დროის ერთეულები: 'წ', 'წთ', 'სთ'.",
|
||||
"Edit Doc": "დოკუმენტის ედიტირება",
|
||||
"Edit User": "მომხმარებლის ედიტირება",
|
||||
"Email": "ელ-ფოსტა",
|
||||
"Embedding model: {{embedding_model}}": "ჩაშენების მოდელი: {{embedding_model}}",
|
||||
"Enable Chat History": "მიმოწერის ისტორიის ჩართვა",
|
||||
"Enable New Sign Ups": "ახალი რეგისტრაციების ჩართვა",
|
||||
"Enabled": "ჩართულია",
|
||||
"Enter {{role}} message here": "შეიყვანე {{role}} შეტყობინება აქ",
|
||||
"Enter API Key": "შეიყვანე API Key",
|
||||
"Enter Chunk Overlap": "შეიყვანეთ ნაწილის გადახურვა",
|
||||
"Enter Chunk Size": "შეიყვანე ბლოკის ზომა",
|
||||
"Enter Image Size (e.g. 512x512)": "შეიყვანეთ სურათის ზომა (მაგ. 512x512)",
|
||||
"Enter LiteLLM API Base URL (litellm_params.api_base)": "შეიყვანეთ LiteLLM API ბაზის მისამართი (litellm_params.api_base)",
|
||||
"Enter LiteLLM API Key (litellm_params.api_key)": "შეიყვანეთ LiteLLM API გასაღები (litellm_params.api_key)",
|
||||
"Enter LiteLLM API RPM (litellm_params.rpm)": "შეიყვანეთ LiteLLM API RPM (litellm_params.rpm)",
|
||||
"Enter LiteLLM Model (litellm_params.model)": "შეიყვანეთ LiteLLM მოდელი (litellm_params.model)",
|
||||
"Enter Max Tokens (litellm_params.max_tokens)": "შეიყვანეთ მაქსიმალური ტოკენები (litellm_params.max_tokens)",
|
||||
"Enter model tag (e.g. {{modelTag}})": "შეიყვანეთ მოდელის ტეგი (მაგ. {{modelTag}})",
|
||||
"Enter Number of Steps (e.g. 50)": "შეიყვანეთ ნაბიჯების რაოდენობა (მაგ. 50)",
|
||||
"Enter stop sequence": "შეიყვანეთ ტოპ თანმიმდევრობა",
|
||||
"Enter Top K": "შეიყვანეთ Top K",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "შეიყვანეთ მისამართი (მაგალითად http://127.0.0.1:7860/)",
|
||||
"Enter Your Email": "შეიყვანეთ თქვენი ელ-ფოსტა",
|
||||
"Enter Your Full Name": "შეიყვანეთ თქვენი სრული სახელი",
|
||||
"Enter Your Password": "შეიყვანეთ თქვენი პაროლი",
|
||||
"Experimental": "ექსპერიმენტალური",
|
||||
"Export All Chats (All Users)": "",
|
||||
"Export Chats": "მიმოწერის ექსპორტირება",
|
||||
"Export Documents Mapping": "დოკუმენტების კავშირის ექსპორტი",
|
||||
"Export Modelfiles": "მოდელური ფაილების ექსპორტი",
|
||||
"Export Prompts": "მოთხოვნების ექსპორტი",
|
||||
"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)": "(საბაზო მოდელი) დან",
|
||||
"Full Screen Mode": "Სრული ეკრანის რეჟიმი",
|
||||
"General": "ზოგადი",
|
||||
"General Settings": "ზოგადი პარამეტრები",
|
||||
"Hello, {{name}}": "გამარჯობა, {{name}}",
|
||||
"Hide": "დამალვა",
|
||||
"Hide Additional Params": "დამატებითი პარამეტრების დამალვა",
|
||||
"How can I help you today?": "როგორ შემიძლია დაგეხმარო დღეს?",
|
||||
"Image Generation (Experimental)": "სურათების გენერაცია (ექსპერიმენტული)",
|
||||
"Image Generation Engine": "სურათის გენერაციის ძრავა",
|
||||
"Image Settings": "სურათის პარამეტრები",
|
||||
"Images": "სურათები",
|
||||
"Import Chats": "მიმოწერების იმპორტი",
|
||||
"Import Documents Mapping": "დოკუმენტების კავშირის იმპორტი",
|
||||
"Import Modelfiles": "მოდელური ფაილების იმპორტი",
|
||||
"Import Prompts": "მოთხოვნების იმპორტი",
|
||||
"Include `--api` flag when running stable-diffusion-webui": "ჩართეთ `--api` დროშა stable-diffusion-webui-ის გაშვებისას",
|
||||
"Interface": "ინტერფეისი",
|
||||
"join our Discord for help.": "შეუერთდით ჩვენს Discord-ს დახმარებისთვის",
|
||||
"JSON": "JSON",
|
||||
"JWT Expiration": "JWT-ის ვადა",
|
||||
"JWT Token": "JWT ტოკენი",
|
||||
"Keep Alive": "აქტიურად დატოვება",
|
||||
"Keyboard shortcuts": "კლავიატურის მალსახმობები",
|
||||
"Language": "ენა",
|
||||
"Light": "მსუბუქი",
|
||||
"Listening...": "გისმენ...",
|
||||
"LLMs can make mistakes. Verify important information.": "შესაძლოა LLM-ებმა შეცდომები დაუშვან. გადაამოწმეთ მნიშვნელოვანი ინფორმაცია.",
|
||||
"Made by OpenWebUI Community": "დამზადებულია OpenWebUI საზოგადოების მიერ",
|
||||
"Make sure to enclose them with": "დარწმუნდით, რომ დაურთეთ ისინი",
|
||||
"Manage LiteLLM Models": "LiteLLM მოდელების მართვა",
|
||||
"Manage Models": "მოდელების მართვა",
|
||||
"Manage Ollama Models": "Ollama მოდელების მართვა",
|
||||
"Max Tokens": "მაქსიმალური ტოკენები",
|
||||
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "მაქსიმუმ 3 მოდელის ჩამოტვირთვა შესაძლებელია ერთდროულად. Გთხოვთ სცადოთ მოგვიანებით.",
|
||||
"Mirostat": "მიროსტატი",
|
||||
"Mirostat Eta": "მიროსტატი ეტა",
|
||||
"Mirostat Tau": "მიროსტატი ტაუ",
|
||||
"MMMM DD, YYYY": "თვე დღე, წელი",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "მოდელი „{{modelName}}“ წარმატებით ჩამოიტვირთა.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "მოდელი „{{modelTag}}“ უკვე ჩამოტვირთვის რიგშია.",
|
||||
"Model {{embedding_model}} update complete!": "მოდელის {{embedding_model}} განახლება დასრულდა!",
|
||||
"Model {{embedding_model}} update failed or not required!": "მოდელის {{embedding_model}} განახლება ვერ მოხერხდა ან არ არის საჭირო!",
|
||||
"Model {{modelId}} not found": "მოდელი {{modelId}} ვერ მოიძებნა",
|
||||
"Model {{modelName}} already exists.": "მოდელი {{modelName}} უკვე არსებობს.",
|
||||
"Model filesystem path detected. Model shortname is required for update, cannot continue.": "აღმოჩენილია მოდელის ფაილური სისტემის გზა. განახლებისთვის საჭიროა მოდელის მოკლე სახელი, გაგრძელება შეუძლებელია.",
|
||||
"Model Name": "მოდელის სახელი",
|
||||
"Model not selected": "მოდელი არ არის არჩეული",
|
||||
"Model Tag Name": "მოდელის ტეგის სახელი",
|
||||
"Model Whitelisting": "მოდელის თეთრ სიაში შეყვანა",
|
||||
"Model(s) Whitelisted": "მოდელ(ებ)ი თეთრ სიაშია",
|
||||
"Modelfile": "მოდელური ფაილი",
|
||||
"Modelfile Advanced Settings": "მოდელური ფაილის პარამეტრები",
|
||||
"Modelfile Content": "მოდელური ფაილის კონტენტი",
|
||||
"Modelfiles": "მოდელური ფაილები",
|
||||
"Models": "მოდელები",
|
||||
"My Documents": "ჩემი დოკუმენტები",
|
||||
"My Modelfiles": "ჩემი მოდელური ფაილები",
|
||||
"My Prompts": "ჩემი მოთხოვნები",
|
||||
"Name": "სახელი",
|
||||
"Name Tag": "სახელის ტეგი",
|
||||
"Name your modelfile": "თქვენი მოდელური ფაილის სახელი",
|
||||
"New Chat": "ახალი მიმოწერა",
|
||||
"New Password": "ახალი პაროლი",
|
||||
"Not sure what to add?": "არ იცი რა დაამატო?",
|
||||
"Not sure what to write? Switch to": "არ იცი რა დაწერო? გადართვა:",
|
||||
"Off": "გამორთვა",
|
||||
"Okay, Let's Go!": "კარგი, წავედით!",
|
||||
"Ollama Base URL": "Ollama ბაზისური მისამართი",
|
||||
"Ollama Version": "Ollama ვერსია",
|
||||
"On": "ჩართვა",
|
||||
"Only": "მხოლოდ",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "ბრძანების სტრიქონში დაშვებულია მხოლოდ ალფანუმერული სიმბოლოები და დეფისები.",
|
||||
"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.": "უპს! გამაგრდი! თქვენი ფაილები ჯერ კიდევ დამუშავების ღუმელშია. ჩვენ მათ სრულყოფილებამდე ვამზადებთ. გთხოვთ მოითმინოთ და ჩვენ შეგატყობინებთ, როგორც კი ისინი მზად იქნებიან.",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "უი! როგორც ჩანს, მისამართი არასწორია. გთხოვთ, გადაამოწმოთ და ისევ სცადოთ.",
|
||||
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "უპს! თქვენ იყენებთ მხარდაუჭერელ მეთოდს (მხოლოდ frontend). გთხოვთ, მოემსახუროთ WebUI-ს ბექენდიდან",
|
||||
"Open": "ღია",
|
||||
"Open AI": "ღია AI",
|
||||
"Open AI (Dall-E)": "Open AI (Dall-E)",
|
||||
"Open new chat": "ახალი მიმოწერის გახსნა",
|
||||
"OpenAI API": "OpenAI API",
|
||||
"OpenAI API Key": "OpenAI API გასაღები",
|
||||
"OpenAI API Key is required.": "OpenAI API გასაღები აუცილებელია",
|
||||
"or": "ან",
|
||||
"Parameters": "პარამეტრები",
|
||||
"Password": "პაროლი",
|
||||
"PDF Extract Images (OCR)": "PDF იდან ამოღებული სურათები (OCR)",
|
||||
"pending": "ლოდინის რეჟიმშია",
|
||||
"Permission denied when accessing microphone: {{error}}": "ნებართვა უარყოფილია მიკროფონზე წვდომისას: {{error}}",
|
||||
"Playground": "სათამაშო მოედანი",
|
||||
"Profile": "პროფილი",
|
||||
"Prompt Content": "მოთხოვნის შინაარსი",
|
||||
"Prompt suggestions": "მოთხოვნის რჩევები",
|
||||
"Prompts": "მოთხოვნები",
|
||||
"Pull a model from Ollama.com": "Ollama.com იდან მოდელის გადაწერა ",
|
||||
"Pull Progress": "პროგრესის გადაწერა",
|
||||
"Query Params": "პარამეტრების ძიება",
|
||||
"RAG Template": "RAG შაბლონი",
|
||||
"Raw Format": "საწყისი ფორმატი",
|
||||
"Record voice": "ხმის ჩაწერა",
|
||||
"Redirecting you to OpenWebUI Community": "გადამისამართდებით OpenWebUI საზოგადოებაში",
|
||||
"Release Notes": "Გამოშვების შენიშვნები",
|
||||
"Repeat Last N": "გაიმეორეთ ბოლო N",
|
||||
"Repeat Penalty": "გაიმეორეთ პენალტი",
|
||||
"Request Mode": "მოთხოვნის რეჟიმი",
|
||||
"Reset Vector Storage": "ვექტორული მეხსიერების გადატვირთვა",
|
||||
"Response AutoCopy to Clipboard": "პასუხის ავტომატური კოპირება ბუფერში",
|
||||
"Role": "როლი",
|
||||
"Rosé Pine": "ვარდისფერი ფიჭვის ხე",
|
||||
"Rosé Pine Dawn": "ვარდისფერი ფიჭვის გარიჟრაჟი",
|
||||
"Save": "შენახვა",
|
||||
"Save & Create": "დამახსოვრება და შექმნა",
|
||||
"Save & Submit": "დამახსოვრება და გაგზავნა",
|
||||
"Save & Update": "დამახსოვრება და განახლება",
|
||||
"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": "ჩეთის ისტორიის შენახვა პირდაპირ თქვენი ბრაუზერის საცავში აღარ არის მხარდაჭერილი. გთხოვთ, დაუთმოთ და წაშალოთ თქვენი ჩატის ჟურნალები ქვემოთ მოცემულ ღილაკზე დაწკაპუნებით. არ ინერვიულოთ, თქვენ შეგიძლიათ მარტივად ხელახლა შემოიტანოთ თქვენი ჩეთის ისტორია ბექენდში",
|
||||
"Scan": "სკანირება",
|
||||
"Scan complete!": "სკანირება დასრულდა!",
|
||||
"Scan for documents from {{path}}": "დოკუმენტების სკანირება {{ path}}-დან",
|
||||
"Search": "ძიება",
|
||||
"Search Documents": "დოკუმენტების ძიება",
|
||||
"Search Prompts": "მოთხოვნების ძიება",
|
||||
"See readme.md for instructions": "იხილეთ readme.md ინსტრუქციებისთვის",
|
||||
"See what's new": "სიახლეების ნახვა",
|
||||
"Seed": "სიდი",
|
||||
"Select a mode": "რეჟიმის არჩევა",
|
||||
"Select a model": "მოდელის არჩევა",
|
||||
"Select an Ollama instance": "",
|
||||
"Send a Message": "შეტყობინების გაგზავნა",
|
||||
"Send message": "შეტყობინების გაგზავნა",
|
||||
"Server connection verified": "სერვერთან კავშირი დადასტურებულია",
|
||||
"Set as default": "დეფოლტად დაყენება",
|
||||
"Set Default Model": "დეფოლტ მოდელის დაყენება",
|
||||
"Set Image Size": "სურათის ზომის დაყენება",
|
||||
"Set Steps": "ნაბიჯების დაყენება",
|
||||
"Set Title Auto-Generation Model": "სათაურის ავტომატური გენერაციის მოდელის დაყენება",
|
||||
"Set Voice": "ხმის დაყენება",
|
||||
"Settings": "ხელსაწყოები",
|
||||
"Settings saved successfully!": "პარამეტრები წარმატებით განახლდა!",
|
||||
"Share to OpenWebUI Community": "გააზიარე OpenWebUI საზოგადოებაში ",
|
||||
"short-summary": "მოკლე შინაარსი",
|
||||
"Show": "ჩვენება",
|
||||
"Show Additional Params": "დამატებითი პარამეტრების ჩვენება",
|
||||
"Show shortcuts": "მალსახმობების ჩვენება",
|
||||
"sidebar": "საიდბარი",
|
||||
"Sign in": "ავტორიზაცია",
|
||||
"Sign Out": "გასვლა",
|
||||
"Sign up": "რეგისტრაცია",
|
||||
"Speech recognition error: {{error}}": "მეტყველების ამოცნობის შეცდომა: {{error}}",
|
||||
"Speech-to-Text Engine": "ხმოვან-ტექსტური ძრავი",
|
||||
"SpeechRecognition API is not supported in this browser.": "მეტყველების ამოცნობის API არ არის მხარდაჭერილი ამ ბრაუზერში.",
|
||||
"Stop Sequence": "შეჩერების თანმიმდევრობა",
|
||||
"STT Settings": "მეტყველების ამოცნობის პარამეტრები",
|
||||
"Submit": "გაგზავნა",
|
||||
"Success": "წარმატება",
|
||||
"Successfully updated.": "წარმატებით განახლდა",
|
||||
"Sync All": "სინქრონიზაცია",
|
||||
"System": "სისტემა",
|
||||
"System Prompt": "სისტემური მოთხოვნა",
|
||||
"Tags": "ტეგები",
|
||||
"Temperature": "ტემპერატურა",
|
||||
"Template": "შაბლონი",
|
||||
"Text Completion": "ტექსტის დასრულება",
|
||||
"Text-to-Speech Engine": "ტექსტურ-ხმოვანი ძრავი",
|
||||
"Tfs Z": "Tfs Z",
|
||||
"Theme": "თემა",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "ეს უზრუნველყოფს, რომ თქვენი ძვირფასი საუბრები უსაფრთხოდ შეინახება თქვენს backend მონაცემთა ბაზაში. Გმადლობთ!",
|
||||
"This setting does not sync across browsers or devices.": "ეს პარამეტრი არ სინქრონიზდება ბრაუზერებსა და მოწყობილობებში",
|
||||
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "რჩევა: განაახლეთ რამდენიმე ცვლადი სლოტი თანმიმდევრულად, ყოველი ჩანაცვლების შემდეგ ჩატის ღილაკზე დაჭერით.",
|
||||
"Title": "სათაური",
|
||||
"Title Auto-Generation": "სათაურის ავტო-გენერაცია",
|
||||
"Title Generation Prompt": "სათაურის გენერაციის მოთხოვნა ",
|
||||
"to": "ში",
|
||||
"To access the available model names for downloading,": "ჩამოტვირთვისთვის ხელმისაწვდომი მოდელების სახელებზე წვდომისთვის",
|
||||
"To access the GGUF models available for downloading,": "ჩასატვირთად ხელმისაწვდომი GGUF მოდელებზე წვდომისთვის",
|
||||
"to chat input.": "ჩატში",
|
||||
"Toggle settings": "პარამეტრების გადართვა",
|
||||
"Toggle sidebar": "გვერდითი ზოლის გადართვა",
|
||||
"Top K": "ტოპ K",
|
||||
"Top P": "ტოპ P",
|
||||
"Trouble accessing Ollama?": "Ollama-ს ვერ უკავშირდები?",
|
||||
"TTS Settings": "TTS პარამეტრები",
|
||||
"Type Hugging Face Resolve (Download) URL": "სცადე გადმოწერო Hugging Face Resolve URL",
|
||||
"Uh-oh! There was an issue connecting to {{provider}}.": "{{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": "უცნობი ფაილის ტიპი „{{file_type}}“, მაგრამ მიიღება და განიხილება როგორც მარტივი ტექსტი",
|
||||
"Update": "განახლება",
|
||||
"Update embedding model {{embedding_model}}": "განაახლე ჩაშენების მოდელი {{embedding_model}}",
|
||||
"Update password": "პაროლის განახლება",
|
||||
"Upload a GGUF model": "GGUF მოდელის ატვირთვა",
|
||||
"Upload files": "ფაილების ატვირთვა",
|
||||
"Upload Progress": "პროგრესის ატვირთვა",
|
||||
"URL Mode": "URL რეჟიმი",
|
||||
"Use '#' in the prompt input to load and select your documents.": "",
|
||||
"Use Gravatar": "გამოიყენე Gravatar",
|
||||
"Use Initials": "გამოიყენე ინიციალები",
|
||||
"user": "მომხმარებელი",
|
||||
"User Permissions": "მომხმარებლის უფლებები",
|
||||
"Users": "მომხმარებლები",
|
||||
"Utilize": "გამოყენება",
|
||||
"Valid time units:": "მოქმედი დროის ერთეულები",
|
||||
"variable": "ცვლადი",
|
||||
"variable to have them replaced with clipboard content.": "ცვლადი, რომ შეცვალოს ისინი ბუფერში შიგთავსით.",
|
||||
"Version": "ვერსია",
|
||||
"Web": "ვები",
|
||||
"WebUI Add-ons": "WebUI დანამატები",
|
||||
"WebUI Settings": "WebUI პარამეტრები",
|
||||
"WebUI will make requests to": "WebUI გამოგიგზავნით მოთხოვნებს",
|
||||
"What’s New in": "რა არის ახალი",
|
||||
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "როდესაც ისტორია გამორთულია, ახალი ჩეთები ამ ბრაუზერში არ გამოჩნდება თქვენს ისტორიაში არცერთ მოწყობილობაზე.",
|
||||
"Whisper (Local)": "ჩურჩული (ადგილობრივი)",
|
||||
"Write a prompt suggestion (e.g. Who are you?)": "დაწერეთ მოკლე წინადადება (მაგ. ვინ ხარ?",
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "დაწერეთ რეზიუმე 50 სიტყვით, რომელიც აჯამებს [თემას ან საკვანძო სიტყვას].",
|
||||
"You": "თქვენ",
|
||||
"You're a helpful assistant.": "თქვენ სასარგებლო ასისტენტი ხართ.",
|
||||
"You're now logged in.": "თქვენ შესული ხართ."
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'초', '분', '시간', '일', '주' 또는 만료 없음 '-1'",
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' 또는 만료 없음 '-1'",
|
||||
"(Beta)": "(Beta)",
|
||||
"(e.g. `sh webui.sh --api`)": "(예: `sh webui.sh --api`)",
|
||||
"(latest)": "(latest)",
|
||||
@@ -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"
|
||||
@@ -39,6 +43,10 @@
|
||||
"code": "ja-JP",
|
||||
"title": "Japanese"
|
||||
},
|
||||
{
|
||||
"code": "ka-GE",
|
||||
"title": "Georgian"
|
||||
},
|
||||
{
|
||||
"code": "ko-KR",
|
||||
"title": "Korean"
|
||||
@@ -47,6 +55,14 @@
|
||||
"code": "nl-NL",
|
||||
"title": "Dutch (Netherlands)"
|
||||
},
|
||||
{
|
||||
"code": "pl-PL",
|
||||
"title": "Polish"
|
||||
},
|
||||
{
|
||||
"code": "pt-BR",
|
||||
"title": "Portuguese (Brazil)"
|
||||
},
|
||||
{
|
||||
"code": "pt-PT",
|
||||
"title": "Portuguese (Portugal)"
|
||||
@@ -55,6 +71,10 @@
|
||||
"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,372 @@
|
||||
{
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' lub '-1' dla bez wygaśnięcia.",
|
||||
"(Beta)": "(Beta)",
|
||||
"(e.g. `sh webui.sh --api`)": "(np. `sh webui.sh --api`)",
|
||||
"(latest)": "(najnowszy)",
|
||||
"{{modelName}} is thinking...": "{{modelName}} myśli...",
|
||||
"{{webUIName}} Backend Required": "Backend {{webUIName}} wymagane",
|
||||
"a user": "użytkownik",
|
||||
"About": "O nas",
|
||||
"Account": "Konto",
|
||||
"Action": "Akcja",
|
||||
"Add a model": "Dodaj model",
|
||||
"Add a model tag name": "Dodaj nazwę tagu modelu",
|
||||
"Add a short description about what this modelfile does": "Dodaj krótki opis tego, co robi ten plik modelu",
|
||||
"Add a short title for this prompt": "Dodaj krótki tytuł tego polecenia",
|
||||
"Add a tag": "Dodaj tag",
|
||||
"Add Docs": "Dodaj dokumenty",
|
||||
"Add Files": "Dodaj pliki",
|
||||
"Add message": "Dodaj wiadomość",
|
||||
"add tags": "dodaj tagi",
|
||||
"Adjusting these settings will apply changes universally to all users.": "Dostosowanie tych ustawień spowoduje zastosowanie zmian uniwersalnie do wszystkich użytkowników.",
|
||||
"admin": "admin",
|
||||
"Admin Panel": "Panel administracyjny",
|
||||
"Admin Settings": "Ustawienia administratora",
|
||||
"Advanced Parameters": "Zaawansowane parametry",
|
||||
"all": "wszyscy",
|
||||
"All Users": "Wszyscy użytkownicy",
|
||||
"Allow": "Pozwól",
|
||||
"Allow Chat Deletion": "Pozwól na usuwanie czatu",
|
||||
"alphanumeric characters and hyphens": "znaki alfanumeryczne i myślniki",
|
||||
"Already have an account?": "Masz już konto?",
|
||||
"an assistant": "asystent",
|
||||
"and": "i",
|
||||
"API Base URL": "Podstawowy adres URL interfejsu API",
|
||||
"API Key": "Klucz API",
|
||||
"API RPM": "Pakiet API RPM",
|
||||
"are allowed - Activate this command by typing": "są dozwolone - Aktywuj to polecenie, wpisując",
|
||||
"Are you sure?": "Jesteś pewien?",
|
||||
"Audio": "Dźwięk",
|
||||
"Auto-playback response": "Odtwarzanie automatyczne odpowiedzi",
|
||||
"Auto-send input after 3 sec.": "Wysyłanie automatyczne po 3 sek.",
|
||||
"AUTOMATIC1111 Base URL": "Podstawowy adres URL AUTOMATIC1111",
|
||||
"AUTOMATIC1111 Base URL is required.": "Podstawowy adres URL AUTOMATIC1111 jest wymagany.",
|
||||
"available!": "dostępny!",
|
||||
"Back": "Wstecz",
|
||||
"Builder Mode": "Tryb budowniczego",
|
||||
"Cancel": "Anuluj",
|
||||
"Categories": "Kategorie",
|
||||
"Change Password": "Zmień hasło",
|
||||
"Chat": "Czat",
|
||||
"Chat History": "Historia czatu",
|
||||
"Chat History is off for this browser.": "Historia czatu jest wyłączona dla tej przeglądarki.",
|
||||
"Chats": "Czaty",
|
||||
"Check Again": "Sprawdź ponownie",
|
||||
"Check for updates": "Sprawdź aktualizacje",
|
||||
"Checking for updates...": "Sprawdzanie aktualizacji...",
|
||||
"Choose a model before saving...": "Wybierz model przed zapisaniem...",
|
||||
"Chunk Overlap": "Zachodzenie bloku",
|
||||
"Chunk Params": "Parametry bloku",
|
||||
"Chunk Size": "Rozmiar bloku",
|
||||
"Click here for help.": "Kliknij tutaj, aby uzyskać pomoc.",
|
||||
"Click here to check other modelfiles.": "Kliknij tutaj, aby sprawdzić inne pliki modelowe.",
|
||||
"Click here to select": "Kliknij tutaj, aby wybrać",
|
||||
"Click here to select documents.": "Kliknij tutaj, aby wybrać dokumenty.",
|
||||
"click here.": "kliknij tutaj.",
|
||||
"Click on the user role button to change a user's role.": "Kliknij przycisk roli użytkownika, aby zmienić rolę użytkownika.",
|
||||
"Close": "Zamknij",
|
||||
"Collection": "Kolekcja",
|
||||
"Command": "Polecenie",
|
||||
"Confirm Password": "Potwierdź hasło",
|
||||
"Connections": "Połączenia",
|
||||
"Content": "Zawartość",
|
||||
"Context Length": "Długość kontekstu",
|
||||
"Conversation Mode": "Tryb rozmowy",
|
||||
"Copy last code block": "Skopiuj ostatni blok kodu",
|
||||
"Copy last response": "Skopiuj ostatnią odpowiedź",
|
||||
"Copying to clipboard was successful!": "Kopiowanie do schowka zakończone powodzeniem!",
|
||||
"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':": "Utwórz zwięzłą frazę składającą się z 3-5 słów jako nagłówek dla następującego zapytania, ściśle przestrzegając limitu od 3 do 5 słów i unikając użycia słowa 'tytuł':",
|
||||
"Create a modelfile": "Utwórz plik modelu",
|
||||
"Create Account": "Utwórz konto",
|
||||
"Created at": "Utworzono o",
|
||||
"Created by": "Utworzono przez",
|
||||
"Current Model": "Bieżący model",
|
||||
"Current Password": "Bieżące hasło",
|
||||
"Custom": "Niestandardowy",
|
||||
"Customize Ollama models for a specific purpose": "Dostosuj modele Ollama do określonego celu",
|
||||
"Dark": "Ciemny",
|
||||
"Database": "Baza danych",
|
||||
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
|
||||
"Default": "Domyślny",
|
||||
"Default (Automatic1111)": "Domyślny (Automatic1111)",
|
||||
"Default (Web API)": "Domyślny (Interfejs API)",
|
||||
"Default model updated": "Domyślny model zaktualizowany",
|
||||
"Default Prompt Suggestions": "Domyślne sugestie promptów",
|
||||
"Default User Role": "Domyślna rola użytkownika",
|
||||
"delete": "Usuń",
|
||||
"Delete a model": "Usuń model",
|
||||
"Delete chat": "Usuń czat",
|
||||
"Delete Chats": "Usuń czaty",
|
||||
"Deleted {{deleteModelTag}}": "Usunięto {{deleteModelTag}}",
|
||||
"Deleted {tagName}": "Usunięto {tagName}",
|
||||
"Description": "Opis",
|
||||
"Notifications": "Powiadomienia",
|
||||
"Disabled": "Wyłączone",
|
||||
"Discover a modelfile": "Odkryj plik modelu",
|
||||
"Discover a prompt": "Odkryj prompt",
|
||||
"Discover, download, and explore custom prompts": "Odkryj, pobierz i eksploruj niestandardowe prompty",
|
||||
"Discover, download, and explore model presets": "Odkryj, pobierz i eksploruj ustawienia modeli",
|
||||
"Display the username instead of You in the Chat": "Wyświetl nazwę użytkownika zamiast Ty w czacie",
|
||||
"Document": "Dokument",
|
||||
"Document Settings": "Ustawienia dokumentu",
|
||||
"Documents": "Dokumenty",
|
||||
"does not make any external connections, and your data stays securely on your locally hosted server.": "nie nawiązuje żadnych zewnętrznych połączeń, a Twoje dane pozostają bezpiecznie na Twoim lokalnie hostowanym serwerze.",
|
||||
"Don't Allow": "Nie zezwalaj",
|
||||
"Don't have an account?": "Nie masz konta?",
|
||||
"Download as a File": "Pobierz jako plik",
|
||||
"Download Database": "Pobierz bazę danych",
|
||||
"Drop any files here to add to the conversation": "Upuść pliki tutaj, aby dodać do rozmowy",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "np. '30s', '10m'. Poprawne jednostki czasu to 's', 'm', 'h'.",
|
||||
"Edit Doc": "Edytuj dokument",
|
||||
"Edit User": "Edytuj użytkownika",
|
||||
"Email": "Email",
|
||||
"Embedding model: {{embedding_model}}": "Osadzony model: {{embedding_model}}",
|
||||
"Enable Chat History": "Włącz historię czatu",
|
||||
"Enable New Sign Ups": "Włącz nowe rejestracje",
|
||||
"Enabled": "Włączone",
|
||||
"Enter {{role}} message here": "Wprowadź wiadomość {{role}} tutaj",
|
||||
"Enter API Key": "Wprowadź klucz API",
|
||||
"Enter Chunk Overlap": "Wprowadź zakchodzenie bloku",
|
||||
"Enter Chunk Size": "Wprowadź rozmiar bloku",
|
||||
"Enter Image Size (e.g. 512x512)": "Wprowadź rozmiar obrazu (np. 512x512)",
|
||||
"Enter LiteLLM API Base URL (litellm_params.api_base)": "Wprowadź bazowy adres URL LiteLLM API (litellm_params.api_base)",
|
||||
"Enter LiteLLM API Key (litellm_params.api_key)": "Wprowadź klucz API LiteLLM (litellm_params.api_key)",
|
||||
"Enter LiteLLM API RPM (litellm_params.rpm)": "Wprowadź API LiteLLM RPM(litellm_params.rpm)",
|
||||
"Enter LiteLLM Model (litellm_params.model)": "Wprowadź model LiteLLM (litellm_params.model)",
|
||||
"Enter Max Tokens (litellm_params.max_tokens)": "Wprowadź maksymalną liczbę tokenów (litellm_params.max_tokens)",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Wprowadź tag modelu (np. {{modelTag}})",
|
||||
"Enter Number of Steps (e.g. 50)": "Wprowadź liczbę kroków (np. 50)",
|
||||
"Enter stop sequence": "Wprowadź sekwencję zatrzymania",
|
||||
"Enter Top K": "Wprowadź Top K",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "Wprowadź adres URL (np. http://127.0.0.1:7860/)",
|
||||
"Enter Your Email": "Wprowadź swój adres email",
|
||||
"Enter Your Full Name": "Wprowadź swoje imię i nazwisko",
|
||||
"Enter Your Password": "Wprowadź swoje hasło",
|
||||
"Experimental": "Eksperymentalne",
|
||||
"Export All Chats (All Users)": "Eksportuj wszystkie czaty (wszyscy użytkownicy)",
|
||||
"Export Chats": "Eksportuj czaty",
|
||||
"Export Documents Mapping": "Eksportuj mapowanie dokumentów",
|
||||
"Export Modelfiles": "Eksportuj pliki modeli",
|
||||
"Export Prompts": "Eksportuj prompty",
|
||||
"Failed to read clipboard contents": "Nie udało się odczytać zawartości schowka",
|
||||
"File Mode": "Tryb pliku",
|
||||
"File not found.": "Plik nie został znaleziony.",
|
||||
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Wykryto podszywanie się pod odcisk palca: Nie można używać inicjałów jako awatara. Przechodzenie do domyślnego obrazu profilowego.",
|
||||
"Focus chat input": "Skoncentruj na czacie",
|
||||
"Format your variables using square brackets like this:": "Formatuj swoje zmienne, używając nawiasów kwadratowych, np.",
|
||||
"From (Base Model)": "Z (Model Podstawowy)",
|
||||
"Full Screen Mode": "Tryb pełnoekranowy",
|
||||
"General": "Ogólne",
|
||||
"General Settings": "Ogólne ustawienia",
|
||||
"Hello, {{name}}": "Witaj, {{name}}",
|
||||
"Hide": "Ukryj",
|
||||
"Hide Additional Params": "Ukryj dodatkowe parametry",
|
||||
"How can I help you today?": "Jak mogę Ci dzisiaj pomóc?",
|
||||
"Image Generation (Experimental)": "Generowanie obrazu (eksperymentalne)",
|
||||
"Image Generation Engine": "Silnik generowania obrazu",
|
||||
"Image Settings": "Ustawienia obrazu",
|
||||
"Images": "Obrazy",
|
||||
"Import Chats": "Importuj rozmowy",
|
||||
"Import Documents Mapping": "Importuj mapowanie dokumentów",
|
||||
"Import Modelfiles": "Importuj pliki modeli",
|
||||
"Import Prompts": "Importuj prompty",
|
||||
"Include `--api` flag when running stable-diffusion-webui": "Dołącz flagę `--api` podczas uruchamiania stable-diffusion-webui",
|
||||
"Interface": "Interfejs",
|
||||
"join our Discord for help.": "Dołącz do naszego Discorda po pomoc.",
|
||||
"JSON": "JSON",
|
||||
"JWT Expiration": "Wygaśnięcie JWT",
|
||||
"JWT Token": "Token JWT",
|
||||
"Keep Alive": "Zachowaj łączność",
|
||||
"Keyboard shortcuts": "Skróty klawiszowe",
|
||||
"Language": "Język",
|
||||
"Light": "Jasny",
|
||||
"Listening...": "Nasłuchiwanie...",
|
||||
"LLMs can make mistakes. Verify important information.": "LLMy mogą popełniać błędy. Zweryfikuj ważne informacje.",
|
||||
"Made by OpenWebUI Community": "Stworzone przez społeczność OpenWebUI",
|
||||
"Make sure to enclose them with": "Upewnij się, że są one zamknięte w",
|
||||
"Manage LiteLLM Models": "Zarządzaj modelami LiteLLM",
|
||||
"Manage Models": "Zarządzaj modelami",
|
||||
"Manage Ollama Models": "Zarządzaj modelami Ollama",
|
||||
"Max Tokens": "Maksymalna liczba tokenów",
|
||||
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Maksymalnie 3 modele można pobierać jednocześnie. Spróbuj ponownie później.",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"MMMM DD, YYYY": "MMMM DD, YYYY",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' został pomyślnie pobrany.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' jest już w kolejce do pobrania.",
|
||||
"Model {{embedding_model}} update complete!": "Aktualizacja modelu {{embedding_model}} zakończona pomyślnie!",
|
||||
"Model {{embedding_model}} update failed or not required!": "Model {{embedding_model}} aktualizacja nie powiodła się lub nie jest wymagana!",
|
||||
"Model {{modelId}} not found": "Model {{modelId}} nie został znaleziony",
|
||||
"Model {{modelName}} already exists.": "Model {{modelName}} już istnieje.",
|
||||
"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Wykryto ścieżkę systemu plików modelu. Wymagana jest krótka nazwa modelu do aktualizacji, nie można kontynuować.",
|
||||
"Model Name": "Nazwa modelu",
|
||||
"Model not selected": "Model nie został wybrany",
|
||||
"Model Tag Name": "Nazwa tagu modelu",
|
||||
"Model Whitelisting": "Whitelisting modelu",
|
||||
"Model(s) Whitelisted": "Model(e) dodane do listy białej",
|
||||
"Modelfile": "Plik modelu",
|
||||
"Modelfile Advanced Settings": "Zaawansowane ustawienia pliku modelu",
|
||||
"Modelfile Content": "Zawartość pliku modelu",
|
||||
"Modelfiles": "Pliki modeli",
|
||||
"Models": "Modele",
|
||||
"My Documents": "Moje dokumenty",
|
||||
"My Modelfiles": "Moje pliki modeli",
|
||||
"My Prompts": "Moje prompty",
|
||||
"Name": "Nazwa",
|
||||
"Name Tag": "Etykieta nazwy",
|
||||
"Name your modelfile": "Nadaj nazwę swojemu plikowi modelu",
|
||||
"New Chat": "Nowy czat",
|
||||
"New Password": "Nowe hasło",
|
||||
"Not sure what to add?": "Nie wiesz, co dodać?",
|
||||
"Not sure what to write? Switch to": "Nie wiesz, co napisać? Przełącz się na",
|
||||
"Off": "Wyłączony",
|
||||
"Okay, Let's Go!": "Okej, zaczynamy!",
|
||||
"Ollama Base URL": "Adres bazowy URL Ollama",
|
||||
"Ollama Version": "Wersja Ollama",
|
||||
"On": "Włączony",
|
||||
"Only": "Tylko",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "W poleceniu dozwolone są tylko znaki alfanumeryczne i myślniki.",
|
||||
"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.": "Ups! Trzymaj się! Twoje pliki są wciąż w procesie obróbki. Gotujemy je do perfekcji. Prosimy o cierpliwość, poinformujemy Cię, gdy będą gotowe.",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ups! Wygląda na to, że URL jest nieprawidłowy. Sprawdź jeszcze raz i spróbuj ponownie.",
|
||||
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ups! Używasz nieobsługiwaniej metody (tylko interfejs front-end). Proszę obsłużyć interfejs WebUI z poziomu backendu.",
|
||||
"Open": "Otwórz",
|
||||
"Open AI": "Open AI",
|
||||
"Open AI (Dall-E)": "Open AI (Dall-E)",
|
||||
"Open new chat": "Otwórz nowy czat",
|
||||
"OpenAI API": "OpenAI API",
|
||||
"OpenAI API Key": "Klucz API OpenAI",
|
||||
"OpenAI API Key is required.": "Klucz API OpenAI jest wymagany.",
|
||||
"or": "lub",
|
||||
"Parameters": "Parametry",
|
||||
"Password": "Hasło",
|
||||
"PDF Extract Images (OCR)": "PDF Wyodrębnij obrazy (OCR)",
|
||||
"pending": "oczekujące",
|
||||
"Permission denied when accessing microphone: {{error}}": "Odmowa dostępu do mikrofonu: {{error}}",
|
||||
"Playground": "Plac zabaw",
|
||||
"Profile": "Profil",
|
||||
"Prompt Content": "Zawartość prompta",
|
||||
"Prompt suggestions": "Sugestie prompta",
|
||||
"Prompts": "Prompty",
|
||||
"Pull a model from Ollama.com": "Pobierz model z Ollama.com",
|
||||
"Pull Progress": "Postęp pobierania",
|
||||
"Query Params": "Parametry zapytania",
|
||||
"RAG Template": "Szablon RAG",
|
||||
"Raw Format": "Format bez obróbki",
|
||||
"Record voice": "Nagraj głos",
|
||||
"Redirecting you to OpenWebUI Community": "Przekierowujemy Cię do społeczności OpenWebUI",
|
||||
"Release Notes": "Notatki wydania",
|
||||
"Repeat Last N": "Powtórz ostatnie N",
|
||||
"Repeat Penalty": "Kara za powtórzenie",
|
||||
"Request Mode": "Tryb żądania",
|
||||
"Reset Vector Storage": "Resetuj przechowywanie wektorów",
|
||||
"Response AutoCopy to Clipboard": "Automatyczne kopiowanie odpowiedzi do schowka",
|
||||
"Role": "Rola",
|
||||
"Rosé Pine": "Rosé Pine",
|
||||
"Rosé Pine Dawn": "Rosé Pine Dawn",
|
||||
"Save": "Zapisz",
|
||||
"Save & Create": "Zapisz i utwórz",
|
||||
"Save & Submit": "Zapisz i wyślij",
|
||||
"Save & Update": "Zapisz i zaktualizuj",
|
||||
"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": "Bezpośrednie zapisywanie dzienników czatu w pamięci przeglądarki nie jest już obsługiwane. Prosimy o pobranie i usunięcie dzienników czatu, klikając poniższy przycisk. Nie martw się, możesz łatwo ponownie zaimportować dzienniki czatu do backendu za pomocą",
|
||||
"Scan": "Skanuj",
|
||||
"Scan complete!": "Skanowanie zakończone!",
|
||||
"Scan for documents from {{path}}": "Skanuj dokumenty z {{path}}",
|
||||
"Search": "Szukaj",
|
||||
"Search Documents": "Szukaj dokumentów",
|
||||
"Search Prompts": "Szukaj promptów",
|
||||
"See readme.md for instructions": "Zajrzyj do readme.md po instrukcje",
|
||||
"See what's new": "Zobacz co nowego",
|
||||
"Seed": "Seed",
|
||||
"Select a mode": "Wybierz tryb",
|
||||
"Select a model": "Wybierz model",
|
||||
"Select an Ollama instance": "Wybierz instancję Ollama",
|
||||
"Send a Message": "Wyślij Wiadomość",
|
||||
"Send message": "Wyślij wiadomość",
|
||||
"Server connection verified": "Połączenie z serwerem zweryfikowane",
|
||||
"Set as default": "Ustaw jako domyślne",
|
||||
"Set Default Model": "Ustaw domyślny model",
|
||||
"Set Image Size": "Ustaw rozmiar obrazu",
|
||||
"Set Steps": "Ustaw kroki",
|
||||
"Set Title Auto-Generation Model": "Ustaw model automatycznego generowania tytułów",
|
||||
"Set Voice": "Ustaw głos",
|
||||
"Settings": "Ustawienia",
|
||||
"Settings saved successfully!": "Ustawienia zapisane pomyślnie!",
|
||||
"Share to OpenWebUI Community": "Dziel się z społecznością OpenWebUI",
|
||||
"short-summary": "Krótkie podsumowanie",
|
||||
"Show": "Pokaż",
|
||||
"Show Additional Params": "Pokaż dodatkowe parametry",
|
||||
"Show shortcuts": "Pokaż skróty",
|
||||
"sidebar": "Panel boczny",
|
||||
"Sign in": "Zaloguj się",
|
||||
"Sign Out": "Wyloguj się",
|
||||
"Sign up": "Zarejestruj się",
|
||||
"Speech recognition error: {{error}}": "Błąd rozpoznawania mowy: {{error}}",
|
||||
"Speech-to-Text Engine": "Silnik mowy na tekst",
|
||||
"SpeechRecognition API is not supported in this browser.": "API Rozpoznawania Mowy nie jest obsługiwane w tej przeglądarce.",
|
||||
"Stop Sequence": "Zatrzymaj sekwencję",
|
||||
"STT Settings": "Ustawienia STT",
|
||||
"Submit": "Zatwierdź",
|
||||
"Success": "Sukces",
|
||||
"Successfully updated.": "Pomyślnie zaktualizowano.",
|
||||
"Sync All": "Synchronizuj wszystko",
|
||||
"System": "System",
|
||||
"System Prompt": "Prompt systemowy",
|
||||
"Tags": "Tagi",
|
||||
"Temperature": "Temperatura",
|
||||
"Template": "Szablon",
|
||||
"Text Completion": "Uzupełnienie tekstu",
|
||||
"Text-to-Speech Engine": "Silnik tekstu na mowę",
|
||||
"Tfs Z": "Tfs Z",
|
||||
"Theme": "Motyw",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "To zapewnia, że Twoje cenne rozmowy są bezpiecznie zapisywane w bazie danych backendowej. Dziękujemy!",
|
||||
"This setting does not sync across browsers or devices.": "To ustawienie nie synchronizuje się między przeglądarkami ani urządzeniami.",
|
||||
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Porada: Aktualizuj wiele zmiennych kolejno, naciskając klawisz tabulatora w polu wprowadzania czatu po każdej zmianie.",
|
||||
"Title": "Tytuł",
|
||||
"Title Auto-Generation": "Automatyczne generowanie tytułu",
|
||||
"Title Generation Prompt": "Prompt generowania tytułu",
|
||||
"to": "do",
|
||||
"To access the available model names for downloading,": "Aby uzyskać dostęp do dostępnych nazw modeli do pobrania,",
|
||||
"To access the GGUF models available for downloading,": "Aby uzyskać dostęp do dostępnych modeli GGUF do pobrania,",
|
||||
"to chat input.": "do pola wprowadzania czatu.",
|
||||
"Toggle settings": "Przełącz ustawienia",
|
||||
"Toggle sidebar": "Przełącz panel boczny",
|
||||
"Top K": "Najlepsze K",
|
||||
"Top P": "Najlepsze P",
|
||||
"Trouble accessing Ollama?": "Problemy z dostępem do Ollama?",
|
||||
"TTS Settings": "Ustawienia TTS",
|
||||
"Type Hugging Face Resolve (Download) URL": "Wprowadź adres URL do pobrania z Hugging Face",
|
||||
"Uh-oh! There was an issue connecting to {{provider}}.": "O nie! Wystąpił problem z połączeniem z {{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!": "Zrozum, że aktualizacja lub zmiana modelu osadzania wymaga zresetowania bazy wektorów i ponownego zaimportowania wszystkich dokumentów. Zostałeś ostrzeżony!",
|
||||
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Nieznany typ pliku '{{file_type}}', ale akceptowany i traktowany jako zwykły tekst",
|
||||
"Update": "Aktualizacja",
|
||||
"Update embedding model {{embedding_model}}": "Aktualizuj modelu osadzania {{embedding_model}}",
|
||||
"Update password": "Aktualizacja hasła",
|
||||
"Upload a GGUF model": "Prześlij model GGUF",
|
||||
"Upload files": "Prześlij pliki",
|
||||
"Upload Progress": "Postęp przesyłania",
|
||||
"URL Mode": "Tryb adresu URL",
|
||||
"Use '#' in the prompt input to load and select your documents.": "Użyj '#' w polu wprowadzania polecenia, aby załadować i wybrać swoje dokumenty.",
|
||||
"Use Gravatar": "Użyj Gravatara",
|
||||
"Use Initials": "Użyj inicjałów",
|
||||
"user": "użytkownik",
|
||||
"User Permissions": "Uprawnienia użytkownika",
|
||||
"Users": "Użytkownicy",
|
||||
"Utilize": "Wykorzystaj",
|
||||
"Valid time units:": "Poprawne jednostki czasu:",
|
||||
"variable": "zmienna",
|
||||
"variable to have them replaced with clipboard content.": "zmienna która zostanie zastąpiona zawartością schowka.",
|
||||
"Version": "Wersja",
|
||||
"Web": "Sieć",
|
||||
"WebUI Add-ons": "Dodatki do interfejsu WebUI",
|
||||
"WebUI Settings": "Ustawienia interfejsu WebUI",
|
||||
"WebUI will make requests to": "Interfejs sieciowy będzie wysyłał żądania do",
|
||||
"What’s New in": "Co nowego w",
|
||||
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Kiedy historia jest wyłączona, nowe rozmowy na tej przeglądarce nie będą widoczne w historii na żadnym z twoich urządzeń.",
|
||||
"Whisper (Local)": "Whisper (Lokalnie)",
|
||||
"Write a prompt suggestion (e.g. Who are you?)": "Napisz sugestię do polecenia (np. Kim jesteś?)",
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "Napisz podsumowanie w 50 słowach, które podsumowuje [temat lub słowo kluczowe].",
|
||||
"You": "Ty",
|
||||
"You're a helpful assistant.": "Jesteś pomocnym asystentem.",
|
||||
"You're now logged in.": "Jesteś teraz zalogowany."
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
{
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' 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/YYYY 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": "DD/MM/YYYY",
|
||||
"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."
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 's' ou '-1' para nenhuma expiração.",
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' ou '-1' para nenhuma expiração.",
|
||||
"(Beta)": "(Beta)",
|
||||
"(e.g. `sh webui.sh --api`)": "(por exemplo, `sh webui.sh --api`)",
|
||||
"(latest)": "(mais recente)",
|
||||
@@ -86,7 +86,7 @@
|
||||
"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",
|
||||
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
|
||||
"Default": "Padrão",
|
||||
"Default (Automatic1111)": "Padrão (Automatic1111)",
|
||||
"Default (Web API)": "Padrão (API Web)",
|
||||
@@ -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",
|
||||
@@ -190,7 +190,7 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"MMMM DD, YYYY": "MMMM DD, AAAA",
|
||||
"MMMM DD, YYYY": "DD/MM/YYYY",
|
||||
"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",
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
"Database": "База данных",
|
||||
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
|
||||
"Default": "По умолчанию",
|
||||
"Default (Automatic1111)": "По умолчанию (Автоматический1111)",
|
||||
"Default (Automatic1111)": "По умолчанию (Automatic1111)",
|
||||
"Default (Web API)": "По умолчанию (Web API)",
|
||||
"Default model updated": "Модель по умолчанию обновлена",
|
||||
"Default Prompt Suggestions": "Предложения промтов по умолчанию",
|
||||
@@ -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": "Знайти промт",
|
||||
|
||||
@@ -55,9 +55,9 @@
|
||||
"Check for updates": "Kiểm tra cập nhật",
|
||||
"Checking for updates...": "Đang kiểm tra cập nhật...",
|
||||
"Choose a model before saving...": "Chọn mô hình trước khi lưu...",
|
||||
"Chunk Overlap": "Kích thước chồng lấn (overlap)",
|
||||
"Chunk Overlap": "Chồng lấn (overlap)",
|
||||
"Chunk Params": "Cài đặt số lượng ký tự cho khối ký tự (chunk)",
|
||||
"Chunk Size": "Kích thức khối (size)",
|
||||
"Chunk Size": "Kích thước khối (size)",
|
||||
"Click here for help.": "Bấm vào đây để được trợ giúp.",
|
||||
"Click here to check other modelfiles.": "Bấm vào đây để kiểm tra các tệp mô tả mô hình (modelfiles) khác.",
|
||||
"Click here to select": "Bấm vào đây để chọn",
|
||||
@@ -65,7 +65,7 @@
|
||||
"click here.": "bấm vào đây.",
|
||||
"Click on the user role button to change a user's role.": "Bấm vào nút trong cột VAI TRÒ để thay đổi quyền của người sử dụng.",
|
||||
"Close": "Đóng",
|
||||
"Collection": "Bộ sưu tập",
|
||||
"Collection": "Tổng hợp mọi tài liệu",
|
||||
"Command": "Lệnh",
|
||||
"Confirm Password": "Xác nhận Mật khẩu",
|
||||
"Connections": "Kết nối",
|
||||
@@ -76,7 +76,7 @@
|
||||
"Copy last response": "Sao chép phản hồi cuối cùng",
|
||||
"Copying to clipboard was successful!": "Sao chép vào clipboard thành công!",
|
||||
"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':": "Tạo một cụm từ súc tích, 3-5 từ làm tiêu đề cho truy vấn sau, tuân thủ nghiêm ngặt giới hạn 3-5 từ và tránh sử dụng từ 'tiêu đề':",
|
||||
"Create a modelfile": "Tạo tệp mô tả mô hình",
|
||||
"Create a modelfile": "Tạo tệp mô tả cho mô hình",
|
||||
"Create Account": "Tạo Tài khoản",
|
||||
"Created at": "Được tạo vào lúc",
|
||||
"Created by": "Được tạo bởi",
|
||||
@@ -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",
|
||||
@@ -347,7 +347,7 @@
|
||||
"Valid time units:": "Đơn vị thời gian hợp lệ:",
|
||||
"variable": "biến",
|
||||
"variable to have them replaced with clipboard content.": "biến để có chúng được thay thế bằng nội dung clipboard.",
|
||||
"Version": "Phiên bản",
|
||||
"Version": "Version",
|
||||
"Web": "Web",
|
||||
"WebUI Add-ons": "Tiện ích WebUI",
|
||||
"WebUI Settings": "Cài đặt WebUI",
|
||||
@@ -359,5 +359,20 @@
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "Viết một tóm tắt trong vòng 50 từ cho [chủ đề hoặc từ khóa].",
|
||||
"You": "Bạn",
|
||||
"You're a helpful assistant.": "Bạn là một trợ lý hữu ích.",
|
||||
"You're now logged in.": "Bạn đã đăng nhập."
|
||||
"You're now logged in.": "Bạn đã đăng nhập.",
|
||||
"Accurate information": "Thông tin chính xác",
|
||||
"Followed instructions perfectly": "Tuân theo chỉ dẫn một cách hoàn hảo",
|
||||
"Showcased creativity": "Thể hiện sự sáng tạo",
|
||||
"Positive attitude": "Thể hiện thái độ tích cực",
|
||||
"Attention to detail": "Có sự chú ý đến chi tiết của vấn đề",
|
||||
"Thorough explanation": "Giải thích kỹ lưỡng",
|
||||
"Don't like the style": "Không thích phong cách trả lời",
|
||||
"Not factually correct": "Không chính xác so với thực tế",
|
||||
"Didn't fully follow instructions": "Không tuân theo chỉ dẫn một cách đầy đủ",
|
||||
"Refused when it shouldn't have": "Từ chối trả lời mà nhẽ không nên làm vậy",
|
||||
"Being lazy": "Lười biếng",
|
||||
"Other": "Khác",
|
||||
"Thanks for your feedback!": "Cám ơn bạn đã gửi phản hồi!",
|
||||
"Tell us more:": "Hãy cho chúng tôi hiểu thêm về chất lượng của câu trả lời:",
|
||||
"Feel free to add specific details": "Mô tả chi tiết về chất lượng của câu hỏi và phương án trả lời"
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"Deleted {{deleteModelTag}}": "已删除{{deleteModelTag}}",
|
||||
"Deleted {tagName}": "已删除{tagName}",
|
||||
"Description": "描述",
|
||||
"Desktop Notifications": "桌面通知",
|
||||
"Notifications": "桌面通知",
|
||||
"Disabled": "禁用",
|
||||
"Discover a modelfile": "探索模型文件",
|
||||
"Discover a prompt": "探索提示词",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user