feat: add new notes feature with editor and list components, and update routing for notes management
This commit is contained in:
+7
-1
@@ -24,6 +24,12 @@
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Vazirmatn';
|
||||
src: url('/assets/fonts/Vazirmatn-Variable.ttf');
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
html {
|
||||
word-break: break-word;
|
||||
}
|
||||
@@ -75,7 +81,7 @@ textarea::placeholder {
|
||||
}
|
||||
|
||||
.font-primary {
|
||||
font-family: 'Archivo', sans-serif;
|
||||
font-family: 'Archivo', 'Vazirmatn', sans-serif;
|
||||
}
|
||||
|
||||
.drag-region {
|
||||
|
||||
@@ -354,7 +354,8 @@ export const addUser = async (
|
||||
name: string,
|
||||
email: string,
|
||||
password: string,
|
||||
role: string = 'pending'
|
||||
role: string = 'pending',
|
||||
profile_image_url: null | string = null
|
||||
) => {
|
||||
let error = null;
|
||||
|
||||
@@ -368,7 +369,8 @@ export const addUser = async (
|
||||
name: name,
|
||||
email: email,
|
||||
password: password,
|
||||
role: role
|
||||
role: role,
|
||||
...(profile_image_url && { profile_image_url: profile_image_url })
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
import { t } from 'i18next';
|
||||
|
||||
type ChannelForm = {
|
||||
name: string;
|
||||
|
||||
+34
-2
@@ -539,7 +539,7 @@ export const updateTaskConfig = async (token: string, config: object) => {
|
||||
export const generateTitle = async (
|
||||
token: string = '',
|
||||
model: string,
|
||||
messages: string[],
|
||||
messages: object[],
|
||||
chat_id?: string
|
||||
) => {
|
||||
let error = null;
|
||||
@@ -573,7 +573,39 @@ export const generateTitle = async (
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res?.choices[0]?.message?.content.replace(/["']/g, '') ?? 'New Chat';
|
||||
try {
|
||||
// Step 1: Safely extract the response string
|
||||
const response = res?.choices[0]?.message?.content ?? '';
|
||||
|
||||
// Step 2: Attempt to fix common JSON format issues like single quotes
|
||||
const sanitizedResponse = response.replace(/['‘’`]/g, '"'); // Convert single quotes to double quotes for valid JSON
|
||||
|
||||
// Step 3: Find the relevant JSON block within the response
|
||||
const jsonStartIndex = sanitizedResponse.indexOf('{');
|
||||
const jsonEndIndex = sanitizedResponse.lastIndexOf('}');
|
||||
|
||||
// Step 4: Check if we found a valid JSON block (with both `{` and `}`)
|
||||
if (jsonStartIndex !== -1 && jsonEndIndex !== -1) {
|
||||
const jsonResponse = sanitizedResponse.substring(jsonStartIndex, jsonEndIndex + 1);
|
||||
|
||||
// Step 5: Parse the JSON block
|
||||
const parsed = JSON.parse(jsonResponse);
|
||||
|
||||
// Step 6: If there's a "tags" key, return the tags array; otherwise, return an empty array
|
||||
if (parsed && parsed.title) {
|
||||
return parsed.title;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid JSON block found, return an empty array
|
||||
return null;
|
||||
} catch (e) {
|
||||
// Catch and safely return empty array on any parsing errors
|
||||
console.error('Failed to parse response: ', e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const generateTags = async (
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
import { getTimeRange } from '$lib/utils';
|
||||
|
||||
type NoteItem = {
|
||||
title: string;
|
||||
data: object;
|
||||
meta?: null | object;
|
||||
access_control?: null | object;
|
||||
};
|
||||
|
||||
export const createNewNote = async (token: string, note: NoteItem) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/notes/create`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...note
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.log(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getNotes = async (token: string = '') => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/notes/`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.then((json) => {
|
||||
return json;
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
console.log(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!Array.isArray(res)) {
|
||||
return {}; // or throw new Error("Notes response is not an array")
|
||||
}
|
||||
|
||||
// Build the grouped object
|
||||
const grouped: Record<string, any[]> = {};
|
||||
for (const note of res) {
|
||||
const timeRange = getTimeRange(note.updated_at / 1000000000);
|
||||
if (!grouped[timeRange]) {
|
||||
grouped[timeRange] = [];
|
||||
}
|
||||
grouped[timeRange].push({
|
||||
...note,
|
||||
timeRange
|
||||
});
|
||||
}
|
||||
|
||||
return grouped;
|
||||
};
|
||||
|
||||
export const getNoteById = async (token: string, id: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/notes/${id}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.then((json) => {
|
||||
return json;
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
|
||||
console.log(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const updateNoteById = async (token: string, id: string, note: NoteItem) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/notes/${id}/update`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...note
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.then((json) => {
|
||||
return json;
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
|
||||
console.log(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const deleteNoteById = async (token: string, id: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/notes/${id}/delete`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.then((json) => {
|
||||
return json;
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err.detail;
|
||||
|
||||
console.log(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
@@ -116,10 +116,33 @@ export const updateUserRole = async (token: string, id: string, role: string) =>
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getUsers = async (token: string) => {
|
||||
export const getUsers = async (
|
||||
token: string,
|
||||
query?: string,
|
||||
orderBy?: string,
|
||||
direction?: string,
|
||||
page = 1
|
||||
) => {
|
||||
let error = null;
|
||||
let res = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/users/`, {
|
||||
let searchParams = new URLSearchParams();
|
||||
|
||||
searchParams.set('page', `${page}`);
|
||||
|
||||
if (query) {
|
||||
searchParams.set('query', query);
|
||||
}
|
||||
|
||||
if (orderBy) {
|
||||
searchParams.set('order_by', orderBy);
|
||||
}
|
||||
|
||||
if (direction) {
|
||||
searchParams.set('direction', direction);
|
||||
}
|
||||
|
||||
res = await fetch(`${WEBUI_API_BASE_URL}/users/?${searchParams.toString()}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -140,7 +163,35 @@ export const getUsers = async (token: string) => {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res ? res : [];
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getAllUsers = async (token: string) => {
|
||||
let error = null;
|
||||
let res = null;
|
||||
|
||||
res = await fetch(`${WEBUI_API_BASE_URL}/users/all`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
error = err.detail;
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getUserSettings = async (token: string) => {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
{#if content}
|
||||
{content}
|
||||
{:else}
|
||||
{$i18n.t('Drop any files here to add to the conversation')}
|
||||
{$i18n.t('Drop any files here to upload')}
|
||||
{/if}
|
||||
</div>
|
||||
</slot>
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<p class="sr-only">{$i18n.t('Close')}</p>
|
||||
<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"
|
||||
/>
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
<div class="flex justify-center mt-8">
|
||||
<div class="flex flex-col justify-center items-center">
|
||||
<button
|
||||
aria-labelledby="get-started"
|
||||
class="relative z-20 flex p-1 rounded-full bg-white/5 hover:bg-white/10 transition font-medium text-sm"
|
||||
on:click={() => {
|
||||
getStartedHandler();
|
||||
@@ -94,12 +95,12 @@
|
||||
>
|
||||
<ArrowRightCircle className="size-6" />
|
||||
</button>
|
||||
<div class="mt-1.5 font-primary text-base font-medium">{$i18n.t(`Get started`)}</div>
|
||||
<div id="get-started" class="mt-1.5 font-primary text-base font-medium">
|
||||
{$i18n.t(`Get started`)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="absolute bottom-12 left-0 right-0 w-full"></div> -->
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
|
||||
<svelte:head>
|
||||
<title>
|
||||
{$i18n.t('Functions')} | {$WEBUI_NAME}
|
||||
{$i18n.t('Functions')} • {$WEBUI_NAME}
|
||||
</title>
|
||||
</svelte:head>
|
||||
|
||||
|
||||
@@ -387,7 +387,7 @@ class Pipe:
|
||||
<div class="flex-1 pr-3">
|
||||
<div class="text-xs text-gray-500 line-clamp-2">
|
||||
<span class=" font-semibold dark:text-gray-200">{$i18n.t('Warning:')}</span>
|
||||
{$i18n.t('Functions allow arbitrary code execution')} <br />—
|
||||
{$i18n.t('Functions allow arbitrary code execution.')} <br />—
|
||||
<span class=" font-medium dark:text-gray-400"
|
||||
>{$i18n.t(`don't install random functions from sources you don't trust.`)}</span
|
||||
>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
let TTS_VOICE = '';
|
||||
let TTS_SPLIT_ON: TTS_RESPONSE_SPLIT = TTS_RESPONSE_SPLIT.PUNCTUATION;
|
||||
let TTS_AZURE_SPEECH_REGION = '';
|
||||
let TTS_AZURE_SPEECH_BASE_URL = '';
|
||||
let TTS_AZURE_SPEECH_OUTPUT_FORMAT = '';
|
||||
|
||||
let STT_OPENAI_API_BASE_URL = '';
|
||||
@@ -42,6 +43,8 @@
|
||||
let STT_AZURE_API_KEY = '';
|
||||
let STT_AZURE_REGION = '';
|
||||
let STT_AZURE_LOCALES = '';
|
||||
let STT_AZURE_BASE_URL = '';
|
||||
let STT_AZURE_MAX_SPEAKERS = '';
|
||||
let STT_DEEPGRAM_API_KEY = '';
|
||||
|
||||
let STT_WHISPER_MODEL_LOADING = false;
|
||||
@@ -103,6 +106,7 @@
|
||||
VOICE: TTS_VOICE,
|
||||
SPLIT_ON: TTS_SPLIT_ON,
|
||||
AZURE_SPEECH_REGION: TTS_AZURE_SPEECH_REGION,
|
||||
AZURE_SPEECH_BASE_URL: TTS_AZURE_SPEECH_BASE_URL,
|
||||
AZURE_SPEECH_OUTPUT_FORMAT: TTS_AZURE_SPEECH_OUTPUT_FORMAT
|
||||
},
|
||||
stt: {
|
||||
@@ -114,7 +118,9 @@
|
||||
DEEPGRAM_API_KEY: STT_DEEPGRAM_API_KEY,
|
||||
AZURE_API_KEY: STT_AZURE_API_KEY,
|
||||
AZURE_REGION: STT_AZURE_REGION,
|
||||
AZURE_LOCALES: STT_AZURE_LOCALES
|
||||
AZURE_LOCALES: STT_AZURE_LOCALES,
|
||||
AZURE_BASE_URL: STT_AZURE_BASE_URL,
|
||||
AZURE_MAX_SPEAKERS: STT_AZURE_MAX_SPEAKERS
|
||||
}
|
||||
});
|
||||
|
||||
@@ -145,8 +151,9 @@
|
||||
|
||||
TTS_SPLIT_ON = res.tts.SPLIT_ON || TTS_RESPONSE_SPLIT.PUNCTUATION;
|
||||
|
||||
TTS_AZURE_SPEECH_OUTPUT_FORMAT = res.tts.AZURE_SPEECH_OUTPUT_FORMAT;
|
||||
TTS_AZURE_SPEECH_REGION = res.tts.AZURE_SPEECH_REGION;
|
||||
TTS_AZURE_SPEECH_BASE_URL = res.tts.AZURE_SPEECH_BASE_URL;
|
||||
TTS_AZURE_SPEECH_OUTPUT_FORMAT = res.tts.AZURE_SPEECH_OUTPUT_FORMAT;
|
||||
|
||||
STT_OPENAI_API_BASE_URL = res.stt.OPENAI_API_BASE_URL;
|
||||
STT_OPENAI_API_KEY = res.stt.OPENAI_API_KEY;
|
||||
@@ -157,6 +164,8 @@
|
||||
STT_AZURE_API_KEY = res.stt.AZURE_API_KEY;
|
||||
STT_AZURE_REGION = res.stt.AZURE_REGION;
|
||||
STT_AZURE_LOCALES = res.stt.AZURE_LOCALES;
|
||||
STT_AZURE_BASE_URL = res.stt.AZURE_BASE_URL;
|
||||
STT_AZURE_MAX_SPEAKERS = res.stt.AZURE_MAX_SPEAKERS;
|
||||
STT_DEEPGRAM_API_KEY = res.stt.DEEPGRAM_API_KEY;
|
||||
}
|
||||
|
||||
@@ -266,16 +275,23 @@
|
||||
bind:value={STT_AZURE_API_KEY}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
placeholder={$i18n.t('Azure Region')}
|
||||
bind:value={STT_AZURE_REGION}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Azure Region')}</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
bind:value={STT_AZURE_REGION}
|
||||
placeholder={$i18n.t('e.g., westus (leave blank for eastus)')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Language Locales')}</div>
|
||||
<div class="flex w-full">
|
||||
@@ -288,6 +304,32 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Endpoint URL')}</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
bind:value={STT_AZURE_BASE_URL}
|
||||
placeholder={$i18n.t('(leave blank for to use commercial endpoint)')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Max Speakers')}</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
bind:value={STT_AZURE_MAX_SPEAKERS}
|
||||
placeholder={$i18n.t('e.g., 3, 4, 5 (leave blank for default)')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if STT_ENGINE === ''}
|
||||
<div>
|
||||
@@ -436,18 +478,35 @@
|
||||
{:else if TTS_ENGINE === 'azure'}
|
||||
<div>
|
||||
<div class="mt-1 flex gap-2 mb-1">
|
||||
<input
|
||||
class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
placeholder={$i18n.t('API Key')}
|
||||
bind:value={TTS_API_KEY}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
placeholder={$i18n.t('Azure Region')}
|
||||
bind:value={TTS_AZURE_SPEECH_REGION}
|
||||
required
|
||||
/>
|
||||
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={TTS_API_KEY} required />
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Azure Region')}</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
bind:value={TTS_AZURE_SPEECH_REGION}
|
||||
placeholder={$i18n.t('e.g., westus (leave blank for eastus)')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Endpoint URL')}</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
bind:value={TTS_AZURE_SPEECH_BASE_URL}
|
||||
placeholder={$i18n.t('(leave blank for to use commercial endpoint)')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -123,35 +123,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
const rerankingModelUpdateHandler = async () => {
|
||||
console.log('Update reranking model attempt:', rerankingModel);
|
||||
|
||||
updateRerankingModelLoading = true;
|
||||
const res = await updateRerankingConfig(localStorage.token, {
|
||||
reranking_model: rerankingModel
|
||||
}).catch(async (error) => {
|
||||
toast.error(`${error}`);
|
||||
await setRerankingConfig();
|
||||
return null;
|
||||
});
|
||||
updateRerankingModelLoading = false;
|
||||
|
||||
if (res) {
|
||||
console.log('rerankingModelUpdateHandler:', res);
|
||||
if (res.status === true) {
|
||||
if (rerankingModel === '') {
|
||||
toast.success($i18n.t('Reranking model disabled', res), {
|
||||
duration: 1000 * 10
|
||||
});
|
||||
} else {
|
||||
toast.success($i18n.t('Reranking model set to "{{reranking_model}}"', res), {
|
||||
duration: 1000 * 10
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const submitHandler = async () => {
|
||||
if (RAGConfig.CONTENT_EXTRACTION_ENGINE === 'tika' && RAGConfig.TIKA_SERVER_URL === '') {
|
||||
toast.error($i18n.t('Tika Server URL required.'));
|
||||
@@ -161,6 +132,16 @@
|
||||
toast.error($i18n.t('Docling Server URL required.'));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
RAGConfig.CONTENT_EXTRACTION_ENGINE === 'docling' &&
|
||||
((RAGConfig.DOCLING_OCR_ENGINE === '' && RAGConfig.DOCLING_OCR_LANG !== '') ||
|
||||
(RAGConfig.DOCLING_OCR_ENGINE !== '' && RAGConfig.DOCLING_OCR_LANG === ''))
|
||||
) {
|
||||
toast.error(
|
||||
$i18n.t('Both Docling OCR Engine and Language(s) must be provided or both left empty.')
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
RAGConfig.CONTENT_EXTRACTION_ENGINE === 'document_intelligence' &&
|
||||
@@ -180,10 +161,6 @@
|
||||
|
||||
if (!RAGConfig.BYPASS_EMBEDDING_AND_RETRIEVAL) {
|
||||
await embeddingModelUpdateHandler();
|
||||
|
||||
if (RAGConfig.ENABLE_RAG_HYBRID_SEARCH) {
|
||||
await rerankingModelUpdateHandler();
|
||||
}
|
||||
}
|
||||
|
||||
const res = await updateRAGConfig(localStorage.token, RAGConfig);
|
||||
@@ -205,18 +182,8 @@
|
||||
OllamaUrl = embeddingConfig.ollama_config.url;
|
||||
}
|
||||
};
|
||||
|
||||
const setRerankingConfig = async () => {
|
||||
const rerankingConfig = await getRerankingConfig(localStorage.token);
|
||||
|
||||
if (rerankingConfig) {
|
||||
rerankingModel = rerankingConfig.reranking_model;
|
||||
}
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
await setEmbeddingConfig();
|
||||
await setRerankingConfig();
|
||||
|
||||
RAGConfig = await getRAGConfig(localStorage.token);
|
||||
});
|
||||
@@ -326,6 +293,18 @@
|
||||
bind:value={RAGConfig.DOCLING_SERVER_URL}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex w-full mt-2">
|
||||
<input
|
||||
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Enter Docling OCR Engine')}
|
||||
bind:value={RAGConfig.DOCLING_OCR_ENGINE}
|
||||
/>
|
||||
<input
|
||||
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Enter Docling OCR Language(s)')}
|
||||
bind:value={RAGConfig.DOCLING_OCR_LANG}
|
||||
/>
|
||||
</div>
|
||||
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'document_intelligence'}
|
||||
<div class="my-0.5 flex gap-2 pr-2">
|
||||
<input
|
||||
@@ -633,6 +612,48 @@
|
||||
</div>
|
||||
|
||||
{#if RAGConfig.ENABLE_RAG_HYBRID_SEARCH === true}
|
||||
<div class=" mb-2.5 flex flex-col w-full justify-between">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Reranking Engine')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={RAGConfig.RAG_RERANKING_ENGINE}
|
||||
placeholder="Select a reranking model engine"
|
||||
on:change={(e) => {
|
||||
if (e.target.value === 'external') {
|
||||
RAGConfig.RAG_RERANKING_MODEL = '';
|
||||
} else if (e.target.value === '') {
|
||||
RAGConfig.RAG_RERANKING_MODEL = 'BAAI/bge-reranker-v2-m3';
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="">{$i18n.t('Default (SentenceTransformers)')}</option>
|
||||
<option value="external">{$i18n.t('External')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if RAGConfig.RAG_RERANKING_ENGINE === 'external'}
|
||||
<div class="my-0.5 flex gap-2 pr-2">
|
||||
<input
|
||||
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={RAGConfig.RAG_EXTERNAL_RERANKER_URL}
|
||||
required
|
||||
/>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('API Key')}
|
||||
bind:value={RAGConfig.RAG_EXTERNAL_RERANKER_API_KEY}
|
||||
required={false}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs font-medium">{$i18n.t('Reranking Model')}</div>
|
||||
|
||||
@@ -644,62 +665,9 @@
|
||||
placeholder={$i18n.t('Set reranking model (e.g. {{model}})', {
|
||||
model: 'BAAI/bge-reranker-v2-m3'
|
||||
})}
|
||||
bind:value={rerankingModel}
|
||||
bind:value={RAGConfig.RAG_RERANKING_MODEL}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="px-2.5 bg-transparent text-gray-800 dark:bg-transparent dark:text-gray-100 rounded-lg transition"
|
||||
on:click={() => {
|
||||
rerankingModelUpdateHandler();
|
||||
}}
|
||||
disabled={updateRerankingModelLoading}
|
||||
>
|
||||
{#if updateRerankingModelLoading}
|
||||
<div class="self-center">
|
||||
<svg
|
||||
class=" w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<style>
|
||||
.spinner_ajPY {
|
||||
transform-origin: center;
|
||||
animation: spinner_AtaB 0.75s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes spinner_AtaB {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<path
|
||||
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||
opacity=".25"
|
||||
/>
|
||||
<path
|
||||
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
||||
class="spinner_ajPY"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
|
||||
/>
|
||||
<path
|
||||
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
import { getBackendConfig, getVersionUpdates, getWebhookUrl, updateWebhookUrl } from '$lib/apis';
|
||||
import { getVersionUpdates, getWebhookUrl, updateWebhookUrl } from '$lib/apis';
|
||||
import {
|
||||
getAdminConfig,
|
||||
getLdapConfig,
|
||||
@@ -601,6 +601,14 @@
|
||||
<Switch bind:state={adminConfig.ENABLE_MESSAGE_RATING} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Notes')} ({$i18n.t('Beta')})
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_NOTES} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Channels')} ({$i18n.t('Beta')})
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { getBackendConfig, getTaskConfig, updateTaskConfig } from '$lib/apis';
|
||||
import { getBackendConfig, getModels, getTaskConfig, updateTaskConfig } from '$lib/apis';
|
||||
import { setDefaultPromptSuggestions } from '$lib/apis/configs';
|
||||
import { config, models, settings, user } from '$lib/stores';
|
||||
import { config, settings, user } from '$lib/stores';
|
||||
import { createEventDispatcher, onMount, getContext } from 'svelte';
|
||||
|
||||
import { banners as _banners } from '$lib/stores';
|
||||
@@ -15,6 +15,8 @@
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
import Textarea from '$lib/components/common/Textarea.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import { getBaseModels } from '$lib/apis/models';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -49,6 +51,7 @@
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
await init();
|
||||
taskConfig = await getTaskConfig(localStorage.token);
|
||||
|
||||
promptSuggestions = $config?.default_prompt_suggestions ?? [];
|
||||
@@ -58,9 +61,40 @@
|
||||
const updateBanners = async () => {
|
||||
_banners.set(await setBanners(localStorage.token, banners));
|
||||
};
|
||||
|
||||
let workspaceModels = null;
|
||||
let baseModels = null;
|
||||
|
||||
let models = null;
|
||||
|
||||
const init = async () => {
|
||||
workspaceModels = await getBaseModels(localStorage.token);
|
||||
baseModels = await getModels(localStorage.token, null, true);
|
||||
|
||||
models = baseModels.map((m) => {
|
||||
const workspaceModel = workspaceModels.find((wm) => wm.id === m.id);
|
||||
|
||||
if (workspaceModel) {
|
||||
return {
|
||||
...m,
|
||||
...workspaceModel
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...m,
|
||||
id: m.id,
|
||||
name: m.name,
|
||||
|
||||
is_active: true
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
console.log('models', models);
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if taskConfig}
|
||||
{#if models !== null && taskConfig}
|
||||
<form
|
||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||
on:submit|preventDefault={() => {
|
||||
@@ -105,9 +139,27 @@
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
bind:value={taskConfig.TASK_MODEL}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
on:change={() => {
|
||||
if (taskConfig.TASK_MODEL) {
|
||||
const model = models.find((m) => m.id === taskConfig.TASK_MODEL);
|
||||
if (model) {
|
||||
if (model?.access_control !== null) {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'This model is not publicly available. Please select another model.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
taskConfig.TASK_MODEL = model.id;
|
||||
} else {
|
||||
taskConfig.TASK_MODEL = '';
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="" selected>{$i18n.t('Current Model')}</option>
|
||||
{#each $models.filter((m) => m.owned_by === 'ollama') as model}
|
||||
{#each models.filter((m) => m.owned_by === 'ollama') as model}
|
||||
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">
|
||||
{model.name}
|
||||
</option>
|
||||
@@ -121,9 +173,27 @@
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
bind:value={taskConfig.TASK_MODEL_EXTERNAL}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
on:change={() => {
|
||||
if (taskConfig.TASK_MODEL_EXTERNAL) {
|
||||
const model = models.find((m) => m.id === taskConfig.TASK_MODEL_EXTERNAL);
|
||||
if (model) {
|
||||
if (model?.access_control !== null) {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'This model is not publicly available. Please select another model.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
taskConfig.TASK_MODEL_EXTERNAL = model.id;
|
||||
} else {
|
||||
taskConfig.TASK_MODEL_EXTERNAL = '';
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="" selected>{$i18n.t('Current Model')}</option>
|
||||
{#each $models as model}
|
||||
{#each models as model}
|
||||
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">
|
||||
{model.name}
|
||||
</option>
|
||||
@@ -480,4 +550,8 @@
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{:else}
|
||||
<div class=" h-full w-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
let webSearchEngines = [
|
||||
'searxng',
|
||||
'yacy',
|
||||
'google_pse',
|
||||
'brave',
|
||||
'kagi',
|
||||
@@ -30,9 +31,11 @@
|
||||
'bing',
|
||||
'exa',
|
||||
'perplexity',
|
||||
'sougou'
|
||||
'sougou',
|
||||
'firecrawl',
|
||||
'external'
|
||||
];
|
||||
let webLoaderEngines = ['playwright', 'firecrawl', 'tavily'];
|
||||
let webLoaderEngines = ['playwright', 'firecrawl', 'tavily', 'external'];
|
||||
|
||||
let webConfig = null;
|
||||
|
||||
@@ -143,6 +146,53 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'yacy'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Yacy Instance URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Yacy URL (e.g. http://yacy.example.com:8090)')}
|
||||
bind:value={webConfig.YACY_QUERY_URL}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div class="flex gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Yacy Username')}
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
placeholder={$i18n.t('Enter Yacy Username')}
|
||||
bind:value={webConfig.YACY_USERNAME}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Yacy Password')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Yacy Password')}
|
||||
bind:value={webConfig.YACY_PASSWORD}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'google_pse'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
@@ -431,6 +481,68 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'firecrawl'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Firecrawl API Base URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Firecrawl API Base URL')}
|
||||
bind:value={webConfig.FIRECRAWL_API_BASE_URL}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Firecrawl API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Firecrawl API Key')}
|
||||
bind:value={webConfig.FIRECRAWL_API_KEY}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'external'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('External Web Search URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter External Web Search URL')}
|
||||
bind:value={webConfig.EXTERNAL_WEB_SEARCH_URL}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('External Web Search API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter External Web Search API Key')}
|
||||
bind:value={webConfig.EXTERNAL_WEB_SEARCH_API_KEY}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -588,7 +700,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.WEB_LOADER_ENGINE === 'firecrawl'}
|
||||
{:else if webConfig.WEB_LOADER_ENGINE === 'firecrawl' && webConfig.WEB_SEARCH_ENGINE !== 'firecrawl'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
@@ -652,6 +764,37 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if webConfig.WEB_LOADER_ENGINE === 'external'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('External Web Loader URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter External Web Loader URL')}
|
||||
bind:value={webConfig.EXTERNAL_WEB_LOADER_URL}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('External Web Loader API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter External Web Loader API Key')}
|
||||
bind:value={webConfig.EXTERNAL_WEB_LOADER_API_KEY}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
|
||||
@@ -5,32 +5,19 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { user } from '$lib/stores';
|
||||
|
||||
import { getUsers } from '$lib/apis/users';
|
||||
|
||||
import UserList from './Users/UserList.svelte';
|
||||
import Groups from './Users/Groups.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
let users = [];
|
||||
|
||||
let selectedTab = 'overview';
|
||||
let loaded = false;
|
||||
|
||||
$: if (selectedTab) {
|
||||
getUsersHandler();
|
||||
}
|
||||
|
||||
const getUsersHandler = async () => {
|
||||
users = await getUsers(localStorage.token);
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
if ($user?.role !== 'admin') {
|
||||
await goto('/');
|
||||
} else {
|
||||
users = await getUsers(localStorage.token);
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
|
||||
const containerElement = document.getElementById('users-tabs-container');
|
||||
@@ -102,9 +89,9 @@
|
||||
|
||||
<div class="flex-1 mt-1 lg:mt-0 overflow-y-scroll">
|
||||
{#if selectedTab === 'overview'}
|
||||
<UserList {users} />
|
||||
<UserList />
|
||||
{:else if selectedTab === 'groups'}
|
||||
<Groups {users} />
|
||||
<Groups />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,13 +23,18 @@
|
||||
import GroupItem from './Groups/GroupItem.svelte';
|
||||
import AddGroupModal from './Groups/AddGroupModal.svelte';
|
||||
import { createNewGroup, getGroups } from '$lib/apis/groups';
|
||||
import { getUserDefaultPermissions, updateUserDefaultPermissions } from '$lib/apis/users';
|
||||
import {
|
||||
getUserDefaultPermissions,
|
||||
getAllUsers,
|
||||
updateUserDefaultPermissions
|
||||
} from '$lib/apis/users';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
let loaded = false;
|
||||
|
||||
export let users = [];
|
||||
let users = [];
|
||||
let total = 0;
|
||||
|
||||
let groups = [];
|
||||
let filteredGroups;
|
||||
@@ -63,6 +68,8 @@
|
||||
file_upload: true,
|
||||
delete: true,
|
||||
edit: true,
|
||||
share: true,
|
||||
export: true,
|
||||
stt: true,
|
||||
tts: true,
|
||||
call: true,
|
||||
@@ -74,7 +81,8 @@
|
||||
direct_tool_servers: false,
|
||||
web_search: true,
|
||||
image_generation: true,
|
||||
code_interpreter: true
|
||||
code_interpreter: true,
|
||||
notes: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -116,10 +124,22 @@
|
||||
onMount(async () => {
|
||||
if ($user?.role !== 'admin') {
|
||||
await goto('/');
|
||||
} else {
|
||||
await setGroups();
|
||||
defaultPermissions = await getUserDefaultPermissions(localStorage.token);
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await getAllUsers(localStorage.token).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res) {
|
||||
users = res.users;
|
||||
total = res.total;
|
||||
}
|
||||
|
||||
await setGroups();
|
||||
defaultPermissions = await getUserDefaultPermissions(localStorage.token);
|
||||
|
||||
loaded = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
file_upload: true,
|
||||
delete: true,
|
||||
edit: true,
|
||||
share: true,
|
||||
export: true,
|
||||
stt: true,
|
||||
tts: true,
|
||||
call: true,
|
||||
@@ -35,7 +37,8 @@
|
||||
direct_tool_servers: false,
|
||||
web_search: true,
|
||||
image_generation: true,
|
||||
code_interpreter: true
|
||||
code_interpreter: true,
|
||||
notes: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -276,6 +279,22 @@
|
||||
<Switch bind:state={permissions.chat.edit} />
|
||||
</div>
|
||||
|
||||
<div class=" flex w-full justify-between my-2 pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Allow Chat Share')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={permissions.chat.share} />
|
||||
</div>
|
||||
|
||||
<div class=" flex w-full justify-between my-2 pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Allow Chat Export')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={permissions.chat.export} />
|
||||
</div>
|
||||
|
||||
<div class=" flex w-full justify-between my-2 pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Allow Speech to Text')}
|
||||
@@ -362,5 +381,13 @@
|
||||
|
||||
<Switch bind:state={permissions.features.code_interpreter} />
|
||||
</div>
|
||||
|
||||
<div class=" flex w-full justify-between my-2 pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Notes')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={permissions.features.notes} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
import AddUserModal from '$lib/components/admin/Users/UserList/AddUserModal.svelte';
|
||||
|
||||
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
import RoleUpdateConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
|
||||
import Badge from '$lib/components/common/Badge.svelte';
|
||||
import Plus from '$lib/components/icons/Plus.svelte';
|
||||
import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
|
||||
@@ -30,22 +32,37 @@
|
||||
import About from '$lib/components/chat/Settings/About.svelte';
|
||||
import Banner from '$lib/components/common/Banner.svelte';
|
||||
import Markdown from '$lib/components/chat/Messages/Markdown.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let users = [];
|
||||
|
||||
let search = '';
|
||||
let selectedUser = null;
|
||||
|
||||
let page = 1;
|
||||
|
||||
let users = null;
|
||||
let total = null;
|
||||
|
||||
let query = '';
|
||||
let orderBy = 'created_at'; // default sort key
|
||||
let direction = 'asc'; // default sort order
|
||||
|
||||
let selectedUser = null;
|
||||
|
||||
let showDeleteConfirmDialog = false;
|
||||
let showAddUserModal = false;
|
||||
|
||||
let showUserChatsModal = false;
|
||||
let showEditUserModal = false;
|
||||
let showUpdateRoleModal = false;
|
||||
|
||||
const onUpdateRole = (user) => {
|
||||
if (user.role === 'user') {
|
||||
updateRoleHandler(user.id, 'admin');
|
||||
} else if (user.role === 'pending') {
|
||||
updateRoleHandler(user.id, 'user');
|
||||
} else {
|
||||
updateRoleHandler(user.id, 'pending');
|
||||
}
|
||||
};
|
||||
const updateRoleHandler = async (id, role) => {
|
||||
const res = await updateUserRole(localStorage.token, id, role).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
@@ -53,7 +70,7 @@
|
||||
});
|
||||
|
||||
if (res) {
|
||||
users = await getUsers(localStorage.token);
|
||||
getUserList();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -62,42 +79,51 @@
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
// if the user is deleted and the current page has only one user, go back to the previous page
|
||||
if (users.length === 1 && page > 1) {
|
||||
page -= 1;
|
||||
}
|
||||
|
||||
if (res) {
|
||||
users = await getUsers(localStorage.token);
|
||||
getUserList();
|
||||
}
|
||||
};
|
||||
|
||||
let sortKey = 'created_at'; // default sort key
|
||||
let sortOrder = 'asc'; // default sort order
|
||||
|
||||
function setSortKey(key) {
|
||||
if (sortKey === key) {
|
||||
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
|
||||
const setSortKey = (key) => {
|
||||
if (orderBy === key) {
|
||||
direction = direction === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
sortKey = key;
|
||||
sortOrder = 'asc';
|
||||
orderBy = key;
|
||||
direction = 'asc';
|
||||
}
|
||||
};
|
||||
|
||||
const getUserList = async () => {
|
||||
try {
|
||||
const res = await getUsers(localStorage.token, query, orderBy, direction, page).catch(
|
||||
(error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
if (res) {
|
||||
users = res.users;
|
||||
total = res.total;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
$: if (page) {
|
||||
getUserList();
|
||||
}
|
||||
|
||||
let filteredUsers;
|
||||
|
||||
$: filteredUsers = users
|
||||
.filter((user) => {
|
||||
if (search === '') {
|
||||
return true;
|
||||
} else {
|
||||
let name = user.name.toLowerCase();
|
||||
let email = user.email.toLowerCase();
|
||||
const query = search.toLowerCase();
|
||||
return name.includes(query) || email.includes(query);
|
||||
}
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (a[sortKey] < b[sortKey]) return sortOrder === 'asc' ? -1 : 1;
|
||||
if (a[sortKey] > b[sortKey]) return sortOrder === 'asc' ? 1 : -1;
|
||||
return 0;
|
||||
})
|
||||
.slice((page - 1) * 20, page * 20);
|
||||
$: if (query !== null && orderBy && direction) {
|
||||
getUserList();
|
||||
}
|
||||
</script>
|
||||
|
||||
<ConfirmDialog
|
||||
@@ -107,13 +133,28 @@
|
||||
}}
|
||||
/>
|
||||
|
||||
<RoleUpdateConfirmDialog
|
||||
bind:show={showUpdateRoleModal}
|
||||
on:confirm={() => {
|
||||
onUpdateRole(selectedUser);
|
||||
}}
|
||||
message={$i18n.t(`Are you sure you want to update this user\'s role to **{{ROLE}}**?`, {
|
||||
ROLE:
|
||||
selectedUser?.role === 'user'
|
||||
? 'admin'
|
||||
: selectedUser?.role === 'pending'
|
||||
? 'user'
|
||||
: 'pending'
|
||||
})}
|
||||
/>
|
||||
|
||||
{#key selectedUser}
|
||||
<EditUserModal
|
||||
bind:show={showEditUserModal}
|
||||
{selectedUser}
|
||||
sessionUser={$user}
|
||||
on:save={async () => {
|
||||
users = await getUsers(localStorage.token);
|
||||
getUserList();
|
||||
}}
|
||||
/>
|
||||
{/key}
|
||||
@@ -121,12 +162,12 @@
|
||||
<AddUserModal
|
||||
bind:show={showAddUserModal}
|
||||
on:save={async () => {
|
||||
users = await getUsers(localStorage.token);
|
||||
getUserList();
|
||||
}}
|
||||
/>
|
||||
<UserChatsModal bind:show={showUserChatsModal} user={selectedUser} />
|
||||
|
||||
{#if ($config?.license_metadata?.seats ?? null) !== null && users.length > $config?.license_metadata?.seats}
|
||||
{#if ($config?.license_metadata?.seats ?? null) !== null && total && total > $config?.license_metadata?.seats}
|
||||
<div class=" mt-1 mb-2 text-xs text-red-500">
|
||||
<Banner
|
||||
className="mx-0"
|
||||
@@ -141,319 +182,293 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-0.5 mb-2 gap-1 flex flex-col md:flex-row justify-between">
|
||||
<div class="flex md:self-center text-lg font-medium px-0.5">
|
||||
<div class="flex-shrink-0">
|
||||
{$i18n.t('Users')}
|
||||
</div>
|
||||
<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
|
||||
{#if users === null || total === null}
|
||||
<div class="my-10">
|
||||
<Spinner />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="mt-0.5 mb-2 gap-1 flex flex-col md:flex-row justify-between">
|
||||
<div class="flex md:self-center text-lg font-medium px-0.5">
|
||||
<div class="flex-shrink-0">
|
||||
{$i18n.t('Users')}
|
||||
</div>
|
||||
<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
|
||||
|
||||
{#if ($config?.license_metadata?.seats ?? null) !== null}
|
||||
{#if users.length > $config?.license_metadata?.seats}
|
||||
<span class="text-lg font-medium text-red-500"
|
||||
>{users.length} of {$config?.license_metadata?.seats}
|
||||
<span class="text-sm font-normal">available users</span></span
|
||||
>
|
||||
{#if ($config?.license_metadata?.seats ?? null) !== null}
|
||||
{#if total > $config?.license_metadata?.seats}
|
||||
<span class="text-lg font-medium text-red-500"
|
||||
>{total} of {$config?.license_metadata?.seats}
|
||||
<span class="text-sm font-normal">available users</span></span
|
||||
>
|
||||
{:else}
|
||||
<span class="text-lg font-medium text-gray-500 dark:text-gray-300"
|
||||
>{total} of {$config?.license_metadata?.seats}
|
||||
<span class="text-sm font-normal">available users</span></span
|
||||
>
|
||||
{/if}
|
||||
{:else}
|
||||
<span class="text-lg font-medium text-gray-500 dark:text-gray-300"
|
||||
>{users.length} of {$config?.license_metadata?.seats}
|
||||
<span class="text-sm font-normal">available users</span></span
|
||||
>
|
||||
<span class="text-lg font-medium text-gray-500 dark:text-gray-300">{total}</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<span class="text-lg font-medium text-gray-500 dark:text-gray-300">{users.length}</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-1">
|
||||
<div class=" flex w-full space-x-2">
|
||||
<div class="flex flex-1">
|
||||
<div class=" self-center ml-1 mr-3">
|
||||
<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="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-hidden bg-transparent"
|
||||
bind:value={search}
|
||||
placeholder={$i18n.t('Search')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Tooltip content={$i18n.t('Add User')}>
|
||||
<button
|
||||
class=" p-2 rounded-xl hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition font-medium text-sm flex items-center space-x-1"
|
||||
on:click={() => {
|
||||
showAddUserModal = !showAddUserModal;
|
||||
}}
|
||||
>
|
||||
<Plus className="size-3.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded-sm pt-0.5"
|
||||
>
|
||||
<table
|
||||
class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full rounded-sm"
|
||||
>
|
||||
<thead
|
||||
class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400 -translate-y-0.5"
|
||||
>
|
||||
<tr class="">
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-1.5 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('role')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Role')}
|
||||
|
||||
{#if sortKey === 'role'}
|
||||
<span class="font-normal"
|
||||
>{#if sortOrder === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
<div class="flex gap-1">
|
||||
<div class=" flex w-full space-x-2">
|
||||
<div class="flex flex-1">
|
||||
<div class=" self-center ml-1 mr-3">
|
||||
<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="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-1.5 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('name')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Name')}
|
||||
<input
|
||||
class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-hidden bg-transparent"
|
||||
bind:value={query}
|
||||
placeholder={$i18n.t('Search')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if sortKey === 'name'}
|
||||
<span class="font-normal"
|
||||
>{#if sortOrder === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-1.5 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('email')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Email')}
|
||||
|
||||
{#if sortKey === 'email'}
|
||||
<span class="font-normal"
|
||||
>{#if sortOrder === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-1.5 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('last_active_at')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Last Active')}
|
||||
|
||||
{#if sortKey === 'last_active_at'}
|
||||
<span class="font-normal"
|
||||
>{#if sortOrder === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-1.5 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('created_at')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Created at')}
|
||||
{#if sortKey === 'created_at'}
|
||||
<span class="font-normal"
|
||||
>{#if sortOrder === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-1.5 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('oauth_sub')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('OAuth ID')}
|
||||
|
||||
{#if sortKey === 'oauth_sub'}
|
||||
<span class="font-normal"
|
||||
>{#if sortOrder === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th scope="col" class="px-3 py-2 text-right" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="">
|
||||
{#each filteredUsers as user, userIdx}
|
||||
<tr class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs">
|
||||
<td class="px-3 py-1 min-w-[7rem] w-28">
|
||||
<div>
|
||||
<Tooltip content={$i18n.t('Add User')}>
|
||||
<button
|
||||
class=" translate-y-0.5"
|
||||
class=" p-2 rounded-xl hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition font-medium text-sm flex items-center space-x-1"
|
||||
on:click={() => {
|
||||
if (user.role === 'user') {
|
||||
updateRoleHandler(user.id, 'admin');
|
||||
} else if (user.role === 'pending') {
|
||||
updateRoleHandler(user.id, 'user');
|
||||
} else {
|
||||
updateRoleHandler(user.id, 'pending');
|
||||
}
|
||||
showAddUserModal = !showAddUserModal;
|
||||
}}
|
||||
>
|
||||
<Badge
|
||||
type={user.role === 'admin' ? 'info' : user.role === 'user' ? 'success' : 'muted'}
|
||||
content={$i18n.t(user.role)}
|
||||
/>
|
||||
<Plus className="size-3.5" />
|
||||
</button>
|
||||
</td>
|
||||
<td class="px-3 py-1 font-medium text-gray-900 dark:text-white w-max">
|
||||
<div class="flex flex-row w-max">
|
||||
<img
|
||||
class=" rounded-full w-6 h-6 object-cover mr-2.5"
|
||||
src={user.profile_image_url.startsWith(WEBUI_BASE_URL) ||
|
||||
user.profile_image_url.startsWith('https://www.gravatar.com/avatar/') ||
|
||||
user.profile_image_url.startsWith('data:')
|
||||
? user.profile_image_url
|
||||
: `/user.png`}
|
||||
alt="user"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" font-medium self-center">{user.name}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class=" px-3 py-1"> {user.email} </td>
|
||||
<div
|
||||
class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded-sm pt-0.5"
|
||||
>
|
||||
<table
|
||||
class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full rounded-sm"
|
||||
>
|
||||
<thead
|
||||
class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400 -translate-y-0.5"
|
||||
>
|
||||
<tr class="">
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-1.5 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('role')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Role')}
|
||||
|
||||
<td class=" px-3 py-1">
|
||||
{dayjs(user.last_active_at * 1000).fromNow()}
|
||||
</td>
|
||||
|
||||
<td class=" px-3 py-1">
|
||||
{dayjs(user.created_at * 1000).format('LL')}
|
||||
</td>
|
||||
|
||||
<td class=" px-3 py-1"> {user.oauth_sub ?? ''} </td>
|
||||
|
||||
<td class="px-3 py-1 text-right">
|
||||
<div class="flex justify-end w-full">
|
||||
{#if $config.features.enable_admin_chat_access && user.role !== 'admin'}
|
||||
<Tooltip content={$i18n.t('Chats')}>
|
||||
<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 () => {
|
||||
showUserChatsModal = !showUserChatsModal;
|
||||
selectedUser = user;
|
||||
}}
|
||||
>
|
||||
<ChatBubbles />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{#if orderBy === 'role'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-1.5 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('name')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Name')}
|
||||
|
||||
<Tooltip content={$i18n.t('Edit User')}>
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
on:click={async () => {
|
||||
showEditUserModal = !showEditUserModal;
|
||||
selectedUser = user;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{#if orderBy === 'name'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-1.5 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('email')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Email')}
|
||||
|
||||
{#if user.role !== 'admin'}
|
||||
<Tooltip content={$i18n.t('Delete User')}>
|
||||
{#if orderBy === 'email'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-1.5 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('last_active_at')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Last Active')}
|
||||
|
||||
{#if orderBy === 'last_active_at'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-1.5 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('created_at')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Created at')}
|
||||
{#if orderBy === 'created_at'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-1.5 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('oauth_sub')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('OAuth ID')}
|
||||
|
||||
{#if orderBy === 'oauth_sub'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th scope="col" class="px-3 py-2 text-right" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="">
|
||||
{#each users as user, userIdx}
|
||||
<tr class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs">
|
||||
<td class="px-3 py-1 min-w-[7rem] w-28">
|
||||
<button
|
||||
class=" translate-y-0.5"
|
||||
on:click={() => {
|
||||
selectedUser = user;
|
||||
showUpdateRoleModal = true;
|
||||
}}
|
||||
>
|
||||
<Badge
|
||||
type={user.role === 'admin' ? 'info' : user.role === 'user' ? 'success' : 'muted'}
|
||||
content={$i18n.t(user.role)}
|
||||
/>
|
||||
</button>
|
||||
</td>
|
||||
<td class="px-3 py-1 font-medium text-gray-900 dark:text-white w-max">
|
||||
<div class="flex flex-row w-max">
|
||||
<img
|
||||
class=" rounded-full w-6 h-6 object-cover mr-2.5"
|
||||
src={user.profile_image_url.startsWith(WEBUI_BASE_URL) ||
|
||||
user.profile_image_url.startsWith('https://www.gravatar.com/avatar/') ||
|
||||
user.profile_image_url.startsWith('data:')
|
||||
? user.profile_image_url
|
||||
: `/user.png`}
|
||||
alt="user"
|
||||
/>
|
||||
|
||||
<div class=" font-medium self-center">{user.name}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class=" px-3 py-1"> {user.email} </td>
|
||||
|
||||
<td class=" px-3 py-1">
|
||||
{dayjs(user.last_active_at * 1000).fromNow()}
|
||||
</td>
|
||||
|
||||
<td class=" px-3 py-1">
|
||||
{dayjs(user.created_at * 1000).format('LL')}
|
||||
</td>
|
||||
|
||||
<td class=" px-3 py-1"> {user.oauth_sub ?? ''} </td>
|
||||
|
||||
<td class="px-3 py-1 text-right">
|
||||
<div class="flex justify-end w-full">
|
||||
{#if $config.features.enable_admin_chat_access && user.role !== 'admin'}
|
||||
<Tooltip content={$i18n.t('Chats')}>
|
||||
<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 () => {
|
||||
showUserChatsModal = !showUserChatsModal;
|
||||
selectedUser = user;
|
||||
}}
|
||||
>
|
||||
<ChatBubbles />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
<Tooltip content={$i18n.t('Edit User')}>
|
||||
<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 () => {
|
||||
showDeleteConfirmDialog = true;
|
||||
showEditUserModal = !showEditUserModal;
|
||||
selectedUser = user;
|
||||
}}
|
||||
>
|
||||
@@ -468,28 +483,55 @@
|
||||
<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"
|
||||
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class=" text-gray-500 text-xs mt-1.5 text-right">
|
||||
ⓘ {$i18n.t("Click on the user role button to change a user's role.")}
|
||||
</div>
|
||||
{#if user.role !== 'admin'}
|
||||
<Tooltip content={$i18n.t('Delete User')}>
|
||||
<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 () => {
|
||||
showDeleteConfirmDialog = true;
|
||||
selectedUser = user;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="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>
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<Pagination bind:page count={users.length} />
|
||||
<div class=" text-gray-500 text-xs mt-1.5 text-right">
|
||||
ⓘ {$i18n.t("Click on the user role button to change a user's role.")}
|
||||
</div>
|
||||
|
||||
<Pagination bind:page count={total} perPage={30} />
|
||||
{/if}
|
||||
|
||||
{#if !$config?.license_metadata}
|
||||
{#if users.length > 50}
|
||||
{#if total > 50}
|
||||
<div class="text-sm">
|
||||
<Markdown
|
||||
content={`
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
|
||||
import Modal from '$lib/components/common/Modal.svelte';
|
||||
import { generateInitialsImage } from '$lib/utils';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
const dispatch = createEventDispatcher();
|
||||
@@ -47,7 +48,8 @@
|
||||
_user.name,
|
||||
_user.email,
|
||||
_user.password,
|
||||
_user.role
|
||||
_user.role,
|
||||
generateInitialsImage(_user.name)
|
||||
).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
});
|
||||
@@ -83,7 +85,8 @@
|
||||
columns[0],
|
||||
columns[1],
|
||||
columns[2],
|
||||
columns[3].toLowerCase()
|
||||
columns[3].toLowerCase(),
|
||||
generateInitialsImage(columns[0])
|
||||
).catch((error) => {
|
||||
toast.error(`Row ${idx + 1}: ${error}`);
|
||||
return null;
|
||||
@@ -109,7 +112,7 @@
|
||||
stopLoading();
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
reader.readAsText(file, 'utf-8');
|
||||
} else {
|
||||
toast.error($i18n.t('File not found.'));
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
<Modal size="sm" bind:show>
|
||||
<div>
|
||||
<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
|
||||
<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
|
||||
<div class=" text-lg font-medium self-center">{$i18n.t('Edit User')}</div>
|
||||
<button
|
||||
class="self-center"
|
||||
@@ -65,9 +65,8 @@
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<hr class="border-gray-100 dark:border-gray-850" />
|
||||
|
||||
<div class="flex flex-col md:flex-row w-full p-5 md:space-x-4 dark:text-gray-200">
|
||||
<div class="flex flex-col md:flex-row w-full 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">
|
||||
<form
|
||||
class="flex flex-col w-full"
|
||||
@@ -75,7 +74,7 @@
|
||||
submitHandler();
|
||||
}}
|
||||
>
|
||||
<div class=" flex items-center rounded-md py-2 px-4 w-full">
|
||||
<div class=" flex items-center rounded-md px-5 py-2 w-full">
|
||||
<div class=" self-center mr-5">
|
||||
<img
|
||||
src={selectedUser.profile_image_url}
|
||||
@@ -94,59 +93,62 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100 dark:border-gray-850 my-3 w-full" />
|
||||
<div class=" px-5 pt-3 pb-5">
|
||||
<div class=" flex flex-col space-y-1.5">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Email')}</div>
|
||||
|
||||
<div class=" flex flex-col space-y-1.5">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Email')}</div>
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-sm text-sm bg-transparent disabled:text-gray-500 dark:disabled:text-gray-500 outline-hidden"
|
||||
type="email"
|
||||
bind:value={_user.email}
|
||||
placeholder={$i18n.t('Enter Your Email')}
|
||||
autocomplete="off"
|
||||
required
|
||||
disabled={_user.id == sessionUser.id}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-sm py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 disabled:text-gray-500 dark:disabled:text-gray-500 outline-hidden"
|
||||
type="email"
|
||||
bind:value={_user.email}
|
||||
autocomplete="off"
|
||||
required
|
||||
disabled={_user.id == sessionUser.id}
|
||||
/>
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-sm text-sm bg-transparent outline-hidden"
|
||||
type="text"
|
||||
bind:value={_user.name}
|
||||
placeholder={$i18n.t('Enter Your Name')}
|
||||
autocomplete="off"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('New Password')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-sm text-sm bg-transparent outline-hidden"
|
||||
type="password"
|
||||
placeholder={$i18n.t('Enter New Password')}
|
||||
bind:value={_user.password}
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-sm py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-hidden"
|
||||
type="text"
|
||||
bind:value={_user.name}
|
||||
autocomplete="off"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-end pt-3 text-sm font-medium">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center"
|
||||
type="submit"
|
||||
>
|
||||
{$i18n.t('Save')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('New Password')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-sm py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-hidden"
|
||||
type="password"
|
||||
bind:value={_user.password}
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-medium">
|
||||
<button
|
||||
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
|
||||
type="submit"
|
||||
>
|
||||
{$i18n.t('Save')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>#{channel?.name ?? 'Channel'} | Open WebUI</title>
|
||||
<title>#{channel?.name ?? 'Channel'} • Open WebUI</title>
|
||||
</svelte:head>
|
||||
|
||||
<div
|
||||
@@ -262,7 +262,7 @@
|
||||
{#if threadId !== null}
|
||||
<Drawer
|
||||
show={threadId !== null}
|
||||
on:close={() => {
|
||||
onClose={() => {
|
||||
threadId = null;
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -357,14 +357,14 @@
|
||||
{#if recording}
|
||||
<VoiceRecording
|
||||
bind:recording
|
||||
on:cancel={async () => {
|
||||
onCancel={async () => {
|
||||
recording = false;
|
||||
|
||||
await tick();
|
||||
document.getElementById(`chat-input-${id}`)?.focus();
|
||||
}}
|
||||
on:confirm={async (e) => {
|
||||
const { text, filename } = e.detail;
|
||||
onConfirm={async (data) => {
|
||||
const { text, filename } = data;
|
||||
content = `${content}${text} `;
|
||||
recording = false;
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
|
||||
export let chatIdProp = '';
|
||||
|
||||
let loading = false;
|
||||
let loading = true;
|
||||
|
||||
const eventTarget = new EventTarget();
|
||||
let controlPane;
|
||||
@@ -142,7 +142,6 @@
|
||||
$: if (chatIdProp) {
|
||||
(async () => {
|
||||
loading = true;
|
||||
console.log(chatIdProp);
|
||||
|
||||
prompt = '';
|
||||
files = [];
|
||||
@@ -150,22 +149,26 @@
|
||||
webSearchEnabled = false;
|
||||
imageGenerationEnabled = false;
|
||||
|
||||
if (chatIdProp && (await loadChat())) {
|
||||
await tick();
|
||||
loading = false;
|
||||
|
||||
if (localStorage.getItem(`chat-input-${chatIdProp}`)) {
|
||||
try {
|
||||
const input = JSON.parse(localStorage.getItem(`chat-input-${chatIdProp}`));
|
||||
if (localStorage.getItem(`chat-input${chatIdProp ? `-${chatIdProp}` : ''}`)) {
|
||||
try {
|
||||
const input = JSON.parse(
|
||||
localStorage.getItem(`chat-input${chatIdProp ? `-${chatIdProp}` : ''}`)
|
||||
);
|
||||
|
||||
if (!$temporaryChatEnabled) {
|
||||
prompt = input.prompt;
|
||||
files = input.files;
|
||||
selectedToolIds = input.selectedToolIds;
|
||||
webSearchEnabled = input.webSearchEnabled;
|
||||
imageGenerationEnabled = input.imageGenerationEnabled;
|
||||
} catch (e) {}
|
||||
}
|
||||
codeInterpreterEnabled = input.codeInterpreterEnabled;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (chatIdProp && (await loadChat())) {
|
||||
await tick();
|
||||
loading = false;
|
||||
window.setTimeout(() => scrollToBottom(), 0);
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
chatInput?.focus();
|
||||
@@ -236,9 +239,11 @@
|
||||
await tick();
|
||||
await tick();
|
||||
|
||||
const messageElement = document.getElementById(`message-${message.id}`);
|
||||
if (messageElement) {
|
||||
messageElement.scrollIntoView({ behavior: 'smooth' });
|
||||
if ($settings?.scrollOnBranchChange ?? true) {
|
||||
const messageElement = document.getElementById(`message-${message.id}`);
|
||||
if (messageElement) {
|
||||
messageElement.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
await tick();
|
||||
@@ -397,6 +402,7 @@
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
loading = true;
|
||||
console.log('mounted');
|
||||
window.addEventListener('message', onMessageHandler);
|
||||
$socket?.on('chat-events', chatEventHandler);
|
||||
@@ -414,21 +420,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (localStorage.getItem(`chat-input-${chatIdProp}`)) {
|
||||
if (localStorage.getItem(`chat-input${chatIdProp ? `-${chatIdProp}` : ''}`)) {
|
||||
prompt = '';
|
||||
files = [];
|
||||
selectedToolIds = [];
|
||||
webSearchEnabled = false;
|
||||
imageGenerationEnabled = false;
|
||||
codeInterpreterEnabled = false;
|
||||
|
||||
try {
|
||||
const input = JSON.parse(localStorage.getItem(`chat-input-${chatIdProp}`));
|
||||
prompt = input.prompt;
|
||||
files = input.files;
|
||||
selectedToolIds = input.selectedToolIds;
|
||||
webSearchEnabled = input.webSearchEnabled;
|
||||
imageGenerationEnabled = input.imageGenerationEnabled;
|
||||
} catch (e) {
|
||||
prompt = '';
|
||||
files = [];
|
||||
selectedToolIds = [];
|
||||
webSearchEnabled = false;
|
||||
imageGenerationEnabled = false;
|
||||
}
|
||||
const input = JSON.parse(
|
||||
localStorage.getItem(`chat-input${chatIdProp ? `-${chatIdProp}` : ''}`)
|
||||
);
|
||||
|
||||
if (!$temporaryChatEnabled) {
|
||||
prompt = input.prompt;
|
||||
files = input.files;
|
||||
selectedToolIds = input.selectedToolIds;
|
||||
webSearchEnabled = input.webSearchEnabled;
|
||||
imageGenerationEnabled = input.imageGenerationEnabled;
|
||||
codeInterpreterEnabled = input.codeInterpreterEnabled;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (!chatIdProp) {
|
||||
loading = false;
|
||||
await tick();
|
||||
}
|
||||
|
||||
showControls.subscribe(async (value) => {
|
||||
@@ -1482,9 +1500,20 @@
|
||||
};
|
||||
|
||||
const sendPromptSocket = async (_history, model, responseMessageId, _chatId) => {
|
||||
const chatMessages = createMessagesList(history, history.currentId);
|
||||
const responseMessage = _history.messages[responseMessageId];
|
||||
const userMessage = _history.messages[responseMessage.parentId];
|
||||
|
||||
const chatMessageFiles = chatMessages
|
||||
.filter((message) => message.files)
|
||||
.flatMap((message) => message.files);
|
||||
|
||||
// Filter chatFiles to only include files that are in the chatMessageFiles
|
||||
chatFiles = chatFiles.filter((item) => {
|
||||
const fileExists = chatMessageFiles.some((messageFile) => messageFile.id === item.id);
|
||||
return fileExists;
|
||||
});
|
||||
|
||||
let files = JSON.parse(JSON.stringify(chatFiles));
|
||||
files.push(
|
||||
...(userMessage?.files ?? []).filter((item) =>
|
||||
@@ -1919,7 +1948,7 @@
|
||||
<svelte:head>
|
||||
<title>
|
||||
{$chatTitle
|
||||
? `${$chatTitle.length > 30 ? `${$chatTitle.slice(0, 30)}...` : $chatTitle} | ${$WEBUI_NAME}`
|
||||
? `${$chatTitle.length > 30 ? `${$chatTitle.slice(0, 30)}...` : $chatTitle} • ${$WEBUI_NAME}`
|
||||
: `${$WEBUI_NAME}`}
|
||||
</title>
|
||||
</svelte:head>
|
||||
@@ -2038,10 +2067,13 @@
|
||||
{stopResponse}
|
||||
{createMessagePair}
|
||||
onChange={(input) => {
|
||||
if (input.prompt) {
|
||||
localStorage.setItem(`chat-input-${$chatId}`, JSON.stringify(input));
|
||||
if (input.prompt !== null) {
|
||||
localStorage.setItem(
|
||||
`chat-input${$chatId ? `-${$chatId}` : ''}`,
|
||||
JSON.stringify(input)
|
||||
);
|
||||
} else {
|
||||
localStorage.removeItem(`chat-input-${$chatId}`);
|
||||
localStorage.removeItem(`chat-input${$chatId ? `-${$chatId}` : ''}`);
|
||||
}
|
||||
}}
|
||||
on:upload={async (e) => {
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
{#if $showControls}
|
||||
<Drawer
|
||||
show={$showControls}
|
||||
on:close={() => {
|
||||
onClose={() => {
|
||||
showControls.set(false);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -86,10 +86,11 @@
|
||||
|
||||
$: onChange({
|
||||
prompt,
|
||||
files,
|
||||
files: files.filter((file) => file.type !== 'image'),
|
||||
selectedToolIds,
|
||||
imageGenerationEnabled,
|
||||
webSearchEnabled
|
||||
webSearchEnabled,
|
||||
codeInterpreterEnabled
|
||||
});
|
||||
|
||||
let showTools = false;
|
||||
@@ -395,39 +396,37 @@
|
||||
</div>
|
||||
|
||||
<div class="w-full relative">
|
||||
{#if atSelectedModel !== undefined || selectedToolIds.length > 0 || webSearchEnabled || ($settings?.webSearch ?? false) === 'always' || imageGenerationEnabled || codeInterpreterEnabled}
|
||||
{#if atSelectedModel !== undefined}
|
||||
<div
|
||||
class="px-3 pb-0.5 pt-1.5 text-left w-full flex flex-col absolute bottom-0 left-0 right-0 bg-linear-to-t from-white dark:from-gray-900 z-10"
|
||||
>
|
||||
{#if atSelectedModel !== undefined}
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="pl-[1px] flex items-center gap-2 text-sm dark:text-gray-500">
|
||||
<img
|
||||
crossorigin="anonymous"
|
||||
alt="model profile"
|
||||
class="size-3.5 max-w-[28px] object-cover rounded-full"
|
||||
src={$models.find((model) => model.id === atSelectedModel.id)?.info?.meta
|
||||
?.profile_image_url ??
|
||||
($i18n.language === 'dg-DG'
|
||||
? `/doge.png`
|
||||
: `${WEBUI_BASE_URL}/static/favicon.png`)}
|
||||
/>
|
||||
<div class="translate-y-[0.5px]">
|
||||
Talking to <span class=" font-medium">{atSelectedModel.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="flex items-center dark:text-gray-500"
|
||||
on:click={() => {
|
||||
atSelectedModel = undefined;
|
||||
}}
|
||||
>
|
||||
<XMark />
|
||||
</button>
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="pl-[1px] flex items-center gap-2 text-sm dark:text-gray-500">
|
||||
<img
|
||||
crossorigin="anonymous"
|
||||
alt="model profile"
|
||||
class="size-3.5 max-w-[28px] object-cover rounded-full"
|
||||
src={$models.find((model) => model.id === atSelectedModel.id)?.info?.meta
|
||||
?.profile_image_url ??
|
||||
($i18n.language === 'dg-DG'
|
||||
? `/doge.png`
|
||||
: `${WEBUI_BASE_URL}/static/favicon.png`)}
|
||||
/>
|
||||
<div class="translate-y-[0.5px]">
|
||||
Talking to <span class=" font-medium">{atSelectedModel.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div>
|
||||
<button
|
||||
class="flex items-center dark:text-gray-500"
|
||||
on:click={() => {
|
||||
atSelectedModel = undefined;
|
||||
}}
|
||||
>
|
||||
<XMark />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -481,14 +480,14 @@
|
||||
{#if recording}
|
||||
<VoiceRecording
|
||||
bind:recording
|
||||
on:cancel={async () => {
|
||||
onCancel={async () => {
|
||||
recording = false;
|
||||
|
||||
await tick();
|
||||
document.getElementById('chat-input')?.focus();
|
||||
}}
|
||||
on:confirm={async (e) => {
|
||||
const { text, filename } = e.detail;
|
||||
onConfirm={async (data) => {
|
||||
const { text, filename } = data;
|
||||
prompt = `${prompt}${text} `;
|
||||
|
||||
recording = false;
|
||||
@@ -605,7 +604,7 @@
|
||||
<div class="px-2.5">
|
||||
{#if $settings?.richTextInput ?? true}
|
||||
<div
|
||||
class="scrollbar-hidden text-left bg-transparent dark:text-gray-100 outline-hidden w-full pt-3 px-1 resize-none h-fit max-h-80 overflow-auto"
|
||||
class="scrollbar-hidden rtl:text-right ltr:text-left bg-transparent dark:text-gray-100 outline-hidden w-full pt-3 px-1 resize-none h-fit max-h-80 overflow-auto"
|
||||
id="chat-input-container"
|
||||
>
|
||||
<RichTextInput
|
||||
@@ -1063,9 +1062,9 @@
|
||||
);
|
||||
}
|
||||
}}
|
||||
uploadOneDriveHandler={async () => {
|
||||
uploadOneDriveHandler={async (authorityType) => {
|
||||
try {
|
||||
const fileData = await pickAndDownloadFile();
|
||||
const fileData = await pickAndDownloadFile(authorityType);
|
||||
if (fileData) {
|
||||
const file = new File([fileData.blob], fileData.name, {
|
||||
type: fileData.blob.type || 'application/octet-stream'
|
||||
|
||||
@@ -265,7 +265,7 @@
|
||||
{/each}
|
||||
{:else}
|
||||
<div class=" text-gray-500 text-xs mt-1 mb-2">
|
||||
{$i18n.t('No files found.')}
|
||||
{$i18n.t('File not found.')}
|
||||
</div>
|
||||
{/if}
|
||||
</div> -->
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
let tools = {};
|
||||
let show = false;
|
||||
let showAllTools = false;
|
||||
|
||||
$: if (show) {
|
||||
init();
|
||||
@@ -102,7 +103,7 @@
|
||||
transition={flyAndScale}
|
||||
>
|
||||
{#if Object.keys(tools).length > 0}
|
||||
<div class=" max-h-28 overflow-y-auto scrollbar-hidden">
|
||||
<div class="{showAllTools ? '' : 'max-h-28'} overflow-y-auto scrollbar-thin">
|
||||
{#each Object.keys(tools) as toolId}
|
||||
<button
|
||||
class="flex w-full justify-between gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer rounded-xl"
|
||||
@@ -141,12 +142,34 @@
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if Object.keys(tools).length > 3}
|
||||
<button
|
||||
class="flex w-full justify-center items-center text-sm font-medium cursor-pointer rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
on:click={() => {
|
||||
showAllTools = !showAllTools;
|
||||
}}
|
||||
title={showAllTools ? $i18n.t('Show Less') : $i18n.t('Show All')}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2.5"
|
||||
stroke="currentColor"
|
||||
class="size-3 transition-transform duration-200 {showAllTools
|
||||
? 'rotate-180'
|
||||
: ''} text-gray-300 dark:text-gray-600"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
<hr class="border-black/5 dark:border-white/5 my-1" />
|
||||
{/if}
|
||||
|
||||
<Tooltip
|
||||
content={!fileUploadEnabled ? $i18n.t('You do not have permission to upload files') : ''}
|
||||
content={!fileUploadEnabled ? $i18n.t('You do not have permission to upload files.') : ''}
|
||||
className="w-full"
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
@@ -173,7 +196,7 @@
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
content={!fileUploadEnabled ? $i18n.t('You do not have permission to upload files') : ''}
|
||||
content={!fileUploadEnabled ? $i18n.t('You do not have permission to upload files.') : ''}
|
||||
className="w-full"
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
@@ -229,94 +252,119 @@
|
||||
{/if}
|
||||
|
||||
{#if $config?.features?.enable_onedrive_integration}
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
||||
on:click={() => {
|
||||
uploadOneDriveHandler();
|
||||
}}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" class="w-5 h-5" fill="none">
|
||||
<mask
|
||||
id="mask0_87_7796"
|
||||
style="mask-type:alpha"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="6"
|
||||
width="32"
|
||||
height="20"
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" class="w-5 h-5" fill="none">
|
||||
<mask
|
||||
id="mask0_87_7796"
|
||||
style="mask-type:alpha"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="6"
|
||||
width="32"
|
||||
height="20"
|
||||
>
|
||||
<path
|
||||
d="M7.82979 26C3.50549 26 0 22.5675 0 18.3333C0 14.1921 3.35322 10.8179 7.54613 10.6716C9.27535 7.87166 12.4144 6 16 6C20.6308 6 24.5169 9.12183 25.5829 13.3335C29.1316 13.3603 32 16.1855 32 19.6667C32 23.0527 29 26 25.8723 25.9914L7.82979 26Z"
|
||||
fill="#C4C4C4"
|
||||
/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_87_7796)">
|
||||
<path
|
||||
d="M7.83017 26.0001C5.37824 26.0001 3.18957 24.8966 1.75391 23.1691L18.0429 16.3335L30.7089 23.4647C29.5926 24.9211 27.9066 26.0001 26.0004 25.9915C23.1254 26.0001 12.0629 26.0001 7.83017 26.0001Z"
|
||||
fill="url(#paint0_linear_87_7796)"
|
||||
/>
|
||||
<path
|
||||
d="M25.5785 13.3149L18.043 16.3334L30.709 23.4647C31.5199 22.4065 32.0004 21.0916 32.0004 19.6669C32.0004 16.1857 29.1321 13.3605 25.5833 13.3337C25.5817 13.3274 25.5801 13.3212 25.5785 13.3149Z"
|
||||
fill="url(#paint1_linear_87_7796)"
|
||||
/>
|
||||
<path
|
||||
d="M7.06445 10.7028L18.0423 16.3333L25.5779 13.3148C24.5051 9.11261 20.6237 6 15.9997 6C12.4141 6 9.27508 7.87166 7.54586 10.6716C7.3841 10.6773 7.22358 10.6877 7.06445 10.7028Z"
|
||||
fill="url(#paint2_linear_87_7796)"
|
||||
/>
|
||||
<path
|
||||
d="M1.7535 23.1687L18.0425 16.3331L7.06471 10.7026C3.09947 11.0792 0 14.3517 0 18.3331C0 20.1665 0.657197 21.8495 1.7535 23.1687Z"
|
||||
fill="url(#paint3_linear_87_7796)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_87_7796"
|
||||
x1="4.42591"
|
||||
y1="24.6668"
|
||||
x2="27.2309"
|
||||
y2="23.2764"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#2086B8" />
|
||||
<stop offset="1" stop-color="#46D3F6" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_87_7796"
|
||||
x1="23.8302"
|
||||
y1="19.6668"
|
||||
x2="30.2108"
|
||||
y2="15.2082"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#1694DB" />
|
||||
<stop offset="1" stop-color="#62C3FE" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_87_7796"
|
||||
x1="8.51037"
|
||||
y1="7.33333"
|
||||
x2="23.3335"
|
||||
y2="15.9348"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#0D3D78" />
|
||||
<stop offset="1" stop-color="#063B83" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_87_7796"
|
||||
x1="-0.340429"
|
||||
y1="19.9998"
|
||||
x2="14.5634"
|
||||
y2="14.4649"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#16589B" />
|
||||
<stop offset="1" stop-color="#1464B7" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<div class="line-clamp-1">{$i18n.t('Microsoft OneDrive')}</div>
|
||||
</DropdownMenu.SubTrigger>
|
||||
<DropdownMenu.SubContent
|
||||
class="w-[calc(100vw-2rem)] max-w-[280px] rounded-xl px-1 py-1 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-sm"
|
||||
side={$mobile ? 'bottom' : 'right'}
|
||||
sideOffset={$mobile ? 5 : 0}
|
||||
alignOffset={$mobile ? 0 : -8}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
||||
on:click={() => {
|
||||
uploadOneDriveHandler('personal');
|
||||
}}
|
||||
>
|
||||
<path
|
||||
d="M7.82979 26C3.50549 26 0 22.5675 0 18.3333C0 14.1921 3.35322 10.8179 7.54613 10.6716C9.27535 7.87166 12.4144 6 16 6C20.6308 6 24.5169 9.12183 25.5829 13.3335C29.1316 13.3603 32 16.1855 32 19.6667C32 23.0527 29 26 25.8723 25.9914L7.82979 26Z"
|
||||
fill="#C4C4C4"
|
||||
/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_87_7796)">
|
||||
<path
|
||||
d="M7.83017 26.0001C5.37824 26.0001 3.18957 24.8966 1.75391 23.1691L18.0429 16.3335L30.7089 23.4647C29.5926 24.9211 27.9066 26.0001 26.0004 25.9915C23.1254 26.0001 12.0629 26.0001 7.83017 26.0001Z"
|
||||
fill="url(#paint0_linear_87_7796)"
|
||||
/>
|
||||
<path
|
||||
d="M25.5785 13.3149L18.043 16.3334L30.709 23.4647C31.5199 22.4065 32.0004 21.0916 32.0004 19.6669C32.0004 16.1857 29.1321 13.3605 25.5833 13.3337C25.5817 13.3274 25.5801 13.3212 25.5785 13.3149Z"
|
||||
fill="url(#paint1_linear_87_7796)"
|
||||
/>
|
||||
<path
|
||||
d="M7.06445 10.7028L18.0423 16.3333L25.5779 13.3148C24.5051 9.11261 20.6237 6 15.9997 6C12.4141 6 9.27508 7.87166 7.54586 10.6716C7.3841 10.6773 7.22358 10.6877 7.06445 10.7028Z"
|
||||
fill="url(#paint2_linear_87_7796)"
|
||||
/>
|
||||
<path
|
||||
d="M1.7535 23.1687L18.0425 16.3331L7.06471 10.7026C3.09947 11.0792 0 14.3517 0 18.3331C0 20.1665 0.657197 21.8495 1.7535 23.1687Z"
|
||||
fill="url(#paint3_linear_87_7796)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_87_7796"
|
||||
x1="4.42591"
|
||||
y1="24.6668"
|
||||
x2="27.2309"
|
||||
y2="23.2764"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#2086B8" />
|
||||
<stop offset="1" stop-color="#46D3F6" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_87_7796"
|
||||
x1="23.8302"
|
||||
y1="19.6668"
|
||||
x2="30.2108"
|
||||
y2="15.2082"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#1694DB" />
|
||||
<stop offset="1" stop-color="#62C3FE" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_87_7796"
|
||||
x1="8.51037"
|
||||
y1="7.33333"
|
||||
x2="23.3335"
|
||||
y2="15.9348"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#0D3D78" />
|
||||
<stop offset="1" stop-color="#063B83" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_87_7796"
|
||||
x1="-0.340429"
|
||||
y1="19.9998"
|
||||
x2="14.5634"
|
||||
y2="14.4649"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#16589B" />
|
||||
<stop offset="1" stop-color="#1464B7" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<div class="line-clamp-1">{$i18n.t('OneDrive')}</div>
|
||||
</DropdownMenu.Item>
|
||||
<div class="line-clamp-1">{$i18n.t('Microsoft OneDrive (personal)')}</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
||||
on:click={() => {
|
||||
uploadOneDriveHandler('organizations');
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<div class="line-clamp-1">{$i18n.t('Microsoft OneDrive (work/school)')}</div>
|
||||
<div class="text-xs text-gray-500">Includes SharePoint</div>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { createEventDispatcher, tick, getContext, onMount, onDestroy } from 'svelte';
|
||||
import { tick, getContext, onMount, onDestroy } from 'svelte';
|
||||
import { config, settings } from '$lib/stores';
|
||||
import { blobToFile, calculateSHA256, extractCurlyBraceWords } from '$lib/utils';
|
||||
|
||||
import { transcribeAudio } from '$lib/apis/audio';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import LocalizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
dayjs.extend(LocalizedFormat);
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let recording = false;
|
||||
export let transcribe = true;
|
||||
export let displayMedia = false;
|
||||
|
||||
export let className = ' p-2.5 w-full max-w-full';
|
||||
|
||||
export let onCancel = () => {};
|
||||
export let onConfirm = (data) => {};
|
||||
|
||||
let loading = false;
|
||||
let confirmed = false;
|
||||
|
||||
@@ -130,45 +138,73 @@
|
||||
detectSound();
|
||||
};
|
||||
|
||||
const transcribeHandler = async (audioBlob) => {
|
||||
const onStopHandler = async (audioBlob, ext: string = 'wav') => {
|
||||
// Create a blob from the audio chunks
|
||||
|
||||
await tick();
|
||||
const file = blobToFile(audioBlob, 'recording.wav');
|
||||
const file = blobToFile(audioBlob, `Recording-${dayjs().format('L LT')}.${ext}`);
|
||||
|
||||
const res = await transcribeAudio(localStorage.token, file).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
if (transcribe) {
|
||||
if ($config.audio.stt.engine === 'web' || ($settings?.audio?.stt?.engine ?? '') === 'web') {
|
||||
// with web stt, we don't need to send the file to the server
|
||||
return;
|
||||
}
|
||||
|
||||
if (res) {
|
||||
console.log(res);
|
||||
dispatch('confirm', res);
|
||||
const res = await transcribeAudio(localStorage.token, file).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res) {
|
||||
console.log(res);
|
||||
onConfirm(res);
|
||||
}
|
||||
} else {
|
||||
onConfirm({
|
||||
file: file,
|
||||
blob: audioBlob
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const saveRecording = (blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
document.body.appendChild(a);
|
||||
a.style = 'display: none';
|
||||
a.href = url;
|
||||
a.download = 'recording.wav';
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const startRecording = async () => {
|
||||
loading = true;
|
||||
|
||||
stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true,
|
||||
autoGainControl: true
|
||||
try {
|
||||
if (displayMedia) {
|
||||
const mediaStream = await navigator.mediaDevices.getDisplayMedia({
|
||||
audio: true
|
||||
});
|
||||
|
||||
stream = new MediaStream();
|
||||
for (const track of mediaStream.getAudioTracks()) {
|
||||
stream.addTrack(track);
|
||||
}
|
||||
|
||||
for (const track of mediaStream.getVideoTracks()) {
|
||||
track.stop();
|
||||
}
|
||||
} else {
|
||||
stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true,
|
||||
autoGainControl: true
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error accessing media devices.', err);
|
||||
toast.error($i18n.t('Error accessing media devices.'));
|
||||
loading = false;
|
||||
recording = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mediaRecorder = new MediaRecorder(stream, {
|
||||
mimeType: 'audio/webm; codecs=opus'
|
||||
});
|
||||
mediaRecorder = new MediaRecorder(stream);
|
||||
|
||||
mediaRecorder.onstart = () => {
|
||||
console.log('Recording started');
|
||||
loading = false;
|
||||
@@ -180,77 +216,99 @@
|
||||
mediaRecorder.ondataavailable = (event) => audioChunks.push(event.data);
|
||||
mediaRecorder.onstop = async () => {
|
||||
console.log('Recording stopped');
|
||||
if ($config.audio.stt.engine === 'web' || ($settings?.audio?.stt?.engine ?? '') === 'web') {
|
||||
audioChunks = [];
|
||||
} else {
|
||||
if (confirmed) {
|
||||
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
|
||||
|
||||
await transcribeHandler(audioBlob);
|
||||
if (confirmed) {
|
||||
// Use the actual type provided by MediaRecorder
|
||||
let type = audioChunks[0]?.type || mediaRecorder.mimeType || 'audio/webm';
|
||||
|
||||
confirmed = false;
|
||||
loading = false;
|
||||
// split `/` and `;` to get the extension
|
||||
let ext = type.split('/')[1].split(';')[0] || 'webm';
|
||||
|
||||
// If not audio, default to audio/webm
|
||||
if (!type.startsWith('audio/')) {
|
||||
ext = 'webm';
|
||||
}
|
||||
audioChunks = [];
|
||||
recording = false;
|
||||
|
||||
const audioBlob = new Blob(audioChunks, { type: type });
|
||||
await onStopHandler(audioBlob, ext);
|
||||
|
||||
confirmed = false;
|
||||
loading = false;
|
||||
}
|
||||
|
||||
audioChunks = [];
|
||||
recording = false;
|
||||
};
|
||||
mediaRecorder.start();
|
||||
if ($config.audio.stt.engine === 'web' || ($settings?.audio?.stt?.engine ?? '') === 'web') {
|
||||
if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
|
||||
// Create a SpeechRecognition object
|
||||
speechRecognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
|
||||
|
||||
// Set continuous to true for continuous recognition
|
||||
speechRecognition.continuous = true;
|
||||
try {
|
||||
mediaRecorder.start();
|
||||
} catch (error) {
|
||||
console.error('Error starting recording:', error);
|
||||
toast.error($i18n.t('Error starting recording.'));
|
||||
loading = false;
|
||||
recording = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the timeout for turning off the recognition after inactivity (in milliseconds)
|
||||
const inactivityTimeout = 2000; // 3 seconds
|
||||
if (transcribe) {
|
||||
if ($config.audio.stt.engine === 'web' || ($settings?.audio?.stt?.engine ?? '') === 'web') {
|
||||
if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
|
||||
// Create a SpeechRecognition object
|
||||
speechRecognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
|
||||
|
||||
let timeoutId;
|
||||
// Start recognition
|
||||
speechRecognition.start();
|
||||
// Set continuous to true for continuous recognition
|
||||
speechRecognition.continuous = true;
|
||||
|
||||
// Event triggered when speech is recognized
|
||||
speechRecognition.onresult = async (event) => {
|
||||
// Clear the inactivity timeout
|
||||
clearTimeout(timeoutId);
|
||||
// Set the timeout for turning off the recognition after inactivity (in milliseconds)
|
||||
const inactivityTimeout = 2000; // 3 seconds
|
||||
|
||||
// Handle recognized speech
|
||||
console.log(event);
|
||||
const transcript = event.results[Object.keys(event.results).length - 1][0].transcript;
|
||||
let timeoutId;
|
||||
// Start recognition
|
||||
speechRecognition.start();
|
||||
|
||||
transcription = `${transcription}${transcript}`;
|
||||
// Event triggered when speech is recognized
|
||||
speechRecognition.onresult = async (event) => {
|
||||
// Clear the inactivity timeout
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
await tick();
|
||||
document.getElementById('chat-input')?.focus();
|
||||
// Handle recognized speech
|
||||
console.log(event);
|
||||
const transcript = event.results[Object.keys(event.results).length - 1][0].transcript;
|
||||
|
||||
// Restart the inactivity timeout
|
||||
timeoutId = setTimeout(() => {
|
||||
console.log('Speech recognition turned off due to inactivity.');
|
||||
speechRecognition.stop();
|
||||
}, inactivityTimeout);
|
||||
};
|
||||
transcription = `${transcription}${transcript}`;
|
||||
|
||||
// Event triggered when recognition is ended
|
||||
speechRecognition.onend = function () {
|
||||
// Restart recognition after it ends
|
||||
console.log('recognition ended');
|
||||
await tick();
|
||||
document.getElementById('chat-input')?.focus();
|
||||
|
||||
confirmRecording();
|
||||
dispatch('confirm', { text: transcription });
|
||||
confirmed = false;
|
||||
loading = false;
|
||||
};
|
||||
// Restart the inactivity timeout
|
||||
timeoutId = setTimeout(() => {
|
||||
console.log('Speech recognition turned off due to inactivity.');
|
||||
speechRecognition.stop();
|
||||
}, inactivityTimeout);
|
||||
};
|
||||
|
||||
// Event triggered when an error occurs
|
||||
speechRecognition.onerror = function (event) {
|
||||
console.log(event);
|
||||
toast.error($i18n.t(`Speech recognition error: {{error}}`, { error: event.error }));
|
||||
dispatch('cancel');
|
||||
// Event triggered when recognition is ended
|
||||
speechRecognition.onend = function () {
|
||||
// Restart recognition after it ends
|
||||
console.log('recognition ended');
|
||||
|
||||
stopRecording();
|
||||
};
|
||||
confirmRecording();
|
||||
onConfirm({
|
||||
text: transcription
|
||||
});
|
||||
confirmed = false;
|
||||
loading = false;
|
||||
};
|
||||
|
||||
// Event triggered when an error occurs
|
||||
speechRecognition.onerror = function (event) {
|
||||
console.log(event);
|
||||
toast.error($i18n.t(`Speech recognition error: {{error}}`, { error: event.error }));
|
||||
onCancel();
|
||||
|
||||
stopRecording();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -339,7 +397,7 @@
|
||||
rounded-full"
|
||||
on:click={async () => {
|
||||
stopRecording();
|
||||
dispatch('cancel');
|
||||
onCancel();
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
|
||||
@@ -255,7 +255,7 @@
|
||||
await updateChat();
|
||||
};
|
||||
|
||||
const editMessage = async (messageId, content, submit = true) => {
|
||||
const editMessage = async (messageId, { content, files }, submit = true) => {
|
||||
if (history.messages[messageId].role === 'user') {
|
||||
if (submit) {
|
||||
// New user message
|
||||
@@ -268,7 +268,7 @@
|
||||
childrenIds: [],
|
||||
role: 'user',
|
||||
content: userPrompt,
|
||||
...(history.messages[messageId].files && { files: history.messages[messageId].files }),
|
||||
...(files && { files: files }),
|
||||
models: selectedModels,
|
||||
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
|
||||
};
|
||||
@@ -290,6 +290,7 @@
|
||||
} else {
|
||||
// Edit user message
|
||||
history.messages[messageId].content = content;
|
||||
history.messages[messageId].files = files;
|
||||
await updateChat();
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
console.log('citations', citations);
|
||||
|
||||
showRelevance = calculateShowRelevance(citations);
|
||||
showPercentage = shouldShowPercentage(citations);
|
||||
|
||||
@@ -139,13 +139,16 @@
|
||||
{percentage.toFixed(2)}%
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if typeof document?.distance === 'number'}
|
||||
<span class="text-gray-500 dark:text-gray-500">
|
||||
({(document?.distance ?? 0).toFixed(4)})
|
||||
</span>
|
||||
{/if}
|
||||
{:else if typeof document?.distance === 'number'}
|
||||
<span class="text-gray-500 dark:text-gray-500">
|
||||
({(document?.distance ?? 0).toFixed(4)})
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-gray-500 dark:text-gray-500">
|
||||
{(document?.distance ?? 0).toFixed(4)}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
@@ -154,11 +154,11 @@
|
||||
}, [])}
|
||||
{onSourceClick}
|
||||
{onTaskClick}
|
||||
on:update={(e) => {
|
||||
dispatch('update', e.detail);
|
||||
onUpdate={(value) => {
|
||||
dispatch('update', value);
|
||||
}}
|
||||
on:code={(e) => {
|
||||
const { lang, code } = e.detail;
|
||||
onCode={(value) => {
|
||||
const { lang, code } = value;
|
||||
|
||||
if (
|
||||
($settings?.detectArtifacts ?? true) &&
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
import markedKatexExtension from '$lib/utils/marked/katex-extension';
|
||||
|
||||
import MarkdownTokens from './Markdown/MarkdownTokens.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let id = '';
|
||||
export let content;
|
||||
@@ -18,6 +15,9 @@
|
||||
|
||||
export let sourceIds = [];
|
||||
|
||||
export let onUpdate = () => {};
|
||||
export let onCode = () => {};
|
||||
|
||||
export let onSourceClick = () => {};
|
||||
export let onTaskClick = () => {};
|
||||
|
||||
@@ -40,17 +40,5 @@
|
||||
</script>
|
||||
|
||||
{#key id}
|
||||
<MarkdownTokens
|
||||
{tokens}
|
||||
{id}
|
||||
{save}
|
||||
{onTaskClick}
|
||||
{onSourceClick}
|
||||
on:update={(e) => {
|
||||
dispatch('update', e.detail);
|
||||
}}
|
||||
on:code={(e) => {
|
||||
dispatch('code', e.detail);
|
||||
}}
|
||||
/>
|
||||
<MarkdownTokens {tokens} {id} {save} {onTaskClick} {onSourceClick} {onUpdate} {onCode} />
|
||||
{/key}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<script lang="ts">
|
||||
import DOMPurify from 'dompurify';
|
||||
import type { Token } from 'marked';
|
||||
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import Source from './Source.svelte';
|
||||
import { settings } from '$lib/stores';
|
||||
|
||||
export let id: string;
|
||||
export let token: Token;
|
||||
|
||||
export let onSourceClick: Function = () => {};
|
||||
|
||||
let html: string | null = null;
|
||||
|
||||
$: if (token.type === 'html' && token?.text) {
|
||||
html = DOMPurify.sanitize(token.text);
|
||||
} else {
|
||||
html = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if token.type === 'html'}
|
||||
{#if html && html.includes('<video')}
|
||||
{@const video = html.match(/<video[^>]*>([\s\S]*?)<\/video>/)}
|
||||
{@const videoSrc = video && video[1]}
|
||||
{#if videoSrc}
|
||||
<!-- svelte-ignore a11y-media-has-caption -->
|
||||
<video
|
||||
class="w-full my-2"
|
||||
src={videoSrc}
|
||||
title="Video player"
|
||||
frameborder="0"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
controls
|
||||
allowfullscreen
|
||||
></video>
|
||||
{:else}
|
||||
{token.text}
|
||||
{/if}
|
||||
{:else if token.text && token.text.match(/<iframe\s+[^>]*src="https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})(?:\?[^"]*)?"[^>]*><\/iframe>/)}
|
||||
{@const match = token.text.match(
|
||||
/<iframe\s+[^>]*src="https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})(?:\?[^"]*)?"[^>]*><\/iframe>/
|
||||
)}
|
||||
{@const ytId = match && match[1]}
|
||||
{#if ytId}
|
||||
<iframe
|
||||
class="w-full aspect-video my-2"
|
||||
src={`https://www.youtube.com/embed/${ytId}`}
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen
|
||||
>
|
||||
</iframe>
|
||||
{/if}
|
||||
{:else if token.text.includes(`<file type="html"`)}
|
||||
{@const match = token.text.match(/<file type="html" id="([^"]+)"/)}
|
||||
{@const fileId = match && match[1]}
|
||||
{#if fileId}
|
||||
<iframe
|
||||
class="w-full my-2"
|
||||
src={`${WEBUI_BASE_URL}/api/v1/files/${fileId}/content/html`}
|
||||
title="Content"
|
||||
frameborder="0"
|
||||
sandbox="allow-scripts{($settings?.iframeSandboxAllowForms ?? false)
|
||||
? ' allow-forms'
|
||||
: ''}{($settings?.iframeSandboxAllowSameOrigin ?? false) ? ' allow-same-origin' : ''}"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen
|
||||
width="100%"
|
||||
onload="this.style.height=(this.contentWindow.document.body.scrollHeight+20)+'px';"
|
||||
></iframe>
|
||||
{/if}
|
||||
{:else if token.text.includes(`<source_id`)}
|
||||
<Source {id} {token} onClick={onSourceClick} />
|
||||
{:else}
|
||||
{token.text}
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -13,6 +13,7 @@
|
||||
import Image from '$lib/components/common/Image.svelte';
|
||||
import KatexRenderer from './KatexRenderer.svelte';
|
||||
import Source from './Source.svelte';
|
||||
import HtmlToken from './HTMLToken.svelte';
|
||||
|
||||
export let id: string;
|
||||
export let tokens: Token[];
|
||||
@@ -23,16 +24,7 @@
|
||||
{#if token.type === 'escape'}
|
||||
{unescapeHtml(token.text)}
|
||||
{:else if token.type === 'html'}
|
||||
{@const html = DOMPurify.sanitize(token.text)}
|
||||
{#if html && html.includes('<video')}
|
||||
{@html html}
|
||||
{:else if token.text.includes(`<iframe src="${WEBUI_BASE_URL}/api/v1/files/`)}
|
||||
{@html `${token.text}`}
|
||||
{:else if token.text.includes(`<source_id`)}
|
||||
<Source {id} {token} onClick={onSourceClick} />
|
||||
{:else}
|
||||
{@html html}
|
||||
{/if}
|
||||
<HtmlToken {id} {token} {onSourceClick} />
|
||||
{:else if token.type === 'link'}
|
||||
{#if token.tokens}
|
||||
<a href={token.href} target="_blank" rel="nofollow" title={token.title}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import DOMPurify from 'dompurify';
|
||||
import { createEventDispatcher, onMount, getContext } from 'svelte';
|
||||
import { onMount, getContext } from 'svelte';
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import fileSaver from 'file-saver';
|
||||
@@ -21,8 +21,7 @@
|
||||
|
||||
import Source from './Source.svelte';
|
||||
import { settings } from '$lib/stores';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
import HtmlToken from './HTMLToken.svelte';
|
||||
|
||||
export let id: string;
|
||||
export let tokens: Token[];
|
||||
@@ -31,6 +30,9 @@
|
||||
|
||||
export let save = false;
|
||||
|
||||
export let onUpdate: Function = () => {};
|
||||
export let onCode: Function = () => {};
|
||||
|
||||
export let onTaskClick: Function = () => {};
|
||||
export let onSourceClick: Function = () => {};
|
||||
|
||||
@@ -93,11 +95,9 @@
|
||||
code={token?.text ?? ''}
|
||||
{attributes}
|
||||
{save}
|
||||
onCode={(value) => {
|
||||
dispatch('code', value);
|
||||
}}
|
||||
{onCode}
|
||||
onSave={(value) => {
|
||||
dispatch('update', {
|
||||
onUpdate({
|
||||
raw: token.raw,
|
||||
oldContent: token.text,
|
||||
newContent: value
|
||||
@@ -267,16 +267,7 @@
|
||||
</div>
|
||||
</Collapsible>
|
||||
{:else if token.type === 'html'}
|
||||
{@const html = DOMPurify.sanitize(token.text)}
|
||||
{#if html && html.includes('<video')}
|
||||
{@html html}
|
||||
{:else if token.text.includes(`<iframe src="${WEBUI_BASE_URL}/api/v1/files/`)}
|
||||
{@html `${token.text}`}
|
||||
{:else if token.text.includes(`<source_id`)}
|
||||
<Source {id} {token} onClick={onSourceClick} />
|
||||
{:else}
|
||||
{token.text}
|
||||
{/if}
|
||||
<HtmlToken {id} {token} {onSourceClick} />
|
||||
{:else if token.type === 'iframe'}
|
||||
<iframe
|
||||
src="{WEBUI_BASE_URL}/api/v1/files/{token.fileId}/content"
|
||||
|
||||
@@ -200,9 +200,11 @@
|
||||
await initHandler();
|
||||
await tick();
|
||||
|
||||
const messageElement = document.getElementById(`message-${messageId}`);
|
||||
if (messageElement) {
|
||||
messageElement.scrollIntoView({ block: 'start' });
|
||||
if ($settings?.scrollOnBranchChange ?? true) {
|
||||
const messageElement = document.getElementById(`message-${messageId}`);
|
||||
if (messageElement) {
|
||||
messageElement.scrollIntoView({ block: 'start' });
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -238,10 +240,9 @@
|
||||
messageChildrenIds = history.messages[currentMessageId].childrenIds;
|
||||
}
|
||||
history.currentId = currentMessageId;
|
||||
|
||||
await tick();
|
||||
await updateChat();
|
||||
triggerScroll();
|
||||
// await tick();
|
||||
// await updateChat();
|
||||
// triggerScroll();
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -293,7 +294,7 @@
|
||||
|
||||
<div class="w-full rounded-xl pl-5 pr-2 py-2">
|
||||
<Name>
|
||||
Merged Response
|
||||
{$i18n.t('Merged Response')}
|
||||
|
||||
{#if message.timestamp}
|
||||
<span
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
|
||||
const editMessageConfirmHandler = async () => {
|
||||
const messageContent = postprocessAfterEditing(editedContent ? editedContent : '');
|
||||
editMessage(message.id, messageContent, false);
|
||||
editMessage(message.id, { content: messageContent }, false);
|
||||
|
||||
edit = false;
|
||||
editedContent = '';
|
||||
@@ -388,7 +388,7 @@
|
||||
const saveAsCopyHandler = async () => {
|
||||
const messageContent = postprocessAfterEditing(editedContent ? editedContent : '');
|
||||
|
||||
editMessage(message.id, messageContent);
|
||||
editMessage(message.id, { content: messageContent });
|
||||
|
||||
edit = false;
|
||||
editedContent = '';
|
||||
@@ -623,12 +623,6 @@
|
||||
).at(-1)}
|
||||
{#if !status?.hidden}
|
||||
<div class="status-description flex items-center gap-2 py-0.5">
|
||||
{#if status?.done === false}
|
||||
<div class="">
|
||||
<Spinner className="size-4" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if status?.action === 'web_search' && status?.urls}
|
||||
<WebSearchResults {status}>
|
||||
<div class="flex flex-col justify-center -space-y-0.5">
|
||||
@@ -683,6 +677,8 @@
|
||||
{$i18n.t('No search query generated')}
|
||||
{:else if status?.description === 'Generating search query'}
|
||||
{$i18n.t('Generating search query')}
|
||||
{:else if status?.description === 'Searching the web'}
|
||||
{$i18n.t('Searching the web...')}
|
||||
{:else}
|
||||
{status?.description}
|
||||
{/if}
|
||||
@@ -777,7 +773,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="w-full flex flex-col relative" id="response-content-container">
|
||||
{#if message.content === '' && !message.error}
|
||||
{#if message.content === '' && !message.error && (message?.statusHistory ?? [...(message?.status ? [message?.status] : [])]).length === 0}
|
||||
<Skeleton />
|
||||
{:else if message.content && message.error !== true}
|
||||
<!-- always show message contents even if there's an error -->
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
|
||||
let edit = false;
|
||||
let editedContent = '';
|
||||
let editedFiles = [];
|
||||
|
||||
let messageEditTextAreaElement: HTMLTextAreaElement;
|
||||
|
||||
let message = JSON.parse(JSON.stringify(history.messages[messageId]));
|
||||
@@ -62,25 +64,30 @@
|
||||
const editMessageHandler = async () => {
|
||||
edit = true;
|
||||
editedContent = message.content;
|
||||
editedFiles = message.files;
|
||||
|
||||
await tick();
|
||||
|
||||
messageEditTextAreaElement.style.height = '';
|
||||
messageEditTextAreaElement.style.height = `${messageEditTextAreaElement.scrollHeight}px`;
|
||||
if (messageEditTextAreaElement) {
|
||||
messageEditTextAreaElement.style.height = '';
|
||||
messageEditTextAreaElement.style.height = `${messageEditTextAreaElement.scrollHeight}px`;
|
||||
|
||||
messageEditTextAreaElement?.focus();
|
||||
messageEditTextAreaElement?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const editMessageConfirmHandler = async (submit = true) => {
|
||||
editMessage(message.id, editedContent, submit);
|
||||
editMessage(message.id, { content: editedContent, files: editedFiles }, submit);
|
||||
|
||||
edit = false;
|
||||
editedContent = '';
|
||||
editedFiles = [];
|
||||
};
|
||||
|
||||
const cancelEditMessage = () => {
|
||||
edit = false;
|
||||
editedContent = '';
|
||||
editedFiles = [];
|
||||
};
|
||||
|
||||
const deleteMessageHandler = async () => {
|
||||
@@ -139,30 +146,90 @@
|
||||
{/if}
|
||||
|
||||
<div class="chat-{message.role} w-full min-w-full markdown-prose">
|
||||
{#if message.files}
|
||||
<div class="mt-2.5 mb-1 w-full flex flex-col justify-end overflow-x-auto gap-1 flex-wrap">
|
||||
{#each message.files as file}
|
||||
<div class={($settings?.chatBubble ?? true) ? 'self-end' : ''}>
|
||||
{#if file.type === 'image'}
|
||||
<Image src={file.url} imageClassName=" max-h-96 rounded-lg" />
|
||||
{:else}
|
||||
<FileItem
|
||||
item={file}
|
||||
url={file.url}
|
||||
name={file.name}
|
||||
type={file.type}
|
||||
size={file?.size}
|
||||
colorClassName="bg-white dark:bg-gray-850 "
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if edit !== true}
|
||||
{#if message.files}
|
||||
<div class="mt-2.5 mb-1 w-full flex flex-col justify-end overflow-x-auto gap-1 flex-wrap">
|
||||
{#each message.files as file}
|
||||
<div class={($settings?.chatBubble ?? true) ? 'self-end' : ''}>
|
||||
{#if file.type === 'image'}
|
||||
<Image src={file.url} imageClassName=" max-h-96 rounded-lg" />
|
||||
{:else}
|
||||
<FileItem
|
||||
item={file}
|
||||
url={file.url}
|
||||
name={file.name}
|
||||
type={file.type}
|
||||
size={file?.size}
|
||||
colorClassName="bg-white dark:bg-gray-850 "
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if message.content !== ''}
|
||||
{#if edit === true}
|
||||
<div class=" w-full bg-gray-50 dark:bg-gray-800 rounded-3xl px-5 py-3 mb-2">
|
||||
{#if (editedFiles ?? []).length > 0}
|
||||
<div class="flex items-center flex-wrap gap-2 -mx-2 mb-1">
|
||||
{#each editedFiles as file, fileIdx}
|
||||
{#if file.type === 'image'}
|
||||
<div class=" relative group">
|
||||
<div class="relative flex items-center">
|
||||
<Image
|
||||
src={file.url}
|
||||
alt="input"
|
||||
imageClassName=" size-14 rounded-xl object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class=" absolute -top-1 -right-1">
|
||||
<button
|
||||
class=" bg-white text-black border border-white rounded-full group-hover:visible invisible transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
editedFiles.splice(fileIdx, 1);
|
||||
|
||||
editedFiles = editedFiles;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-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}
|
||||
<FileItem
|
||||
item={file}
|
||||
name={file.name}
|
||||
type={file.type}
|
||||
size={file?.size}
|
||||
loading={file.status === 'uploading'}
|
||||
dismissible={true}
|
||||
edit={true}
|
||||
on:dismiss={async () => {
|
||||
editedFiles.splice(fileIdx, 1);
|
||||
|
||||
editedFiles = editedFiles;
|
||||
}}
|
||||
on:click={() => {
|
||||
console.log(file);
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="max-h-96 overflow-auto">
|
||||
<textarea
|
||||
id="message-edit-{message.id}"
|
||||
|
||||
@@ -778,7 +778,7 @@
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{:else if filteredItems.length === 0}
|
||||
{:else}
|
||||
<div class="mb-3"></div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
|
||||
<ShareChatModal bind:show={showShareChatModal} chatId={$chatId} />
|
||||
|
||||
<nav class="sticky top-0 z-30 w-full py-1.5 -mb-8 flex flex-col items-center drag-region">
|
||||
<div class="flex items-center w-full px-1.5">
|
||||
<nav class="sticky top-0 z-30 w-full py-1 -mb-8 flex flex-col items-center drag-region">
|
||||
<div class="flex items-center w-full pl-1.5 pr-1">
|
||||
<div
|
||||
class=" bg-linear-to-b via-50% from-white via-white to-transparent dark:from-gray-900 dark:via-gray-900 dark:to-transparent pointer-events-none absolute inset-0 -bottom-7 z-[-1]"
|
||||
></div>
|
||||
|
||||
@@ -201,6 +201,13 @@
|
||||
{stopResponse}
|
||||
{createMessagePair}
|
||||
placeholder={$i18n.t('How can I help you today?')}
|
||||
onChange={(input) => {
|
||||
if (input.prompt !== null) {
|
||||
localStorage.setItem(`chat-input`, JSON.stringify(input));
|
||||
} else {
|
||||
localStorage.removeItem(`chat-input`);
|
||||
}
|
||||
}}
|
||||
on:upload={(e) => {
|
||||
dispatch('upload', e.detail);
|
||||
}}
|
||||
|
||||
@@ -159,15 +159,21 @@ Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
4. Notwithstanding any other provision of this License, and as a material condition of the rights granted herein, licensees are strictly prohibited from altering, removing, obscuring, or replacing any "Open WebUI" branding, including but not limited to the name, logo, or any visual, textual, or symbolic identifiers that distinguish the software and its interfaces, in any deployment or distribution, regardless of the number of users, except as explicitly set forth in Clauses 5 and 6 below.
|
||||
|
||||
5. The branding restriction enumerated in Clause 4 shall not apply in the following limited circumstances: (i) deployments or distributions where the total number of end users (defined as individual natural persons with direct access to the application) does not exceed fifty (50) within any rolling thirty (30) day period; (ii) cases in which the licensee is an official contributor to the codebase—with a substantive code change successfully merged into the main branch of the official codebase maintained by the copyright holder—who has obtained specific prior written permission for branding adjustment from the copyright holder; or (iii) where the licensee has obtained a duly executed enterprise license expressly permitting such modification. For all other cases, any removal or alteration of the "Open WebUI" branding shall constitute a material breach of license.
|
||||
|
||||
6. All code, modifications, or derivative works incorporated into this project prior to the incorporation of this branding clause remain licensed under the BSD 3-Clause License, and prior contributors retain all BSD-3 rights therein; if any such contributor requests the removal of their BSD-3-licensed code, the copyright holder will do so, and any replacement code will be licensed under the project's primary license then in effect. By contributing after this clause's adoption, you agree to the project's Contributor License Agreement (CLA) and to these updated terms for all new contributions.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col h-full justify-between text-sm">
|
||||
<div class=" space-y-3 overflow-y-scroll max-h-[28rem] lg:max-h-full">
|
||||
<div class=" overflow-y-scroll max-h-[28rem] lg:max-h-full">
|
||||
<input
|
||||
id="profile-image-input"
|
||||
bind:this={profileImageInputElement}
|
||||
@@ -236,7 +236,7 @@
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full text-sm dark:text-gray-300 bg-transparent outline-hidden"
|
||||
type="text"
|
||||
bind:value={name}
|
||||
required
|
||||
@@ -265,98 +265,46 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="py-0.5">
|
||||
<hr class="border-gray-50 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class="my-2">
|
||||
<UpdatePassword />
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-50 dark:border-gray-850 my-2" />
|
||||
{#if ($config?.features?.enable_api_key ?? true) || $user?.role === 'admin'}
|
||||
<div class="flex justify-between items-center text-sm mb-2">
|
||||
<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 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>
|
||||
{#if showAPIKeys}
|
||||
<div class="flex flex-col gap-4">
|
||||
{#if $user?.role === 'admin'}
|
||||
<div class="justify-between w-full">
|
||||
<div class="flex justify-between w-full">
|
||||
<div class="self-center text-xs font-medium mb-1">{$i18n.t('JWT Token')}</div>
|
||||
</div>
|
||||
|
||||
{#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>
|
||||
|
||||
<div class="flex mt-2">
|
||||
<SensitiveInput value={localStorage.token} readOnly={true} />
|
||||
|
||||
<button
|
||||
class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-850 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>
|
||||
{#if $config?.features?.enable_api_key ?? true}
|
||||
<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}
|
||||
<SensitiveInput value={APIKey} readOnly={true} />
|
||||
<div class="flex">
|
||||
<SensitiveInput value={localStorage.token} readOnly={true} />
|
||||
|
||||
<button
|
||||
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"
|
||||
@@ -389,46 +337,105 @@
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $config?.features?.enable_api_key ?? true}
|
||||
<div class="justify-between w-full">
|
||||
{#if $user?.role === 'admin'}
|
||||
<div class="flex justify-between w-full">
|
||||
<div class="self-center text-xs font-medium mb-1">{$i18n.t('API Key')}</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex">
|
||||
{#if APIKey}
|
||||
<SensitiveInput value={APIKey} readOnly={true} />
|
||||
|
||||
<Tooltip content={$i18n.t('Create new key')}>
|
||||
<button
|
||||
class=" px-1.5 py-1 dark:hover:bg-gray-850transition 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={$i18n.t('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-850 transition"
|
||||
on:click={() => {
|
||||
createAPIKeyHandler();
|
||||
}}
|
||||
>
|
||||
<Plus strokeWidth="2" className=" size-3.5" />
|
||||
<Plus strokeWidth="2" className=" size-3.5" />
|
||||
|
||||
{$i18n.t('Create new secret key')}</button
|
||||
>
|
||||
{/if}
|
||||
{$i18n.t('Create new secret key')}</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -334,7 +334,7 @@
|
||||
class="w-full rounded-lg pl-2 py-2 px-1 text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
type="text"
|
||||
placeholder={$i18n.t(
|
||||
'Enter comma-seperated "token:bias_value" pairs (example: 5432:100, 413:-100)'
|
||||
'Enter comma-separated "token:bias_value" pairs (example: 5432:100, 413:-100)'
|
||||
)}
|
||||
bind:value={params.logit_bias}
|
||||
autocomplete="off"
|
||||
|
||||
@@ -140,28 +140,31 @@
|
||||
</div>
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Import Chats')}</div>
|
||||
</button>
|
||||
<button
|
||||
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
exportChats();
|
||||
}}
|
||||
>
|
||||
<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="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Export Chats')}</div>
|
||||
</button>
|
||||
|
||||
{#if $user?.role === 'admin' || ($user.permissions?.chat?.export ?? true)}
|
||||
<button
|
||||
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
exportChats();
|
||||
}}
|
||||
>
|
||||
<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="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Export Chats')}</div>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-850" />
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { getBackendConfig } from '$lib/apis';
|
||||
import { setDefaultPromptSuggestions } from '$lib/apis/configs';
|
||||
import { config, models, settings, user } from '$lib/stores';
|
||||
import { createEventDispatcher, onMount, getContext } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
@@ -30,7 +28,9 @@
|
||||
// Interface
|
||||
let defaultModelId = '';
|
||||
let showUsername = false;
|
||||
|
||||
let notificationSound = true;
|
||||
let notificationSoundAlways = false;
|
||||
|
||||
let detectArtifacts = true;
|
||||
|
||||
@@ -54,6 +54,9 @@
|
||||
height: ''
|
||||
};
|
||||
|
||||
// chat export
|
||||
let stylizedPdfExport = true;
|
||||
|
||||
// Admin - Show Update Available Toast
|
||||
let showUpdateToast = true;
|
||||
let showChangelog = true;
|
||||
@@ -117,6 +120,11 @@
|
||||
saveSettings({ notificationSound: notificationSound });
|
||||
};
|
||||
|
||||
const toggleNotificationSoundAlways = async () => {
|
||||
notificationSoundAlways = !notificationSoundAlways;
|
||||
saveSettings({ notificationSoundAlways: notificationSoundAlways });
|
||||
};
|
||||
|
||||
const toggleShowChangelog = async () => {
|
||||
showChangelog = !showChangelog;
|
||||
saveSettings({ showChangelog: showChangelog });
|
||||
@@ -147,6 +155,11 @@
|
||||
saveSettings({ hapticFeedback: hapticFeedback });
|
||||
};
|
||||
|
||||
const toggleStylizedPdfExport = async () => {
|
||||
stylizedPdfExport = !stylizedPdfExport;
|
||||
saveSettings({ stylizedPdfExport: stylizedPdfExport });
|
||||
};
|
||||
|
||||
const toggleUserLocation = async () => {
|
||||
userLocation = !userLocation;
|
||||
|
||||
@@ -294,7 +307,13 @@
|
||||
chatDirection = $settings.chatDirection ?? 'auto';
|
||||
userLocation = $settings.userLocation ?? false;
|
||||
|
||||
notificationSound = $settings.notificationSound ?? true;
|
||||
notificationSound = $settings?.notificationSound ?? true;
|
||||
notificationSoundAlways = $settings?.notificationSoundAlways ?? false;
|
||||
|
||||
iframeSandboxAllowSameOrigin = $settings?.iframeSandboxAllowSameOrigin ?? false;
|
||||
iframeSandboxAllowForms = $settings?.iframeSandboxAllowForms ?? false;
|
||||
|
||||
stylizedPdfExport = $settings?.stylizedPdfExport ?? true;
|
||||
|
||||
hapticFeedback = $settings.hapticFeedback ?? false;
|
||||
ctrlEnterToSend = $settings.ctrlEnterToSend ?? false;
|
||||
@@ -477,6 +496,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if notificationSound}
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">
|
||||
{$i18n.t('Always Play Notification Sound')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
on:click={() => {
|
||||
toggleNotificationSoundAlways();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if notificationSoundAlways === true}
|
||||
<span class="ml-2 self-center">{$i18n.t('On')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $user?.role === 'admin'}
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
@@ -855,7 +898,7 @@
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">
|
||||
{$i18n.t('Scroll to bottom when switching between branches')}
|
||||
{$i18n.t('Scroll On Branch Change')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -934,6 +977,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">
|
||||
{$i18n.t('Stylized PDF Export')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
on:click={() => {
|
||||
toggleStylizedPdfExport();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if stylizedPdfExport === true}
|
||||
<span class="ml-2 self-center">{$i18n.t('On')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" my-1.5 text-sm font-medium">{$i18n.t('Voice')}</div>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { getBackendConfig } from '$lib/apis';
|
||||
import { setDefaultPromptSuggestions } from '$lib/apis/configs';
|
||||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
import { config, models, settings, user } from '$lib/stores';
|
||||
import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
|
||||
|
||||
@@ -123,16 +123,28 @@
|
||||
'alwaysonwebsearch'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'connections',
|
||||
title: 'Connections',
|
||||
keywords: []
|
||||
},
|
||||
{
|
||||
id: 'tools',
|
||||
title: 'Tools',
|
||||
keywords: []
|
||||
},
|
||||
...($user?.role === 'admin' ||
|
||||
($user?.role === 'user' && $config?.features?.enable_direct_connections)
|
||||
? [
|
||||
{
|
||||
id: 'connections',
|
||||
title: 'Connections',
|
||||
keywords: []
|
||||
}
|
||||
]
|
||||
: []),
|
||||
|
||||
...($user?.role === 'admin' ||
|
||||
($user?.role === 'user' && $user?.permissions?.features?.direct_tool_servers)
|
||||
? [
|
||||
{
|
||||
id: 'tools',
|
||||
title: 'Tools',
|
||||
keywords: []
|
||||
}
|
||||
]
|
||||
: []),
|
||||
|
||||
{
|
||||
id: 'personalization',
|
||||
title: 'Personalization',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import Fuse from 'fuse.js';
|
||||
import Bolt from '$lib/components/icons/Bolt.svelte';
|
||||
import { onMount, getContext, createEventDispatcher } from 'svelte';
|
||||
import { WEBUI_NAME } from '$lib/stores';
|
||||
import { settings, WEBUI_NAME } from '$lib/stores';
|
||||
import { WEBUI_VERSION } from '$lib/constants';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
@@ -72,7 +72,9 @@
|
||||
<!-- Keine Vorschläge -->
|
||||
|
||||
<div
|
||||
class="flex w-full text-center items-center justify-center self-start text-gray-400 dark:text-gray-600"
|
||||
class="flex w-full {$settings?.landingPageMode === 'chat'
|
||||
? ' -mt-1'
|
||||
: 'text-center items-center justify-center'} self-start text-gray-400 dark:text-gray-600"
|
||||
>
|
||||
{$WEBUI_NAME} ‧ v{WEBUI_VERSION}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { decode } from 'html-entities';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { getContext, createEventDispatcher } from 'svelte';
|
||||
import { getContext } from 'svelte';
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import dayjs from '$lib/dayjs';
|
||||
@@ -26,9 +26,6 @@
|
||||
// Assuming $i18n.languages is an array of language codes
|
||||
$: loadLocale($i18n.languages);
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
$: dispatch('change', open);
|
||||
|
||||
import { slide } from 'svelte/transition';
|
||||
import { quintOut } from 'svelte/easing';
|
||||
|
||||
@@ -55,6 +52,10 @@
|
||||
export let disabled = false;
|
||||
export let hide = false;
|
||||
|
||||
export let onChange: Function = () => {};
|
||||
|
||||
$: onChange(open);
|
||||
|
||||
const collapsibleId = uuidv4();
|
||||
|
||||
function parseJSONString(str) {
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { onMount, getContext, createEventDispatcher } from 'svelte';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
import { onMount, getContext, createEventDispatcher, onDestroy } from 'svelte';
|
||||
import * as FocusTrap from 'focus-trap';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
import { fade } from 'svelte/transition';
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
import { marked } from 'marked';
|
||||
|
||||
export let title = '';
|
||||
export let message = '';
|
||||
@@ -23,6 +28,8 @@
|
||||
let modalElement = null;
|
||||
let mounted = false;
|
||||
|
||||
let focusTrap: FocusTrap.FocusTrap | null = null;
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
console.log('Escape');
|
||||
@@ -48,16 +55,30 @@
|
||||
$: if (mounted) {
|
||||
if (show && modalElement) {
|
||||
document.body.appendChild(modalElement);
|
||||
focusTrap = FocusTrap.createFocusTrap(modalElement);
|
||||
focusTrap.activate();
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else if (modalElement) {
|
||||
focusTrap.deactivate();
|
||||
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
document.body.removeChild(modalElement);
|
||||
|
||||
document.body.style.overflow = 'unset';
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
show = false;
|
||||
if (focusTrap) {
|
||||
focusTrap.deactivate();
|
||||
}
|
||||
if (modalElement) {
|
||||
document.body.removeChild(modalElement);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if show}
|
||||
@@ -90,7 +111,8 @@
|
||||
<slot>
|
||||
<div class=" text-sm text-gray-500 flex-1">
|
||||
{#if message !== ''}
|
||||
{message}
|
||||
{@const html = DOMPurify.sanitize(marked.parse(message))}
|
||||
{@html html}
|
||||
{:else}
|
||||
{$i18n.t('This action cannot be undone. Do you wish to continue?')}
|
||||
{/if}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount, createEventDispatcher } from 'svelte';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
import { fade, fly, slide } from 'svelte/transition';
|
||||
import { isApp } from '$lib/stores';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let show = false;
|
||||
export let className = '';
|
||||
export let onClose = () => {};
|
||||
|
||||
let modalElement = null;
|
||||
let mounted = false;
|
||||
@@ -33,7 +32,7 @@
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else if (modalElement) {
|
||||
dispatch('close');
|
||||
onClose();
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
|
||||
if (document.body.contains(modalElement)) {
|
||||
|
||||
@@ -16,10 +16,22 @@
|
||||
export let edit = false;
|
||||
|
||||
let enableFullContent = false;
|
||||
|
||||
let isPdf = false;
|
||||
let isAudio = false;
|
||||
|
||||
$: isPDF =
|
||||
item?.meta?.content_type === 'application/pdf' ||
|
||||
(item?.name && item?.name.toLowerCase().endsWith('.pdf'));
|
||||
|
||||
$: isAudio =
|
||||
item?.meta?.content_type.startsWith('audio/') ||
|
||||
(item?.name && item?.name.toLowerCase().endsWith('.mp3')) ||
|
||||
(item?.name && item?.name.toLowerCase().endsWith('.wav')) ||
|
||||
(item?.name && item?.name.toLowerCase().endsWith('.ogg')) ||
|
||||
(item?.name && item?.name.toLowerCase().endsWith('.m4a')) ||
|
||||
(item?.name && item?.name.toLowerCase().endsWith('.webm'));
|
||||
|
||||
onMount(() => {
|
||||
console.log(item);
|
||||
if (item?.context === 'full') {
|
||||
@@ -122,6 +134,15 @@
|
||||
class="w-full h-[70vh] border-0 rounded-lg mt-4"
|
||||
/>
|
||||
{:else}
|
||||
{#if isAudio}
|
||||
<audio
|
||||
src={`${WEBUI_API_BASE_URL}/files/${item.id}/content`}
|
||||
class="w-full border-0 rounded-lg mb-2"
|
||||
controls
|
||||
playsinline
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap">
|
||||
{item?.file?.data?.content ?? 'No content'}
|
||||
</div>
|
||||
|
||||
@@ -121,8 +121,8 @@
|
||||
bind:open
|
||||
className="w-full "
|
||||
buttonClassName="w-full"
|
||||
on:change={(e) => {
|
||||
dispatch('change', e.detail);
|
||||
onChange={(state) => {
|
||||
dispatch('change', state);
|
||||
}}
|
||||
>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
|
||||
@@ -8,20 +8,48 @@
|
||||
export let className = ' w-full outline-hidden focus:outline-hidden';
|
||||
export let imageClassName = 'rounded-lg';
|
||||
|
||||
export let dismissible = false;
|
||||
export let onDismiss = () => {};
|
||||
|
||||
let _src = '';
|
||||
$: _src = src.startsWith('/') ? `${WEBUI_BASE_URL}${src}` : src;
|
||||
|
||||
let showImagePreview = false;
|
||||
</script>
|
||||
|
||||
<button
|
||||
class={className}
|
||||
on:click={() => {
|
||||
showImagePreview = true;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<img src={_src} {alt} class={imageClassName} draggable="false" data-cy="image" />
|
||||
</button>
|
||||
|
||||
<ImagePreview bind:show={showImagePreview} src={_src} {alt} />
|
||||
|
||||
<div class=" relative group w-fit">
|
||||
<button
|
||||
class={className}
|
||||
on:click={() => {
|
||||
showImagePreview = true;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<img src={_src} {alt} class={imageClassName} draggable="false" data-cy="image" />
|
||||
</button>
|
||||
|
||||
{#if dismissible}
|
||||
<div class=" absolute -top-1 -right-1">
|
||||
<button
|
||||
class=" bg-white text-black border border-white rounded-full group-hover:visible invisible transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onDismiss();
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-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>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
|
||||
import * as FocusTrap from 'focus-trap';
|
||||
export let show = true;
|
||||
export let size = 'md';
|
||||
export let containerClassName = 'p-3';
|
||||
@@ -11,6 +11,10 @@
|
||||
|
||||
let modalElement = null;
|
||||
let mounted = false;
|
||||
// Create focus trap to trap user tabs inside modal
|
||||
// https://www.w3.org/WAI/WCAG21/Understanding/focus-order.html
|
||||
// https://www.w3.org/WAI/WCAG21/Understanding/keyboard.html
|
||||
let focusTrap: FocusTrap.FocusTrap | null = null;
|
||||
|
||||
const sizeToWidth = (size) => {
|
||||
if (size === 'full') {
|
||||
@@ -45,9 +49,12 @@
|
||||
|
||||
$: if (show && modalElement) {
|
||||
document.body.appendChild(modalElement);
|
||||
focusTrap = FocusTrap.createFocusTrap(modalElement);
|
||||
focusTrap.activate();
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else if (modalElement) {
|
||||
focusTrap.deactivate();
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
document.body.removeChild(modalElement);
|
||||
document.body.style.overflow = 'unset';
|
||||
@@ -55,6 +62,9 @@
|
||||
|
||||
onDestroy(() => {
|
||||
show = false;
|
||||
if (focusTrap) {
|
||||
focusTrap.deactivate();
|
||||
}
|
||||
if (modalElement) {
|
||||
document.body.removeChild(modalElement);
|
||||
}
|
||||
@@ -66,6 +76,8 @@
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
bind:this={modalElement}
|
||||
aria-modal="true"
|
||||
role="dialog"
|
||||
class="modal fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full h-screen max-h-[100dvh] {containerClassName} flex justify-center z-9999 overflow-y-auto overscroll-contain"
|
||||
in:fade={{ duration: 10 }}
|
||||
on:mousedown={() => {
|
||||
|
||||
@@ -29,16 +29,21 @@
|
||||
|
||||
export let oncompositionstart = (e) => {};
|
||||
export let oncompositionend = (e) => {};
|
||||
export let onChange = (e) => {};
|
||||
|
||||
// create a lowlight instance with all languages loaded
|
||||
const lowlight = createLowlight(all);
|
||||
|
||||
export let className = 'input-prose';
|
||||
export let placeholder = 'Type here...';
|
||||
export let value = '';
|
||||
export let id = '';
|
||||
|
||||
export let id = '';
|
||||
export let value = '';
|
||||
export let html = '';
|
||||
|
||||
export let json = false;
|
||||
export let raw = false;
|
||||
export let editable = true;
|
||||
|
||||
export let preserveBreaks = false;
|
||||
export let generateAutoCompletion: Function = async () => null;
|
||||
@@ -54,6 +59,16 @@
|
||||
throwOnError: false
|
||||
};
|
||||
|
||||
$: if (editor) {
|
||||
editor.setOptions({
|
||||
editable: editable
|
||||
});
|
||||
}
|
||||
|
||||
$: if (value === null && html !== null && editor) {
|
||||
editor.commands.setContent(html);
|
||||
}
|
||||
|
||||
// Function to find the next template in the document
|
||||
function findNextTemplate(doc, from = 0) {
|
||||
const patterns = [{ start: '{{', end: '}}' }];
|
||||
@@ -128,41 +143,47 @@
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
console.log(value);
|
||||
|
||||
if (preserveBreaks) {
|
||||
turndownService.addRule('preserveBreaks', {
|
||||
filter: 'br', // Target <br> elements
|
||||
replacement: function (content) {
|
||||
return '<br/>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let content = value;
|
||||
|
||||
if (!raw) {
|
||||
async function tryParse(value, attempts = 3, interval = 100) {
|
||||
try {
|
||||
// Try parsing the value
|
||||
return marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), {
|
||||
breaks: false
|
||||
});
|
||||
} catch (error) {
|
||||
// If no attempts remain, fallback to plain text
|
||||
if (attempts <= 1) {
|
||||
return value;
|
||||
if (!json) {
|
||||
if (preserveBreaks) {
|
||||
turndownService.addRule('preserveBreaks', {
|
||||
filter: 'br', // Target <br> elements
|
||||
replacement: function (content) {
|
||||
return '<br/>';
|
||||
}
|
||||
// Wait for the interval, then retry
|
||||
await new Promise((resolve) => setTimeout(resolve, interval));
|
||||
return tryParse(value, attempts - 1, interval); // Recursive call
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Usage example
|
||||
content = await tryParse(value);
|
||||
if (!raw) {
|
||||
async function tryParse(value, attempts = 3, interval = 100) {
|
||||
try {
|
||||
// Try parsing the value
|
||||
return marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), {
|
||||
breaks: false
|
||||
});
|
||||
} catch (error) {
|
||||
// If no attempts remain, fallback to plain text
|
||||
if (attempts <= 1) {
|
||||
return value;
|
||||
}
|
||||
// Wait for the interval, then retry
|
||||
await new Promise((resolve) => setTimeout(resolve, interval));
|
||||
return tryParse(value, attempts - 1, interval); // Recursive call
|
||||
}
|
||||
}
|
||||
|
||||
// Usage example
|
||||
content = await tryParse(value);
|
||||
}
|
||||
} else {
|
||||
if (html && !content) {
|
||||
content = html;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('content', content);
|
||||
|
||||
editor = new Editor({
|
||||
element: element,
|
||||
extensions: [
|
||||
@@ -198,32 +219,44 @@
|
||||
// force re-render so `editor.isActive` works as expected
|
||||
editor = editor;
|
||||
|
||||
if (!raw) {
|
||||
let newValue = turndownService
|
||||
.turndown(
|
||||
editor
|
||||
.getHTML()
|
||||
.replace(/<p><\/p>/g, '<br/>')
|
||||
.replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
|
||||
)
|
||||
.replace(/\u00a0/g, ' ');
|
||||
html = editor.getHTML();
|
||||
|
||||
if (!preserveBreaks) {
|
||||
newValue = newValue.replace(/<br\/>/g, '');
|
||||
}
|
||||
onChange({
|
||||
html: editor.getHTML(),
|
||||
json: editor.getJSON(),
|
||||
md: turndownService.turndown(editor.getHTML())
|
||||
});
|
||||
|
||||
if (value !== newValue) {
|
||||
value = newValue;
|
||||
if (json) {
|
||||
value = editor.getJSON();
|
||||
} else {
|
||||
if (!raw) {
|
||||
let newValue = turndownService
|
||||
.turndown(
|
||||
editor
|
||||
.getHTML()
|
||||
.replace(/<p><\/p>/g, '<br/>')
|
||||
.replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
|
||||
)
|
||||
.replace(/\u00a0/g, ' ');
|
||||
|
||||
// check if the node is paragraph as well
|
||||
if (editor.isActive('paragraph')) {
|
||||
if (value === '') {
|
||||
editor.commands.clearContent();
|
||||
if (!preserveBreaks) {
|
||||
newValue = newValue.replace(/<br\/>/g, '');
|
||||
}
|
||||
|
||||
if (value !== newValue) {
|
||||
value = newValue;
|
||||
|
||||
// check if the node is paragraph as well
|
||||
if (editor.isActive('paragraph')) {
|
||||
if (value === '') {
|
||||
editor.commands.clearContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
value = editor.getHTML();
|
||||
}
|
||||
} else {
|
||||
value = editor.getHTML();
|
||||
}
|
||||
},
|
||||
editorProps: {
|
||||
@@ -356,35 +389,49 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Update the editor content if the external `value` changes
|
||||
$: if (
|
||||
editor &&
|
||||
(raw
|
||||
? value !== editor.getHTML()
|
||||
: value !==
|
||||
turndownService
|
||||
.turndown(
|
||||
(preserveBreaks
|
||||
? editor.getHTML().replace(/<p><\/p>/g, '<br/>')
|
||||
: editor.getHTML()
|
||||
).replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
|
||||
)
|
||||
.replace(/\u00a0/g, ' '))
|
||||
) {
|
||||
if (raw) {
|
||||
editor.commands.setContent(value);
|
||||
} else {
|
||||
preserveBreaks
|
||||
? editor.commands.setContent(value)
|
||||
: editor.commands.setContent(
|
||||
marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), {
|
||||
breaks: false
|
||||
})
|
||||
); // Update editor content
|
||||
}
|
||||
|
||||
selectTemplate();
|
||||
$: if (value !== null && editor) {
|
||||
onValueChange();
|
||||
}
|
||||
|
||||
const onValueChange = () => {
|
||||
if (!editor) return;
|
||||
|
||||
if (json) {
|
||||
if (JSON.stringify(value) !== JSON.stringify(editor.getJSON())) {
|
||||
editor.commands.setContent(value);
|
||||
selectTemplate();
|
||||
}
|
||||
} else {
|
||||
if (raw) {
|
||||
if (value !== editor.getHTML()) {
|
||||
editor.commands.setContent(value);
|
||||
selectTemplate();
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
value !==
|
||||
turndownService
|
||||
.turndown(
|
||||
(preserveBreaks
|
||||
? editor.getHTML().replace(/<p><\/p>/g, '<br/>')
|
||||
: editor.getHTML()
|
||||
).replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
|
||||
)
|
||||
.replace(/\u00a0/g, ' ')
|
||||
) {
|
||||
preserveBreaks
|
||||
? editor.commands.setContent(value)
|
||||
: editor.commands.setContent(
|
||||
marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), {
|
||||
breaks: false
|
||||
})
|
||||
); // Update editor content
|
||||
|
||||
selectTemplate();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div bind:this={element} class="relative w-full min-w-full h-full min-h-fit {className}" />
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
export let value = '';
|
||||
export let placeholder = '';
|
||||
export let rows = 1;
|
||||
export let minSize = null;
|
||||
export let required = false;
|
||||
export let className =
|
||||
'w-full rounded-lg px-3 py-2 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden h-full';
|
||||
@@ -29,7 +30,9 @@
|
||||
const resize = () => {
|
||||
if (textareaElement) {
|
||||
textareaElement.style.height = '';
|
||||
textareaElement.style.height = `${textareaElement.scrollHeight}px`;
|
||||
textareaElement.style.height = minSize
|
||||
? `${Math.max(textareaElement.scrollHeight, minSize)}px`
|
||||
: `${textareaElement.scrollHeight}px`;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-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="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-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="M9 15 3 9m0 0 6-6M3 9h12a6 6 0 0 1 0 12h-3"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-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 15 6-6m0 0-6-6m6 6H9a6 6 0 0 0 0 12h3"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-4';
|
||||
export let strokeWidth = '1.5';
|
||||
</script>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class={className}>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M2 3.75A.75.75 0 0 1 2.75 3h10.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 3.75ZM2 8a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 8Zm0 4.25a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-3';
|
||||
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="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-4';
|
||||
</script>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M6.75 2.25A.75.75 0 0 1 7.5 3v1.5h9V3A.75.75 0 0 1 18 3v1.5h.75a3 3 0 0 1 3 3v11.25a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3V7.5a3 3 0 0 1 3-3H6V3a.75.75 0 0 1 .75-.75Zm13.5 9a1.5 1.5 0 0 0-1.5-1.5H5.25a1.5 1.5 0 0 0-1.5 1.5v7.5a1.5 1.5 0 0 0 1.5 1.5h13.5a1.5 1.5 0 0 0 1.5-1.5v-7.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-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.042 21.672 13.684 16.6m0 0-2.51 2.225.569-9.47 5.227 7.917-3.286-.672ZM12 2.25V4.5m5.834.166-1.591 1.591M20.25 10.5H18M7.757 14.743l-1.59 1.59M6 10.5H3.75m4.007-4.243-1.59-1.59"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-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="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m6.75 12-3-3m0 0-3 3m3-3v6m-1.5-15H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -1,10 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-4';
|
||||
export let strokeWidth = '1.5';
|
||||
</script>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class={className}>
|
||||
<path d="M7 4a3 3 0 0 1 6 0v6a3 3 0 1 1-6 0V4Z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={strokeWidth}
|
||||
stroke="currentColor"
|
||||
class={className}
|
||||
>
|
||||
<path
|
||||
d="M5.5 9.643a.75.75 0 0 0-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 0 0 0 1.5h4.5a.75.75 0 0 0 0-1.5h-1.5v-1.546A6.001 6.001 0 0 0 16 10v-.357a.75.75 0 0 0-1.5 0V10a4.5 4.5 0 0 1-9 0v-.357Z"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 1 1 6 0v8.25a3 3 0 0 1-3 3Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-4';
|
||||
</script>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class={className}>
|
||||
<path d="M7 4a3 3 0 0 1 6 0v6a3 3 0 1 1-6 0V4Z" />
|
||||
<path
|
||||
d="M5.5 9.643a.75.75 0 0 0-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 0 0 0 1.5h4.5a.75.75 0 0 0 0-1.5h-1.5v-1.546A6.001 6.001 0 0 0 16 10v-.357a.75.75 0 0 0-1.5 0V10a4.5 4.5 0 0 1-9 0v-.357Z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-3';
|
||||
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 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -18,7 +18,9 @@
|
||||
showArtifacts,
|
||||
mobile,
|
||||
temporaryChatEnabled,
|
||||
theme
|
||||
theme,
|
||||
user,
|
||||
settings
|
||||
} from '$lib/stores';
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
|
||||
@@ -62,75 +64,124 @@
|
||||
};
|
||||
|
||||
const downloadPdf = async () => {
|
||||
const containerElement = document.getElementById('messages-container');
|
||||
if ($settings?.stylizedPdfExport ?? true) {
|
||||
const containerElement = document.getElementById('messages-container');
|
||||
|
||||
if (containerElement) {
|
||||
try {
|
||||
const isDarkMode = document.documentElement.classList.contains('dark');
|
||||
if (containerElement) {
|
||||
try {
|
||||
const isDarkMode = document.documentElement.classList.contains('dark');
|
||||
const virtualWidth = 800; // Fixed width in px
|
||||
const pagePixelHeight = 1200; // Each slice height (adjust to avoid canvas bugs; generally 2–4k is safe)
|
||||
|
||||
console.log('isDarkMode', isDarkMode);
|
||||
// Clone & style once
|
||||
const clonedElement = containerElement.cloneNode(true);
|
||||
clonedElement.classList.add('text-black');
|
||||
clonedElement.classList.add('dark:text-white');
|
||||
clonedElement.style.width = `${virtualWidth}px`;
|
||||
clonedElement.style.position = 'absolute';
|
||||
clonedElement.style.left = '-9999px'; // Offscreen
|
||||
clonedElement.style.height = 'auto';
|
||||
document.body.appendChild(clonedElement);
|
||||
|
||||
// Define a fixed virtual screen size
|
||||
const virtualWidth = 800; // Fixed width (adjust as needed)
|
||||
// Clone the container to avoid layout shifts
|
||||
const clonedElement = containerElement.cloneNode(true);
|
||||
clonedElement.classList.add('text-black');
|
||||
clonedElement.classList.add('dark:text-white');
|
||||
clonedElement.style.width = `${virtualWidth}px`; // Apply fixed width
|
||||
clonedElement.style.height = 'auto'; // Allow content to expand
|
||||
// Get total height after attached to DOM
|
||||
const totalHeight = clonedElement.scrollHeight;
|
||||
let offsetY = 0;
|
||||
let page = 0;
|
||||
|
||||
document.body.appendChild(clonedElement); // Temporarily add to DOM
|
||||
// Prepare PDF
|
||||
const pdf = new jsPDF('p', 'mm', 'a4');
|
||||
const imgWidth = 210; // A4 mm
|
||||
const pageHeight = 297; // A4 mm
|
||||
|
||||
// Render to canvas with predefined width
|
||||
const canvas = await html2canvas(clonedElement, {
|
||||
backgroundColor: isDarkMode ? '#000' : '#fff',
|
||||
useCORS: true,
|
||||
scale: 2, // Keep at 1x to avoid unexpected enlargements
|
||||
width: virtualWidth, // Set fixed virtual screen width
|
||||
windowWidth: virtualWidth // Ensure consistent rendering
|
||||
});
|
||||
while (offsetY < totalHeight) {
|
||||
// For each slice, adjust scrollTop to show desired part
|
||||
clonedElement.scrollTop = offsetY;
|
||||
|
||||
document.body.removeChild(clonedElement); // Clean up temp element
|
||||
// Optionally: mask/hide overflowing content via CSS if needed
|
||||
clonedElement.style.maxHeight = `${pagePixelHeight}px`;
|
||||
// Only render the visible part
|
||||
const canvas = await html2canvas(clonedElement, {
|
||||
backgroundColor: isDarkMode ? '#000' : '#fff',
|
||||
useCORS: true,
|
||||
scale: 2,
|
||||
width: virtualWidth,
|
||||
height: Math.min(pagePixelHeight, totalHeight - offsetY),
|
||||
// Optionally: y offset for correct region?
|
||||
windowWidth: virtualWidth
|
||||
//windowHeight: pagePixelHeight,
|
||||
});
|
||||
const imgData = canvas.toDataURL('image/png');
|
||||
// Maintain aspect ratio
|
||||
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
||||
const position = 0; // Always first line, since we've clipped vertically
|
||||
|
||||
const imgData = canvas.toDataURL('image/png');
|
||||
if (page > 0) pdf.addPage();
|
||||
|
||||
// A4 page settings
|
||||
const pdf = new jsPDF('p', 'mm', 'a4');
|
||||
const imgWidth = 210; // A4 width in mm
|
||||
const pageHeight = 297; // A4 height in mm
|
||||
// Set page background for dark mode
|
||||
if (isDarkMode) {
|
||||
pdf.setFillColor(0, 0, 0);
|
||||
pdf.rect(0, 0, imgWidth, pageHeight, 'F'); // black bg
|
||||
}
|
||||
|
||||
// Maintain aspect ratio
|
||||
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
||||
let heightLeft = imgHeight;
|
||||
let position = 0;
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
|
||||
// Set page background for dark mode
|
||||
if (isDarkMode) {
|
||||
pdf.setFillColor(0, 0, 0);
|
||||
pdf.rect(0, 0, imgWidth, pageHeight, 'F'); // Apply black bg
|
||||
}
|
||||
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
heightLeft -= pageHeight;
|
||||
|
||||
// Handle additional pages
|
||||
while (heightLeft > 0) {
|
||||
position -= pageHeight;
|
||||
pdf.addPage();
|
||||
|
||||
if (isDarkMode) {
|
||||
pdf.setFillColor(0, 0, 0);
|
||||
pdf.rect(0, 0, imgWidth, pageHeight, 'F');
|
||||
offsetY += pagePixelHeight;
|
||||
page++;
|
||||
}
|
||||
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
heightLeft -= pageHeight;
|
||||
}
|
||||
document.body.removeChild(clonedElement);
|
||||
|
||||
pdf.save(`chat-${chat.chat.title}.pdf`);
|
||||
} catch (error) {
|
||||
console.error('Error generating PDF', error);
|
||||
pdf.save(`chat-${chat.chat.title}.pdf`);
|
||||
} catch (error) {
|
||||
console.error('Error generating PDF', error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('Downloading PDF');
|
||||
|
||||
const chatText = await getChatAsText();
|
||||
|
||||
const doc = new jsPDF();
|
||||
|
||||
// Margins
|
||||
const left = 15;
|
||||
const top = 20;
|
||||
const right = 15;
|
||||
const bottom = 20;
|
||||
|
||||
const pageWidth = doc.internal.pageSize.getWidth();
|
||||
const pageHeight = doc.internal.pageSize.getHeight();
|
||||
const usableWidth = pageWidth - left - right;
|
||||
const usableHeight = pageHeight - top - bottom;
|
||||
|
||||
// Font size and line height
|
||||
const fontSize = 8;
|
||||
doc.setFontSize(fontSize);
|
||||
const lineHeight = fontSize * 1; // adjust if needed
|
||||
|
||||
// Split the markdown into lines (handles \n)
|
||||
const paragraphs = chatText.split('\n');
|
||||
|
||||
let y = top;
|
||||
|
||||
for (let paragraph of paragraphs) {
|
||||
// Wrap each paragraph to fit the width
|
||||
const lines = doc.splitTextToSize(paragraph, usableWidth);
|
||||
|
||||
for (let line of lines) {
|
||||
// If the line would overflow the bottom, add a new page
|
||||
if (y + lineHeight > pageHeight - bottom) {
|
||||
doc.addPage();
|
||||
y = top;
|
||||
}
|
||||
doc.text(line, left, y);
|
||||
y += lineHeight * 0.5;
|
||||
}
|
||||
// Add empty line at paragraph breaks
|
||||
y += lineHeight * 0.1;
|
||||
}
|
||||
|
||||
doc.save(`chat-${chat.chat.title}.pdf`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -212,7 +263,7 @@
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
|
||||
{#if !$temporaryChatEnabled}
|
||||
{#if !$temporaryChatEnabled && ($user?.role === 'admin' || ($user.permissions?.chat?.share ?? true))}
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
id="chat-share-button"
|
||||
@@ -288,14 +339,16 @@
|
||||
transition={flyAndScale}
|
||||
sideOffset={8}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
on:click={() => {
|
||||
downloadJSONExport();
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center line-clamp-1">{$i18n.t('Export chat (.json)')}</div>
|
||||
</DropdownMenu.Item>
|
||||
{#if $user?.role === 'admin' || ($user.permissions?.chat?.export ?? true)}
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
on:click={() => {
|
||||
downloadJSONExport();
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center line-clamp-1">{$i18n.t('Export chat (.json)')}</div>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
on:click={() => {
|
||||
|
||||
@@ -570,6 +570,48 @@
|
||||
</div>
|
||||
{/if} -->
|
||||
|
||||
{#if ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))}
|
||||
<div class="px-1.5 flex justify-center text-gray-800 dark:text-gray-200">
|
||||
<a
|
||||
class="grow flex items-center space-x-3 rounded-lg px-2 py-[7px] hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
||||
href="/notes"
|
||||
on:click={() => {
|
||||
selectedChatId = null;
|
||||
chatId.set('');
|
||||
|
||||
if ($mobile) {
|
||||
showSidebar.set(false);
|
||||
}
|
||||
}}
|
||||
draggable="false"
|
||||
>
|
||||
<div class="self-center">
|
||||
<svg
|
||||
class="size-4"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 3v4a1 1 0 0 1-1 1H5m4 8h6m-6-4h6m4-8v16a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7.914a1 1 0 0 1 .293-.707l3.914-3.914A1 1 0 0 1 9.914 3H18a1 1 0 0 1 1 1Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="flex self-center translate-y-[0.5px]">
|
||||
<div class=" self-center font-medium text-sm font-primary">{$i18n.t('Notes')}</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools}
|
||||
<div class="px-1.5 flex justify-center text-gray-800 dark:text-gray-200">
|
||||
<a
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
import Check from '$lib/components/icons/Check.svelte';
|
||||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
import Document from '$lib/components/icons/Document.svelte';
|
||||
import Sparkles from '$lib/components/icons/Sparkles.svelte';
|
||||
import { generateTitle } from '$lib/apis';
|
||||
|
||||
export let className = '';
|
||||
|
||||
@@ -133,12 +135,10 @@
|
||||
dispatch('change');
|
||||
};
|
||||
|
||||
const focusEdit = async (node: HTMLInputElement) => {
|
||||
node.focus();
|
||||
};
|
||||
|
||||
let itemElement;
|
||||
|
||||
let generating = false;
|
||||
let doubleClicked = false;
|
||||
let dragged = false;
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
@@ -213,6 +213,55 @@
|
||||
chatTitle = '';
|
||||
}
|
||||
};
|
||||
|
||||
const renameHandler = async () => {
|
||||
chatTitle = title;
|
||||
confirmEdit = true;
|
||||
|
||||
await tick();
|
||||
|
||||
setTimeout(() => {
|
||||
const input = document.getElementById(`chat-title-input-${id}`);
|
||||
if (input) input.focus();
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const generateTitleHandler = async () => {
|
||||
generating = true;
|
||||
if (!chat) {
|
||||
chat = await getChatById(localStorage.token, id);
|
||||
}
|
||||
|
||||
const messages = (chat.chat?.messages ?? []).map((message) => {
|
||||
return {
|
||||
role: message.role,
|
||||
content: message.content
|
||||
};
|
||||
});
|
||||
|
||||
const model = chat.chat.models.at(0) ?? chat.models.at(0) ?? '';
|
||||
|
||||
chatTitle = '';
|
||||
|
||||
const generatedTitle = await generateTitle(localStorage.token, model, messages).catch(
|
||||
(error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
if (generatedTitle) {
|
||||
if (generatedTitle !== title) {
|
||||
editChatTitle(id, generatedTitle);
|
||||
}
|
||||
|
||||
confirmEdit = false;
|
||||
} else {
|
||||
chatTitle = title;
|
||||
}
|
||||
|
||||
generating = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<ShareChatModal bind:show={showShareChatModal} chatId={id} />
|
||||
@@ -254,14 +303,43 @@
|
||||
? 'bg-gray-200 dark:bg-gray-900'
|
||||
: selected
|
||||
? 'bg-gray-100 dark:bg-gray-950'
|
||||
: 'group-hover:bg-gray-100 dark:group-hover:bg-gray-950'} whitespace-nowrap text-ellipsis"
|
||||
: 'group-hover:bg-gray-100 dark:group-hover:bg-gray-950'} whitespace-nowrap text-ellipsis relative {generating
|
||||
? 'cursor-not-allowed'
|
||||
: ''}"
|
||||
>
|
||||
<input
|
||||
use:focusEdit
|
||||
bind:value={chatTitle}
|
||||
id="chat-title-input-{id}"
|
||||
bind:value={chatTitle}
|
||||
class=" bg-transparent w-full outline-hidden mr-10"
|
||||
placeholder={generating ? $i18n.t('Generating...') : ''}
|
||||
on:keydown={chatTitleInputKeydownHandler}
|
||||
on:blur={async (e) => {
|
||||
// check if target is generate button
|
||||
if (e.relatedTarget?.id === 'generate-title-button') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (doubleClicked) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
await tick();
|
||||
setTimeout(() => {
|
||||
const input = document.getElementById(`chat-title-input-${id}`);
|
||||
if (input) input.focus();
|
||||
}, 0);
|
||||
|
||||
doubleClicked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (chatTitle !== title) {
|
||||
editChatTitle(id, chatTitle);
|
||||
}
|
||||
|
||||
confirmEdit = false;
|
||||
chatTitle = '';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
@@ -280,9 +358,12 @@
|
||||
showSidebar.set(false);
|
||||
}
|
||||
}}
|
||||
on:dblclick={() => {
|
||||
chatTitle = title;
|
||||
confirmEdit = true;
|
||||
on:dblclick={async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
doubleClicked = true;
|
||||
renameHandler();
|
||||
}}
|
||||
on:mouseenter={(e) => {
|
||||
mouseOver = true;
|
||||
@@ -311,7 +392,7 @@
|
||||
: 'invisible group-hover:visible from-gray-100 dark:from-gray-950'}
|
||||
absolute {className === 'pr-2'
|
||||
? 'right-[8px]'
|
||||
: 'right-0'} top-[4px] py-1 pr-0.5 mr-1.5 pl-5 bg-linear-to-l from-80%
|
||||
: 'right-1'} top-[4px] py-1 pr-0.5 mr-1.5 pl-5 bg-linear-to-l from-80%
|
||||
|
||||
to-transparent"
|
||||
on:mouseenter={(e) => {
|
||||
@@ -325,28 +406,19 @@
|
||||
<div
|
||||
class="flex self-center items-center space-x-1.5 z-10 translate-y-[0.5px] -translate-x-[0.5px]"
|
||||
>
|
||||
<Tooltip content={$i18n.t('Confirm')}>
|
||||
<Tooltip content={$i18n.t('Generate')}>
|
||||
<button
|
||||
class=" self-center dark:hover:text-white transition"
|
||||
on:click={() => {
|
||||
editChatTitle(id, chatTitle);
|
||||
confirmEdit = false;
|
||||
chatTitle = '';
|
||||
}}
|
||||
>
|
||||
<Check className=" size-3.5" strokeWidth="2.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
id="generate-title-button"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
e.stopPropagation();
|
||||
|
||||
<Tooltip content={$i18n.t('Cancel')}>
|
||||
<button
|
||||
class=" self-center dark:hover:text-white transition"
|
||||
on:click={() => {
|
||||
confirmEdit = false;
|
||||
chatTitle = '';
|
||||
generateTitleHandler();
|
||||
}}
|
||||
>
|
||||
<XMark strokeWidth="2.5" />
|
||||
<Sparkles strokeWidth="2" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -377,7 +449,7 @@
|
||||
</Tooltip>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex self-center space-x-1 z-10">
|
||||
<div class="flex self-center z-10 items-end">
|
||||
<ChatMenu
|
||||
chatId={id}
|
||||
cloneChatHandler={() => {
|
||||
@@ -389,16 +461,7 @@
|
||||
archiveChatHandler={() => {
|
||||
archiveChatHandler(id);
|
||||
}}
|
||||
renameHandler={async () => {
|
||||
chatTitle = title;
|
||||
confirmEdit = true;
|
||||
|
||||
await tick();
|
||||
const input = document.getElementById(`chat-title-input-${id}`);
|
||||
if (input) {
|
||||
input.focus();
|
||||
}
|
||||
}}
|
||||
{renameHandler}
|
||||
deleteHandler={() => {
|
||||
showDeleteConfirm = true;
|
||||
}}
|
||||
@@ -414,7 +477,7 @@
|
||||
>
|
||||
<button
|
||||
aria-label="Chat Menu"
|
||||
class=" self-center dark:hover:text-white transition"
|
||||
class=" self-center dark:hover:text-white transition m-0"
|
||||
on:click={() => {
|
||||
dispatch('select');
|
||||
}}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
getChatPinnedStatusById,
|
||||
toggleChatPinnedStatusById
|
||||
} from '$lib/apis/chats';
|
||||
import { chats, theme } from '$lib/stores';
|
||||
import { chats, settings, theme, user } from '$lib/stores';
|
||||
import { createMessagesList } from '$lib/utils';
|
||||
import { downloadChatAsPDF } from '$lib/apis/utils';
|
||||
import Download from '$lib/components/icons/Download.svelte';
|
||||
@@ -81,74 +81,124 @@
|
||||
const downloadPdf = async () => {
|
||||
const chat = await getChatById(localStorage.token, chatId);
|
||||
|
||||
const containerElement = document.getElementById('messages-container');
|
||||
if ($settings?.stylizedPdfExport ?? true) {
|
||||
const containerElement = document.getElementById('messages-container');
|
||||
|
||||
if (containerElement) {
|
||||
try {
|
||||
const isDarkMode = $theme.includes('dark'); // Check theme mode
|
||||
if (containerElement) {
|
||||
try {
|
||||
const isDarkMode = document.documentElement.classList.contains('dark');
|
||||
const virtualWidth = 800; // Fixed width in px
|
||||
const pagePixelHeight = 1200; // Each slice height (adjust to avoid canvas bugs; generally 2–4k is safe)
|
||||
|
||||
// Define a fixed virtual screen size
|
||||
const virtualWidth = 1024; // Fixed width (adjust as needed)
|
||||
const virtualHeight = 1400; // Fixed height (adjust as needed)
|
||||
// Clone & style once
|
||||
const clonedElement = containerElement.cloneNode(true);
|
||||
clonedElement.classList.add('text-black');
|
||||
clonedElement.classList.add('dark:text-white');
|
||||
clonedElement.style.width = `${virtualWidth}px`;
|
||||
clonedElement.style.position = 'absolute';
|
||||
clonedElement.style.left = '-9999px'; // Offscreen
|
||||
clonedElement.style.height = 'auto';
|
||||
document.body.appendChild(clonedElement);
|
||||
|
||||
// Clone the container to avoid layout shifts
|
||||
const clonedElement = containerElement.cloneNode(true);
|
||||
clonedElement.style.width = `${virtualWidth}px`; // Apply fixed width
|
||||
clonedElement.style.height = 'auto'; // Allow content to expand
|
||||
// Get total height after attached to DOM
|
||||
const totalHeight = clonedElement.scrollHeight;
|
||||
let offsetY = 0;
|
||||
let page = 0;
|
||||
|
||||
document.body.appendChild(clonedElement); // Temporarily add to DOM
|
||||
// Prepare PDF
|
||||
const pdf = new jsPDF('p', 'mm', 'a4');
|
||||
const imgWidth = 210; // A4 mm
|
||||
const pageHeight = 297; // A4 mm
|
||||
|
||||
// Render to canvas with predefined width
|
||||
const canvas = await html2canvas(clonedElement, {
|
||||
backgroundColor: isDarkMode ? '#000' : '#fff',
|
||||
useCORS: true,
|
||||
scale: 2, // Keep at 1x to avoid unexpected enlargements
|
||||
width: virtualWidth, // Set fixed virtual screen width
|
||||
windowWidth: virtualWidth, // Ensure consistent rendering
|
||||
windowHeight: virtualHeight
|
||||
});
|
||||
while (offsetY < totalHeight) {
|
||||
// For each slice, adjust scrollTop to show desired part
|
||||
clonedElement.scrollTop = offsetY;
|
||||
|
||||
document.body.removeChild(clonedElement); // Clean up temp element
|
||||
// Optionally: mask/hide overflowing content via CSS if needed
|
||||
clonedElement.style.maxHeight = `${pagePixelHeight}px`;
|
||||
// Only render the visible part
|
||||
const canvas = await html2canvas(clonedElement, {
|
||||
backgroundColor: isDarkMode ? '#000' : '#fff',
|
||||
useCORS: true,
|
||||
scale: 2,
|
||||
width: virtualWidth,
|
||||
height: Math.min(pagePixelHeight, totalHeight - offsetY),
|
||||
// Optionally: y offset for correct region?
|
||||
windowWidth: virtualWidth
|
||||
//windowHeight: pagePixelHeight,
|
||||
});
|
||||
const imgData = canvas.toDataURL('image/png');
|
||||
// Maintain aspect ratio
|
||||
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
||||
const position = 0; // Always first line, since we've clipped vertically
|
||||
|
||||
const imgData = canvas.toDataURL('image/png');
|
||||
if (page > 0) pdf.addPage();
|
||||
|
||||
// A4 page settings
|
||||
const pdf = new jsPDF('p', 'mm', 'a4');
|
||||
const imgWidth = 210; // A4 width in mm
|
||||
const pageHeight = 297; // A4 height in mm
|
||||
// Set page background for dark mode
|
||||
if (isDarkMode) {
|
||||
pdf.setFillColor(0, 0, 0);
|
||||
pdf.rect(0, 0, imgWidth, pageHeight, 'F'); // black bg
|
||||
}
|
||||
|
||||
// Maintain aspect ratio
|
||||
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
||||
let heightLeft = imgHeight;
|
||||
let position = 0;
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
|
||||
// Set page background for dark mode
|
||||
if (isDarkMode) {
|
||||
pdf.setFillColor(0, 0, 0);
|
||||
pdf.rect(0, 0, imgWidth, pageHeight, 'F'); // Apply black bg
|
||||
}
|
||||
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
heightLeft -= pageHeight;
|
||||
|
||||
// Handle additional pages
|
||||
while (heightLeft > 0) {
|
||||
position -= pageHeight;
|
||||
pdf.addPage();
|
||||
|
||||
if (isDarkMode) {
|
||||
pdf.setFillColor(0, 0, 0);
|
||||
pdf.rect(0, 0, imgWidth, pageHeight, 'F');
|
||||
offsetY += pagePixelHeight;
|
||||
page++;
|
||||
}
|
||||
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
heightLeft -= pageHeight;
|
||||
}
|
||||
document.body.removeChild(clonedElement);
|
||||
|
||||
pdf.save(`chat-${chat.chat.title}.pdf`);
|
||||
} catch (error) {
|
||||
console.error('Error generating PDF', error);
|
||||
pdf.save(`chat-${chat.chat.title}.pdf`);
|
||||
} catch (error) {
|
||||
console.error('Error generating PDF', error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('Downloading PDF');
|
||||
|
||||
const chatText = await getChatAsText(chat);
|
||||
|
||||
const doc = new jsPDF();
|
||||
|
||||
// Margins
|
||||
const left = 15;
|
||||
const top = 20;
|
||||
const right = 15;
|
||||
const bottom = 20;
|
||||
|
||||
const pageWidth = doc.internal.pageSize.getWidth();
|
||||
const pageHeight = doc.internal.pageSize.getHeight();
|
||||
const usableWidth = pageWidth - left - right;
|
||||
const usableHeight = pageHeight - top - bottom;
|
||||
|
||||
// Font size and line height
|
||||
const fontSize = 8;
|
||||
doc.setFontSize(fontSize);
|
||||
const lineHeight = fontSize * 1; // adjust if needed
|
||||
|
||||
// Split the markdown into lines (handles \n)
|
||||
const paragraphs = chatText.split('\n');
|
||||
|
||||
let y = top;
|
||||
|
||||
for (let paragraph of paragraphs) {
|
||||
// Wrap each paragraph to fit the width
|
||||
const lines = doc.splitTextToSize(paragraph, usableWidth);
|
||||
|
||||
for (let line of lines) {
|
||||
// If the line would overflow the bottom, add a new page
|
||||
if (y + lineHeight > pageHeight - bottom) {
|
||||
doc.addPage();
|
||||
y = top;
|
||||
}
|
||||
doc.text(line, left, y);
|
||||
y += lineHeight;
|
||||
}
|
||||
// Add empty line at paragraph breaks
|
||||
y += lineHeight * 0.5;
|
||||
}
|
||||
|
||||
doc.save(`chat-${chat.chat.title}.pdf`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -233,15 +283,17 @@
|
||||
<div class="flex items-center">{$i18n.t('Archive')}</div>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
on:click={() => {
|
||||
shareHandler();
|
||||
}}
|
||||
>
|
||||
<Share />
|
||||
<div class="flex items-center">{$i18n.t('Share')}</div>
|
||||
</DropdownMenu.Item>
|
||||
{#if $user?.role === 'admin' || ($user.permissions?.chat?.share ?? true)}
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
on:click={() => {
|
||||
shareHandler();
|
||||
}}
|
||||
>
|
||||
<Share />
|
||||
<div class="flex items-center">{$i18n.t('Share')}</div>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger
|
||||
@@ -256,14 +308,17 @@
|
||||
transition={flyAndScale}
|
||||
sideOffset={8}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
on:click={() => {
|
||||
downloadJSONExport();
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center line-clamp-1">{$i18n.t('Export chat (.json)')}</div>
|
||||
</DropdownMenu.Item>
|
||||
{#if $user?.role === 'admin' || ($user.permissions?.chat?.export ?? true)}
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
on:click={() => {
|
||||
downloadJSONExport();
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center line-clamp-1">{$i18n.t('Export chat (.json)')}</div>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
on:click={() => {
|
||||
|
||||
@@ -252,7 +252,7 @@
|
||||
|
||||
const nameUpdateHandler = async () => {
|
||||
if (name === '') {
|
||||
toast.error($i18n.t('Folder name cannot be empty'));
|
||||
toast.error($i18n.t('Folder name cannot be empty.'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -374,8 +374,8 @@
|
||||
buttonClassName="w-full"
|
||||
hide={(folders[folderId]?.childrenIds ?? []).length === 0 &&
|
||||
(folders[folderId].items?.chats ?? []).length === 0}
|
||||
on:change={(e) => {
|
||||
dispatch('open', e.detail);
|
||||
onChange={(state) => {
|
||||
dispatch('open', state);
|
||||
}}
|
||||
>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
|
||||
@@ -0,0 +1,971 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onDestroy, onMount, tick } from 'svelte';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import fileSaver from 'file-saver';
|
||||
const { saveAs } = fileSaver;
|
||||
|
||||
import jsPDF from 'jspdf';
|
||||
import html2canvas from 'html2canvas-pro';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import { marked } from 'marked';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { config, models, settings, showSidebar } from '$lib/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { compressImage, copyToClipboard, splitStream } from '$lib/utils';
|
||||
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
||||
import { uploadFile } from '$lib/apis/files';
|
||||
|
||||
import dayjs from '$lib/dayjs';
|
||||
import calendar from 'dayjs/plugin/calendar';
|
||||
import duration from 'dayjs/plugin/duration';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
|
||||
dayjs.extend(calendar);
|
||||
dayjs.extend(duration);
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
async function loadLocale(locales) {
|
||||
for (const locale of locales) {
|
||||
try {
|
||||
dayjs.locale(locale);
|
||||
break; // Stop after successfully loading the first available locale
|
||||
} catch (error) {
|
||||
console.error(`Could not load locale '${locale}':`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assuming $i18n.languages is an array of language codes
|
||||
$: loadLocale($i18n.languages);
|
||||
|
||||
import { deleteNoteById, getNoteById, updateNoteById } from '$lib/apis/notes';
|
||||
|
||||
import RichTextInput from '../common/RichTextInput.svelte';
|
||||
import Spinner from '../common/Spinner.svelte';
|
||||
import MicSolid from '../icons/MicSolid.svelte';
|
||||
import VoiceRecording from '../chat/MessageInput/VoiceRecording.svelte';
|
||||
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
|
||||
import Calendar from '../icons/Calendar.svelte';
|
||||
import Users from '../icons/Users.svelte';
|
||||
|
||||
import Image from '../common/Image.svelte';
|
||||
import FileItem from '../common/FileItem.svelte';
|
||||
import FilesOverlay from '../chat/MessageInput/FilesOverlay.svelte';
|
||||
import RecordMenu from './RecordMenu.svelte';
|
||||
import NoteMenu from './Notes/NoteMenu.svelte';
|
||||
import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
|
||||
import Sparkles from '../icons/Sparkles.svelte';
|
||||
import SparklesSolid from '../icons/SparklesSolid.svelte';
|
||||
import Tooltip from '../common/Tooltip.svelte';
|
||||
import Bars3BottomLeft from '../icons/Bars3BottomLeft.svelte';
|
||||
import ArrowUturnLeft from '../icons/ArrowUturnLeft.svelte';
|
||||
import ArrowUturnRight from '../icons/ArrowUturnRight.svelte';
|
||||
import Sidebar from '../common/Sidebar.svelte';
|
||||
import ArrowRight from '../icons/ArrowRight.svelte';
|
||||
import Cog6 from '../icons/Cog6.svelte';
|
||||
import { chatCompletion } from '$lib/apis/openai';
|
||||
|
||||
export let id: null | string = null;
|
||||
|
||||
let note = null;
|
||||
|
||||
const newNote = {
|
||||
title: '',
|
||||
data: {
|
||||
content: {
|
||||
json: null,
|
||||
html: '',
|
||||
md: ''
|
||||
},
|
||||
versions: [],
|
||||
files: null
|
||||
},
|
||||
meta: null,
|
||||
access_control: null
|
||||
};
|
||||
|
||||
let files = [];
|
||||
|
||||
let versionIdx = null;
|
||||
let selectedModelId = null;
|
||||
|
||||
let recording = false;
|
||||
let displayMediaRecord = false;
|
||||
|
||||
let showSettings = false;
|
||||
let showDeleteConfirm = false;
|
||||
|
||||
let dragged = false;
|
||||
let loading = false;
|
||||
|
||||
let enhancing = false;
|
||||
let streaming = false;
|
||||
|
||||
const init = async () => {
|
||||
loading = true;
|
||||
const res = await getNoteById(localStorage.token, id).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res) {
|
||||
note = res;
|
||||
files = res.data.files || [];
|
||||
} else {
|
||||
goto('/');
|
||||
return;
|
||||
}
|
||||
|
||||
loading = false;
|
||||
};
|
||||
|
||||
let debounceTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
const changeDebounceHandler = () => {
|
||||
if (debounceTimeout) {
|
||||
clearTimeout(debounceTimeout);
|
||||
}
|
||||
|
||||
debounceTimeout = setTimeout(async () => {
|
||||
if (!note || enhancing || versionIdx !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Saving note:', note);
|
||||
|
||||
const res = await updateNoteById(localStorage.token, id, {
|
||||
...note,
|
||||
title: note.title === '' ? $i18n.t('Untitled') : note.title
|
||||
}).catch((e) => {
|
||||
toast.error(`${e}`);
|
||||
});
|
||||
}, 200);
|
||||
};
|
||||
|
||||
$: if (note) {
|
||||
changeDebounceHandler();
|
||||
}
|
||||
|
||||
$: if (id) {
|
||||
init();
|
||||
}
|
||||
|
||||
function areContentsEqual(a, b) {
|
||||
return JSON.stringify(a) === JSON.stringify(b);
|
||||
}
|
||||
|
||||
function insertNoteVersion(note) {
|
||||
const current = {
|
||||
json: note.data.content.json,
|
||||
html: note.data.content.html,
|
||||
md: note.data.content.md
|
||||
};
|
||||
const lastVersion = note.data.versions?.at(-1);
|
||||
|
||||
if (!lastVersion || !areContentsEqual(lastVersion, current)) {
|
||||
note.data.versions = (note.data.versions ?? []).concat(current);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function enhanceNoteHandler() {
|
||||
if (selectedModelId === '') {
|
||||
toast.error($i18n.t('Please select a model.'));
|
||||
return;
|
||||
}
|
||||
|
||||
const model = $models
|
||||
.filter((model) => model.id === selectedModelId && !(model?.info?.meta?.hidden ?? false))
|
||||
.find((model) => model.id === selectedModelId);
|
||||
|
||||
if (!model) {
|
||||
selectedModelId = '';
|
||||
return;
|
||||
}
|
||||
|
||||
enhancing = true;
|
||||
|
||||
insertNoteVersion(note);
|
||||
await enhanceCompletionHandler(model);
|
||||
|
||||
enhancing = false;
|
||||
versionIdx = null;
|
||||
}
|
||||
|
||||
function setContentByVersion(versionIdx) {
|
||||
if (!note.data.versions?.length) return;
|
||||
let idx = versionIdx;
|
||||
|
||||
if (idx === null) idx = note.data.versions.length - 1; // latest
|
||||
const v = note.data.versions[idx];
|
||||
|
||||
note.data.content.json = v.json;
|
||||
note.data.content.html = v.html;
|
||||
note.data.content.md = v.md;
|
||||
|
||||
if (versionIdx === null) {
|
||||
const lastVersion = note.data.versions.at(-1);
|
||||
const currentContent = note.data.content;
|
||||
|
||||
if (areContentsEqual(lastVersion, currentContent)) {
|
||||
// remove the last version
|
||||
note.data.versions = note.data.versions.slice(0, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation
|
||||
function versionNavigateHandler(direction) {
|
||||
if (!note.data.versions || note.data.versions.length === 0) return;
|
||||
|
||||
if (versionIdx === null) {
|
||||
// Get latest snapshots
|
||||
const lastVersion = note.data.versions.at(-1);
|
||||
const currentContent = note.data.content;
|
||||
|
||||
if (!areContentsEqual(lastVersion, currentContent)) {
|
||||
// If the current content is different from the last version, insert a new version
|
||||
insertNoteVersion(note);
|
||||
versionIdx = note.data.versions.length - 1;
|
||||
} else {
|
||||
versionIdx = note.data.versions.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (direction === 'prev') {
|
||||
if (versionIdx > 0) versionIdx -= 1;
|
||||
} else if (direction === 'next') {
|
||||
if (versionIdx < note.data.versions.length - 1) versionIdx += 1;
|
||||
else versionIdx = null; // Reset to latest
|
||||
|
||||
if (versionIdx === note.data.versions.length - 1) {
|
||||
// If we reach the latest version, reset to null
|
||||
versionIdx = null;
|
||||
}
|
||||
}
|
||||
|
||||
setContentByVersion(versionIdx);
|
||||
}
|
||||
|
||||
const uploadFileHandler = async (file) => {
|
||||
const tempItemId = uuidv4();
|
||||
const fileItem = {
|
||||
type: 'file',
|
||||
file: '',
|
||||
id: null,
|
||||
url: '',
|
||||
name: file.name,
|
||||
collection_name: '',
|
||||
status: 'uploading',
|
||||
size: file.size,
|
||||
error: '',
|
||||
itemId: tempItemId
|
||||
};
|
||||
|
||||
if (fileItem.size == 0) {
|
||||
toast.error($i18n.t('You cannot upload an empty file.'));
|
||||
return null;
|
||||
}
|
||||
|
||||
files = [...files, fileItem];
|
||||
|
||||
try {
|
||||
// During the file upload, file content is automatically extracted.
|
||||
const uploadedFile = await uploadFile(localStorage.token, file);
|
||||
|
||||
if (uploadedFile) {
|
||||
console.log('File upload completed:', {
|
||||
id: uploadedFile.id,
|
||||
name: fileItem.name,
|
||||
collection: uploadedFile?.meta?.collection_name
|
||||
});
|
||||
|
||||
if (uploadedFile.error) {
|
||||
console.warn('File upload warning:', uploadedFile.error);
|
||||
toast.warning(uploadedFile.error);
|
||||
}
|
||||
|
||||
fileItem.status = 'uploaded';
|
||||
fileItem.file = uploadedFile;
|
||||
fileItem.id = uploadedFile.id;
|
||||
fileItem.collection_name =
|
||||
uploadedFile?.meta?.collection_name || uploadedFile?.collection_name;
|
||||
|
||||
fileItem.url = `${WEBUI_API_BASE_URL}/files/${uploadedFile.id}`;
|
||||
|
||||
files = files;
|
||||
} else {
|
||||
files = files.filter((item) => item?.itemId !== tempItemId);
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(`${e}`);
|
||||
files = files.filter((item) => item?.itemId !== tempItemId);
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
note.data.files = files;
|
||||
} else {
|
||||
note.data.files = null;
|
||||
}
|
||||
};
|
||||
|
||||
const inputFilesHandler = async (inputFiles) => {
|
||||
console.log('Input files handler called with:', inputFiles);
|
||||
inputFiles.forEach((file) => {
|
||||
console.log('Processing file:', {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
extension: file.name.split('.').at(-1)
|
||||
});
|
||||
|
||||
if (
|
||||
($config?.file?.max_size ?? null) !== null &&
|
||||
file.size > ($config?.file?.max_size ?? 0) * 1024 * 1024
|
||||
) {
|
||||
console.log('File exceeds max size limit:', {
|
||||
fileSize: file.size,
|
||||
maxSize: ($config?.file?.max_size ?? 0) * 1024 * 1024
|
||||
});
|
||||
toast.error(
|
||||
$i18n.t(`File size should not exceed {{maxSize}} MB.`, {
|
||||
maxSize: $config?.file?.max_size
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
['image/gif', 'image/webp', 'image/jpeg', 'image/png', 'image/avif'].includes(file['type'])
|
||||
) {
|
||||
let reader = new FileReader();
|
||||
reader.onload = async (event) => {
|
||||
let imageUrl = event.target.result;
|
||||
|
||||
if ($settings?.imageCompression ?? false) {
|
||||
const width = $settings?.imageCompressionSize?.width ?? null;
|
||||
const height = $settings?.imageCompressionSize?.height ?? null;
|
||||
|
||||
if (width || height) {
|
||||
imageUrl = await compressImage(imageUrl, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
files = [
|
||||
...files,
|
||||
{
|
||||
type: 'image',
|
||||
url: `${imageUrl}`
|
||||
}
|
||||
];
|
||||
note.data.files = files;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
uploadFileHandler(file);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const downloadHandler = async (type) => {
|
||||
console.log('downloadHandler', type);
|
||||
if (type === 'md') {
|
||||
const blob = new Blob([note.data.content.md], { type: 'text/markdown' });
|
||||
saveAs(blob, `${note.title}.md`);
|
||||
} else if (type === 'pdf') {
|
||||
await downloadPdf(note);
|
||||
}
|
||||
};
|
||||
|
||||
const downloadPdf = async (note) => {
|
||||
try {
|
||||
// Define a fixed virtual screen size
|
||||
const virtualWidth = 1024; // Fixed width (adjust as needed)
|
||||
const virtualHeight = 1400; // Fixed height (adjust as needed)
|
||||
|
||||
// STEP 1. Get a DOM node to render
|
||||
const html = note.data?.content?.html ?? '';
|
||||
let node;
|
||||
if (html instanceof HTMLElement) {
|
||||
node = html;
|
||||
} else {
|
||||
// If it's HTML string, render to a temporary hidden element
|
||||
node = document.createElement('div');
|
||||
node.innerHTML = html;
|
||||
document.body.appendChild(node);
|
||||
}
|
||||
|
||||
// Render to canvas with predefined width
|
||||
const canvas = await html2canvas(node, {
|
||||
useCORS: true,
|
||||
scale: 2, // Keep at 1x to avoid unexpected enlargements
|
||||
width: virtualWidth, // Set fixed virtual screen width
|
||||
windowWidth: virtualWidth, // Ensure consistent rendering
|
||||
windowHeight: virtualHeight
|
||||
});
|
||||
|
||||
// Remove hidden node if needed
|
||||
if (!(html instanceof HTMLElement)) {
|
||||
document.body.removeChild(node);
|
||||
}
|
||||
|
||||
const imgData = canvas.toDataURL('image/png');
|
||||
|
||||
// A4 page settings
|
||||
const pdf = new jsPDF('p', 'mm', 'a4');
|
||||
const imgWidth = 210; // A4 width in mm
|
||||
const pageHeight = 297; // A4 height in mm
|
||||
|
||||
// Maintain aspect ratio
|
||||
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
||||
let heightLeft = imgHeight;
|
||||
let position = 0;
|
||||
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
heightLeft -= pageHeight;
|
||||
|
||||
// Handle additional pages
|
||||
while (heightLeft > 0) {
|
||||
position -= pageHeight;
|
||||
pdf.addPage();
|
||||
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
heightLeft -= pageHeight;
|
||||
}
|
||||
|
||||
pdf.save(`${note.title}.pdf`);
|
||||
} catch (error) {
|
||||
console.error('Error generating PDF', error);
|
||||
|
||||
toast.error(`${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteNoteHandler = async (id) => {
|
||||
const res = await deleteNoteById(localStorage.token, id).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res) {
|
||||
toast.success($i18n.t('Note deleted successfully'));
|
||||
goto('/notes');
|
||||
} else {
|
||||
toast.error($i18n.t('Failed to delete note'));
|
||||
}
|
||||
};
|
||||
|
||||
const scrollToBottom = () => {
|
||||
const element = document.getElementById('note-content-container');
|
||||
|
||||
if (element) {
|
||||
element.scrollTop = element?.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
const enhanceCompletionHandler = async (model) => {
|
||||
let enhancedContent = {
|
||||
json: null,
|
||||
html: '',
|
||||
md: ''
|
||||
};
|
||||
|
||||
const systemPrompt = `Enhance existing notes using additional context provided from audio transcription or uploaded file content. Your task is to make the notes more useful and comprehensive by incorporating relevant information from the provided context.
|
||||
|
||||
Input will be provided within <notes> and <context> XML tags, providing a structure for the existing notes and context respectively.
|
||||
|
||||
# Output Format
|
||||
|
||||
Provide the enhanced notes in markdown format. Use markdown syntax for headings, lists, and emphasis to improve clarity and presentation. Ensure that all integrated content from the context is accurately reflected. Return only the markdown formatted note.
|
||||
`;
|
||||
|
||||
const [res, controller] = await chatCompletion(
|
||||
localStorage.token,
|
||||
{
|
||||
model: model.id,
|
||||
stream: true,
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: systemPrompt
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content:
|
||||
`<notes>${note.data.content.md}</notes>` +
|
||||
(files && files.length > 0
|
||||
? `\n<context>${files.map((file) => `${file.name}: ${file?.file?.data?.content ?? 'Could not extract content'}\n`).join('')}</context>`
|
||||
: '')
|
||||
}
|
||||
]
|
||||
},
|
||||
`${WEBUI_BASE_URL}/api`
|
||||
);
|
||||
|
||||
await tick();
|
||||
|
||||
streaming = true;
|
||||
|
||||
if (res && res.ok) {
|
||||
const reader = res.body
|
||||
.pipeThrough(new TextDecoderStream())
|
||||
.pipeThrough(splitStream('\n'))
|
||||
.getReader();
|
||||
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
let lines = value.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line !== '') {
|
||||
console.log(line);
|
||||
if (line === 'data: [DONE]') {
|
||||
console.log(line);
|
||||
} else {
|
||||
let data = JSON.parse(line.replace(/^data: /, ''));
|
||||
console.log(data);
|
||||
|
||||
if (data.choices && data.choices.length > 0) {
|
||||
const choice = data.choices[0];
|
||||
if (choice.delta && choice.delta.content) {
|
||||
enhancedContent.md += choice.delta.content;
|
||||
enhancedContent.html = marked.parse(enhancedContent.md);
|
||||
|
||||
note.data.content.md = enhancedContent.md;
|
||||
note.data.content.html = enhancedContent.html;
|
||||
note.data.content.json = null;
|
||||
|
||||
scrollToBottom();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
streaming = false;
|
||||
};
|
||||
|
||||
const onDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Check if a file is being dragged.
|
||||
if (e.dataTransfer?.types?.includes('Files')) {
|
||||
dragged = true;
|
||||
} else {
|
||||
dragged = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onDragLeave = () => {
|
||||
dragged = false;
|
||||
};
|
||||
|
||||
const onDrop = async (e) => {
|
||||
e.preventDefault();
|
||||
console.log(e);
|
||||
|
||||
if (e.dataTransfer?.files) {
|
||||
const inputFiles = Array.from(e.dataTransfer?.files);
|
||||
if (inputFiles && inputFiles.length > 0) {
|
||||
console.log(inputFiles);
|
||||
inputFilesHandler(inputFiles);
|
||||
}
|
||||
}
|
||||
|
||||
dragged = false;
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
await tick();
|
||||
|
||||
if ($settings?.models) {
|
||||
selectedModelId = $settings?.models[0];
|
||||
} else if ($config?.default_models) {
|
||||
selectedModelId = $config?.default_models.split(',')[0];
|
||||
} else {
|
||||
selectedModelId = '';
|
||||
}
|
||||
|
||||
if (selectedModelId) {
|
||||
const model = $models
|
||||
.filter((model) => model.id === selectedModelId && !(model?.info?.meta?.hidden ?? false))
|
||||
.find((model) => model.id === selectedModelId);
|
||||
|
||||
if (!model) {
|
||||
selectedModelId = '';
|
||||
}
|
||||
}
|
||||
|
||||
const dropzoneElement = document.getElementById('note-editor');
|
||||
|
||||
dropzoneElement?.addEventListener('dragover', onDragOver);
|
||||
dropzoneElement?.addEventListener('drop', onDrop);
|
||||
dropzoneElement?.addEventListener('dragleave', onDragLeave);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
console.log('destroy');
|
||||
const dropzoneElement = document.getElementById('note-editor');
|
||||
|
||||
if (dropzoneElement) {
|
||||
dropzoneElement?.removeEventListener('dragover', onDragOver);
|
||||
dropzoneElement?.removeEventListener('drop', onDrop);
|
||||
dropzoneElement?.removeEventListener('dragleave', onDragLeave);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<FilesOverlay show={dragged} />
|
||||
|
||||
<DeleteConfirmDialog
|
||||
bind:show={showDeleteConfirm}
|
||||
title={$i18n.t('Delete note?')}
|
||||
on:confirm={() => {
|
||||
deleteNoteHandler(note.id);
|
||||
showDeleteConfirm = false;
|
||||
}}
|
||||
>
|
||||
<div class=" text-sm text-gray-500">
|
||||
{$i18n.t('This will delete')} <span class=" font-semibold">{note.title}</span>.
|
||||
</div>
|
||||
</DeleteConfirmDialog>
|
||||
|
||||
<div class="relative flex-1 w-full h-full flex justify-center" id="note-editor">
|
||||
<Sidebar bind:show={showSettings} className=" bg-white dark:bg-gray-900" width="300px">
|
||||
<div class="flex flex-col px-5 py-3 text-sm">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<div class=" font-medium text-base">Settings</div>
|
||||
|
||||
<div class=" translate-x-1.5">
|
||||
<button
|
||||
class="p-1.5 bg-transparent hover:bg-white/5 transition rounded-lg"
|
||||
on:click={() => {
|
||||
showSettings = !showSettings;
|
||||
}}
|
||||
>
|
||||
<ArrowRight className="size-3" strokeWidth="2.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-1">
|
||||
<div>
|
||||
<div class=" text-xs font-medium mb-1">Model</div>
|
||||
|
||||
<div class="w-full">
|
||||
<select
|
||||
class="w-full bg-transparent text-sm outline-hidden"
|
||||
bind:value={selectedModelId}
|
||||
>
|
||||
<option value="" class="bg-gray-50 dark:bg-gray-700" disabled>
|
||||
{$i18n.t('Select a model')}
|
||||
</option>
|
||||
{#each $models.filter((model) => !(model?.info?.meta?.hidden ?? false)) as model}
|
||||
<option value={model.id} class="bg-gray-50 dark:bg-gray-700">{model.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Sidebar>
|
||||
|
||||
{#if loading}
|
||||
<div class=" absolute top-0 bottom-0 left-0 right-0 flex">
|
||||
<div class="m-auto">
|
||||
<Spinner />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class=" w-full flex flex-col {loading ? 'opacity-20' : ''}">
|
||||
<div class="shrink-0 w-full flex justify-between items-center px-4.5 mb-1.5">
|
||||
<div class="w-full flex items-center">
|
||||
<input
|
||||
class="w-full text-2xl font-medium bg-transparent outline-hidden"
|
||||
type="text"
|
||||
bind:value={note.title}
|
||||
placeholder={$i18n.t('Title')}
|
||||
required
|
||||
/>
|
||||
|
||||
<div class="flex items-center gap-2 translate-x-1">
|
||||
{#if note.data?.versions?.length > 0}
|
||||
<div>
|
||||
<div class="flex items-center gap-0.5 self-center min-w-fit" dir="ltr">
|
||||
<button
|
||||
class="self-center p-1 hover:enabled:bg-black/5 dark:hover:enabled:bg-white/5 dark:hover:enabled:text-white hover:enabled:text-black rounded-md transition disabled:cursor-not-allowed disabled:text-gray-500 disabled:hover:text-gray-500"
|
||||
on:click={() => {
|
||||
versionNavigateHandler('prev');
|
||||
}}
|
||||
disabled={(versionIdx === null && note.data.versions.length === 0) ||
|
||||
versionIdx === 0}
|
||||
>
|
||||
<ArrowUturnLeft className="size-4" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="self-center p-1 hover:enabled:bg-black/5 dark:hover:enabled:bg-white/5 dark:hover:enabled:text-white hover:enabled:text-black rounded-md transition disabled:cursor-not-allowed disabled:text-gray-500 disabled:hover:text-gray-500"
|
||||
on:click={() => {
|
||||
versionNavigateHandler('next');
|
||||
}}
|
||||
disabled={versionIdx >= note.data.versions.length || versionIdx === null}
|
||||
>
|
||||
<ArrowUturnRight className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<NoteMenu
|
||||
onDownload={(type) => {
|
||||
downloadHandler(type);
|
||||
}}
|
||||
onCopyToClipboard={async () => {
|
||||
const res = await copyToClipboard(note.data.content.md).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res) {
|
||||
toast.success($i18n.t('Copied to clipboard'));
|
||||
}
|
||||
}}
|
||||
onDelete={() => {
|
||||
showDeleteConfirm = true;
|
||||
}}
|
||||
>
|
||||
<EllipsisHorizontal className="size-5" />
|
||||
</NoteMenu>
|
||||
|
||||
<button
|
||||
class="p-1.5 bg-transparent hover:bg-white/5 transition rounded-lg"
|
||||
on:click={() => {
|
||||
showSettings = !showSettings;
|
||||
}}
|
||||
>
|
||||
<Cog6 />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 px-3.5">
|
||||
<div class="flex gap-1 items-center text-xs font-medium text-gray-500 dark:text-gray-500">
|
||||
<button class=" flex items-center gap-1 w-fit py-1 px-1.5 rounded-lg">
|
||||
<Calendar className="size-3.5" strokeWidth="2" />
|
||||
|
||||
<span>{dayjs(note.created_at / 1000000).calendar()}</span>
|
||||
</button>
|
||||
|
||||
<button class=" flex items-center gap-1 w-fit py-1 px-1.5 rounded-lg">
|
||||
<Users className="size-3.5" strokeWidth="2" />
|
||||
|
||||
<span> You </span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class=" flex-1 w-full h-full overflow-auto px-4 pb-20 relative"
|
||||
id="note-content-container"
|
||||
>
|
||||
{#if enhancing}
|
||||
<div
|
||||
class="w-full h-full fixed top-0 left-0 {streaming
|
||||
? ''
|
||||
: ' backdrop-blur-xs bg-white/10 dark:bg-gray-900/10'} flex items-center justify-center z-10 cursor-not-allowed"
|
||||
></div>
|
||||
{/if}
|
||||
|
||||
{#if files && files.length > 0}
|
||||
<div class="mb-3.5 mt-1.5 w-full flex gap-1 flex-wrap z-40">
|
||||
{#each files as file, fileIdx}
|
||||
<div class="w-fit">
|
||||
{#if file.type === 'image'}
|
||||
<Image
|
||||
src={file.url}
|
||||
imageClassName=" max-h-96 rounded-lg"
|
||||
dismissible={true}
|
||||
onDismiss={() => {
|
||||
files = files.filter((item, idx) => idx !== fileIdx);
|
||||
note.data.files = files.length > 0 ? files : null;
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<FileItem
|
||||
item={file}
|
||||
dismissible={true}
|
||||
url={file.url}
|
||||
name={file.name}
|
||||
type={file.type}
|
||||
size={file?.size}
|
||||
loading={file.status === 'uploading'}
|
||||
on:dismiss={() => {
|
||||
files = files.filter((item) => item?.id !== file.id);
|
||||
note.data.files = files.length > 0 ? files : null;
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<RichTextInput
|
||||
className="input-prose-sm px-0.5"
|
||||
bind:value={note.data.content.json}
|
||||
placeholder={$i18n.t('Write something...')}
|
||||
html={note.data?.content?.html}
|
||||
json={true}
|
||||
editable={versionIdx === null && !enhancing}
|
||||
onChange={(content) => {
|
||||
note.data.content.html = content.html;
|
||||
note.data.content.md = content.md;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="absolute z-20 bottom-0 right-0 p-5 max-w-full {$showSidebar
|
||||
? 'md:max-w-[calc(100%-260px)]'
|
||||
: ''} w-full flex justify-end"
|
||||
>
|
||||
<div class="flex gap-1 justify-between w-full max-w-full">
|
||||
{#if recording}
|
||||
<div class="flex-1 w-full">
|
||||
<VoiceRecording
|
||||
bind:recording
|
||||
className="p-1 w-full max-w-full"
|
||||
transcribe={false}
|
||||
displayMedia={displayMediaRecord}
|
||||
onCancel={() => {
|
||||
recording = false;
|
||||
displayMediaRecord = false;
|
||||
}}
|
||||
onConfirm={(data) => {
|
||||
if (data?.file) {
|
||||
uploadFileHandler(data?.file);
|
||||
}
|
||||
|
||||
recording = false;
|
||||
displayMediaRecord = false;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<RecordMenu
|
||||
onRecord={async () => {
|
||||
displayMediaRecord = false;
|
||||
|
||||
try {
|
||||
let stream = await navigator.mediaDevices
|
||||
.getUserMedia({ audio: true })
|
||||
.catch(function (err) {
|
||||
toast.error(
|
||||
$i18n.t(`Permission denied when accessing microphone: {{error}}`, {
|
||||
error: err
|
||||
})
|
||||
);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (stream) {
|
||||
recording = true;
|
||||
const tracks = stream.getTracks();
|
||||
tracks.forEach((track) => track.stop());
|
||||
}
|
||||
stream = null;
|
||||
} catch {
|
||||
toast.error($i18n.t('Permission denied when accessing microphone'));
|
||||
}
|
||||
}}
|
||||
onCaptureAudio={async () => {
|
||||
displayMediaRecord = true;
|
||||
|
||||
recording = true;
|
||||
}}
|
||||
onUpload={async () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'audio/*';
|
||||
input.multiple = false;
|
||||
input.click();
|
||||
|
||||
input.onchange = async (e) => {
|
||||
const files = e.target.files;
|
||||
|
||||
if (files && files.length > 0) {
|
||||
await uploadFileHandler(files[0]);
|
||||
}
|
||||
};
|
||||
}}
|
||||
>
|
||||
<Tooltip content={$i18n.t('Record')} placement="top">
|
||||
<button
|
||||
class="cursor-pointer p-2.5 flex rounded-full border border-gray-50 bg-white dark:border-none dark:bg-gray-850 hover:bg-gray-50 dark:hover:bg-gray-800 transition shadow-xl"
|
||||
type="button"
|
||||
>
|
||||
<MicSolid className="size-4.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</RecordMenu>
|
||||
|
||||
<div
|
||||
class="cursor-pointer flex gap-0.5 rounded-full border border-gray-50 dark:border-gray-850 dark:bg-gray-850 transition shadow-xl"
|
||||
>
|
||||
<!-- <Tooltip content={$i18n.t('My Notes')} placement="top">
|
||||
<button
|
||||
class="p-2 size-8.5 flex justify-center items-center {selectedVersion === 'note'
|
||||
? 'bg-gray-100 dark:bg-gray-800 '
|
||||
: ' hover:bg-gray-50 dark:hover:bg-gray-800'} rounded-full transition shrink-0"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
selectedVersion = 'note';
|
||||
versionToggleHandler();
|
||||
}}
|
||||
>
|
||||
<Bars3BottomLeft />
|
||||
</button>
|
||||
</Tooltip> -->
|
||||
|
||||
<Tooltip content={$i18n.t('Enhance')} placement="top">
|
||||
<button
|
||||
class="{enhancing
|
||||
? 'p-2'
|
||||
: 'p-2.5'} flex justify-center items-center hover:bg-gray-50 dark:hover:bg-gray-800 rounded-full transition shrink-0"
|
||||
on:click={() => {
|
||||
enhanceNoteHandler();
|
||||
}}
|
||||
disabled={enhancing}
|
||||
type="button"
|
||||
>
|
||||
{#if enhancing}
|
||||
<Spinner className="size-5" />
|
||||
{:else}
|
||||
<SparklesSolid />
|
||||
{/if}
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,468 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import fileSaver from 'file-saver';
|
||||
const { saveAs } = fileSaver;
|
||||
|
||||
import jsPDF from 'jspdf';
|
||||
import html2canvas from 'html2canvas-pro';
|
||||
|
||||
import dayjs from '$lib/dayjs';
|
||||
import duration from 'dayjs/plugin/duration';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
|
||||
dayjs.extend(duration);
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
async function loadLocale(locales) {
|
||||
for (const locale of locales) {
|
||||
try {
|
||||
dayjs.locale(locale);
|
||||
break; // Stop after successfully loading the first available locale
|
||||
} catch (error) {
|
||||
console.error(`Could not load locale '${locale}':`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assuming $i18n.languages is an array of language codes
|
||||
$: loadLocale($i18n.languages);
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount, getContext, onDestroy } from 'svelte';
|
||||
import { WEBUI_NAME, config, prompts as _prompts, user } from '$lib/stores';
|
||||
|
||||
import { createNewNote, deleteNoteById, getNotes } from '$lib/apis/notes';
|
||||
import { capitalizeFirstLetter } from '$lib/utils';
|
||||
|
||||
import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
|
||||
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
import Search from '../icons/Search.svelte';
|
||||
import Plus from '../icons/Plus.svelte';
|
||||
import ChevronRight from '../icons/ChevronRight.svelte';
|
||||
import Spinner from '../common/Spinner.svelte';
|
||||
import Tooltip from '../common/Tooltip.svelte';
|
||||
import NoteMenu from './Notes/NoteMenu.svelte';
|
||||
import FilesOverlay from '../chat/MessageInput/FilesOverlay.svelte';
|
||||
import { marked } from 'marked';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
let loaded = false;
|
||||
|
||||
let importFiles = '';
|
||||
let query = '';
|
||||
|
||||
let notes = [];
|
||||
let selectedNote = null;
|
||||
|
||||
let showDeleteConfirm = false;
|
||||
|
||||
const init = async () => {
|
||||
notes = await getNotes(localStorage.token);
|
||||
};
|
||||
|
||||
const createNoteHandler = async () => {
|
||||
const res = await createNewNote(localStorage.token, {
|
||||
title: $i18n.t('New Note'),
|
||||
data: {
|
||||
content: {
|
||||
json: null,
|
||||
html: '',
|
||||
md: ''
|
||||
}
|
||||
},
|
||||
meta: null,
|
||||
access_control: null
|
||||
}).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res) {
|
||||
goto(`/notes/${res.id}`);
|
||||
}
|
||||
};
|
||||
|
||||
const downloadHandler = async (type) => {
|
||||
console.log('downloadHandler', type);
|
||||
console.log('selectedNote', selectedNote);
|
||||
if (type === 'md') {
|
||||
const blob = new Blob([selectedNote.data.content.md], { type: 'text/markdown' });
|
||||
saveAs(blob, `${selectedNote.title}.md`);
|
||||
} else if (type === 'pdf') {
|
||||
await downloadPdf(selectedNote);
|
||||
}
|
||||
};
|
||||
|
||||
const downloadPdf = async (note) => {
|
||||
try {
|
||||
// Define a fixed virtual screen size
|
||||
const virtualWidth = 1024; // Fixed width (adjust as needed)
|
||||
const virtualHeight = 1400; // Fixed height (adjust as needed)
|
||||
|
||||
// STEP 1. Get a DOM node to render
|
||||
const html = note.data?.content?.html ?? '';
|
||||
let node;
|
||||
if (html instanceof HTMLElement) {
|
||||
node = html;
|
||||
} else {
|
||||
// If it's HTML string, render to a temporary hidden element
|
||||
node = document.createElement('div');
|
||||
node.innerHTML = html;
|
||||
document.body.appendChild(node);
|
||||
}
|
||||
|
||||
// Render to canvas with predefined width
|
||||
const canvas = await html2canvas(node, {
|
||||
useCORS: true,
|
||||
scale: 2, // Keep at 1x to avoid unexpected enlargements
|
||||
width: virtualWidth, // Set fixed virtual screen width
|
||||
windowWidth: virtualWidth, // Ensure consistent rendering
|
||||
windowHeight: virtualHeight
|
||||
});
|
||||
|
||||
// Remove hidden node if needed
|
||||
if (!(html instanceof HTMLElement)) {
|
||||
document.body.removeChild(node);
|
||||
}
|
||||
|
||||
const imgData = canvas.toDataURL('image/png');
|
||||
|
||||
// A4 page settings
|
||||
const pdf = new jsPDF('p', 'mm', 'a4');
|
||||
const imgWidth = 210; // A4 width in mm
|
||||
const pageHeight = 297; // A4 height in mm
|
||||
|
||||
// Maintain aspect ratio
|
||||
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
||||
let heightLeft = imgHeight;
|
||||
let position = 0;
|
||||
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
heightLeft -= pageHeight;
|
||||
|
||||
// Handle additional pages
|
||||
while (heightLeft > 0) {
|
||||
position -= pageHeight;
|
||||
pdf.addPage();
|
||||
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
heightLeft -= pageHeight;
|
||||
}
|
||||
|
||||
pdf.save(`${note.title}.pdf`);
|
||||
} catch (error) {
|
||||
console.error('Error generating PDF', error);
|
||||
|
||||
toast.error(`${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteNoteHandler = async (id) => {
|
||||
const res = await deleteNoteById(localStorage.token, id).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res) {
|
||||
init();
|
||||
}
|
||||
};
|
||||
|
||||
const inputFilesHandler = async (inputFiles) => {
|
||||
// Check if all the file is a markdown file and extract name and content
|
||||
|
||||
for (const file of inputFiles) {
|
||||
if (file.type !== 'text/markdown') {
|
||||
toast.error($i18n.t('Only markdown files are allowed'));
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (event) => {
|
||||
const content = event.target.result;
|
||||
let name = file.name.replace(/\.md$/, '');
|
||||
|
||||
if (typeof content !== 'string') {
|
||||
toast.error($i18n.t('Invalid file content'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new note with the content
|
||||
const res = await createNewNote(localStorage.token, {
|
||||
title: name,
|
||||
data: {
|
||||
content: {
|
||||
json: null,
|
||||
html: marked.parse(content ?? ''),
|
||||
md: content
|
||||
}
|
||||
},
|
||||
meta: null,
|
||||
access_control: null
|
||||
}).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res) {
|
||||
init();
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
|
||||
let dragged = false;
|
||||
|
||||
const onDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Check if a file is being dragged.
|
||||
if (e.dataTransfer?.types?.includes('Files')) {
|
||||
dragged = true;
|
||||
} else {
|
||||
dragged = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onDragLeave = () => {
|
||||
dragged = false;
|
||||
};
|
||||
|
||||
const onDrop = async (e) => {
|
||||
e.preventDefault();
|
||||
console.log(e);
|
||||
|
||||
if (e.dataTransfer?.files) {
|
||||
const inputFiles = Array.from(e.dataTransfer?.files);
|
||||
if (inputFiles && inputFiles.length > 0) {
|
||||
console.log(inputFiles);
|
||||
inputFilesHandler(inputFiles);
|
||||
}
|
||||
}
|
||||
|
||||
dragged = false;
|
||||
};
|
||||
|
||||
onDestroy(() => {
|
||||
console.log('destroy');
|
||||
const dropzoneElement = document.getElementById('notes-container');
|
||||
|
||||
if (dropzoneElement) {
|
||||
dropzoneElement?.removeEventListener('dragover', onDragOver);
|
||||
dropzoneElement?.removeEventListener('drop', onDrop);
|
||||
dropzoneElement?.removeEventListener('dragleave', onDragLeave);
|
||||
}
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
await init();
|
||||
loaded = true;
|
||||
|
||||
const dropzoneElement = document.getElementById('notes-container');
|
||||
|
||||
dropzoneElement?.addEventListener('dragover', onDragOver);
|
||||
dropzoneElement?.addEventListener('drop', onDrop);
|
||||
dropzoneElement?.addEventListener('dragleave', onDragLeave);
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>
|
||||
{$i18n.t('Notes')} • {$WEBUI_NAME}
|
||||
</title>
|
||||
</svelte:head>
|
||||
|
||||
<FilesOverlay show={dragged} />
|
||||
|
||||
<div id="notes-container" class="w-full min-h-full h-full">
|
||||
{#if loaded}
|
||||
<DeleteConfirmDialog
|
||||
bind:show={showDeleteConfirm}
|
||||
title={$i18n.t('Delete note?')}
|
||||
on:confirm={() => {
|
||||
deleteNoteHandler(selectedNote.id);
|
||||
showDeleteConfirm = false;
|
||||
}}
|
||||
>
|
||||
<div class=" text-sm text-gray-500">
|
||||
{$i18n.t('This will delete')} <span class=" font-semibold">{selectedNote.title}</span>.
|
||||
</div>
|
||||
</DeleteConfirmDialog>
|
||||
|
||||
<div class="px-4.5 @container h-full pt-2">
|
||||
{#if Object.keys(notes).length > 0}
|
||||
<div class="pb-10">
|
||||
{#each Object.keys(notes) as timeRange}
|
||||
<div class="w-full text-xs text-gray-500 dark:text-gray-500 font-medium pb-2.5">
|
||||
{$i18n.t(timeRange)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mb-5 gap-2.5 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4"
|
||||
>
|
||||
{#each notes[timeRange] as note, idx (note.id)}
|
||||
<div
|
||||
class=" flex space-x-4 cursor-pointer w-full px-4.5 py-4 bg-gray-50 dark:bg-gray-850 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl transition"
|
||||
>
|
||||
<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
|
||||
<a
|
||||
href={`/notes/${note.id}`}
|
||||
class="w-full -translate-y-0.5 flex flex-col justify-between"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<div class=" flex items-center gap-2 self-center mb-1 justify-between">
|
||||
<div class=" font-semibold line-clamp-1 capitalize">{note.title}</div>
|
||||
|
||||
<div>
|
||||
<NoteMenu
|
||||
onDownload={(type) => {
|
||||
selectedNote = note;
|
||||
|
||||
downloadHandler(type);
|
||||
}}
|
||||
onDelete={() => {
|
||||
selectedNote = note;
|
||||
showDeleteConfirm = true;
|
||||
}}
|
||||
>
|
||||
<button
|
||||
class="self-center w-fit text-sm p-1 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
type="button"
|
||||
>
|
||||
<EllipsisHorizontal className="size-5" />
|
||||
</button>
|
||||
</NoteMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class=" text-xs text-gray-500 dark:text-gray-500 mb-3 line-clamp-5 min-h-18"
|
||||
>
|
||||
{#if note.data?.content?.md}
|
||||
{note.data?.content?.md}
|
||||
{:else}
|
||||
{$i18n.t('No content')}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" text-xs px-0.5 w-full flex justify-between items-center">
|
||||
<div>
|
||||
{dayjs(note.updated_at / 1000000).fromNow()}
|
||||
</div>
|
||||
<Tooltip
|
||||
content={note?.user?.email ?? $i18n.t('Deleted User')}
|
||||
className="flex shrink-0"
|
||||
placement="top-start"
|
||||
>
|
||||
<div class="shrink-0 text-gray-500">
|
||||
{$i18n.t('By {{name}}', {
|
||||
name: capitalizeFirstLetter(
|
||||
note?.user?.name ?? note?.user?.email ?? $i18n.t('Deleted User')
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="w-full h-full flex flex-col items-center justify-center">
|
||||
<div class="pb-20 text-center">
|
||||
<div class=" text-xl font-medium text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('No Notes')}
|
||||
</div>
|
||||
|
||||
<div class="mt-1 text-sm text-gray-300 dark:text-gray-700">
|
||||
{$i18n.t('Create your first note by clicking on the plus button below.')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="absolute bottom-0 left-0 right-0 p-5 max-w-full flex justify-end">
|
||||
<div class="flex gap-0.5 justify-end w-full">
|
||||
<Tooltip content={$i18n.t('Create Note')}>
|
||||
<button
|
||||
class="cursor-pointer p-2.5 flex rounded-full border border-gray-50 bg-white dark:border-none dark:bg-gray-850 hover:bg-gray-50 dark:hover:bg-gray-800 transition shadow-xl"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
createNoteHandler();
|
||||
}}
|
||||
>
|
||||
<Plus className="size-4.5" strokeWidth="2.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<!-- <button
|
||||
class="cursor-pointer p-2.5 flex rounded-full hover:bg-gray-100 dark:hover:bg-gray-850 transition shadow-xl"
|
||||
>
|
||||
<SparklesSolid className="size-4" />
|
||||
</button> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- {#if $user?.role === 'admin'}
|
||||
<div class=" flex justify-end w-full mb-3">
|
||||
<div class="flex space-x-2">
|
||||
<input
|
||||
id="notes-import-input"
|
||||
bind:files={importFiles}
|
||||
type="file"
|
||||
accept=".md"
|
||||
hidden
|
||||
on:change={() => {
|
||||
console.log(importFiles);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (event) => {
|
||||
console.log(event.target.result);
|
||||
};
|
||||
|
||||
reader.readAsText(importFiles[0]);
|
||||
}}
|
||||
/>
|
||||
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={() => {
|
||||
const notesImportInputElement = document.getElementById('notes-import-input');
|
||||
if (notesImportInputElement) {
|
||||
notesImportInputElement.click();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Import Notes')}</div>
|
||||
|
||||
<div class=" self-center">
|
||||
<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="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if} -->
|
||||
{:else}
|
||||
<div class="w-full h-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,116 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu } from 'bits-ui';
|
||||
import { getContext, onMount } from 'svelte';
|
||||
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
import { fade, slide } from 'svelte/transition';
|
||||
|
||||
import { showSettings, activeUserIds, USAGE_POOL, mobile, showSidebar, user } from '$lib/stores';
|
||||
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
|
||||
import Download from '$lib/components/icons/Download.svelte';
|
||||
import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
|
||||
import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte';
|
||||
import Share from '$lib/components/icons/Share.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let show = false;
|
||||
export let className = 'max-w-[180px]';
|
||||
|
||||
export let onDownload = (type) => {};
|
||||
export let onDelete = () => {};
|
||||
export let onCopyToClipboard = () => {};
|
||||
|
||||
export let onChange = () => {};
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root
|
||||
bind:open={show}
|
||||
onOpenChange={(state) => {
|
||||
onChange(state);
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.Trigger>
|
||||
<slot />
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<slot name="content">
|
||||
<DropdownMenu.Content
|
||||
class="w-full {className} text-sm rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg font-primary"
|
||||
sideOffset={6}
|
||||
side="bottom"
|
||||
align="start"
|
||||
transition={(e) => fade(e, { duration: 100 })}
|
||||
>
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
>
|
||||
<Download strokeWidth="2" />
|
||||
|
||||
<div class="flex items-center">{$i18n.t('Download')}</div>
|
||||
</DropdownMenu.SubTrigger>
|
||||
<DropdownMenu.SubContent
|
||||
class="w-full rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
|
||||
transition={flyAndScale}
|
||||
sideOffset={8}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
on:click={() => {
|
||||
onDownload('md');
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center line-clamp-1">{$i18n.t('Plain text (.md)')}</div>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
on:click={() => {
|
||||
onDownload('pdf');
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center line-clamp-1">{$i18n.t('PDF document (.pdf)')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
>
|
||||
<Share strokeWidth="2" />
|
||||
|
||||
<div class="flex items-center">{$i18n.t('Share')}</div>
|
||||
</DropdownMenu.SubTrigger>
|
||||
<DropdownMenu.SubContent
|
||||
class="w-full rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
|
||||
transition={flyAndScale}
|
||||
sideOffset={8}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
on:click={() => {
|
||||
onCopyToClipboard();
|
||||
}}
|
||||
>
|
||||
<DocumentDuplicate strokeWidth="2" />
|
||||
<div class="flex items-center">{$i18n.t('Copy to clipboard')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
|
||||
on:click={() => {
|
||||
onDelete();
|
||||
}}
|
||||
>
|
||||
<GarbageBin strokeWidth="2" />
|
||||
<div class="flex items-center">{$i18n.t('Delete')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</slot>
|
||||
</DropdownMenu.Root>
|
||||
@@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu } from 'bits-ui';
|
||||
import { createEventDispatcher, getContext, onMount } from 'svelte';
|
||||
|
||||
import { showSettings, activeUserIds, USAGE_POOL, mobile, showSidebar, user } from '$lib/stores';
|
||||
import { fade, slide } from 'svelte/transition';
|
||||
|
||||
import Mic from '../icons/Mic.svelte';
|
||||
import CursorArrowRays from '../icons/CursorArrowRays.svelte';
|
||||
import DocumentArrowUp from '../icons/DocumentArrowUp.svelte';
|
||||
import CloudArrowUp from '../icons/CloudArrowUp.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let show = false;
|
||||
export let className = 'max-w-[160px]';
|
||||
|
||||
export let onRecord = () => {};
|
||||
export let onCaptureAudio = () => {};
|
||||
export let onUpload = () => {};
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root
|
||||
bind:open={show}
|
||||
onOpenChange={(state) => {
|
||||
dispatch('change', state);
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.Trigger>
|
||||
<slot />
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<slot name="content">
|
||||
<DropdownMenu.Content
|
||||
class="w-full {className} text-sm rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg font-primary"
|
||||
sideOffset={8}
|
||||
side="bottom"
|
||||
align="start"
|
||||
transition={(e) => fade(e, { duration: 100 })}
|
||||
>
|
||||
<button
|
||||
class="flex rounded-md py-2 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
|
||||
on:click={async () => {
|
||||
onRecord();
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2">
|
||||
<Mic className="size-5" strokeWidth="1.5" />
|
||||
</div>
|
||||
<div class=" self-center truncate">{$i18n.t('Record')}</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex rounded-md py-2 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
onCaptureAudio();
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2">
|
||||
<CursorArrowRays className="size-5" strokeWidth="1.5" />
|
||||
</div>
|
||||
<div class=" self-center truncate">{$i18n.t('Capture Audio')}</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex rounded-md py-2 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
onUpload();
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2">
|
||||
<CloudArrowUp className="size-5" strokeWidth="1.5" />
|
||||
</div>
|
||||
<div class=" self-center truncate">{$i18n.t('Upload Audio')}</div>
|
||||
</button>
|
||||
</DropdownMenu.Content>
|
||||
</slot>
|
||||
</DropdownMenu.Root>
|
||||
@@ -1,122 +0,0 @@
|
||||
<script>
|
||||
import { getContext } from 'svelte';
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import RichTextInput from '../common/RichTextInput.svelte';
|
||||
import Spinner from '../common/Spinner.svelte';
|
||||
import Sparkles from '../icons/Sparkles.svelte';
|
||||
import SparklesSolid from '../icons/SparklesSolid.svelte';
|
||||
import Mic from '../icons/Mic.svelte';
|
||||
import VoiceRecording from '../chat/MessageInput/VoiceRecording.svelte';
|
||||
import Tooltip from '../common/Tooltip.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
let name = '';
|
||||
let content = '';
|
||||
|
||||
let voiceInput = false;
|
||||
let loading = false;
|
||||
</script>
|
||||
|
||||
<div class="relative flex-1 w-full h-full flex justify-center overflow-auto px-5">
|
||||
{#if loading}
|
||||
<div class=" absolute top-0 bottom-0 left-0 right-0 flex">
|
||||
<div class="m-auto">
|
||||
<Spinner />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class=" w-full flex flex-col gap-2 {loading ? 'opacity-20' : ''}">
|
||||
<div class="shrink-0 w-full flex justify-between items-center">
|
||||
<div class="w-full">
|
||||
<input
|
||||
class="w-full text-2xl font-medium bg-transparent outline-hidden"
|
||||
type="text"
|
||||
bind:value={name}
|
||||
placeholder={$i18n.t('Title')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" flex-1 w-full h-full">
|
||||
<RichTextInput
|
||||
className="input-prose-sm"
|
||||
bind:value={content}
|
||||
placeholder={$i18n.t('Write something...')}
|
||||
preserveBreaks={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute bottom-0 left-0 right-0 p-5 max-w-full flex justify-end">
|
||||
<div class="flex gap-0.5 justify-end w-full">
|
||||
{#if voiceInput}
|
||||
<div class="flex-1 w-full">
|
||||
<VoiceRecording
|
||||
bind:recording={voiceInput}
|
||||
className="p-1 w-full max-w-full"
|
||||
on:cancel={() => {
|
||||
voiceInput = false;
|
||||
}}
|
||||
on:confirm={(e) => {
|
||||
const { text, filename } = e.detail;
|
||||
|
||||
// url is hostname + /cache/audio/transcription/ + filename
|
||||
const url = `${window.location.origin}/cache/audio/transcription/${filename}`;
|
||||
|
||||
// Open in new tab
|
||||
|
||||
if (content.trim() !== '') {
|
||||
content = `${content}\n\n${text}\n\nRecording: ${url}\n\n`;
|
||||
} else {
|
||||
content = `${content}${text}\n\nRecording: ${url}\n\n`;
|
||||
}
|
||||
|
||||
voiceInput = false;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<Tooltip content={$i18n.t('Voice Input')}>
|
||||
<button
|
||||
class="cursor-pointer p-2.5 flex rounded-full hover:bg-gray-100 dark:hover:bg-gray-850 transition shadow-xl"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
try {
|
||||
let stream = await navigator.mediaDevices
|
||||
.getUserMedia({ audio: true })
|
||||
.catch(function (err) {
|
||||
toast.error(
|
||||
$i18n.t(`Permission denied when accessing microphone: {{error}}`, {
|
||||
error: err
|
||||
})
|
||||
);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (stream) {
|
||||
voiceInput = true;
|
||||
const tracks = stream.getTracks();
|
||||
tracks.forEach((track) => track.stop());
|
||||
}
|
||||
stream = null;
|
||||
} catch {
|
||||
toast.error($i18n.t('Permission denied when accessing microphone'));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Mic className="size-4" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
<!-- <button
|
||||
class="cursor-pointer p-2.5 flex rounded-full hover:bg-gray-100 dark:hover:bg-gray-850 transition shadow-xl"
|
||||
>
|
||||
<SparklesSolid className="size-4" />
|
||||
</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
<svelte:head>
|
||||
<title>
|
||||
{$i18n.t('Knowledge')} | {$WEBUI_NAME}
|
||||
{$i18n.t('Knowledge')} • {$WEBUI_NAME}
|
||||
</title>
|
||||
</svelte:head>
|
||||
|
||||
|
||||
@@ -11,7 +11,12 @@
|
||||
import { page } from '$app/stores';
|
||||
import { mobile, showSidebar, knowledge as _knowledge, config, user } from '$lib/stores';
|
||||
|
||||
import { updateFileDataContentById, uploadFile, deleteFileById } from '$lib/apis/files';
|
||||
import {
|
||||
updateFileDataContentById,
|
||||
uploadFile,
|
||||
deleteFileById,
|
||||
getFileById
|
||||
} from '$lib/apis/files';
|
||||
import {
|
||||
addFileToKnowledgeById,
|
||||
getKnowledgeById,
|
||||
@@ -84,12 +89,15 @@
|
||||
|
||||
let selectedFile = null;
|
||||
let selectedFileId = null;
|
||||
let selectedFileContent = '';
|
||||
|
||||
// Add cache object
|
||||
let fileContentCache = new Map();
|
||||
|
||||
$: if (selectedFileId) {
|
||||
const file = (knowledge?.files ?? []).find((file) => file.id === selectedFileId);
|
||||
if (file) {
|
||||
file.data = file.data ?? { content: '' };
|
||||
selectedFile = file;
|
||||
fileSelectHandler(file);
|
||||
} else {
|
||||
selectedFile = null;
|
||||
}
|
||||
@@ -394,7 +402,10 @@
|
||||
|
||||
const updateFileContentHandler = async () => {
|
||||
const fileId = selectedFile.id;
|
||||
const content = selectedFile.data.content;
|
||||
const content = selectedFileContent;
|
||||
|
||||
// Clear the cache for this file since we're updating it
|
||||
fileContentCache.delete(fileId);
|
||||
|
||||
const res = updateFileDataContentById(localStorage.token, fileId, content).catch((e) => {
|
||||
toast.error(`${e}`);
|
||||
@@ -450,6 +461,29 @@
|
||||
}
|
||||
};
|
||||
|
||||
const fileSelectHandler = async (file) => {
|
||||
try {
|
||||
selectedFile = file;
|
||||
|
||||
// Check cache first
|
||||
if (fileContentCache.has(file.id)) {
|
||||
selectedFileContent = fileContentCache.get(file.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await getFileById(localStorage.token, file.id);
|
||||
if (response) {
|
||||
selectedFileContent = response.data.content;
|
||||
// Cache the content
|
||||
fileContentCache.set(file.id, response.data.content);
|
||||
} else {
|
||||
toast.error($i18n.t('No content found in file.'));
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error($i18n.t('Failed to load file content.'));
|
||||
}
|
||||
};
|
||||
|
||||
const onDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -728,9 +762,9 @@
|
||||
{#key selectedFile.id}
|
||||
<RichTextInput
|
||||
className="input-prose-sm"
|
||||
bind:value={selectedFile.data.content}
|
||||
bind:value={selectedFileContent}
|
||||
placeholder={$i18n.t('Add content here')}
|
||||
preserveBreaks={true}
|
||||
preserveBreaks={false}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
@@ -747,7 +781,7 @@
|
||||
<Drawer
|
||||
className="h-full"
|
||||
show={selectedFileId !== null}
|
||||
on:close={() => {
|
||||
onClose={() => {
|
||||
selectedFileId = null;
|
||||
}}
|
||||
>
|
||||
@@ -786,9 +820,9 @@
|
||||
{#key selectedFile.id}
|
||||
<RichTextInput
|
||||
className="input-prose-sm"
|
||||
bind:value={selectedFile.data.content}
|
||||
bind:value={selectedFileContent}
|
||||
placeholder={$i18n.t('Add content here')}
|
||||
preserveBreaks={true}
|
||||
preserveBreaks={false}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Modal from '$lib/components/common/Modal.svelte';
|
||||
import RichTextInput from '$lib/components/common/RichTextInput.svelte';
|
||||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
import Mic from '$lib/components/icons/Mic.svelte';
|
||||
import MicSolid from '$lib/components/icons/MicSolid.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import VoiceRecording from '$lib/components/chat/MessageInput/VoiceRecording.svelte';
|
||||
export let show = false;
|
||||
@@ -85,11 +85,11 @@
|
||||
<VoiceRecording
|
||||
bind:recording={voiceInput}
|
||||
className="p-1"
|
||||
on:cancel={() => {
|
||||
onCancel={() => {
|
||||
voiceInput = false;
|
||||
}}
|
||||
on:confirm={(e) => {
|
||||
const { text, filename } = e.detail;
|
||||
onConfirm={(data) => {
|
||||
const { text, filename } = data;
|
||||
content = `${content}${text} `;
|
||||
|
||||
voiceInput = false;
|
||||
@@ -125,7 +125,7 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Mic className="size-5" />
|
||||
<MicSolid className="size-5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
@@ -196,7 +196,7 @@
|
||||
|
||||
<svelte:head>
|
||||
<title>
|
||||
{$i18n.t('Models')} | {$WEBUI_NAME}
|
||||
{$i18n.t('Models')} • {$WEBUI_NAME}
|
||||
</title>
|
||||
</svelte:head>
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
let system = '';
|
||||
let info = {
|
||||
id: '',
|
||||
base_model_id: null,
|
||||
@@ -155,6 +156,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
info.params.system = system.trim() === '' ? null : system;
|
||||
info.params.stop = params.stop ? params.stop.split(',').filter((s) => s.trim()) : null;
|
||||
Object.keys(info.params).forEach((key) => {
|
||||
if (info.params[key] === '' || info.params[key] === null) {
|
||||
@@ -201,6 +203,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
system = model?.params?.system ?? '';
|
||||
|
||||
params = { ...params, ...model?.params };
|
||||
params.stop = params?.stop
|
||||
? (typeof params.stop === 'string' ? params.stop.split(',') : (params?.stop ?? [])).join(
|
||||
@@ -553,7 +557,7 @@
|
||||
className=" text-sm w-full bg-transparent outline-hidden resize-none overflow-y-hidden "
|
||||
placeholder={`Write your model system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.`}
|
||||
rows={4}
|
||||
bind:value={info.params.system}
|
||||
bind:value={system}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
|
||||
<svelte:head>
|
||||
<title>
|
||||
{$i18n.t('Prompts')} | {$WEBUI_NAME}
|
||||
{$i18n.t('Prompts')} • {$WEBUI_NAME}
|
||||
</title>
|
||||
</svelte:head>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import LockClosed from '$lib/components/icons/LockClosed.svelte';
|
||||
import AccessControlModal from '../common/AccessControlModal.svelte';
|
||||
import { user } from '$lib/stores';
|
||||
import { slugify } from '$lib/utils';
|
||||
|
||||
export let onSubmit: Function;
|
||||
export let edit = false;
|
||||
@@ -25,8 +26,15 @@
|
||||
|
||||
let showAccessControlModal = false;
|
||||
|
||||
$: if (!edit) {
|
||||
command = title !== '' ? `${title.replace(/\s+/g, '-').toLowerCase()}` : '';
|
||||
let hasManualEdit = false;
|
||||
|
||||
$: if (!edit && !hasManualEdit) {
|
||||
command = title !== '' ? slugify(title) : '';
|
||||
}
|
||||
|
||||
// Track manual edits
|
||||
function handleCommandInput(e: Event) {
|
||||
hasManualEdit = true;
|
||||
}
|
||||
|
||||
const submitHandler = async () => {
|
||||
@@ -64,7 +72,7 @@
|
||||
command = prompt.command.at(0) === '/' ? prompt.command.slice(1) : prompt.command;
|
||||
content = prompt.content;
|
||||
|
||||
accessControl = prompt?.access_control ?? null;
|
||||
accessControl = prompt?.access_control ?? {};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -125,6 +133,7 @@
|
||||
class=" w-full bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Command')}
|
||||
bind:value={command}
|
||||
on:input={handleCommandInput}
|
||||
required
|
||||
disabled={edit}
|
||||
/>
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
|
||||
<svelte:head>
|
||||
<title>
|
||||
{$i18n.t('Tools')} | {$WEBUI_NAME}
|
||||
{$i18n.t('Tools')} • {$WEBUI_NAME}
|
||||
</title>
|
||||
</svelte:head>
|
||||
|
||||
@@ -503,7 +503,7 @@
|
||||
|
||||
<ul class=" mt-1 list-disc pl-4 text-xs">
|
||||
<li>
|
||||
{$i18n.t('Tools have a function calling system that allows arbitrary code execution')}.
|
||||
{$i18n.t('Tools have a function calling system that allows arbitrary code execution.')}.
|
||||
</li>
|
||||
<li>{$i18n.t('Do not install tools from sources you do not fully trust.')}</li>
|
||||
</ul>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
|
||||
"(e.g. `sh webui.sh --api`)": "( `sh webui.sh --api`مثال)",
|
||||
"(latest)": "(الأخير)",
|
||||
"(leave blank for to use commercial endpoint)": "",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "{{ نماذج }}",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
@@ -62,6 +63,8 @@
|
||||
"Allow Chat Delete": "",
|
||||
"Allow Chat Deletion": "يستطيع حذف المحادثات",
|
||||
"Allow Chat Edit": "",
|
||||
"Allow Chat Export": "",
|
||||
"Allow Chat Share": "",
|
||||
"Allow File Upload": "",
|
||||
"Allow Multiple Models in Chat": "",
|
||||
"Allow non-local voices": "",
|
||||
@@ -76,6 +79,7 @@
|
||||
"Always": "",
|
||||
"Always Collapse Code Blocks": "",
|
||||
"Always Expand Details": "",
|
||||
"Always Play Notification Sound": "",
|
||||
"Amazing": "",
|
||||
"an assistant": "مساعد",
|
||||
"Analyzed": "",
|
||||
@@ -101,6 +105,7 @@
|
||||
"Are you sure you want to delete this channel?": "",
|
||||
"Are you sure you want to delete this message?": "",
|
||||
"Are you sure you want to unarchive all archived chats?": "",
|
||||
"Are you sure you want to update this user's role to **{{ROLE}}**?": "",
|
||||
"Are you sure?": "هل أنت متأكد ؟",
|
||||
"Arena Models": "",
|
||||
"Artifacts": "",
|
||||
@@ -143,6 +148,7 @@
|
||||
"Bing Search V7 Subscription Key": "",
|
||||
"Bocha Search API Key": "",
|
||||
"Boosting or penalizing specific tokens for constrained responses. Bias values will be clamped between -100 and 100 (inclusive). (Default: none)": "",
|
||||
"Both Docling OCR Engine and Language(s) must be provided or both left empty.": "",
|
||||
"Brave Search API Key": "مفتاح واجهة برمجة تطبيقات البحث الشجاع",
|
||||
"By {{name}}": "",
|
||||
"Bypass Embedding and Retrieval": "",
|
||||
@@ -153,6 +159,7 @@
|
||||
"Cancel": "اللغاء",
|
||||
"Capabilities": "قدرات",
|
||||
"Capture": "",
|
||||
"Capture Audio": "",
|
||||
"Certificate Path": "",
|
||||
"Change Password": "تغير الباسورد",
|
||||
"Channel Name": "",
|
||||
@@ -262,6 +269,8 @@
|
||||
"Create Knowledge": "",
|
||||
"Create new key": "عمل مفتاح جديد",
|
||||
"Create new secret key": "عمل سر جديد",
|
||||
"Create Note": "",
|
||||
"Create your first note by clicking on the plus button below.": "",
|
||||
"Created at": "أنشئت في",
|
||||
"Created At": "أنشئت من",
|
||||
"Created by": "",
|
||||
@@ -299,6 +308,7 @@
|
||||
"Delete function?": "",
|
||||
"Delete Message": "",
|
||||
"Delete message?": "",
|
||||
"Delete note?": "",
|
||||
"Delete prompt?": "",
|
||||
"delete this link": "أحذف هذا الرابط",
|
||||
"Delete tool?": "",
|
||||
@@ -354,7 +364,7 @@
|
||||
"Download Database": "تحميل قاعدة البيانات",
|
||||
"Drag and drop a file to upload or select a file to view": "",
|
||||
"Draw": "",
|
||||
"Drop any files here to add to the conversation": "أسقط أية ملفات هنا لإضافتها إلى المحادثة",
|
||||
"Drop any files here to upload": "",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "e.g. '30s','10m'. الوحدات الزمنية الصالحة هي 's', 'm', 'h'.",
|
||||
"e.g. \"json\" or a JSON schema": "",
|
||||
"e.g. 60": "",
|
||||
@@ -364,7 +374,9 @@
|
||||
"e.g. my_filter": "",
|
||||
"e.g. my_tools": "",
|
||||
"e.g. Tools for performing various operations": "",
|
||||
"e.g., 3, 4, 5 (leave blank for default)": "",
|
||||
"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
|
||||
"e.g., westus (leave blank for eastus)": "",
|
||||
"Edit": "تعديل",
|
||||
"Edit Arena Model": "",
|
||||
"Edit Channel": "",
|
||||
@@ -392,7 +404,9 @@
|
||||
"Enable Mirostat sampling for controlling perplexity.": "",
|
||||
"Enable New Sign Ups": "تفعيل عمليات التسجيل الجديدة",
|
||||
"Enabled": "",
|
||||
"Endpoint URL": "",
|
||||
"Enforce Temporary Chat": "",
|
||||
"Enhance": "",
|
||||
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "تأكد من أن ملف CSV الخاص بك يتضمن 4 أعمدة بهذا الترتيب: Name, Email, Password, Role.",
|
||||
"Enter {{role}} message here": "أدخل رسالة {{role}} هنا",
|
||||
"Enter a detail about yourself for your LLMs to recall": "ادخل معلومات عنك تريد أن يتذكرها الموديل",
|
||||
@@ -407,13 +421,19 @@
|
||||
"Enter CFG Scale (e.g. 7.0)": "",
|
||||
"Enter Chunk Overlap": "أدخل الChunk Overlap",
|
||||
"Enter Chunk Size": "أدخل Chunk الحجم",
|
||||
"Enter comma-seperated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "",
|
||||
"Enter comma-separated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "",
|
||||
"Enter description": "",
|
||||
"Enter Docling OCR Engine": "",
|
||||
"Enter Docling OCR Language(s)": "",
|
||||
"Enter Docling Server URL": "",
|
||||
"Enter Document Intelligence Endpoint": "",
|
||||
"Enter Document Intelligence Key": "",
|
||||
"Enter domains separated by commas (e.g., example.com,site.org)": "",
|
||||
"Enter Exa API Key": "",
|
||||
"Enter External Web Loader API Key": "",
|
||||
"Enter External Web Loader URL": "",
|
||||
"Enter External Web Search API Key": "",
|
||||
"Enter External Web Search URL": "",
|
||||
"Enter Firecrawl API Base URL": "",
|
||||
"Enter Firecrawl API Key": "",
|
||||
"Enter Github Raw URL": "أدخل عنوان URL ل Github Raw",
|
||||
@@ -431,6 +451,7 @@
|
||||
"Enter Model ID": "",
|
||||
"Enter model tag (e.g. {{modelTag}})": "(e.g. {{modelTag}}) أدخل الموديل تاق",
|
||||
"Enter Mojeek Search API Key": "",
|
||||
"Enter New Password": "",
|
||||
"Enter Number of Steps (e.g. 50)": "(e.g. 50) أدخل عدد الخطوات",
|
||||
"Enter Perplexity API Key": "",
|
||||
"Enter Playwright Timeout": "",
|
||||
@@ -467,11 +488,15 @@
|
||||
"Enter Top K Reranker": "",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "الرابط (e.g. http://127.0.0.1:7860/)",
|
||||
"Enter URL (e.g. http://localhost:11434)": "URL (e.g. http://localhost:11434)",
|
||||
"Enter Yacy Password": "",
|
||||
"Enter Yacy URL (e.g. http://yacy.example.com:8090)": "",
|
||||
"Enter Yacy Username": "",
|
||||
"Enter your current password": "",
|
||||
"Enter Your Email": "أدخل البريد الاكتروني",
|
||||
"Enter Your Full Name": "أدخل الاسم كامل",
|
||||
"Enter your message": "",
|
||||
"Enter your name": "",
|
||||
"Enter Your Name": "",
|
||||
"Enter your new password": "",
|
||||
"Enter Your Password": "ادخل كلمة المرور",
|
||||
"Enter Your Role": "أدخل الصلاحيات",
|
||||
@@ -480,6 +505,8 @@
|
||||
"Error": "خطأ",
|
||||
"ERROR": "",
|
||||
"Error accessing Google Drive: {{error}}": "",
|
||||
"Error accessing media devices.": "",
|
||||
"Error starting recording.": "",
|
||||
"Error uploading file: {{error}}": "",
|
||||
"Evaluations": "",
|
||||
"Exa API Key": "",
|
||||
@@ -511,10 +538,16 @@
|
||||
"Export Tools": "",
|
||||
"External": "",
|
||||
"External Models": "",
|
||||
"External Web Loader API Key": "",
|
||||
"External Web Loader URL": "",
|
||||
"External Web Search API Key": "",
|
||||
"External Web Search URL": "",
|
||||
"Failed to add file.": "",
|
||||
"Failed to connect to {{URL}} OpenAPI tool server": "",
|
||||
"Failed to create API Key.": "فشل في إنشاء مفتاح API.",
|
||||
"Failed to delete note": "",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to load file content.": "",
|
||||
"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "",
|
||||
@@ -544,7 +577,6 @@
|
||||
"Fluidly stream large external response chunks": "دفق قطع الاستجابة الخارجية الكبيرة بسلاسة",
|
||||
"Focus chat input": "التركيز على إدخال الدردشة",
|
||||
"Folder deleted successfully": "",
|
||||
"Folder name cannot be empty": "",
|
||||
"Folder name cannot be empty.": "",
|
||||
"Folder name updated successfully": "",
|
||||
"Followed instructions perfectly": "اتبعت التعليمات على أكمل وجه",
|
||||
@@ -565,17 +597,18 @@
|
||||
"Function Name": "",
|
||||
"Function updated successfully": "",
|
||||
"Functions": "",
|
||||
"Functions allow arbitrary code execution": "",
|
||||
"Functions allow arbitrary code execution.": "",
|
||||
"Functions imported successfully": "",
|
||||
"Gemini": "",
|
||||
"Gemini API Config": "",
|
||||
"Gemini API Key is required.": "",
|
||||
"General": "عام",
|
||||
"Generate": "",
|
||||
"Generate an image": "",
|
||||
"Generate Image": "",
|
||||
"Generate prompt pair": "",
|
||||
"Generating search query": "إنشاء استعلام بحث",
|
||||
"Generating...": "",
|
||||
"Get started": "",
|
||||
"Get started with {{WEBUI_NAME}}": "",
|
||||
"Global": "",
|
||||
@@ -622,6 +655,7 @@
|
||||
"Import Config from JSON File": "",
|
||||
"Import Functions": "",
|
||||
"Import Models": "استيراد النماذج",
|
||||
"Import Notes": "",
|
||||
"Import Presets": "",
|
||||
"Import Prompts": "مطالبات الاستيراد",
|
||||
"Import Tools": "",
|
||||
@@ -636,6 +670,7 @@
|
||||
"Instant Auto-Send After Voice Transcription": "",
|
||||
"Integration": "",
|
||||
"Interface": "واجهه المستخدم",
|
||||
"Invalid file content": "",
|
||||
"Invalid file format.": "",
|
||||
"Invalid JSON schema": "",
|
||||
"Invalid Tag": "تاق غير صالحة",
|
||||
@@ -706,6 +741,7 @@
|
||||
"Manage Pipelines": "إدارة خطوط الأنابيب",
|
||||
"Manage Tool Servers": "",
|
||||
"March": "مارس",
|
||||
"Max Speakers": "",
|
||||
"Max Tokens (num_predict)": "ماكس توكنز (num_predict)",
|
||||
"Max Upload Count": "",
|
||||
"Max Upload Size": "",
|
||||
@@ -718,8 +754,12 @@
|
||||
"Memory deleted successfully": "",
|
||||
"Memory updated successfully": "",
|
||||
"Merge Responses": "",
|
||||
"Merged Response": "نتيجة الردود المدمجة",
|
||||
"Message rating should be enabled to use this feature": "",
|
||||
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "لن تتم مشاركة الرسائل التي ترسلها بعد إنشاء الرابط الخاص بك. سيتمكن المستخدمون الذين لديهم عنوان URL من عرض الدردشة المشتركة",
|
||||
"Microsoft OneDrive": "",
|
||||
"Microsoft OneDrive (personal)": "",
|
||||
"Microsoft OneDrive (work/school)": "",
|
||||
"Min P": "",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
@@ -753,19 +793,22 @@
|
||||
"Mojeek Search API Key": "",
|
||||
"more": "",
|
||||
"More": "المزيد",
|
||||
"My Notes": "",
|
||||
"Name": "الأسم",
|
||||
"Name your knowledge base": "",
|
||||
"Native": "",
|
||||
"New Chat": "دردشة جديدة",
|
||||
"New Folder": "",
|
||||
"New Note": "",
|
||||
"New Password": "كلمة المرور الجديدة",
|
||||
"new-channel": "",
|
||||
"No content": "",
|
||||
"No content found": "",
|
||||
"No content found in file.": "",
|
||||
"No content to speak": "",
|
||||
"No distance available": "",
|
||||
"No feedbacks found": "",
|
||||
"No file selected": "",
|
||||
"No files found.": "",
|
||||
"No groups with access, add a group to grant access": "",
|
||||
"No HTML, CSS, or JavaScript content found.": "",
|
||||
"No inference engine with management support found": "",
|
||||
@@ -774,6 +817,7 @@
|
||||
"No model IDs": "",
|
||||
"No models found": "",
|
||||
"No models selected": "",
|
||||
"No Notes": "",
|
||||
"No results found": "لا توجد نتايج",
|
||||
"No search query generated": "لم يتم إنشاء استعلام بحث",
|
||||
"No source available": "لا يوجد مصدر متاح",
|
||||
@@ -782,6 +826,7 @@
|
||||
"None": "اي",
|
||||
"Not factually correct": "ليس صحيحا من حيث الواقع",
|
||||
"Not helpful": "",
|
||||
"Note deleted successfully": "",
|
||||
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "ملاحظة: إذا قمت بتعيين الحد الأدنى من النقاط، فلن يؤدي البحث إلا إلى إرجاع المستندات التي لها نقاط أكبر من أو تساوي الحد الأدنى من النقاط.",
|
||||
"Notes": "",
|
||||
"Notification Sound": "",
|
||||
@@ -804,6 +849,7 @@
|
||||
"Only alphanumeric characters and hyphens are allowed": "",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "يُسمح فقط بالأحرف الأبجدية الرقمية والواصلات في سلسلة الأمر.",
|
||||
"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
|
||||
"Only markdown files are allowed": "",
|
||||
"Only select users and groups with permission can access": "",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "خطاء! يبدو أن عنوان URL غير صالح. يرجى التحقق مرة أخرى والمحاولة مرة أخرى.",
|
||||
"Oops! There are files still uploading. Please wait for the upload to complete.": "",
|
||||
@@ -849,6 +895,7 @@
|
||||
"Pipelines": "خطوط الانابيب",
|
||||
"Pipelines Not Detected": "",
|
||||
"Pipelines Valves": "صمامات خطوط الأنابيب",
|
||||
"Plain text (.md)": "",
|
||||
"Plain text (.txt)": "نص عادي (.txt)",
|
||||
"Playground": "مكان التجربة",
|
||||
"Playwright Timeout (ms)": "",
|
||||
@@ -891,6 +938,7 @@
|
||||
"Read": "",
|
||||
"Read Aloud": "أقراء لي",
|
||||
"Reasoning Effort": "",
|
||||
"Record": "",
|
||||
"Record voice": "سجل صوت",
|
||||
"Redirecting you to Open WebUI Community": "OpenWebUI إعادة توجيهك إلى مجتمع ",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative.": "",
|
||||
@@ -911,9 +959,8 @@
|
||||
"Repeat Penalty (Ollama)": "",
|
||||
"Reply in Thread": "",
|
||||
"Request Mode": "وضع الطلب",
|
||||
"Reranking Engine": "",
|
||||
"Reranking Model": "إعادة تقييم النموذج",
|
||||
"Reranking model disabled": "تم تعطيل نموذج إعادة الترتيب",
|
||||
"Reranking model set to \"{{reranking_model}}\"": "تم ضبط نموذج إعادة الترتيب على \"{{reranking_model}}\"",
|
||||
"Reset": "",
|
||||
"Reset All Models": "",
|
||||
"Reset Upload Directory": "",
|
||||
@@ -939,7 +986,7 @@
|
||||
"Save Tag": "",
|
||||
"Saved": "",
|
||||
"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": "لم يعد حفظ سجلات الدردشة مباشرة في مساحة تخزين متصفحك مدعومًا. يرجى تخصيص بعض الوقت لتنزيل وحذف سجلات الدردشة الخاصة بك عن طريق النقر على الزر أدناه. لا تقلق، يمكنك بسهولة إعادة استيراد سجلات الدردشة الخاصة بك إلى الواجهة الخلفية من خلاله",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Scroll On Branch Change": "",
|
||||
"Search": "البحث",
|
||||
"Search a model": "البحث عن موديل",
|
||||
"Search Base": "",
|
||||
@@ -960,6 +1007,7 @@
|
||||
"Searched {{count}} sites": "",
|
||||
"Searching \"{{searchQuery}}\"": "",
|
||||
"Searching Knowledge for \"{{searchQuery}}\"": "",
|
||||
"Searching the web...": "",
|
||||
"Searxng Query URL": "عنوان URL لاستعلام Searxng",
|
||||
"See readme.md for instructions": "readme.md للحصول على التعليمات",
|
||||
"See what's new": "ما الجديد",
|
||||
@@ -1020,6 +1068,8 @@
|
||||
"Show": "عرض",
|
||||
"Show \"What's New\" modal on login": "",
|
||||
"Show Admin Details in Account Pending Overlay": "",
|
||||
"Show All": "",
|
||||
"Show Less": "",
|
||||
"Show Model": "",
|
||||
"Show shortcuts": "إظهار الاختصارات",
|
||||
"Show your support!": "",
|
||||
@@ -1043,6 +1093,7 @@
|
||||
"Stream Chat Response": "",
|
||||
"STT Model": "",
|
||||
"STT Settings": "STT اعدادات",
|
||||
"Stylized PDF Export": "",
|
||||
"Subtitle (e.g. about the Roman Empire)": "(e.g. about the Roman Empire) الترجمة",
|
||||
"Success": "نجاح",
|
||||
"Successfully updated.": "تم التحديث بنجاح",
|
||||
@@ -1089,6 +1140,7 @@
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "وهذا يضمن حفظ محادثاتك القيمة بشكل آمن في قاعدة بياناتك الخلفية. شكرًا لك!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
|
||||
"This model is not publicly available. Please select another model.": "",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
"This option sets the maximum number of tokens the model can generate in its response. Increasing this limit allows the model to provide longer answers, but it may also increase the likelihood of unhelpful or irrelevant content being generated.": "",
|
||||
"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
|
||||
@@ -1140,7 +1192,6 @@
|
||||
"Tools Access": "",
|
||||
"Tools are a function calling system with arbitrary code execution": "",
|
||||
"Tools Function Calling Prompt": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "",
|
||||
"Tools Public Sharing": "",
|
||||
"Top K": "Top K",
|
||||
@@ -1163,6 +1214,7 @@
|
||||
"Unpin": "",
|
||||
"Unravel secrets": "",
|
||||
"Untagged": "",
|
||||
"Untitled": "",
|
||||
"Update": "",
|
||||
"Update and Copy Link": "تحديث ونسخ الرابط",
|
||||
"Update for the latest features and improvements.": "",
|
||||
@@ -1173,6 +1225,7 @@
|
||||
"Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.": "",
|
||||
"Upload": "",
|
||||
"Upload a GGUF model": "GGUF رفع موديل نوع",
|
||||
"Upload Audio": "",
|
||||
"Upload directory": "",
|
||||
"Upload files": "",
|
||||
"Upload Files": "تحميل الملفات",
|
||||
@@ -1246,13 +1299,15 @@
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "اكتب ملخصًا في 50 كلمة يلخص [الموضوع أو الكلمة الرئيسية]",
|
||||
"Write something...": "",
|
||||
"Write your model template content here": "",
|
||||
"Yacy Instance URL": "",
|
||||
"Yacy Password": "",
|
||||
"Yacy Username": "",
|
||||
"Yesterday": "أمس",
|
||||
"You": "انت",
|
||||
"You are currently using a trial license. Please contact support to upgrade your license.": "",
|
||||
"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
|
||||
"You cannot upload an empty file.": "",
|
||||
"You do not have permission to upload files": "",
|
||||
"You do not have permission to upload files.": "",
|
||||
"You have no archived conversations.": "لا تملك محادثات محفوظه",
|
||||
"You have shared this chat": "تم مشاركة هذه المحادثة",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"(e.g. `sh webui.sh --api --api-auth username_password`)": "(مثال: `sh webui.sh --api --api-auth اسم_المستخدم_كلمة_المرور`)",
|
||||
"(e.g. `sh webui.sh --api`)": "(مثال: تشغيل الأمر: `sh webui.sh --api`)",
|
||||
"(latest)": "(أحدث)",
|
||||
"(leave blank for to use commercial endpoint)": "",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "النماذج: {{ models }}",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
@@ -62,6 +63,8 @@
|
||||
"Allow Chat Delete": "السماح بحذف المحادثة",
|
||||
"Allow Chat Deletion": "السماح بحذف المحادثة",
|
||||
"Allow Chat Edit": "السماح بتعديل المحادثة",
|
||||
"Allow Chat Export": "",
|
||||
"Allow Chat Share": "",
|
||||
"Allow File Upload": "السماح بتحميل الملفات",
|
||||
"Allow Multiple Models in Chat": "",
|
||||
"Allow non-local voices": "السماح بالأصوات غير المحلية",
|
||||
@@ -76,6 +79,7 @@
|
||||
"Always": "دائمًا",
|
||||
"Always Collapse Code Blocks": "",
|
||||
"Always Expand Details": "",
|
||||
"Always Play Notification Sound": "",
|
||||
"Amazing": "رائع",
|
||||
"an assistant": "مساعد",
|
||||
"Analyzed": "تم التحليل",
|
||||
@@ -101,6 +105,7 @@
|
||||
"Are you sure you want to delete this channel?": "هل أنت متأكد من رغبتك في حذف هذه القناة؟",
|
||||
"Are you sure you want to delete this message?": "هل أنت متأكد من رغبتك في حذف هذه الرسالة؟",
|
||||
"Are you sure you want to unarchive all archived chats?": "هل أنت متأكد من رغبتك في إلغاء أرشفة جميع المحادثات المؤرشفة؟",
|
||||
"Are you sure you want to update this user's role to **{{ROLE}}**?": "",
|
||||
"Are you sure?": "هل أنت متأكد؟",
|
||||
"Arena Models": "نماذج الساحة",
|
||||
"Artifacts": "القطع الأثرية",
|
||||
@@ -143,6 +148,7 @@
|
||||
"Bing Search V7 Subscription Key": "مفتاح اشتراك Bing Search V7",
|
||||
"Bocha Search API Key": "مفتاح API لـ Bocha Search",
|
||||
"Boosting or penalizing specific tokens for constrained responses. Bias values will be clamped between -100 and 100 (inclusive). (Default: none)": "تعزيز أو معاقبة رموز محددة لردود مقيدة. ستتراوح قيم التحيز بين -100 و100 (شاملة). (افتراضي: لا شيء)",
|
||||
"Both Docling OCR Engine and Language(s) must be provided or both left empty.": "",
|
||||
"Brave Search API Key": "مفتاح API لـ Brave Search",
|
||||
"By {{name}}": "بواسطة {{name}}",
|
||||
"Bypass Embedding and Retrieval": "تجاوز التضمين والاسترجاع",
|
||||
@@ -153,6 +159,7 @@
|
||||
"Cancel": "إلغاء",
|
||||
"Capabilities": "القدرات",
|
||||
"Capture": "التقاط",
|
||||
"Capture Audio": "",
|
||||
"Certificate Path": "مسار الشهادة",
|
||||
"Change Password": "تغيير كلمة المرور",
|
||||
"Channel Name": "اسم القناة",
|
||||
@@ -262,6 +269,8 @@
|
||||
"Create Knowledge": "إنشاء معرفة",
|
||||
"Create new key": "إنشاء مفتاح جديد",
|
||||
"Create new secret key": "إنشاء مفتاح سري جديد",
|
||||
"Create Note": "",
|
||||
"Create your first note by clicking on the plus button below.": "",
|
||||
"Created at": "تم الإنشاء في",
|
||||
"Created At": "تاريخ الإنشاء",
|
||||
"Created by": "تم الإنشاء بواسطة",
|
||||
@@ -299,6 +308,7 @@
|
||||
"Delete function?": "هل تريد حذف الوظيفة؟",
|
||||
"Delete Message": "حذف الرسالة",
|
||||
"Delete message?": "هل تريد حذف الرسالة؟",
|
||||
"Delete note?": "",
|
||||
"Delete prompt?": "هل تريد حذف الموجه؟",
|
||||
"delete this link": "أحذف هذا الرابط",
|
||||
"Delete tool?": "هل تريد حذف الأداة؟",
|
||||
@@ -354,7 +364,7 @@
|
||||
"Download Database": "تحميل قاعدة البيانات",
|
||||
"Drag and drop a file to upload or select a file to view": "اسحب الملف وأفلته للرفع أو اختر ملفًا للعرض",
|
||||
"Draw": "ارسم",
|
||||
"Drop any files here to add to the conversation": "أسقط أية ملفات هنا لإضافتها إلى المحادثة",
|
||||
"Drop any files here to upload": "",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "e.g. '30s','10m'. الوحدات الزمنية الصالحة هي 's', 'm', 'h'.",
|
||||
"e.g. \"json\" or a JSON schema": "",
|
||||
"e.g. 60": "مثال: 60",
|
||||
@@ -364,7 +374,9 @@
|
||||
"e.g. my_filter": "مثال: my_filter",
|
||||
"e.g. my_tools": "مثال: my_tools",
|
||||
"e.g. Tools for performing various operations": "مثال: أدوات لتنفيذ عمليات متنوعة",
|
||||
"e.g., 3, 4, 5 (leave blank for default)": "",
|
||||
"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
|
||||
"e.g., westus (leave blank for eastus)": "",
|
||||
"Edit": "تعديل",
|
||||
"Edit Arena Model": "تعديل نموذج Arena",
|
||||
"Edit Channel": "تعديل القناة",
|
||||
@@ -392,7 +404,9 @@
|
||||
"Enable Mirostat sampling for controlling perplexity.": "تفعيل أخذ عينات Mirostat للتحكم في درجة التعقيد.",
|
||||
"Enable New Sign Ups": "تفعيل عمليات التسجيل الجديدة",
|
||||
"Enabled": "مفعل",
|
||||
"Endpoint URL": "",
|
||||
"Enforce Temporary Chat": "",
|
||||
"Enhance": "",
|
||||
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "تأكد من أن ملف CSV الخاص بك يتضمن 4 أعمدة بهذا الترتيب: Name, Email, Password, Role.",
|
||||
"Enter {{role}} message here": "أدخل رسالة {{role}} هنا",
|
||||
"Enter a detail about yourself for your LLMs to recall": "ادخل معلومات عنك تريد أن يتذكرها الموديل",
|
||||
@@ -407,13 +421,19 @@
|
||||
"Enter CFG Scale (e.g. 7.0)": "أدخل مقياس CFG (مثال: 7.0)",
|
||||
"Enter Chunk Overlap": "أدخل الChunk Overlap",
|
||||
"Enter Chunk Size": "أدخل Chunk الحجم",
|
||||
"Enter comma-seperated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "أدخل أزواج \"الرمز:قيمة التحيز\" مفصولة بفواصل (مثال: 5432:100، 413:-100)",
|
||||
"Enter comma-separated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "أدخل أزواج \"الرمز:قيمة التحيز\" مفصولة بفواصل (مثال: 5432:100، 413:-100)",
|
||||
"Enter description": "أدخل الوصف",
|
||||
"Enter Docling OCR Engine": "",
|
||||
"Enter Docling OCR Language(s)": "",
|
||||
"Enter Docling Server URL": "",
|
||||
"Enter Document Intelligence Endpoint": "أدخل نقطة نهاية تحليل المستندات",
|
||||
"Enter Document Intelligence Key": "أدخل مفتاح تحليل المستندات",
|
||||
"Enter domains separated by commas (e.g., example.com,site.org)": "أدخل النطاقات مفصولة بفواصل (مثال: example.com,site.org)",
|
||||
"Enter Exa API Key": "أدخل مفتاح API لـ Exa",
|
||||
"Enter External Web Loader API Key": "",
|
||||
"Enter External Web Loader URL": "",
|
||||
"Enter External Web Search API Key": "",
|
||||
"Enter External Web Search URL": "",
|
||||
"Enter Firecrawl API Base URL": "",
|
||||
"Enter Firecrawl API Key": "",
|
||||
"Enter Github Raw URL": "أدخل عنوان URL ل Github Raw",
|
||||
@@ -431,6 +451,7 @@
|
||||
"Enter Model ID": "أدخل معرف النموذج",
|
||||
"Enter model tag (e.g. {{modelTag}})": "(e.g. {{modelTag}}) أدخل الموديل تاق",
|
||||
"Enter Mojeek Search API Key": "أدخل مفتاح API لـ Mojeek Search",
|
||||
"Enter New Password": "",
|
||||
"Enter Number of Steps (e.g. 50)": "(e.g. 50) أدخل عدد الخطوات",
|
||||
"Enter Perplexity API Key": "أدخل مفتاح API لـ Perplexity",
|
||||
"Enter Playwright Timeout": "",
|
||||
@@ -467,11 +488,15 @@
|
||||
"Enter Top K Reranker": "",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "الرابط (e.g. http://127.0.0.1:7860/)",
|
||||
"Enter URL (e.g. http://localhost:11434)": "URL (e.g. http://localhost:11434)",
|
||||
"Enter Yacy Password": "",
|
||||
"Enter Yacy URL (e.g. http://yacy.example.com:8090)": "",
|
||||
"Enter Yacy Username": "",
|
||||
"Enter your current password": "أدخل كلمة المرور الحالية",
|
||||
"Enter Your Email": "أدخل البريد الاكتروني",
|
||||
"Enter Your Full Name": "أدخل الاسم كامل",
|
||||
"Enter your message": "أدخل رسالتك",
|
||||
"Enter your name": "",
|
||||
"Enter Your Name": "",
|
||||
"Enter your new password": "أدخل كلمة المرور الجديدة",
|
||||
"Enter Your Password": "ادخل كلمة المرور",
|
||||
"Enter Your Role": "أدخل الصلاحيات",
|
||||
@@ -480,6 +505,8 @@
|
||||
"Error": "خطأ",
|
||||
"ERROR": "خطأ",
|
||||
"Error accessing Google Drive: {{error}}": "حدث خطأ أثناء الوصول إلى Google Drive: {{error}}",
|
||||
"Error accessing media devices.": "",
|
||||
"Error starting recording.": "",
|
||||
"Error uploading file: {{error}}": "حدث خطأ أثناء تحميل الملف: {{error}}",
|
||||
"Evaluations": "التقييمات",
|
||||
"Exa API Key": "مفتاح API لـ Exa",
|
||||
@@ -511,10 +538,16 @@
|
||||
"Export Tools": "تصدير الأدوات",
|
||||
"External": "",
|
||||
"External Models": "نماذج خارجية",
|
||||
"External Web Loader API Key": "",
|
||||
"External Web Loader URL": "",
|
||||
"External Web Search API Key": "",
|
||||
"External Web Search URL": "",
|
||||
"Failed to add file.": "فشل في إضافة الملف.",
|
||||
"Failed to connect to {{URL}} OpenAPI tool server": "",
|
||||
"Failed to create API Key.": "فشل في إنشاء مفتاح API.",
|
||||
"Failed to delete note": "",
|
||||
"Failed to fetch models": "فشل في جلب النماذج",
|
||||
"Failed to load file content.": "",
|
||||
"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "فشل في حفظ إعدادات النماذج",
|
||||
@@ -544,7 +577,6 @@
|
||||
"Fluidly stream large external response chunks": "دفق قطع الاستجابة الخارجية الكبيرة بسلاسة",
|
||||
"Focus chat input": "التركيز على إدخال الدردشة",
|
||||
"Folder deleted successfully": "تم حذف المجلد بنجاح",
|
||||
"Folder name cannot be empty": "لا يمكن أن يكون اسم المجلد فارغًا",
|
||||
"Folder name cannot be empty.": "لا يمكن أن يكون اسم المجلد فارغًا.",
|
||||
"Folder name updated successfully": "تم تحديث اسم المجلد بنجاح",
|
||||
"Followed instructions perfectly": "اتبعت التعليمات على أكمل وجه",
|
||||
@@ -565,17 +597,18 @@
|
||||
"Function Name": "اسم الوظيفة",
|
||||
"Function updated successfully": "تم تحديث الوظيفة بنجاح",
|
||||
"Functions": "الوظائف",
|
||||
"Functions allow arbitrary code execution": "الوظائف تتيح تنفيذ كود برمجي مخصص",
|
||||
"Functions allow arbitrary code execution.": "الوظائف تتيح تنفيذ كود برمجي مخصص.",
|
||||
"Functions imported successfully": "تم استيراد الوظائف بنجاح",
|
||||
"Gemini": "Gemini",
|
||||
"Gemini API Config": "إعدادات واجهة Gemini API",
|
||||
"Gemini API Key is required.": "مفتاح Gemini API مطلوب.",
|
||||
"General": "عام",
|
||||
"Generate": "",
|
||||
"Generate an image": "توليد صورة",
|
||||
"Generate Image": "توليد صورة",
|
||||
"Generate prompt pair": "توليد زوج من التعليمات",
|
||||
"Generating search query": "إنشاء استعلام بحث",
|
||||
"Generating...": "",
|
||||
"Get started": "ابدأ الآن",
|
||||
"Get started with {{WEBUI_NAME}}": "ابدأ باستخدام {{WEBUI_NAME}}",
|
||||
"Global": "عام",
|
||||
@@ -622,6 +655,7 @@
|
||||
"Import Config from JSON File": "استيراد الإعدادات من ملف JSON",
|
||||
"Import Functions": "استيراد الوظائف",
|
||||
"Import Models": "استيراد النماذج",
|
||||
"Import Notes": "",
|
||||
"Import Presets": "استيراد الإعدادات المسبقة",
|
||||
"Import Prompts": "مطالبات الاستيراد",
|
||||
"Import Tools": "استيراد الأدوات",
|
||||
@@ -636,6 +670,7 @@
|
||||
"Instant Auto-Send After Voice Transcription": "إرسال تلقائي فوري بعد تحويل الصوت إلى نص",
|
||||
"Integration": "التكامل",
|
||||
"Interface": "واجهه المستخدم",
|
||||
"Invalid file content": "",
|
||||
"Invalid file format.": "تنسيق ملف غير صالح.",
|
||||
"Invalid JSON schema": "",
|
||||
"Invalid Tag": "تاق غير صالحة",
|
||||
@@ -706,6 +741,7 @@
|
||||
"Manage Pipelines": "إدارة خطوط الأنابيب",
|
||||
"Manage Tool Servers": "",
|
||||
"March": "مارس",
|
||||
"Max Speakers": "",
|
||||
"Max Tokens (num_predict)": "ماكس توكنز (num_predict)",
|
||||
"Max Upload Count": "الحد الأقصى لعدد التحميلات",
|
||||
"Max Upload Size": "الحد الأقصى لحجم الملف المرفوع",
|
||||
@@ -718,8 +754,12 @@
|
||||
"Memory deleted successfully": "تم حذف الذاكرة بنجاح",
|
||||
"Memory updated successfully": "تم تحديث الذاكرة بنجاح",
|
||||
"Merge Responses": "دمج الردود",
|
||||
"Merged Response": "نتيجة الردود المدمجة",
|
||||
"Message rating should be enabled to use this feature": "يجب تفعيل تقييم الرسائل لاستخدام هذه الميزة",
|
||||
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "لن تتم مشاركة الرسائل التي ترسلها بعد إنشاء الرابط الخاص بك. سيتمكن المستخدمون الذين لديهم عنوان URL من عرض الدردشة المشتركة",
|
||||
"Microsoft OneDrive": "",
|
||||
"Microsoft OneDrive (personal)": "",
|
||||
"Microsoft OneDrive (work/school)": "",
|
||||
"Min P": "الحد الأدنى P",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
@@ -753,19 +793,22 @@
|
||||
"Mojeek Search API Key": "مفتاح API لـ Mojeek Search",
|
||||
"more": "المزيد",
|
||||
"More": "المزيد",
|
||||
"My Notes": "",
|
||||
"Name": "الأسم",
|
||||
"Name your knowledge base": "قم بتسمية قاعدة معرفتك",
|
||||
"Native": "أصلي",
|
||||
"New Chat": "دردشة جديدة",
|
||||
"New Folder": "مجلد جديد",
|
||||
"New Note": "",
|
||||
"New Password": "كلمة المرور الجديدة",
|
||||
"new-channel": "قناة جديدة",
|
||||
"No content": "",
|
||||
"No content found": "لم يتم العثور على محتوى",
|
||||
"No content found in file.": "",
|
||||
"No content to speak": "لا يوجد محتوى للتحدث عنه",
|
||||
"No distance available": "لا توجد مسافة متاحة",
|
||||
"No feedbacks found": "لم يتم العثور على ملاحظات",
|
||||
"No file selected": "لم يتم تحديد ملف",
|
||||
"No files found.": "لم يتم العثور على ملفات.",
|
||||
"No groups with access, add a group to grant access": "لا توجد مجموعات لها حق الوصول، أضف مجموعة لمنح الوصول",
|
||||
"No HTML, CSS, or JavaScript content found.": "لم يتم العثور على محتوى HTML أو CSS أو JavaScript.",
|
||||
"No inference engine with management support found": "لم يتم العثور على محرك استدلال يدعم الإدارة",
|
||||
@@ -774,6 +817,7 @@
|
||||
"No model IDs": "لا توجد معرّفات نماذج",
|
||||
"No models found": "لم يتم العثور على نماذج",
|
||||
"No models selected": "لم يتم اختيار نماذج",
|
||||
"No Notes": "",
|
||||
"No results found": "لا توجد نتايج",
|
||||
"No search query generated": "لم يتم إنشاء استعلام بحث",
|
||||
"No source available": "لا يوجد مصدر متاح",
|
||||
@@ -782,6 +826,7 @@
|
||||
"None": "اي",
|
||||
"Not factually correct": "ليس صحيحا من حيث الواقع",
|
||||
"Not helpful": "غير مفيد",
|
||||
"Note deleted successfully": "",
|
||||
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "ملاحظة: إذا قمت بتعيين الحد الأدنى من النقاط، فلن يؤدي البحث إلا إلى إرجاع المستندات التي لها نقاط أكبر من أو تساوي الحد الأدنى من النقاط.",
|
||||
"Notes": "ملاحظات",
|
||||
"Notification Sound": "صوت الإشعارات",
|
||||
@@ -804,6 +849,7 @@
|
||||
"Only alphanumeric characters and hyphens are allowed": "يُسمح فقط بالحروف والأرقام والواصلات",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "يُسمح فقط بالأحرف الأبجدية الرقمية والواصلات في سلسلة الأمر.",
|
||||
"Only collections can be edited, create a new knowledge base to edit/add documents.": "يمكن تعديل المجموعات فقط، أنشئ قاعدة معرفة جديدة لتعديل أو إضافة مستندات.",
|
||||
"Only markdown files are allowed": "",
|
||||
"Only select users and groups with permission can access": "يمكن الوصول فقط من قبل المستخدمين والمجموعات المصرح لهم",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "خطاء! يبدو أن عنوان URL غير صالح. يرجى التحقق مرة أخرى والمحاولة مرة أخرى.",
|
||||
"Oops! There are files still uploading. Please wait for the upload to complete.": "عذرًا! لا تزال بعض الملفات قيد الرفع. يرجى الانتظار حتى يكتمل الرفع.",
|
||||
@@ -849,6 +895,7 @@
|
||||
"Pipelines": "خطوط الانابيب",
|
||||
"Pipelines Not Detected": "لم يتم الكشف عن خطوط المعالجة",
|
||||
"Pipelines Valves": "صمامات خطوط الأنابيب",
|
||||
"Plain text (.md)": "",
|
||||
"Plain text (.txt)": "نص عادي (.txt)",
|
||||
"Playground": "مكان التجربة",
|
||||
"Playwright Timeout (ms)": "",
|
||||
@@ -891,6 +938,7 @@
|
||||
"Read": "قراءة",
|
||||
"Read Aloud": "أقراء لي",
|
||||
"Reasoning Effort": "جهد الاستدلال",
|
||||
"Record": "",
|
||||
"Record voice": "سجل صوت",
|
||||
"Redirecting you to Open WebUI Community": "OpenWebUI إعادة توجيهك إلى مجتمع ",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative.": "يقلل من احتمال توليد إجابات غير منطقية. القيم الأعلى (مثل 100) تعطي إجابات أكثر تنوعًا، بينما القيم الأدنى (مثل 10) تكون أكثر تحفظًا.",
|
||||
@@ -911,9 +959,8 @@
|
||||
"Repeat Penalty (Ollama)": "عقوبة التكرار (Ollama)",
|
||||
"Reply in Thread": "الرد داخل سلسلة الرسائل",
|
||||
"Request Mode": "وضع الطلب",
|
||||
"Reranking Engine": "",
|
||||
"Reranking Model": "إعادة تقييم النموذج",
|
||||
"Reranking model disabled": "تم تعطيل نموذج إعادة الترتيب",
|
||||
"Reranking model set to \"{{reranking_model}}\"": "تم ضبط نموذج إعادة الترتيب على \"{{reranking_model}}\"",
|
||||
"Reset": "إعادة تعيين",
|
||||
"Reset All Models": "إعادة تعيين جميع النماذج",
|
||||
"Reset Upload Directory": "إعادة تعيين مجلد التحميل",
|
||||
@@ -939,7 +986,7 @@
|
||||
"Save Tag": "حفظ الوسم",
|
||||
"Saved": "تم الحفظ",
|
||||
"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": "لم يعد حفظ سجلات الدردشة مباشرة في مساحة تخزين متصفحك مدعومًا. يرجى تخصيص بعض الوقت لتنزيل وحذف سجلات الدردشة الخاصة بك عن طريق النقر على الزر أدناه. لا تقلق، يمكنك بسهولة إعادة استيراد سجلات الدردشة الخاصة بك إلى الواجهة الخلفية من خلاله",
|
||||
"Scroll to bottom when switching between branches": "التمرير للأسفل عند التبديل بين الفروع",
|
||||
"Scroll On Branch Change": "",
|
||||
"Search": "البحث",
|
||||
"Search a model": "البحث عن موديل",
|
||||
"Search Base": "قاعدة البحث",
|
||||
@@ -960,6 +1007,7 @@
|
||||
"Searched {{count}} sites": "تم البحث في {{count}} مواقع",
|
||||
"Searching \"{{searchQuery}}\"": "جارٍ البحث عن \"{{searchQuery}}\"",
|
||||
"Searching Knowledge for \"{{searchQuery}}\"": "جارٍ البحث في المعرفة عن \"{{searchQuery}}\"",
|
||||
"Searching the web...": "",
|
||||
"Searxng Query URL": "عنوان URL لاستعلام Searxng",
|
||||
"See readme.md for instructions": "readme.md للحصول على التعليمات",
|
||||
"See what's new": "ما الجديد",
|
||||
@@ -1020,6 +1068,8 @@
|
||||
"Show": "عرض",
|
||||
"Show \"What's New\" modal on login": "عرض نافذة \"ما الجديد\" عند تسجيل الدخول",
|
||||
"Show Admin Details in Account Pending Overlay": "عرض تفاصيل المشرف في نافذة \"الحساب قيد الانتظار\"",
|
||||
"Show All": "",
|
||||
"Show Less": "",
|
||||
"Show Model": "",
|
||||
"Show shortcuts": "إظهار الاختصارات",
|
||||
"Show your support!": "أظهر دعمك!",
|
||||
@@ -1043,6 +1093,7 @@
|
||||
"Stream Chat Response": "بث استجابة الدردشة",
|
||||
"STT Model": "نموذج تحويل الصوت إلى نص (STT)",
|
||||
"STT Settings": "STT اعدادات",
|
||||
"Stylized PDF Export": "",
|
||||
"Subtitle (e.g. about the Roman Empire)": "(e.g. about the Roman Empire) الترجمة",
|
||||
"Success": "نجاح",
|
||||
"Successfully updated.": "تم التحديث بنجاح",
|
||||
@@ -1089,6 +1140,7 @@
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "وهذا يضمن حفظ محادثاتك القيمة بشكل آمن في قاعدة بياناتك الخلفية. شكرًا لك!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "هذه ميزة تجريبية، وقد لا تعمل كما هو متوقع وقد تتغير في أي وقت.",
|
||||
"This model is not publicly available. Please select another model.": "",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "هذا الخيار يحدد عدد الرموز التي يتم الاحتفاظ بها عند تحديث السياق. مثلاً، إذا تم ضبطه على 2، سيتم الاحتفاظ بآخر رمزين من السياق. الحفاظ على السياق يساعد في استمرارية المحادثة، لكنه قد يحد من التفاعل مع مواضيع جديدة.",
|
||||
"This option sets the maximum number of tokens the model can generate in its response. Increasing this limit allows the model to provide longer answers, but it may also increase the likelihood of unhelpful or irrelevant content being generated.": "يحدد هذا الخيار الحد الأقصى لعدد الرموز التي يمكن للنموذج توليدها في الرد. زيادته تتيح للنموذج تقديم إجابات أطول، لكنها قد تزيد من احتمالية توليد محتوى غير مفيد أو غير ذي صلة.",
|
||||
"This option will delete all existing files in the collection and replace them with newly uploaded files.": "سيؤدي هذا الخيار إلى حذف جميع الملفات الحالية في المجموعة واستبدالها بالملفات التي تم تحميلها حديثًا.",
|
||||
@@ -1140,7 +1192,6 @@
|
||||
"Tools Access": "الوصول إلى الأدوات",
|
||||
"Tools are a function calling system with arbitrary code execution": "الأدوات عبارة عن نظام لاستدعاء الوظائف يسمح بتنفيذ كود برمجي مخصص",
|
||||
"Tools Function Calling Prompt": "توجيه استدعاء وظائف الأدوات",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "تحتوي الأدوات على نظام لاستدعاء الوظائف يتيح تنفيذ كود برمجي مخصص",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "تحتوي الأدوات على نظام لاستدعاء الوظائف يتيح تنفيذ كود برمجي مخصص.",
|
||||
"Tools Public Sharing": "",
|
||||
"Top K": "Top K",
|
||||
@@ -1163,6 +1214,7 @@
|
||||
"Unpin": "إزالة التثبيت",
|
||||
"Unravel secrets": "فكّ الأسرار",
|
||||
"Untagged": "بدون وسوم",
|
||||
"Untitled": "",
|
||||
"Update": "تحديث",
|
||||
"Update and Copy Link": "تحديث ونسخ الرابط",
|
||||
"Update for the latest features and improvements.": "حدّث للحصول على أحدث الميزات والتحسينات.",
|
||||
@@ -1173,6 +1225,7 @@
|
||||
"Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.": "قم بالترقية إلى خطة مرخصة للحصول على ميزات إضافية، مثل التخصيص والدعم المخصص.",
|
||||
"Upload": "رفع",
|
||||
"Upload a GGUF model": "GGUF رفع موديل نوع",
|
||||
"Upload Audio": "",
|
||||
"Upload directory": "رفع مجلد",
|
||||
"Upload files": "رفع ملفات",
|
||||
"Upload Files": "تحميل الملفات",
|
||||
@@ -1246,13 +1299,15 @@
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "اكتب ملخصًا في 50 كلمة يلخص [الموضوع أو الكلمة الرئيسية]",
|
||||
"Write something...": "اكتب شيئًا...",
|
||||
"Write your model template content here": "اكتب هنا محتوى قالب النموذج الخاص بك",
|
||||
"Yacy Instance URL": "",
|
||||
"Yacy Password": "",
|
||||
"Yacy Username": "",
|
||||
"Yesterday": "أمس",
|
||||
"You": "انت",
|
||||
"You are currently using a trial license. Please contact support to upgrade your license.": "أنت تستخدم حالياً ترخيصًا تجريبيًا. يُرجى التواصل مع الدعم للترقية.",
|
||||
"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "يمكنك الدردشة مع {{maxCount}} ملف(ات) كحد أقصى في نفس الوقت.",
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "يمكنك تخصيص تفاعلك مع النماذج اللغوية عن طريق إضافة الذكريات باستخدام زر \"إدارة\" أدناه، مما يجعلها أكثر فائدة وتناسبًا لك.",
|
||||
"You cannot upload an empty file.": "لا يمكنك رفع ملف فارغ.",
|
||||
"You do not have permission to upload files": "ليس لديك صلاحية لرفع الملفات",
|
||||
"You do not have permission to upload files.": "ليس لديك صلاحية لرفع الملفات.",
|
||||
"You have no archived conversations.": "لا تملك محادثات محفوظه",
|
||||
"You have shared this chat": "تم مشاركة هذه المحادثة",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"(e.g. `sh webui.sh --api --api-auth username_password`)": "(напр. `sh webui.sh --api --api-auth username_password`)",
|
||||
"(e.g. `sh webui.sh --api`)": "(напр. `sh webui.sh --api`)",
|
||||
"(latest)": "(последна)",
|
||||
"(leave blank for to use commercial endpoint)": "",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "{{ models }}",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
@@ -62,6 +63,8 @@
|
||||
"Allow Chat Delete": "Разреши изтриване на чат",
|
||||
"Allow Chat Deletion": "Позволи Изтриване на Чат",
|
||||
"Allow Chat Edit": "Разреши редактиране на чат",
|
||||
"Allow Chat Export": "",
|
||||
"Allow Chat Share": "",
|
||||
"Allow File Upload": "Разреши качване на файлове",
|
||||
"Allow Multiple Models in Chat": "",
|
||||
"Allow non-local voices": "Разреши нелокални гласове",
|
||||
@@ -76,6 +79,7 @@
|
||||
"Always": "Винаги",
|
||||
"Always Collapse Code Blocks": "",
|
||||
"Always Expand Details": "",
|
||||
"Always Play Notification Sound": "",
|
||||
"Amazing": "Невероятно",
|
||||
"an assistant": "асистент",
|
||||
"Analyzed": "Анализирано",
|
||||
@@ -101,6 +105,7 @@
|
||||
"Are you sure you want to delete this channel?": "Сигурни ли сте, че искате да изтриете този канал?",
|
||||
"Are you sure you want to delete this message?": "Сигурни ли сте, че искате да изтриете това съобщение?",
|
||||
"Are you sure you want to unarchive all archived chats?": "Сигурни ли сте, че искате да разархивирате всички архивирани чатове?",
|
||||
"Are you sure you want to update this user's role to **{{ROLE}}**?": "",
|
||||
"Are you sure?": "Сигурни ли сте?",
|
||||
"Arena Models": "Arena Модели",
|
||||
"Artifacts": "Артефакти",
|
||||
@@ -143,6 +148,7 @@
|
||||
"Bing Search V7 Subscription Key": "Абонаментен ключ за Bing Search V7",
|
||||
"Bocha Search API Key": "API ключ за Bocha Search",
|
||||
"Boosting or penalizing specific tokens for constrained responses. Bias values will be clamped between -100 and 100 (inclusive). (Default: none)": "",
|
||||
"Both Docling OCR Engine and Language(s) must be provided or both left empty.": "",
|
||||
"Brave Search API Key": "API ключ за Brave Search",
|
||||
"By {{name}}": "От {{name}}",
|
||||
"Bypass Embedding and Retrieval": "",
|
||||
@@ -153,6 +159,7 @@
|
||||
"Cancel": "Отказ",
|
||||
"Capabilities": "Възможности",
|
||||
"Capture": "Заснемане",
|
||||
"Capture Audio": "",
|
||||
"Certificate Path": "Път до сертификата",
|
||||
"Change Password": "Промяна на Парола",
|
||||
"Channel Name": "Име на канала",
|
||||
@@ -262,6 +269,8 @@
|
||||
"Create Knowledge": "Създаване на знания",
|
||||
"Create new key": "Създаване на нов ключ",
|
||||
"Create new secret key": "Създаване на нов секретен ключ",
|
||||
"Create Note": "",
|
||||
"Create your first note by clicking on the plus button below.": "",
|
||||
"Created at": "Създадено на",
|
||||
"Created At": "Създадено на",
|
||||
"Created by": "Създадено от",
|
||||
@@ -299,6 +308,7 @@
|
||||
"Delete function?": "Изтриване на функцията?",
|
||||
"Delete Message": "Изтриване на съобщение",
|
||||
"Delete message?": "Изтриване на съобщението?",
|
||||
"Delete note?": "",
|
||||
"Delete prompt?": "Изтриване на промпта?",
|
||||
"delete this link": "Изтриване на този линк",
|
||||
"Delete tool?": "Изтриване на инструмента?",
|
||||
@@ -354,7 +364,7 @@
|
||||
"Download Database": "Сваляне на база данни",
|
||||
"Drag and drop a file to upload or select a file to view": "Плъзнете и пуснете файл за качване или изберете файл за преглед",
|
||||
"Draw": "Рисуване",
|
||||
"Drop any files here to add to the conversation": "Пускане на файлове тук, за да ги добавите в чата",
|
||||
"Drop any files here to upload": "",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "напр. '30с','10м'. Валидни единици са 'с', 'м', 'ч'.",
|
||||
"e.g. \"json\" or a JSON schema": "",
|
||||
"e.g. 60": "",
|
||||
@@ -364,7 +374,9 @@
|
||||
"e.g. my_filter": "напр. моят_филтър",
|
||||
"e.g. my_tools": "напр. моите_инструменти",
|
||||
"e.g. Tools for performing various operations": "напр. Инструменти за извършване на различни операции",
|
||||
"e.g., 3, 4, 5 (leave blank for default)": "",
|
||||
"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
|
||||
"e.g., westus (leave blank for eastus)": "",
|
||||
"Edit": "Редактиране",
|
||||
"Edit Arena Model": "Редактиране на Arena модел",
|
||||
"Edit Channel": "Редактиране на канал",
|
||||
@@ -392,7 +404,9 @@
|
||||
"Enable Mirostat sampling for controlling perplexity.": "",
|
||||
"Enable New Sign Ups": "Включване на нови регистрации",
|
||||
"Enabled": "Активирано",
|
||||
"Endpoint URL": "",
|
||||
"Enforce Temporary Chat": "",
|
||||
"Enhance": "",
|
||||
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Уверете се, че вашият CSV файл включва 4 колони в следния ред: Име, Имейл, Парола, Роля.",
|
||||
"Enter {{role}} message here": "Въведете съобщение за {{role}} тук",
|
||||
"Enter a detail about yourself for your LLMs to recall": "Въведете подробности за себе си, за да ги запомнят вашите LLMs",
|
||||
@@ -407,13 +421,19 @@
|
||||
"Enter CFG Scale (e.g. 7.0)": "Въведете CFG Scale (напр. 7.0)",
|
||||
"Enter Chunk Overlap": "Въведете припокриване на чънкове",
|
||||
"Enter Chunk Size": "Въведете размер на чънк",
|
||||
"Enter comma-seperated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "",
|
||||
"Enter comma-separated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "",
|
||||
"Enter description": "Въведете описание",
|
||||
"Enter Docling OCR Engine": "",
|
||||
"Enter Docling OCR Language(s)": "",
|
||||
"Enter Docling Server URL": "",
|
||||
"Enter Document Intelligence Endpoint": "",
|
||||
"Enter Document Intelligence Key": "",
|
||||
"Enter domains separated by commas (e.g., example.com,site.org)": "Въведете домейни, разделени със запетаи (напр. example.com,site.org)",
|
||||
"Enter Exa API Key": "Въведете API ключ за Exa",
|
||||
"Enter External Web Loader API Key": "",
|
||||
"Enter External Web Loader URL": "",
|
||||
"Enter External Web Search API Key": "",
|
||||
"Enter External Web Search URL": "",
|
||||
"Enter Firecrawl API Base URL": "",
|
||||
"Enter Firecrawl API Key": "",
|
||||
"Enter Github Raw URL": "Въведете URL адрес на Github Raw",
|
||||
@@ -431,6 +451,7 @@
|
||||
"Enter Model ID": "Въведете ID на модела",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Въведете таг на модел (напр. {{modelTag}})",
|
||||
"Enter Mojeek Search API Key": "Въведете API ключ за Mojeek Search",
|
||||
"Enter New Password": "",
|
||||
"Enter Number of Steps (e.g. 50)": "Въведете брой стъпки (напр. 50)",
|
||||
"Enter Perplexity API Key": "",
|
||||
"Enter Playwright Timeout": "",
|
||||
@@ -467,11 +488,15 @@
|
||||
"Enter Top K Reranker": "",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "Въведете URL (напр. http://127.0.0.1:7860/)",
|
||||
"Enter URL (e.g. http://localhost:11434)": "Въведете URL (напр. http://localhost:11434)",
|
||||
"Enter Yacy Password": "",
|
||||
"Enter Yacy URL (e.g. http://yacy.example.com:8090)": "",
|
||||
"Enter Yacy Username": "",
|
||||
"Enter your current password": "Въведете текущата си парола",
|
||||
"Enter Your Email": "Въведете имейл",
|
||||
"Enter Your Full Name": "Въведете вашето пълно име",
|
||||
"Enter your message": "Въведете съобщението си",
|
||||
"Enter your name": "",
|
||||
"Enter Your Name": "",
|
||||
"Enter your new password": "Въведете новата си парола",
|
||||
"Enter Your Password": "Въведете вашата парола",
|
||||
"Enter Your Role": "Въведете вашата роля",
|
||||
@@ -480,6 +505,8 @@
|
||||
"Error": "Грешка",
|
||||
"ERROR": "ГРЕШКА",
|
||||
"Error accessing Google Drive: {{error}}": "Грешка при достъп до Google Drive: {{error}}",
|
||||
"Error accessing media devices.": "",
|
||||
"Error starting recording.": "",
|
||||
"Error uploading file: {{error}}": "Грешка при качване на файла: {{error}}",
|
||||
"Evaluations": "Оценки",
|
||||
"Exa API Key": "API ключ за Exa",
|
||||
@@ -511,10 +538,16 @@
|
||||
"Export Tools": "Експортиране на инструменти",
|
||||
"External": "",
|
||||
"External Models": "Външни модели",
|
||||
"External Web Loader API Key": "",
|
||||
"External Web Loader URL": "",
|
||||
"External Web Search API Key": "",
|
||||
"External Web Search URL": "",
|
||||
"Failed to add file.": "Неуспешно добавяне на файл.",
|
||||
"Failed to connect to {{URL}} OpenAPI tool server": "",
|
||||
"Failed to create API Key.": "Неуспешно създаване на API ключ.",
|
||||
"Failed to delete note": "",
|
||||
"Failed to fetch models": "Неуспешно извличане на модели",
|
||||
"Failed to load file content.": "",
|
||||
"Failed to read clipboard contents": "Грешка при четене на съдържанието от клипборда",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "Неуспешно запазване на конфигурацията на моделите",
|
||||
@@ -544,7 +577,6 @@
|
||||
"Fluidly stream large external response chunks": "Плавно предаване на големи части от външен отговор",
|
||||
"Focus chat input": "Фокусиране на чат вход",
|
||||
"Folder deleted successfully": "Папката е изтрита успешно",
|
||||
"Folder name cannot be empty": "Името на папката не може да бъде празно",
|
||||
"Folder name cannot be empty.": "Името на папката не може да бъде празно.",
|
||||
"Folder name updated successfully": "Името на папката е актуализирано успешно",
|
||||
"Followed instructions perfectly": "Следвайте инструкциите перфектно",
|
||||
@@ -565,17 +597,18 @@
|
||||
"Function Name": "Име на функцията",
|
||||
"Function updated successfully": "Функцията е актуализирана успешно",
|
||||
"Functions": "Функции",
|
||||
"Functions allow arbitrary code execution": "Функциите позволяват произволно изпълнение на код",
|
||||
"Functions allow arbitrary code execution.": "Функциите позволяват произволно изпълнение на код.",
|
||||
"Functions imported successfully": "Функциите са импортирани успешно",
|
||||
"Gemini": "",
|
||||
"Gemini API Config": "",
|
||||
"Gemini API Key is required.": "",
|
||||
"General": "Основни",
|
||||
"Generate": "",
|
||||
"Generate an image": "Генериране на изображение",
|
||||
"Generate Image": "Генериране на изображение",
|
||||
"Generate prompt pair": "",
|
||||
"Generating search query": "Генериране на заявка за търсене",
|
||||
"Generating...": "",
|
||||
"Get started": "Започнете",
|
||||
"Get started with {{WEBUI_NAME}}": "Започнете с {{WEBUI_NAME}}",
|
||||
"Global": "Глобално",
|
||||
@@ -622,6 +655,7 @@
|
||||
"Import Config from JSON File": "Импортиране на конфигурация от JSON файл",
|
||||
"Import Functions": "Импортиране на функции",
|
||||
"Import Models": "Импортиране на модели",
|
||||
"Import Notes": "",
|
||||
"Import Presets": "Импортиране на предварителни настройки",
|
||||
"Import Prompts": "Импортване на промптове",
|
||||
"Import Tools": "Импортиране на инструменти",
|
||||
@@ -636,6 +670,7 @@
|
||||
"Instant Auto-Send After Voice Transcription": "Незабавно автоматично изпращане след гласова транскрипция",
|
||||
"Integration": "",
|
||||
"Interface": "Интерфейс",
|
||||
"Invalid file content": "",
|
||||
"Invalid file format.": "Невалиден формат на файла.",
|
||||
"Invalid JSON schema": "",
|
||||
"Invalid Tag": "Невалиден таг",
|
||||
@@ -706,6 +741,7 @@
|
||||
"Manage Pipelines": "Управление на пайплайни",
|
||||
"Manage Tool Servers": "",
|
||||
"March": "Март",
|
||||
"Max Speakers": "",
|
||||
"Max Tokens (num_predict)": "Макс токени (num_predict)",
|
||||
"Max Upload Count": "Максимален брой качвания",
|
||||
"Max Upload Size": "Максимален размер на качване",
|
||||
@@ -718,8 +754,12 @@
|
||||
"Memory deleted successfully": "Паметта е изтрита успешно",
|
||||
"Memory updated successfully": "Паметта е актуализирана успешно",
|
||||
"Merge Responses": "Обединяване на отговори",
|
||||
"Merged Response": "Обединен отговор",
|
||||
"Message rating should be enabled to use this feature": "Оценяването на съобщения трябва да бъде активирано, за да използвате тази функция",
|
||||
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Съобщенията, които изпращате след създаването на връзката, няма да бъдат споделяни. Потребителите с URL адреса ще могат да видят споделения чат.",
|
||||
"Microsoft OneDrive": "",
|
||||
"Microsoft OneDrive (personal)": "",
|
||||
"Microsoft OneDrive (work/school)": "",
|
||||
"Min P": "Мин P",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
@@ -753,19 +793,22 @@
|
||||
"Mojeek Search API Key": "API ключ за Mojeek Search",
|
||||
"more": "още",
|
||||
"More": "Повече",
|
||||
"My Notes": "",
|
||||
"Name": "Име",
|
||||
"Name your knowledge base": "Именувайте вашата база от знания",
|
||||
"Native": "Нативен",
|
||||
"New Chat": "Нов чат",
|
||||
"New Folder": "Нова папка",
|
||||
"New Note": "",
|
||||
"New Password": "Нова парола",
|
||||
"new-channel": "нов-канал",
|
||||
"No content": "",
|
||||
"No content found": "Не е намерено съдържание",
|
||||
"No content found in file.": "",
|
||||
"No content to speak": "Няма съдържание за изговаряне",
|
||||
"No distance available": "Няма налично разстояние",
|
||||
"No feedbacks found": "Не са намерени обратни връзки",
|
||||
"No file selected": "Не е избран файл",
|
||||
"No files found.": "Не са намерени файлове.",
|
||||
"No groups with access, add a group to grant access": "Няма групи с достъп, добавете група, за да предоставите достъп",
|
||||
"No HTML, CSS, or JavaScript content found.": "Не е намерено HTML, CSS или JavaScript съдържание.",
|
||||
"No inference engine with management support found": "Не е намерен механизъм за извод с поддръжка на управление",
|
||||
@@ -774,6 +817,7 @@
|
||||
"No model IDs": "Няма ИД-та на модели",
|
||||
"No models found": "Не са намерени модели",
|
||||
"No models selected": "Няма избрани модели",
|
||||
"No Notes": "",
|
||||
"No results found": "Няма намерени резултати",
|
||||
"No search query generated": "Не е генерирана заявка за търсене",
|
||||
"No source available": "Няма наличен източник",
|
||||
@@ -782,6 +826,7 @@
|
||||
"None": "Никой",
|
||||
"Not factually correct": "Не е фактологически правилно",
|
||||
"Not helpful": "Не е полезно",
|
||||
"Note deleted successfully": "",
|
||||
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Забележка: Ако зададете минимален резултат, търсенето ще върне само документи с резултат, по-голям или равен на минималния резултат.",
|
||||
"Notes": "Бележки",
|
||||
"Notification Sound": "Звук за известия",
|
||||
@@ -804,6 +849,7 @@
|
||||
"Only alphanumeric characters and hyphens are allowed": "Разрешени са само буквено-цифрови знаци и тирета",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "Само алфанумерични знаци и тире са разрешени в командния низ.",
|
||||
"Only collections can be edited, create a new knowledge base to edit/add documents.": "Само колекции могат да бъдат редактирани, създайте нова база от знания, за да редактирате/добавяте документи.",
|
||||
"Only markdown files are allowed": "",
|
||||
"Only select users and groups with permission can access": "Само избрани потребители и групи с разрешение могат да имат достъп",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Упс! Изглежда URL адресът е невалиден. Моля, проверете отново и опитайте пак.",
|
||||
"Oops! There are files still uploading. Please wait for the upload to complete.": "Упс! Все още има файлове, които се качват. Моля, изчакайте качването да приключи.",
|
||||
@@ -849,6 +895,7 @@
|
||||
"Pipelines": "Пайплайни",
|
||||
"Pipelines Not Detected": "Не са открити пайплайни",
|
||||
"Pipelines Valves": "Клапани на пайплайни",
|
||||
"Plain text (.md)": "",
|
||||
"Plain text (.txt)": "Обикновен текст (.txt)",
|
||||
"Playground": "Плейграунд",
|
||||
"Playwright Timeout (ms)": "",
|
||||
@@ -891,6 +938,7 @@
|
||||
"Read": "Четене",
|
||||
"Read Aloud": "Прочети на глас",
|
||||
"Reasoning Effort": "Усилие за разсъждение",
|
||||
"Record": "",
|
||||
"Record voice": "Записване на глас",
|
||||
"Redirecting you to Open WebUI Community": "Пренасочване към OpenWebUI общността",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative.": "",
|
||||
@@ -911,9 +959,8 @@
|
||||
"Repeat Penalty (Ollama)": "Наказание за повторение (Ollama)",
|
||||
"Reply in Thread": "Отговори в тред",
|
||||
"Request Mode": "Режим на заявка",
|
||||
"Reranking Engine": "",
|
||||
"Reranking Model": "Модел за преподреждане",
|
||||
"Reranking model disabled": "Моделът за преподреждане е деактивиран",
|
||||
"Reranking model set to \"{{reranking_model}}\"": "Моделът за преподреждане е зададен на \"{{reranking_model}}\"",
|
||||
"Reset": "Нулиране",
|
||||
"Reset All Models": "Нулиране на всички модели",
|
||||
"Reset Upload Directory": "Нулиране на директорията за качване",
|
||||
@@ -939,7 +986,7 @@
|
||||
"Save Tag": "Запиши таг",
|
||||
"Saved": "Запазено",
|
||||
"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": "Запазването на чат логове директно в хранилището на вашия браузър вече не се поддържа. Моля, отделете малко време, за да изтеглите и изтриете чат логовете си, като щракнете върху бутона по-долу. Не се притеснявайте, можете лесно да импортирате отново чат логовете си в бекенда чрез",
|
||||
"Scroll to bottom when switching between branches": "Превъртане до дъното при превключване между клонове",
|
||||
"Scroll On Branch Change": "",
|
||||
"Search": "Търси",
|
||||
"Search a model": "Търси модел",
|
||||
"Search Base": "База за търсене",
|
||||
@@ -960,6 +1007,7 @@
|
||||
"Searched {{count}} sites": "Претърсени {{count}} сайта",
|
||||
"Searching \"{{searchQuery}}\"": "Търсене на \"{{searchQuery}}\"",
|
||||
"Searching Knowledge for \"{{searchQuery}}\"": "Търсене в знанията за \"{{searchQuery}}\"",
|
||||
"Searching the web...": "",
|
||||
"Searxng Query URL": "URL адрес на заявка на Searxng",
|
||||
"See readme.md for instructions": "Виж readme.md за инструкции",
|
||||
"See what's new": "Виж какво е новото",
|
||||
@@ -1020,6 +1068,8 @@
|
||||
"Show": "Покажи",
|
||||
"Show \"What's New\" modal on login": "Покажи модалния прозорец \"Какво е ново\" при вписване",
|
||||
"Show Admin Details in Account Pending Overlay": "Покажи детайлите на администратора в наслагването на изчакващ акаунт",
|
||||
"Show All": "",
|
||||
"Show Less": "",
|
||||
"Show Model": "",
|
||||
"Show shortcuts": "Покажи преки пътища",
|
||||
"Show your support!": "Покажете вашата подкрепа!",
|
||||
@@ -1043,6 +1093,7 @@
|
||||
"Stream Chat Response": "Поточен чат отговор",
|
||||
"STT Model": "STT Модел",
|
||||
"STT Settings": "STT Настройки",
|
||||
"Stylized PDF Export": "",
|
||||
"Subtitle (e.g. about the Roman Empire)": "Подтитул (напр. за Римска империя)",
|
||||
"Success": "Успех",
|
||||
"Successfully updated.": "Успешно обновено.",
|
||||
@@ -1089,6 +1140,7 @@
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Това гарантира, че ценните ви разговори се запазват сигурно във вашата бекенд база данни. Благодарим ви!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Това е експериментална функция, може да не работи според очакванията и подлежи на промяна по всяко време.",
|
||||
"This model is not publicly available. Please select another model.": "",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
"This option sets the maximum number of tokens the model can generate in its response. Increasing this limit allows the model to provide longer answers, but it may also increase the likelihood of unhelpful or irrelevant content being generated.": "",
|
||||
"This option will delete all existing files in the collection and replace them with newly uploaded files.": "Тази опция ще изтрие всички съществуващи файлове в колекцията и ще ги замени с новокачени файлове.",
|
||||
@@ -1140,7 +1192,6 @@
|
||||
"Tools Access": "Достъп до инструменти",
|
||||
"Tools are a function calling system with arbitrary code execution": "Инструментите са система за извикване на функции с произволно изпълнение на код",
|
||||
"Tools Function Calling Prompt": "Промпт за извикване на функции на инструментите",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "Инструментите имат система за извикване на функции, която позволява произволно изпълнение на код",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "Инструментите имат система за извикване на функции, която позволява произволно изпълнение на код.",
|
||||
"Tools Public Sharing": "",
|
||||
"Top K": "Топ K",
|
||||
@@ -1163,6 +1214,7 @@
|
||||
"Unpin": "Откачи",
|
||||
"Unravel secrets": "Разгадай тайни",
|
||||
"Untagged": "Без етикет",
|
||||
"Untitled": "",
|
||||
"Update": "Актуализиране",
|
||||
"Update and Copy Link": "Обнови и копирай връзка",
|
||||
"Update for the latest features and improvements.": "Актуализирайте за най-новите функции и подобрения.",
|
||||
@@ -1173,6 +1225,7 @@
|
||||
"Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.": "Надградете до лицензиран план за разширени възможности, включително персонализирани теми и брандиране, и специализирана поддръжка.",
|
||||
"Upload": "Качване",
|
||||
"Upload a GGUF model": "Качване на GGUF модел",
|
||||
"Upload Audio": "",
|
||||
"Upload directory": "Качване на директория",
|
||||
"Upload files": "Качване на файлове",
|
||||
"Upload Files": "Качване на файлове",
|
||||
@@ -1246,13 +1299,15 @@
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "Напиши описание в 50 думи, което обобщава [тема или ключова дума].",
|
||||
"Write something...": "Напишете нещо...",
|
||||
"Write your model template content here": "Напишете съдържанието на вашия шаблон за модел тук",
|
||||
"Yacy Instance URL": "",
|
||||
"Yacy Password": "",
|
||||
"Yacy Username": "",
|
||||
"Yesterday": "вчера",
|
||||
"You": "Вие",
|
||||
"You are currently using a trial license. Please contact support to upgrade your license.": "",
|
||||
"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Можете да чатите с максимум {{maxCount}} файл(а) наведнъж.",
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Можете да персонализирате взаимодействията си с LLM-и, като добавите спомени чрез бутона 'Управление' по-долу, правейки ги по-полезни и съобразени с вас.",
|
||||
"You cannot upload an empty file.": "Не можете да качите празен файл.",
|
||||
"You do not have permission to upload files": "Нямате разрешение да качвате файлове",
|
||||
"You do not have permission to upload files.": "Нямате разрешение да качвате файлове.",
|
||||
"You have no archived conversations.": "Нямате архивирани разговори.",
|
||||
"You have shared this chat": "Вие сте споделили този чат",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
|
||||
"(e.g. `sh webui.sh --api`)": "(যেমন `sh webui.sh --api`)",
|
||||
"(latest)": "(সর্বশেষ)",
|
||||
"(leave blank for to use commercial endpoint)": "",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "{{ মডেল}}",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
@@ -62,6 +63,8 @@
|
||||
"Allow Chat Delete": "",
|
||||
"Allow Chat Deletion": "চ্যাট ডিলিট করতে দিন",
|
||||
"Allow Chat Edit": "",
|
||||
"Allow Chat Export": "",
|
||||
"Allow Chat Share": "",
|
||||
"Allow File Upload": "",
|
||||
"Allow Multiple Models in Chat": "",
|
||||
"Allow non-local voices": "",
|
||||
@@ -76,6 +79,7 @@
|
||||
"Always": "",
|
||||
"Always Collapse Code Blocks": "",
|
||||
"Always Expand Details": "",
|
||||
"Always Play Notification Sound": "",
|
||||
"Amazing": "",
|
||||
"an assistant": "একটা এসিস্ট্যান্ট",
|
||||
"Analyzed": "",
|
||||
@@ -101,6 +105,7 @@
|
||||
"Are you sure you want to delete this channel?": "",
|
||||
"Are you sure you want to delete this message?": "",
|
||||
"Are you sure you want to unarchive all archived chats?": "",
|
||||
"Are you sure you want to update this user's role to **{{ROLE}}**?": "",
|
||||
"Are you sure?": "আপনি নিশ্চিত?",
|
||||
"Arena Models": "",
|
||||
"Artifacts": "",
|
||||
@@ -143,6 +148,7 @@
|
||||
"Bing Search V7 Subscription Key": "",
|
||||
"Bocha Search API Key": "",
|
||||
"Boosting or penalizing specific tokens for constrained responses. Bias values will be clamped between -100 and 100 (inclusive). (Default: none)": "",
|
||||
"Both Docling OCR Engine and Language(s) must be provided or both left empty.": "",
|
||||
"Brave Search API Key": "সাহসী অনুসন্ধান API কী",
|
||||
"By {{name}}": "",
|
||||
"Bypass Embedding and Retrieval": "",
|
||||
@@ -153,6 +159,7 @@
|
||||
"Cancel": "বাতিল",
|
||||
"Capabilities": "সক্ষমতা",
|
||||
"Capture": "",
|
||||
"Capture Audio": "",
|
||||
"Certificate Path": "",
|
||||
"Change Password": "পাসওয়ার্ড পরিবর্তন করুন",
|
||||
"Channel Name": "",
|
||||
@@ -262,6 +269,8 @@
|
||||
"Create Knowledge": "",
|
||||
"Create new key": "একটি নতুন কী তৈরি করুন",
|
||||
"Create new secret key": "একটি নতুন সিক্রেট কী তৈরি করুন",
|
||||
"Create Note": "",
|
||||
"Create your first note by clicking on the plus button below.": "",
|
||||
"Created at": "নির্মানকাল",
|
||||
"Created At": "নির্মানকাল",
|
||||
"Created by": "",
|
||||
@@ -299,6 +308,7 @@
|
||||
"Delete function?": "",
|
||||
"Delete Message": "",
|
||||
"Delete message?": "",
|
||||
"Delete note?": "",
|
||||
"Delete prompt?": "",
|
||||
"delete this link": "এই লিংক মুছে ফেলুন",
|
||||
"Delete tool?": "",
|
||||
@@ -354,7 +364,7 @@
|
||||
"Download Database": "ডেটাবেজ ডাউনলোড করুন",
|
||||
"Drag and drop a file to upload or select a file to view": "",
|
||||
"Draw": "",
|
||||
"Drop any files here to add to the conversation": "আলোচনায় যুক্ত করার জন্য যে কোন ফাইল এখানে ড্রপ করুন",
|
||||
"Drop any files here to upload": "",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "যেমন '30s','10m'. সময়ের অনুমোদিত অনুমোদিত এককগুলি হচ্ছে 's', 'm', 'h'.",
|
||||
"e.g. \"json\" or a JSON schema": "",
|
||||
"e.g. 60": "",
|
||||
@@ -364,7 +374,9 @@
|
||||
"e.g. my_filter": "",
|
||||
"e.g. my_tools": "",
|
||||
"e.g. Tools for performing various operations": "",
|
||||
"e.g., 3, 4, 5 (leave blank for default)": "",
|
||||
"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
|
||||
"e.g., westus (leave blank for eastus)": "",
|
||||
"Edit": "এডিট করুন",
|
||||
"Edit Arena Model": "",
|
||||
"Edit Channel": "",
|
||||
@@ -392,7 +404,9 @@
|
||||
"Enable Mirostat sampling for controlling perplexity.": "",
|
||||
"Enable New Sign Ups": "নতুন সাইনআপ চালু করুন",
|
||||
"Enabled": "",
|
||||
"Endpoint URL": "",
|
||||
"Enforce Temporary Chat": "",
|
||||
"Enhance": "",
|
||||
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "আপনার সিএসভি ফাইলটিতে এই ক্রমে 4 টি কলাম অন্তর্ভুক্ত রয়েছে তা নিশ্চিত করুন: নাম, ইমেল, পাসওয়ার্ড, ভূমিকা।.",
|
||||
"Enter {{role}} message here": "{{role}} মেসেজ এখানে লিখুন",
|
||||
"Enter a detail about yourself for your LLMs to recall": "আপনার এলএলএমগুলি স্মরণ করার জন্য নিজের সম্পর্কে একটি বিশদ লিখুন",
|
||||
@@ -407,13 +421,19 @@
|
||||
"Enter CFG Scale (e.g. 7.0)": "",
|
||||
"Enter Chunk Overlap": "চাঙ্ক ওভারল্যাপ লিখুন",
|
||||
"Enter Chunk Size": "চাংক সাইজ লিখুন",
|
||||
"Enter comma-seperated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "",
|
||||
"Enter comma-separated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "",
|
||||
"Enter description": "",
|
||||
"Enter Docling OCR Engine": "",
|
||||
"Enter Docling OCR Language(s)": "",
|
||||
"Enter Docling Server URL": "",
|
||||
"Enter Document Intelligence Endpoint": "",
|
||||
"Enter Document Intelligence Key": "",
|
||||
"Enter domains separated by commas (e.g., example.com,site.org)": "",
|
||||
"Enter Exa API Key": "",
|
||||
"Enter External Web Loader API Key": "",
|
||||
"Enter External Web Loader URL": "",
|
||||
"Enter External Web Search API Key": "",
|
||||
"Enter External Web Search URL": "",
|
||||
"Enter Firecrawl API Base URL": "",
|
||||
"Enter Firecrawl API Key": "",
|
||||
"Enter Github Raw URL": "গিটহাব কাঁচা URL লিখুন",
|
||||
@@ -431,6 +451,7 @@
|
||||
"Enter Model ID": "",
|
||||
"Enter model tag (e.g. {{modelTag}})": "মডেল ট্যাগ লিখুন (e.g. {{modelTag}})",
|
||||
"Enter Mojeek Search API Key": "",
|
||||
"Enter New Password": "",
|
||||
"Enter Number of Steps (e.g. 50)": "ধাপের সংখ্যা দিন (যেমন: 50)",
|
||||
"Enter Perplexity API Key": "",
|
||||
"Enter Playwright Timeout": "",
|
||||
@@ -467,11 +488,15 @@
|
||||
"Enter Top K Reranker": "",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "ইউআরএল দিন (যেমন http://127.0.0.1:7860/)",
|
||||
"Enter URL (e.g. http://localhost:11434)": "ইউআরএল দিন (যেমন http://localhost:11434)",
|
||||
"Enter Yacy Password": "",
|
||||
"Enter Yacy URL (e.g. http://yacy.example.com:8090)": "",
|
||||
"Enter Yacy Username": "",
|
||||
"Enter your current password": "",
|
||||
"Enter Your Email": "আপনার ইমেইল লিখুন",
|
||||
"Enter Your Full Name": "আপনার পূর্ণ নাম লিখুন",
|
||||
"Enter your message": "",
|
||||
"Enter your name": "",
|
||||
"Enter Your Name": "",
|
||||
"Enter your new password": "",
|
||||
"Enter Your Password": "আপনার পাসওয়ার্ড লিখুন",
|
||||
"Enter Your Role": "আপনার রোল লিখুন",
|
||||
@@ -480,6 +505,8 @@
|
||||
"Error": "ত্রুটি",
|
||||
"ERROR": "",
|
||||
"Error accessing Google Drive: {{error}}": "",
|
||||
"Error accessing media devices.": "",
|
||||
"Error starting recording.": "",
|
||||
"Error uploading file: {{error}}": "",
|
||||
"Evaluations": "",
|
||||
"Exa API Key": "",
|
||||
@@ -511,10 +538,16 @@
|
||||
"Export Tools": "",
|
||||
"External": "",
|
||||
"External Models": "",
|
||||
"External Web Loader API Key": "",
|
||||
"External Web Loader URL": "",
|
||||
"External Web Search API Key": "",
|
||||
"External Web Search URL": "",
|
||||
"Failed to add file.": "",
|
||||
"Failed to connect to {{URL}} OpenAPI tool server": "",
|
||||
"Failed to create API Key.": "API Key তৈরি করা যায়নি।",
|
||||
"Failed to delete note": "",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to load file content.": "",
|
||||
"Failed to read clipboard contents": "ক্লিপবোর্ডের বিষয়বস্তু পড়া সম্ভব হয়নি",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "",
|
||||
@@ -544,7 +577,6 @@
|
||||
"Fluidly stream large external response chunks": "বড় এক্সটার্নাল রেসপন্স চাঙ্কগুলো মসৃণভাবে প্রবাহিত করুন",
|
||||
"Focus chat input": "চ্যাট ইনপুট ফোকাস করুন",
|
||||
"Folder deleted successfully": "",
|
||||
"Folder name cannot be empty": "",
|
||||
"Folder name cannot be empty.": "",
|
||||
"Folder name updated successfully": "",
|
||||
"Followed instructions perfectly": "নির্দেশাবলী নিখুঁতভাবে অনুসরণ করা হয়েছে",
|
||||
@@ -565,17 +597,18 @@
|
||||
"Function Name": "",
|
||||
"Function updated successfully": "",
|
||||
"Functions": "",
|
||||
"Functions allow arbitrary code execution": "",
|
||||
"Functions allow arbitrary code execution.": "",
|
||||
"Functions imported successfully": "",
|
||||
"Gemini": "",
|
||||
"Gemini API Config": "",
|
||||
"Gemini API Key is required.": "",
|
||||
"General": "সাধারণ",
|
||||
"Generate": "",
|
||||
"Generate an image": "",
|
||||
"Generate Image": "",
|
||||
"Generate prompt pair": "",
|
||||
"Generating search query": "অনুসন্ধান ক্যোয়ারী তৈরি করা হচ্ছে",
|
||||
"Generating...": "",
|
||||
"Get started": "",
|
||||
"Get started with {{WEBUI_NAME}}": "",
|
||||
"Global": "",
|
||||
@@ -622,6 +655,7 @@
|
||||
"Import Config from JSON File": "",
|
||||
"Import Functions": "",
|
||||
"Import Models": "মডেল আমদানি করুন",
|
||||
"Import Notes": "",
|
||||
"Import Presets": "",
|
||||
"Import Prompts": "প্রম্পটগুলো ইমপোর্ট করুন",
|
||||
"Import Tools": "",
|
||||
@@ -636,6 +670,7 @@
|
||||
"Instant Auto-Send After Voice Transcription": "",
|
||||
"Integration": "",
|
||||
"Interface": "ইন্টারফেস",
|
||||
"Invalid file content": "",
|
||||
"Invalid file format.": "",
|
||||
"Invalid JSON schema": "",
|
||||
"Invalid Tag": "অবৈধ ট্যাগ",
|
||||
@@ -706,6 +741,7 @@
|
||||
"Manage Pipelines": "পাইপলাইন পরিচালনা করুন",
|
||||
"Manage Tool Servers": "",
|
||||
"March": "মার্চ",
|
||||
"Max Speakers": "",
|
||||
"Max Tokens (num_predict)": "সর্বোচ্চ টোকেন (num_predict)",
|
||||
"Max Upload Count": "",
|
||||
"Max Upload Size": "",
|
||||
@@ -718,8 +754,12 @@
|
||||
"Memory deleted successfully": "",
|
||||
"Memory updated successfully": "",
|
||||
"Merge Responses": "",
|
||||
"Merged Response": "একত্রিত প্রতিক্রিয়া ফলাফল",
|
||||
"Message rating should be enabled to use this feature": "",
|
||||
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "আপনার লিঙ্ক তৈরি করার পরে আপনার পাঠানো বার্তাগুলি শেয়ার করা হবে না। ইউআরএল ব্যবহারকারীরা শেয়ার করা চ্যাট দেখতে পারবেন।",
|
||||
"Microsoft OneDrive": "",
|
||||
"Microsoft OneDrive (personal)": "",
|
||||
"Microsoft OneDrive (work/school)": "",
|
||||
"Min P": "",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
@@ -753,19 +793,22 @@
|
||||
"Mojeek Search API Key": "",
|
||||
"more": "",
|
||||
"More": "আরো",
|
||||
"My Notes": "",
|
||||
"Name": "নাম",
|
||||
"Name your knowledge base": "",
|
||||
"Native": "",
|
||||
"New Chat": "নতুন চ্যাট",
|
||||
"New Folder": "",
|
||||
"New Note": "",
|
||||
"New Password": "নতুন পাসওয়ার্ড",
|
||||
"new-channel": "",
|
||||
"No content": "",
|
||||
"No content found": "",
|
||||
"No content found in file.": "",
|
||||
"No content to speak": "",
|
||||
"No distance available": "",
|
||||
"No feedbacks found": "",
|
||||
"No file selected": "",
|
||||
"No files found.": "",
|
||||
"No groups with access, add a group to grant access": "",
|
||||
"No HTML, CSS, or JavaScript content found.": "",
|
||||
"No inference engine with management support found": "",
|
||||
@@ -774,6 +817,7 @@
|
||||
"No model IDs": "",
|
||||
"No models found": "",
|
||||
"No models selected": "",
|
||||
"No Notes": "",
|
||||
"No results found": "কোন ফলাফল পাওয়া যায়নি",
|
||||
"No search query generated": "কোনও অনুসন্ধান ক্যোয়ারী উত্পন্ন হয়নি",
|
||||
"No source available": "কোন উৎস পাওয়া যায়নি",
|
||||
@@ -782,6 +826,7 @@
|
||||
"None": "কোনোটিই নয়",
|
||||
"Not factually correct": "তথ্যগত দিক থেকে সঠিক নয়",
|
||||
"Not helpful": "",
|
||||
"Note deleted successfully": "",
|
||||
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "দ্রষ্টব্য: আপনি যদি ন্যূনতম স্কোর সেট করেন তবে অনুসন্ধানটি কেবলমাত্র ন্যূনতম স্কোরের চেয়ে বেশি বা সমান স্কোর সহ নথিগুলি ফেরত দেবে।",
|
||||
"Notes": "",
|
||||
"Notification Sound": "",
|
||||
@@ -804,6 +849,7 @@
|
||||
"Only alphanumeric characters and hyphens are allowed": "",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "কমান্ড স্ট্রিং-এ শুধুমাত্র ইংরেজি অক্ষর, সংখ্যা এবং হাইফেন ব্যবহার করা যাবে।",
|
||||
"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
|
||||
"Only markdown files are allowed": "",
|
||||
"Only select users and groups with permission can access": "",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "ওহ, মনে হচ্ছে ইউআরএলটা ইনভ্যালিড। দয়া করে আর চেক করে চেষ্টা করুন।",
|
||||
"Oops! There are files still uploading. Please wait for the upload to complete.": "",
|
||||
@@ -849,6 +895,7 @@
|
||||
"Pipelines": "পাইপলাইন",
|
||||
"Pipelines Not Detected": "",
|
||||
"Pipelines Valves": "পাইপলাইন ভালভ",
|
||||
"Plain text (.md)": "",
|
||||
"Plain text (.txt)": "প্লায়েন টেক্সট (.txt)",
|
||||
"Playground": "খেলাঘর",
|
||||
"Playwright Timeout (ms)": "",
|
||||
@@ -891,6 +938,7 @@
|
||||
"Read": "",
|
||||
"Read Aloud": "পড়াশোনা করুন",
|
||||
"Reasoning Effort": "",
|
||||
"Record": "",
|
||||
"Record voice": "ভয়েস রেকর্ড করুন",
|
||||
"Redirecting you to Open WebUI Community": "আপনাকে OpenWebUI কমিউনিটিতে পাঠানো হচ্ছে",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative.": "",
|
||||
@@ -911,9 +959,8 @@
|
||||
"Repeat Penalty (Ollama)": "",
|
||||
"Reply in Thread": "",
|
||||
"Request Mode": "রিকোয়েস্ট মোড",
|
||||
"Reranking Engine": "",
|
||||
"Reranking Model": "রির্যাক্টিং মডেল",
|
||||
"Reranking model disabled": "রির্যাক্টিং মডেল নিষ্ক্রিয় করা",
|
||||
"Reranking model set to \"{{reranking_model}}\"": "রির ্যাঙ্কিং মডেল \"{{reranking_model}}\" -এ সেট করা আছে",
|
||||
"Reset": "",
|
||||
"Reset All Models": "",
|
||||
"Reset Upload Directory": "",
|
||||
@@ -939,7 +986,7 @@
|
||||
"Save Tag": "",
|
||||
"Saved": "",
|
||||
"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": "মাধ্যমে",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Scroll On Branch Change": "",
|
||||
"Search": "অনুসন্ধান",
|
||||
"Search a model": "মডেল অনুসন্ধান করুন",
|
||||
"Search Base": "",
|
||||
@@ -960,6 +1007,7 @@
|
||||
"Searched {{count}} sites": "",
|
||||
"Searching \"{{searchQuery}}\"": "",
|
||||
"Searching Knowledge for \"{{searchQuery}}\"": "",
|
||||
"Searching the web...": "",
|
||||
"Searxng Query URL": "Searxng ক্যোয়ারী URL",
|
||||
"See readme.md for instructions": "নির্দেশিকার জন্য readme.md দেখুন",
|
||||
"See what's new": "নতুন কী আছে দেখুন",
|
||||
@@ -1020,6 +1068,8 @@
|
||||
"Show": "দেখান",
|
||||
"Show \"What's New\" modal on login": "",
|
||||
"Show Admin Details in Account Pending Overlay": "",
|
||||
"Show All": "",
|
||||
"Show Less": "",
|
||||
"Show Model": "",
|
||||
"Show shortcuts": "শর্টকাটগুলো দেখান",
|
||||
"Show your support!": "",
|
||||
@@ -1043,6 +1093,7 @@
|
||||
"Stream Chat Response": "",
|
||||
"STT Model": "",
|
||||
"STT Settings": "STT সেটিংস",
|
||||
"Stylized PDF Export": "",
|
||||
"Subtitle (e.g. about the Roman Empire)": "সাবটাইটল (রোমান ইম্পার্টের সম্পর্কে)",
|
||||
"Success": "সফল",
|
||||
"Successfully updated.": "সফলভাবে আপডেট হয়েছে",
|
||||
@@ -1089,6 +1140,7 @@
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "এটা নিশ্চিত করে যে, আপনার গুরুত্বপূর্ণ আলোচনা নিরাপদে আপনার ব্যাকএন্ড ডেটাবেজে সংরক্ষিত আছে। ধন্যবাদ!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
|
||||
"This model is not publicly available. Please select another model.": "",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
"This option sets the maximum number of tokens the model can generate in its response. Increasing this limit allows the model to provide longer answers, but it may also increase the likelihood of unhelpful or irrelevant content being generated.": "",
|
||||
"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
|
||||
@@ -1140,7 +1192,6 @@
|
||||
"Tools Access": "",
|
||||
"Tools are a function calling system with arbitrary code execution": "",
|
||||
"Tools Function Calling Prompt": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "",
|
||||
"Tools Public Sharing": "",
|
||||
"Top K": "Top K",
|
||||
@@ -1163,6 +1214,7 @@
|
||||
"Unpin": "",
|
||||
"Unravel secrets": "",
|
||||
"Untagged": "",
|
||||
"Untitled": "",
|
||||
"Update": "",
|
||||
"Update and Copy Link": "আপডেট এবং লিংক কপি করুন",
|
||||
"Update for the latest features and improvements.": "",
|
||||
@@ -1173,6 +1225,7 @@
|
||||
"Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.": "",
|
||||
"Upload": "",
|
||||
"Upload a GGUF model": "একটি GGUF মডেল আপলোড করুন",
|
||||
"Upload Audio": "",
|
||||
"Upload directory": "",
|
||||
"Upload files": "",
|
||||
"Upload Files": "ফাইল আপলোড করুন",
|
||||
@@ -1246,13 +1299,15 @@
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "৫০ শব্দের মধ্যে [topic or keyword] এর একটি সারসংক্ষেপ লিখুন।",
|
||||
"Write something...": "",
|
||||
"Write your model template content here": "",
|
||||
"Yacy Instance URL": "",
|
||||
"Yacy Password": "",
|
||||
"Yacy Username": "",
|
||||
"Yesterday": "আগামী",
|
||||
"You": "আপনি",
|
||||
"You are currently using a trial license. Please contact support to upgrade your license.": "",
|
||||
"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
|
||||
"You cannot upload an empty file.": "",
|
||||
"You do not have permission to upload files": "",
|
||||
"You do not have permission to upload files.": "",
|
||||
"You have no archived conversations.": "আপনার কোনও আর্কাইভ করা কথোপকথন নেই।",
|
||||
"You have shared this chat": "আপনি এই চ্যাটটি শেয়ার করেছেন",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"(e.g. `sh webui.sh --api --api-auth username_password`)": "(དཔེར་ན། `sh webui.sh --api --api-auth username_password`)",
|
||||
"(e.g. `sh webui.sh --api`)": "(དཔེར་ན། `sh webui.sh --api`)",
|
||||
"(latest)": "(ཆེས་གསར།)",
|
||||
"(leave blank for to use commercial endpoint)": "",
|
||||
"(Ollama)": "(Ollama)",
|
||||
"{{ models }}": "{{ models }}",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
@@ -62,6 +63,8 @@
|
||||
"Allow Chat Delete": "ཁ་བརྡ་བསུབ་པར་གནང་བ་སྤྲོད་པ།",
|
||||
"Allow Chat Deletion": "ཁ་བརྡ་བསུབ་པར་གནང་བ་སྤྲོད་པ།",
|
||||
"Allow Chat Edit": "ཁ་བརྡ་ཞུ་དག་ལ་གནང་བ་སྤྲོད་པ།",
|
||||
"Allow Chat Export": "",
|
||||
"Allow Chat Share": "",
|
||||
"Allow File Upload": "ཡིག་ཆ་སྤར་བར་གནང་བ་སྤྲོད་པ།",
|
||||
"Allow Multiple Models in Chat": "",
|
||||
"Allow non-local voices": "ས་གནས་མིན་པའི་སྐད་གདངས་ལ་གནང་བ་སྤྲོད་པ།",
|
||||
@@ -76,6 +79,7 @@
|
||||
"Always": "རྟག་ཏུ།",
|
||||
"Always Collapse Code Blocks": "རྟག་ཏུ་ཀོཌ་གཏོགས་ཁོངས་བསྐུམ་པ།",
|
||||
"Always Expand Details": "རྟག་ཏུ་ཞིབ་ཕྲ་རྒྱ་བསྐྱེད་པ།",
|
||||
"Always Play Notification Sound": "",
|
||||
"Amazing": "ངོ་མཚར་ཆེན།",
|
||||
"an assistant": "ལག་རོགས་པ།",
|
||||
"Analyzed": "དབྱེ་ཞིབ་བྱས་པ།",
|
||||
@@ -101,6 +105,7 @@
|
||||
"Are you sure you want to delete this channel?": "ཁྱེད་ཀྱིས་བགྲོ་གླེང་འདི་བསུབ་འདོད་ངེས་ཡིན་ནམ།",
|
||||
"Are you sure you want to delete this message?": "འཕྲིན་འདི་བསུབ་འདོད་ངེས་ཡིན་ནམ།",
|
||||
"Are you sure you want to unarchive all archived chats?": "ཁྱེད་ཀྱིས་ཡིག་མཛོད་དུ་བཞག་པའི་ཁ་བརྡ་ཡོངས་རྫོགས་ཕྱིར་འདོན་འདོད་ངེས་ཡིན་ནམ།",
|
||||
"Are you sure you want to update this user's role to **{{ROLE}}**?": "",
|
||||
"Are you sure?": "ཁྱོད་ངེས་པ་ཡིན་ནམ།",
|
||||
"Arena Models": "Arena དཔེ་དབྱིབས།",
|
||||
"Artifacts": "རྫས་རྟེན།",
|
||||
@@ -143,6 +148,7 @@
|
||||
"Bing Search V7 Subscription Key": "Bing Search V7 མངགས་ཉོ་ལྡེ་མིག",
|
||||
"Bocha Search API Key": "Bocha Search API ལྡེ་མིག",
|
||||
"Boosting or penalizing specific tokens for constrained responses. Bias values will be clamped between -100 and 100 (inclusive). (Default: none)": "ཚད་བཀག་ལན་གྱི་ཆེད་དུ་ཊོཀ་ཀེན་ངེས་ཅན་ལ་ཤུགས་སྣོན་ནམ་ཉེས་ཆད་གཏོང་བ། ཕྱོགས་ཞེན་གྱི་རིན་ཐང་ -100 ནས་ 100 བར་བཙིར་ངེས། (ཚུད་པ།) (སྔོན་སྒྲིག་མེད།)",
|
||||
"Both Docling OCR Engine and Language(s) must be provided or both left empty.": "",
|
||||
"Brave Search API Key": "Brave Search API ལྡེ་མིག",
|
||||
"By {{name}}": "{{name}} ཡིས།",
|
||||
"Bypass Embedding and Retrieval": "ཚུད་འཇུག་དང་ལེན་ཚུར་སྒྲུབ་ལས་བརྒལ་བ།",
|
||||
@@ -153,6 +159,7 @@
|
||||
"Cancel": "རྩིས་མེད།",
|
||||
"Capabilities": "ནུས་པ།",
|
||||
"Capture": "འཛིན་པ།",
|
||||
"Capture Audio": "",
|
||||
"Certificate Path": "ལག་ཁྱེར་གྱི་ལམ་བུ།",
|
||||
"Change Password": "གསང་གྲངས་བརྗེ་བ།",
|
||||
"Channel Name": "བགྲོ་གླེང་གི་མིང་།",
|
||||
@@ -262,6 +269,8 @@
|
||||
"Create Knowledge": "ཤེས་བྱ་གསར་བཟོ།",
|
||||
"Create new key": "ལྡེ་མིག་གསར་པ་བཟོ་བ།",
|
||||
"Create new secret key": "གསང་བའི་ལྡེ་མིག་གསར་པ་བཟོ་བ།",
|
||||
"Create Note": "",
|
||||
"Create your first note by clicking on the plus button below.": "",
|
||||
"Created at": "གསར་བཟོ་བྱེད་དུས།",
|
||||
"Created At": "གསར་བཟོ་བྱེད་དུས།",
|
||||
"Created by": "གསར་བཟོ་བྱེད་མཁན།",
|
||||
@@ -299,6 +308,7 @@
|
||||
"Delete function?": "ལས་འགན་བསུབ་པ།?",
|
||||
"Delete Message": "འཕྲིན་བསུབ་པ།",
|
||||
"Delete message?": "འཕྲིན་བསུབ་པ།?",
|
||||
"Delete note?": "",
|
||||
"Delete prompt?": "འགུལ་སློང་བསུབ་པ།?",
|
||||
"delete this link": "སྦྲེལ་ཐག་འདི་བསུབ་པ།",
|
||||
"Delete tool?": "ལག་ཆ་བསུབ་པ།?",
|
||||
@@ -354,7 +364,7 @@
|
||||
"Download Database": "གནས་ཚུལ་མཛོད་ཕབ་ལེན།",
|
||||
"Drag and drop a file to upload or select a file to view": "ཡིག་ཆ་ཞིག་འདྲུད་ནས་འཇོག་སྟེ་སྤར་བའམ། ཡང་ན་ཡིག་ཆ་ཞིག་གདམ་ག་བྱས་ནས་ལྟ་བ།",
|
||||
"Draw": "རི་མོ་འབྲི་བ།",
|
||||
"Drop any files here to add to the conversation": "ཡིག་ཆ་གང་རུང་འདིར་བཞག་ནས་ཁ་བརྡར་སྣོན་པ།",
|
||||
"Drop any files here to upload": "",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "དཔེར་ན། '30s','10m'. ནུས་ལྡན་དུས་ཚོད་ཀྱི་ཚན་པ་ནི། 's', 'm', 'h' ཡིན།",
|
||||
"e.g. \"json\" or a JSON schema": "དཔེར་ན། \"json\" ཡང་ན་ JSON གི་ schema",
|
||||
"e.g. 60": "དཔེར་ན། ༦༠",
|
||||
@@ -364,7 +374,9 @@
|
||||
"e.g. my_filter": "དཔེར་ན། my_filter",
|
||||
"e.g. my_tools": "དཔེར་ན། my_tools",
|
||||
"e.g. Tools for performing various operations": "དཔེར་ན། ལས་ཀ་སྣ་ཚོགས་སྒྲུབ་བྱེད་ཀྱི་ལག་ཆ།",
|
||||
"e.g., 3, 4, 5 (leave blank for default)": "",
|
||||
"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
|
||||
"e.g., westus (leave blank for eastus)": "",
|
||||
"Edit": "ཞུ་དག",
|
||||
"Edit Arena Model": "Arena དཔེ་དབྱིབས་ཞུ་དག",
|
||||
"Edit Channel": "བགྲོ་གླེང་ཞུ་དག",
|
||||
@@ -392,7 +404,9 @@
|
||||
"Enable Mirostat sampling for controlling perplexity.": "རྙོག་འཛིང་ཚད་ཚོད་འཛིན་གྱི་ཆེད་དུ་ Mirostat མ་དཔེ་འདེམས་པ་སྒུལ་བསྐྱོད་བྱེད་པ།",
|
||||
"Enable New Sign Ups": "ཐོ་འགོད་གསར་པ་སྒུལ་བསྐྱོད་བྱེད་པ།",
|
||||
"Enabled": "སྒུལ་བསྐྱོད་བྱས་ཡོད།",
|
||||
"Endpoint URL": "",
|
||||
"Enforce Temporary Chat": "གནས་སྐབས་ཁ་བརྡ་བཙན་བཀོལ་བྱེད་པ།",
|
||||
"Enhance": "",
|
||||
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "ཁྱེད་ཀྱི་ CSV ཡིག་ཆར་གོ་རིམ་འདི་ལྟར། མིང་། ཡིག་ཟམ། གསང་གྲངས། གནས་ཚད། སྟར་པ་ ༤ ཚུད་ཡོད་པ་ཁག་ཐེག་བྱེད་རོགས།",
|
||||
"Enter {{role}} message here": "{{role}} ཡི་འཕྲིན་འདིར་འཇུག་པ།",
|
||||
"Enter a detail about yourself for your LLMs to recall": "ཁྱེད་ཀྱི་ LLMs ཡིས་ཕྱིར་དྲན་ཆེད་དུ་ཁྱེད་རང་གི་སྐོར་གྱི་ཞིབ་ཕྲ་ཞིག་འཇུག་པ།",
|
||||
@@ -407,13 +421,19 @@
|
||||
"Enter CFG Scale (e.g. 7.0)": "CFG ཆེ་ཆུང་འཇུག་པ། (དཔེར་ན། 7.0)",
|
||||
"Enter Chunk Overlap": "དུམ་བུ་བསྣོལ་བ་འཇུག་པ།",
|
||||
"Enter Chunk Size": "དུམ་བུའི་ཆེ་ཆུང་འཇུག་པ།",
|
||||
"Enter comma-seperated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "ཚེག་བསྐུངས་ཀྱིས་ལོགས་སུ་བཀར་བའི་ \"ཊོཀ་ཀེན།:ཕྱོགས་ཞེན་རིན་ཐང་།\" ཆ་འཇུག་པ། (དཔེར། 5432:100, 413:-100)",
|
||||
"Enter comma-separated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "ཚེག་བསྐུངས་ཀྱིས་ལོགས་སུ་བཀར་བའི་ \"ཊོཀ་ཀེན།:ཕྱོགས་ཞེན་རིན་ཐང་།\" ཆ་འཇུག་པ། (དཔེར། 5432:100, 413:-100)",
|
||||
"Enter description": "འགྲེལ་བཤད་འཇུག་པ།",
|
||||
"Enter Docling OCR Engine": "",
|
||||
"Enter Docling OCR Language(s)": "",
|
||||
"Enter Docling Server URL": "Docling སར་བར་གྱི་ URL འཇུག་པ།",
|
||||
"Enter Document Intelligence Endpoint": "ཡིག་ཆའི་རིག་ནུས་མཇུག་མཐུད་འཇུག་པ།",
|
||||
"Enter Document Intelligence Key": "ཡིག་ཆའི་རིག་ནུས་ལྡེ་མིག་འཇུག་པ།",
|
||||
"Enter domains separated by commas (e.g., example.com,site.org)": "ཚེག་བསྐུངས་ཀྱིས་ལོགས་སུ་བཀར་བའི་ཁྱབ་ཁོངས་འཇུག་པ། (དཔེར་ན། example.com,site.org)",
|
||||
"Enter Exa API Key": "Exa API ལྡེ་མིག་འཇུག་པ།",
|
||||
"Enter External Web Loader API Key": "",
|
||||
"Enter External Web Loader URL": "",
|
||||
"Enter External Web Search API Key": "",
|
||||
"Enter External Web Search URL": "",
|
||||
"Enter Firecrawl API Base URL": "",
|
||||
"Enter Firecrawl API Key": "",
|
||||
"Enter Github Raw URL": "Github Raw URL འཇུག་པ།",
|
||||
@@ -431,6 +451,7 @@
|
||||
"Enter Model ID": "དཔེ་དབྱིབས་ཀྱི་ ID འཇུག་པ།",
|
||||
"Enter model tag (e.g. {{modelTag}})": "དཔེ་དབྱིབས་ཀྱི་རྟགས་འཇུག་པ། (དཔེར་ན། {{modelTag}})",
|
||||
"Enter Mojeek Search API Key": "Mojeek Search API ལྡེ་མིག་འཇུག་པ།",
|
||||
"Enter New Password": "",
|
||||
"Enter Number of Steps (e.g. 50)": "གོམ་གྲངས་འཇུག་པ། (དཔེར་ན། ༥༠)",
|
||||
"Enter Perplexity API Key": "Perplexity API ལྡེ་མིག་འཇུག་པ།",
|
||||
"Enter Playwright Timeout": "",
|
||||
@@ -467,11 +488,15 @@
|
||||
"Enter Top K Reranker": "Top K Reranker འཇུག་པ།",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "URL འཇུག་པ། (དཔེར་ན། http://127.0.0.1:7860/)",
|
||||
"Enter URL (e.g. http://localhost:11434)": "URL འཇུག་པ། (དཔེར་ན། http://localhost:11434)",
|
||||
"Enter Yacy Password": "",
|
||||
"Enter Yacy URL (e.g. http://yacy.example.com:8090)": "",
|
||||
"Enter Yacy Username": "",
|
||||
"Enter your current password": "ཁྱེད་ཀྱི་ད་ལྟའི་གསང་གྲངས་འཇུག་པ།",
|
||||
"Enter Your Email": "ཁྱེད་ཀྱི་ཡིག་ཟམ་འཇུག་པ།",
|
||||
"Enter Your Full Name": "ཁྱེད་ཀྱི་མིང་ཆ་ཚང་འཇུག་པ།",
|
||||
"Enter your message": "ཁྱེད་ཀྱི་འཕྲིན་འཇུག་པ།",
|
||||
"Enter your name": "",
|
||||
"Enter Your Name": "",
|
||||
"Enter your new password": "ཁྱེད་ཀྱི་གསང་གྲངས་གསར་པ་འཇུག་པ།",
|
||||
"Enter Your Password": "ཁྱེད་ཀྱི་གསང་གྲངས་འཇུག་པ།",
|
||||
"Enter Your Role": "ཁྱེད་ཀྱི་གནས་ཚད་འཇུག་པ།",
|
||||
@@ -480,6 +505,8 @@
|
||||
"Error": "ནོར་འཁྲུལ།",
|
||||
"ERROR": "ནོར་འཁྲུལ།",
|
||||
"Error accessing Google Drive: {{error}}": "Google Drive འཛུལ་སྤྱོད་སྐབས་ནོར་འཁྲུལ།: {{error}}",
|
||||
"Error accessing media devices.": "",
|
||||
"Error starting recording.": "",
|
||||
"Error uploading file: {{error}}": "ཡིག་ཆ་སྤར་སྐབས་ནོར་འཁྲུལ།: {{error}}",
|
||||
"Evaluations": "གདེང་འཇོག",
|
||||
"Exa API Key": "Exa API ལྡེ་མིག",
|
||||
@@ -511,10 +538,16 @@
|
||||
"Export Tools": "ལག་ཆ་ཕྱིར་གཏོང་།",
|
||||
"External": "ཕྱི་རོལ།",
|
||||
"External Models": "ཕྱི་རོལ་གྱི་དཔེ་དབྱིབས།",
|
||||
"External Web Loader API Key": "",
|
||||
"External Web Loader URL": "",
|
||||
"External Web Search API Key": "",
|
||||
"External Web Search URL": "",
|
||||
"Failed to add file.": "ཡིག་ཆ་སྣོན་པར་མ་ཐུབ།",
|
||||
"Failed to connect to {{URL}} OpenAPI tool server": "{{URL}} OpenAPI ལག་ཆའི་སར་བར་ལ་སྦྲེལ་མཐུད་བྱེད་མ་ཐུབ།",
|
||||
"Failed to create API Key.": "API ལྡེ་མིག་བཟོ་མ་ཐུབ།",
|
||||
"Failed to delete note": "",
|
||||
"Failed to fetch models": "དཔེ་དབྱིབས་ལེན་པར་མ་ཐུབ།",
|
||||
"Failed to load file content.": "",
|
||||
"Failed to read clipboard contents": "སྦྱར་སྡེར་གྱི་ནང་དོན་ཀློག་མ་ཐུབ།",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "དཔེ་དབྱིབས་སྒྲིག་འགོད་ཉར་ཚགས་བྱེད་མ་ཐུབ།",
|
||||
@@ -544,7 +577,6 @@
|
||||
"Fluidly stream large external response chunks": "ཕྱི་རོལ་གྱི་ལན་གྱི་དུམ་བུ་ཆེན་པོ་རྒྱུན་བཞིན་རྒྱུག་པ།",
|
||||
"Focus chat input": "ཁ་བརྡའི་ནང་འཇུག་ལ་དམིགས་པ།",
|
||||
"Folder deleted successfully": "ཡིག་སྣོད་ལེགས་པར་བསུབས་ཟིན།",
|
||||
"Folder name cannot be empty": "ཡིག་སྣོད་ཀྱི་མིང་སྟོང་པ་ཡིན་མི་ཆོག",
|
||||
"Folder name cannot be empty.": "ཡིག་སྣོད་ཀྱི་མིང་སྟོང་པ་ཡིན་མི་ཆོག",
|
||||
"Folder name updated successfully": "ཡིག་སྣོད་ཀྱི་མིང་ལེགས་པར་གསར་སྒྱུར་བྱས་ཟིན།",
|
||||
"Followed instructions perfectly": "ལམ་སྟོན་ཡང་དག་པར་བསྒྲུབས།",
|
||||
@@ -565,17 +597,18 @@
|
||||
"Function Name": "ལས་འགན་གྱི་མིང་།",
|
||||
"Function updated successfully": "ལས་འགན་ལེགས་པར་གསར་སྒྱུར་བྱས་ཟིན།",
|
||||
"Functions": "ལས་འགན།",
|
||||
"Functions allow arbitrary code execution": "ལས་འགན་གྱིས་གང་འདོད་ཀྱི་ཀོཌ་ལག་བསྟར་ལ་གནང་བ་སྤྲོད།",
|
||||
"Functions allow arbitrary code execution.": "ལས་འགན་གྱིས་གང་འདོད་ཀྱི་ཀོཌ་ལག་བསྟར་ལ་གནང་བ་སྤྲོད།",
|
||||
"Functions imported successfully": "ལས་འགན་ལེགས་པར་ནང་འདྲེན་བྱས།",
|
||||
"Gemini": "Gemini",
|
||||
"Gemini API Config": "Gemini API Config",
|
||||
"Gemini API Key is required.": "Gemini API ལྡེ་མིག་དགོས་ངེས།",
|
||||
"General": "སྤྱིར་བཏང་།",
|
||||
"Generate": "",
|
||||
"Generate an image": "པར་ཞིག་བཟོ་བ།",
|
||||
"Generate Image": "པར་བཟོ་བ།",
|
||||
"Generate prompt pair": "འགུལ་སློང་ཆ་ཞིག་བཟོ་བ།",
|
||||
"Generating search query": "འཚོལ་བཤེར་འདྲི་བ་བཟོ་བཞིན་པ།",
|
||||
"Generating...": "",
|
||||
"Get started": "འགོ་འཛུགས།",
|
||||
"Get started with {{WEBUI_NAME}}": "{{WEBUI_NAME}} དང་མཉམ་དུ་འགོ་འཛུགས་པ།",
|
||||
"Global": "འཛམ་གླིང་ཡོངས་ཀྱི་",
|
||||
@@ -622,6 +655,7 @@
|
||||
"Import Config from JSON File": "JSON ཡིག་ཆ་ནས་སྒྲིག་འགོད་ནང་འདྲེན།",
|
||||
"Import Functions": "ལས་འགན་ནང་འདྲེན།",
|
||||
"Import Models": "དཔེ་དབྱིབས་ནང་འདྲེན།",
|
||||
"Import Notes": "",
|
||||
"Import Presets": "སྔོན་སྒྲིག་ནང་འདྲེན།",
|
||||
"Import Prompts": "འགུལ་སློང་ནང་འདྲེན།",
|
||||
"Import Tools": "ལག་ཆ་ནང་འདྲེན།",
|
||||
@@ -636,6 +670,7 @@
|
||||
"Instant Auto-Send After Voice Transcription": "སྐད་ཆ་ཡིག་འབེབས་བྱས་རྗེས་ལམ་སང་རང་འགུལ་གཏོང་བ།",
|
||||
"Integration": "མཉམ་འདྲེས།",
|
||||
"Interface": "ངོས་འཛིན།",
|
||||
"Invalid file content": "",
|
||||
"Invalid file format.": "ཡིག་ཆའི་བཀོད་པ་ནུས་མེད།",
|
||||
"Invalid JSON schema": "JSON schema ནུས་མེད།",
|
||||
"Invalid Tag": "རྟགས་ནུས་མེད།",
|
||||
@@ -706,6 +741,7 @@
|
||||
"Manage Pipelines": "རྒྱུ་ལམ་དོ་དམ།",
|
||||
"Manage Tool Servers": "ལག་ཆའི་སར་བར་དོ་དམ།",
|
||||
"March": "ཟླ་བ་གསུམ་པ།",
|
||||
"Max Speakers": "",
|
||||
"Max Tokens (num_predict)": "ཊོཀ་ཀེན་མང་ཤོས། (num_predict)",
|
||||
"Max Upload Count": "སྤར་བའི་གྲངས་མང་ཤོས།",
|
||||
"Max Upload Size": "སྤར་བའི་ཆེ་ཆུང་མང་ཤོས།",
|
||||
@@ -718,8 +754,12 @@
|
||||
"Memory deleted successfully": "དྲན་ཤེས་ལེགས་པར་བསུབས་ཟིན།",
|
||||
"Memory updated successfully": "དྲན་ཤེས་ལེགས་པར་གསར་སྒྱུར་བྱས་ཟིན།",
|
||||
"Merge Responses": "ལན་ཟླ་སྒྲིལ།",
|
||||
"Merged Response": "བསྡུར་མཐུན་གྱི་ལན་གསལ་གནས་ཡོད།",
|
||||
"Message rating should be enabled to use this feature": "ཁྱད་ཆོས་འདི་བེད་སྤྱོད་གཏོང་བར་འཕྲིན་ལ་སྐར་མ་སྤྲོད་པ་སྒུལ་བསྐྱོད་བྱེད་དགོས།",
|
||||
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "ཁྱེད་ཀྱི་སྦྲེལ་ཐག་བཟོས་རྗེས་ཁྱེད་ཀྱིས་བསྐུར་བའི་འཕྲིན་དག་མཉམ་སྤྱོད་བྱེད་མི་འགྱུར། URL ཡོད་པའི་བེད་སྤྱོད་མཁན་ཚོས་མཉམ་སྤྱོད་ཁ་བརྡ་ལྟ་ཐུབ་ངེས།",
|
||||
"Microsoft OneDrive": "",
|
||||
"Microsoft OneDrive (personal)": "",
|
||||
"Microsoft OneDrive (work/school)": "",
|
||||
"Min P": "P ཉུང་ཤོས།",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
@@ -753,19 +793,22 @@
|
||||
"Mojeek Search API Key": "Mojeek Search API ལྡེ་མིག",
|
||||
"more": "མང་བ།",
|
||||
"More": "མང་བ།",
|
||||
"My Notes": "",
|
||||
"Name": "མིང་།",
|
||||
"Name your knowledge base": "ཁྱེད་ཀྱི་ཤེས་བྱའི་རྟེན་གཞི་ལ་མིང་ཐོགས།",
|
||||
"Native": "ས་སྐྱེས།",
|
||||
"New Chat": "ཁ་བརྡ་གསར་པ།",
|
||||
"New Folder": "ཡིག་སྣོད་གསར་པ།",
|
||||
"New Note": "",
|
||||
"New Password": "གསང་གྲངས་གསར་པ།",
|
||||
"new-channel": "བགྲོ་གླེང་གསར་པ།",
|
||||
"No content": "",
|
||||
"No content found": "ནང་དོན་མ་རྙེད།",
|
||||
"No content found in file.": "",
|
||||
"No content to speak": "བཤད་རྒྱུའི་ནང་དོན་མེད།",
|
||||
"No distance available": "ཐག་རིང་ཚད་མེད།",
|
||||
"No feedbacks found": "བསམ་འཆར་མ་རྙེད།",
|
||||
"No file selected": "ཡིག་ཆ་གདམ་ག་མ་བྱས།",
|
||||
"No files found.": "ཡིག་ཆ་མ་རྙེད།",
|
||||
"No groups with access, add a group to grant access": "འཛུལ་སྤྱོད་ཡོད་པའི་ཚོགས་པ་མེད། འཛུལ་སྤྱོད་སྤྲོད་པར་ཚོགས་པ་ཞིག་སྣོན་པ།",
|
||||
"No HTML, CSS, or JavaScript content found.": "HTML, CSS, ཡང་ན་ JavaScript གི་ནང་དོན་མ་རྙེད།",
|
||||
"No inference engine with management support found": "དོ་དམ་རྒྱབ་སྐྱོར་ཡོད་པའི་དཔོག་རྩིས་འཕྲུལ་འཁོར་མ་རྙེད།",
|
||||
@@ -774,6 +817,7 @@
|
||||
"No model IDs": "དཔེ་དབྱིབས་ཀྱི་ ID མེད།",
|
||||
"No models found": "དཔེ་དབྱིབས་མ་རྙེད།",
|
||||
"No models selected": "དཔེ་དབྱིབས་གདམ་ག་མ་བྱས།",
|
||||
"No Notes": "",
|
||||
"No results found": "འབྲས་བུ་མ་རྙེད།",
|
||||
"No search query generated": "འཚོལ་བཤེར་འདྲི་བ་བཟོས་མེད།",
|
||||
"No source available": "འབྱུང་ཁུངས་མེད།",
|
||||
@@ -782,6 +826,7 @@
|
||||
"None": "གཅིག་ཀྱང་མེད།",
|
||||
"Not factually correct": "དོན་དངོས་དང་མི་མཐུན།",
|
||||
"Not helpful": "ཕན་ཐོགས་མེད།",
|
||||
"Note deleted successfully": "",
|
||||
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "དོ་སྣང་།: གལ་ཏེ་ཁྱེད་ཀྱིས་སྐར་མ་ཉུང་ཤོས་ཤིག་བཀོད་སྒྲིག་བྱས་ན། འཚོལ་བཤེར་གྱིས་སྐར་མ་ཉུང་ཤོས་དེ་དང་མཉམ་པའམ་དེ་ལས་ཆེ་བའི་ཡིག་ཆ་ཁོ་ན་ཕྱིར་སློག་བྱེད་ངེས།",
|
||||
"Notes": "མཆན་བུ།",
|
||||
"Notification Sound": "བརྡ་ཁྱབ་ཀྱི་སྒྲ།",
|
||||
@@ -804,6 +849,7 @@
|
||||
"Only alphanumeric characters and hyphens are allowed": "ཨང་ཀི་དང་དབྱིན་ཡིག་གི་ཡིག་འབྲུ་དང་སྦྲེལ་རྟགས་ཁོ་ན་ཆོག་པ།",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "བཀའ་བརྡའི་ཡིག་ཕྲེང་ནང་ཨང་ཀི་དང་དབྱིན་ཡིག་གི་ཡིག་འབྲུ་དང་སྦྲེལ་རྟགས་ཁོ་ན་ཆོག་པ།",
|
||||
"Only collections can be edited, create a new knowledge base to edit/add documents.": "བསྡུ་གསོག་ཁོ་ན་ཞུ་དག་བྱེད་ཐུབ། ཡིག་ཆ་ཞུ་དག་/སྣོན་པར་ཤེས་བྱའི་རྟེན་གཞི་གསར་པ་ཞིག་བཟོ་བ།",
|
||||
"Only markdown files are allowed": "",
|
||||
"Only select users and groups with permission can access": "དབང་ཚད་ཡོད་པའི་བེད་སྤྱོད་མཁན་དང་ཚོགས་པ་གདམ་ག་བྱས་པ་ཁོ་ན་འཛུལ་སྤྱོད་ཐུབ།",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "ཨོའོ། URL དེ་ནུས་མེད་ཡིན་པ་འདྲ། ཡང་བསྐྱར་ཞིབ་དཔྱད་བྱས་ནས་ཚོད་ལྟ་བྱེད་རོགས།",
|
||||
"Oops! There are files still uploading. Please wait for the upload to complete.": "ཨོའོ། ད་དུང་སྤར་བཞིན་པའི་ཡིག་ཆ་ཡོད། སྤར་ཚར་བར་སྒུག་རོགས།",
|
||||
@@ -849,6 +895,7 @@
|
||||
"Pipelines": "རྒྱུ་ལམ།",
|
||||
"Pipelines Not Detected": "རྒྱུ་ལམ་མ་རྙེད།",
|
||||
"Pipelines Valves": "རྒྱུ་ལམ་གྱི་ Valve",
|
||||
"Plain text (.md)": "",
|
||||
"Plain text (.txt)": "ཡིག་རྐྱང་རྐྱང་པ། (.txt)",
|
||||
"Playground": "རྩེད་ཐང་།",
|
||||
"Playwright Timeout (ms)": "",
|
||||
@@ -891,6 +938,7 @@
|
||||
"Read": "ཀློག་པ།",
|
||||
"Read Aloud": "སྐད་གསལ་པོས་ཀློག་པ།",
|
||||
"Reasoning Effort": "རྒྱུ་མཚན་འདྲེན་པའི་འབད་བརྩོན།",
|
||||
"Record": "",
|
||||
"Record voice": "སྐད་སྒྲ་ཕབ་པ།",
|
||||
"Redirecting you to Open WebUI Community": "ཁྱེད་ Open WebUI སྤྱི་ཚོགས་ལ་ཁ་ཕྱོགས་སྒྱུར་བཞིན་པ།",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative.": "དོན་མེད་བཟོ་བའི་ཆགས་ཚུལ་ཉུང་དུ་གཏོང་བ། རིན་ཐང་མཐོ་བ་ (དཔེར་ན། ༡༠༠) ཡིས་ལན་སྣ་ཚོགས་ཆེ་བ་སྤྲོད་ངེས། དེ་བཞིན་དུ་རིན་ཐང་དམའ་བ་ (དཔེར་ན། ༡༠) ཡིས་སྲུང་འཛིན་ཆེ་བ་ཡོང་ངེས།",
|
||||
@@ -911,9 +959,8 @@
|
||||
"Repeat Penalty (Ollama)": "བསྐྱར་ཟློས་ཀྱི་ཆད་པ། (Ollama)",
|
||||
"Reply in Thread": "བརྗོད་གཞིའི་ནང་ལན་འདེབས།",
|
||||
"Request Mode": "རེ་ཞུའི་མ་དཔེ།",
|
||||
"Reranking Engine": "",
|
||||
"Reranking Model": "བསྐྱར་སྒྲིག་དཔེ་དབྱིབས།",
|
||||
"Reranking model disabled": "བསྐྱར་སྒྲིག་དཔེ་དབྱིབས་ནུས་མེད་བཏང་།",
|
||||
"Reranking model set to \"{{reranking_model}}\"": "བསྐྱར་སྒྲིག་དཔེ་དབྱིབས་ \"{{reranking_model}}\" ལ་བཀོད་སྒྲིག་བྱས།",
|
||||
"Reset": "སླར་སྒྲིག",
|
||||
"Reset All Models": "དཔེ་དབྱིབས་ཡོངས་རྫོགས་སླར་སྒྲིག",
|
||||
"Reset Upload Directory": "སྤར་བའི་ཐོ་འཚོལ་སླར་སྒྲིག",
|
||||
@@ -939,7 +986,7 @@
|
||||
"Save Tag": "རྟགས་ཉར་ཚགས།",
|
||||
"Saved": "ཉར་ཚགས་བྱས།",
|
||||
"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": "ཁ་བརྡའི་ཟིན་ཐོ་ཐད་ཀར་ཁྱེད་ཀྱི་བརྡ་འཚོལ་ཆས་ཀྱི་གསོག་ཆས་སུ་ཉར་ཚགས་བྱེད་པར་ད་ནས་བཟུང་རྒྱབ་སྐྱོར་མེད། གཤམ་གྱི་མཐེབ་གནོན་མནན་ནས་ཁྱེད་ཀྱི་ཁ་བརྡའི་ཟིན་ཐོ་ཕབ་ལེན་དང་བསུབ་པར་དུས་ཚོད་ཅུང་ཟད་བླང་རོགས། སེམས་ཁྲལ་མེད། ཁྱེད་ཀྱིས་སྟབས་བདེ་པོར་ཁྱེད་ཀྱི་ཁ་བརྡའི་ཟིན་ཐོ་རྒྱབ་སྣེ་ལ་བསྐྱར་དུ་ནང་འདྲེན་བྱེད་ཐུབ།",
|
||||
"Scroll to bottom when switching between branches": "ཡན་ལག་བརྗེ་སྐབས་མཐིལ་དུ་འགྲིལ་བ།",
|
||||
"Scroll On Branch Change": "",
|
||||
"Search": "འཚོལ་བཤེར།",
|
||||
"Search a model": "དཔེ་དབྱིབས་ཤིག་འཚོལ་བ།",
|
||||
"Search Base": "འཚོལ་བཤེར་གཞི་རྩ།",
|
||||
@@ -960,6 +1007,7 @@
|
||||
"Searched {{count}} sites": "དྲ་ཚིགས་ {{count}} འཚོལ་བཤེར་བྱས།",
|
||||
"Searching \"{{searchQuery}}\"": "\"{{searchQuery}}\" འཚོལ་བཞིན་པ།",
|
||||
"Searching Knowledge for \"{{searchQuery}}\"": "\"{{searchQuery}}\" ཆེད་དུ་ཤེས་བྱ་འཚོལ་བཞིན་པ།",
|
||||
"Searching the web...": "",
|
||||
"Searxng Query URL": "Searxng Query URL",
|
||||
"See readme.md for instructions": "ལམ་སྟོན་ཆེད་དུ་ readme.md ལ་ལྟ་བ།",
|
||||
"See what's new": "གསར་པ་ཅི་ཡོད་ལྟ་བ།",
|
||||
@@ -1020,6 +1068,8 @@
|
||||
"Show": "སྟོན་པ།",
|
||||
"Show \"What's New\" modal on login": "ནང་འཛུལ་སྐབས་ \"གསར་པ་ཅི་ཡོད\" modal སྟོན་པ།",
|
||||
"Show Admin Details in Account Pending Overlay": "རྩིས་ཁྲ་སྒུག་བཞིན་པའི་གཏོགས་ངོས་སུ་དོ་དམ་པའི་ཞིབ་ཕྲ་སྟོན་པ།",
|
||||
"Show All": "",
|
||||
"Show Less": "",
|
||||
"Show Model": "དཔེ་དབྱིབས་སྟོན་པ།",
|
||||
"Show shortcuts": "མྱུར་ལམ་སྟོན་པ།",
|
||||
"Show your support!": "ཁྱེད་ཀྱི་རྒྱབ་སྐྱོར་སྟོན་པ།",
|
||||
@@ -1043,6 +1093,7 @@
|
||||
"Stream Chat Response": "ཁ་བརྡའི་ལན་རྒྱུག་པ།",
|
||||
"STT Model": "STT དཔེ་དབྱིབས།",
|
||||
"STT Settings": "STT སྒྲིག་འགོད།",
|
||||
"Stylized PDF Export": "",
|
||||
"Subtitle (e.g. about the Roman Empire)": "ཁ་བྱང་ཕལ་པ། (དཔེར་ན། རོམ་མའི་གོང་མའི་རྒྱལ་ཁབ་སྐོར།)",
|
||||
"Success": "ལེགས་འགྲུབ།",
|
||||
"Successfully updated.": "ལེགས་པར་གསར་སྒྱུར་བྱས།",
|
||||
@@ -1089,6 +1140,7 @@
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "འདིས་ཁྱེད་ཀྱི་རྩ་ཆེའི་ཁ་བརྡ་དག་བདེ་འཇགས་ངང་ཁྱེད་ཀྱི་རྒྱབ་སྣེ་གནས་ཚུལ་མཛོད་དུ་ཉར་ཚགས་བྱེད་པ་ཁག་ཐེག་བྱེད། ཐུགས་རྗེ་ཆེ།",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "འདི་ནི་ཚོད་ལྟའི་རང་བཞིན་གྱི་ཁྱད་ཆོས་ཤིག་ཡིན། དེ་རེ་སྒུག་ལྟར་ལས་ཀ་བྱེད་མི་སྲིད། དེ་མིན་དུས་ཚོད་གང་རུང་ལ་འགྱུར་བ་འགྲོ་སྲིད།",
|
||||
"This model is not publicly available. Please select another model.": "",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "འདེམས་ཀ་འདིས་ནང་དོན་གསར་སྒྱུར་བྱེད་སྐབས་ཊོཀ་ཀེན་ག་ཚོད་ཉར་ཚགས་བྱེད་དགོས་ཚོད་འཛིན་བྱེད། དཔེར་ན། གལ་ཏེ་ ༢ ལ་བཀོད་སྒྲིག་བྱས་ན། ཁ་བརྡའི་ནང་དོན་གྱི་ཊོཀ་ཀེན་མཐའ་མ་ ༢ ཉར་ཚགས་བྱེད་ངེས། ནང་དོན་ཉར་ཚགས་བྱས་ན་ཁ་བརྡའི་རྒྱུན་མཐུད་རང་བཞིན་རྒྱུན་སྲུང་བྱེད་པར་རོགས་པ་བྱེད་ཐུབ། འོན་ཀྱང་དེས་བརྗོད་གཞི་གསར་པར་ལན་འདེབས་བྱེད་པའི་ནུས་པ་ཉུང་དུ་གཏོང་སྲིད།",
|
||||
"This option sets the maximum number of tokens the model can generate in its response. Increasing this limit allows the model to provide longer answers, but it may also increase the likelihood of unhelpful or irrelevant content being generated.": "འདེམས་ཀ་འདིས་དཔེ་དབྱིབས་ཀྱིས་དེའི་ལན་ནང་བཟོ་ཐུབ་པའི་ཊོཀ་ཀེན་གྱི་གྲངས་མང་ཤོས་འཇོག་པ། ཚད་བཀག་འདི་མང་དུ་བཏང་ན་དཔེ་དབྱིབས་ཀྱིས་ལན་རིང་བ་སྤྲོད་པར་གནང་བ་སྤྲོད། འོན་ཀྱང་དེས་ཕན་ཐོགས་མེད་པའམ་འབྲེལ་མེད་ཀྱི་ནང་དོན་བཟོ་བའི་ཆགས་ཚུལ་མང་དུ་གཏོང་སྲིད།",
|
||||
"This option will delete all existing files in the collection and replace them with newly uploaded files.": "འདེམས་ཀ་འདིས་བསྡུ་གསོག་ནང་གི་ཡོད་པའི་ཡིག་ཆ་ཡོངས་རྫོགས་བསུབ་ནས་དེ་དག་གསར་དུ་སྤར་བའི་ཡིག་ཆས་ཚབ་བྱེད་ངེས།",
|
||||
@@ -1140,7 +1192,6 @@
|
||||
"Tools Access": "ལག་ཆར་འཛུལ་སྤྱོད།",
|
||||
"Tools are a function calling system with arbitrary code execution": "ལག་ཆ་ནི་གང་འདོད་ཀྱི་ཀོཌ་ལག་བསྟར་ཡོད་པའི་ལས་འགན་འབོད་པའི་མ་ལག་ཅིག་ཡིན།",
|
||||
"Tools Function Calling Prompt": "ལག་ཆ་ལས་འགན་འབོད་པའི་འགུལ་སློང་།",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "ལག་ཆར་གང་འདོད་ཀྱི་ཀོཌ་ལག་བསྟར་ལ་གནང་བ་སྤྲོད་པའི་ལས་འགན་འབོད་པའི་མ་ལག་ཡོད།",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "ལག་ཆར་གང་འདོད་ཀྱི་ཀོཌ་ལག་བསྟར་ལ་གནང་བ་སྤྲོད་པའི་ལས་འགན་འབོད་པའི་མ་ལག་ཡོད།",
|
||||
"Tools Public Sharing": "ལག་ཆ་སྤྱི་སྤྱོད་མཉམ་སྤྱོད།",
|
||||
"Top K": "Top K",
|
||||
@@ -1163,6 +1214,7 @@
|
||||
"Unpin": "ཕྱིར་འདོན།",
|
||||
"Unravel secrets": "གསང་བ་གྲོལ་བ།",
|
||||
"Untagged": "རྟགས་མེད།",
|
||||
"Untitled": "",
|
||||
"Update": "གསར་སྒྱུར།",
|
||||
"Update and Copy Link": "གསར་སྒྱུར་དང་སྦྲེལ་ཐག་འདྲ་བཤུས།",
|
||||
"Update for the latest features and improvements.": "ཁྱད་ཆོས་དང་ལེགས་བཅོས་གསར་ཤོས་ཀྱི་ཆེད་དུ་གསར་སྒྱུར་བྱེད་པ།",
|
||||
@@ -1173,6 +1225,7 @@
|
||||
"Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.": "ནུས་པ་ཤུགས་ཆེ་བ། སྲོལ་བཟོས་བརྗོད་གཞི་དང་ཚོང་རྟགས། དམིགས་བསལ་རྒྱབ་སྐྱོར་ཚུད་པའི་ཆོག་མཆན་ཡོད་པའི་འཆར་གཞི་ལ་རིམ་སྤོར་བྱེད་པ།",
|
||||
"Upload": "སྤར་བ།",
|
||||
"Upload a GGUF model": "GGUF དཔེ་དབྱིབས་ཤིག་སྤར་བ།",
|
||||
"Upload Audio": "",
|
||||
"Upload directory": "སྤར་བའི་ཐོ་འཚོལ།",
|
||||
"Upload files": "ཡིག་ཆ་སྤར་བ།",
|
||||
"Upload Files": "ཡིག་ཆ་སྤར་བ།",
|
||||
@@ -1246,13 +1299,15 @@
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "[བརྗོད་གཞི་ཡང་ན་གནད་ཚིག] ཕྱོགས་སྡོམ་བྱེད་པའི་ཚིག་ ༥༠ ནང་གི་སྙིང་བསྡུས་ཤིག་འབྲི་བ།",
|
||||
"Write something...": "ཅི་ཞིག་འབྲི་བ།...",
|
||||
"Write your model template content here": "ཁྱེད་ཀྱི་དཔེ་དབྱིབས་མ་དཔེའི་ནང་དོན་འདིར་འབྲི་བ།",
|
||||
"Yacy Instance URL": "",
|
||||
"Yacy Password": "",
|
||||
"Yacy Username": "",
|
||||
"Yesterday": "ཁ་ས།",
|
||||
"You": "ཁྱེད།",
|
||||
"You are currently using a trial license. Please contact support to upgrade your license.": "ཁྱེད་ཀྱིས་ད་ལྟ་ཚོད་ལྟའི་ཆོག་མཆན་ཞིག་བེད་སྤྱོད་གཏོང་བཞིན་འདུག ཁྱེད་ཀྱི་ཆོག་མཆན་རིམ་སྤོར་བྱེད་པར་རོགས་སྐྱོར་དང་འབྲེལ་གཏུག་བྱེད་རོགས།",
|
||||
"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "ཁྱེད་ཀྱིས་ཐེངས་གཅིག་ལ་ཡིག་ཆ་ {{maxCount}} ལས་མང་བ་དང་ཁ་བརྡ་བྱེད་མི་ཐུབ།",
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "ཁྱེད་ཀྱིས་གཤམ་གྱི་ 'དོ་དམ་' མཐེབ་གནོན་བརྒྱུད་དྲན་ཤེས་སྣོན་ནས་ LLMs དང་མཉམ་དུ་འབྲེལ་འདྲིས་བྱེད་པ་སྒེར་སྤྱོད་ཅན་བཟོ་ཐུབ། དེ་དག་ཁྱེད་ལ་སྔར་ལས་ཕན་ཐོགས་པ་དང་འཚམ་པོ་བཟོ་ཐུབ།",
|
||||
"You cannot upload an empty file.": "ཁྱེད་ཀྱིས་ཡིག་ཆ་སྟོང་པ་སྤར་མི་ཐུབ།",
|
||||
"You do not have permission to upload files": "ཁྱེད་ལ་ཡིག་ཆ་སྤར་བའི་དབང་ཚད་མེད།",
|
||||
"You do not have permission to upload files.": "ཁྱེད་ལ་ཡིག་ཆ་སྤར་བའི་དབང་ཚད་མེད།",
|
||||
"You have no archived conversations.": "ཁྱེད་ལ་ཡིག་མཛོད་དུ་བཞག་པའི་ཁ་བརྡ་མེད།",
|
||||
"You have shared this chat": "ཁྱེད་ཀྱིས་ཁ་བརྡ་འདི་མཉམ་སྤྱོད་བྱས་ཡོད།",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"(e.g. `sh webui.sh --api --api-auth username_password`)": "(p. ex. `sh webui.sh --api --api-auth username_password`)",
|
||||
"(e.g. `sh webui.sh --api`)": "(p. ex. `sh webui.sh --api`)",
|
||||
"(latest)": "(últim)",
|
||||
"(leave blank for to use commercial endpoint)": "",
|
||||
"(Ollama)": "(Ollama)",
|
||||
"{{ models }}": "{{ models }}",
|
||||
"{{COUNT}} Available Tools": "{{COUNT}} eines disponibles",
|
||||
@@ -57,17 +58,19 @@
|
||||
"All": "Tots",
|
||||
"All Documents": "Tots els documents",
|
||||
"All models deleted successfully": "Tots els models s'han eliminat correctament",
|
||||
"Allow Call": "",
|
||||
"Allow Call": "Permetre la trucada",
|
||||
"Allow Chat Controls": "Permetre els controls de xat",
|
||||
"Allow Chat Delete": "Permetre eliminar el xat",
|
||||
"Allow Chat Deletion": "Permetre la supressió del xat",
|
||||
"Allow Chat Edit": "Permetre editar el xat",
|
||||
"Allow Chat Export": "Permetre exportar el xat",
|
||||
"Allow Chat Share": "Permetre compartir el xat",
|
||||
"Allow File Upload": "Permetre la pujada d'arxius",
|
||||
"Allow Multiple Models in Chat": "",
|
||||
"Allow Multiple Models in Chat": "Permetre múltiple models al xat",
|
||||
"Allow non-local voices": "Permetre veus no locals",
|
||||
"Allow Speech to Text": "",
|
||||
"Allow Speech to Text": "Permetre Parla a Text",
|
||||
"Allow Temporary Chat": "Permetre el xat temporal",
|
||||
"Allow Text to Speech": "",
|
||||
"Allow Text to Speech": "Permetre Text a Parla",
|
||||
"Allow User Location": "Permetre la ubicació de l'usuari",
|
||||
"Allow Voice Interruption in Call": "Permetre la interrupció de la veu en una trucada",
|
||||
"Allowed Endpoints": "Punts d'accés permesos",
|
||||
@@ -76,6 +79,7 @@
|
||||
"Always": "Sempre",
|
||||
"Always Collapse Code Blocks": "Reduir sempre els blocs de codi",
|
||||
"Always Expand Details": "Expandir sempre els detalls",
|
||||
"Always Play Notification Sound": "Reproduir sempre un so de notificació",
|
||||
"Amazing": "Al·lucinant",
|
||||
"an assistant": "un assistent",
|
||||
"Analyzed": "Analitzat",
|
||||
@@ -83,7 +87,7 @@
|
||||
"and": "i",
|
||||
"and {{COUNT}} more": "i {{COUNT}} més",
|
||||
"and create a new shared link.": "i crear un nou enllaç compartit.",
|
||||
"Android": "",
|
||||
"Android": "Android",
|
||||
"API Base URL": "URL Base de l'API",
|
||||
"API Key": "clau API",
|
||||
"API Key created.": "clau API creada.",
|
||||
@@ -101,6 +105,7 @@
|
||||
"Are you sure you want to delete this channel?": "Estàs segur que vols eliminar aquest canal?",
|
||||
"Are you sure you want to delete this message?": "Estàs segur que vols eliminar aquest missatge?",
|
||||
"Are you sure you want to unarchive all archived chats?": "Estàs segur que vols desarxivar tots els xats arxivats?",
|
||||
"Are you sure you want to update this user's role to **{{ROLE}}**?": "",
|
||||
"Are you sure?": "Estàs segur?",
|
||||
"Arena Models": "Models de l'Arena",
|
||||
"Artifacts": "Artefactes",
|
||||
@@ -143,6 +148,7 @@
|
||||
"Bing Search V7 Subscription Key": "Clau de subscripció a Bing Search V7",
|
||||
"Bocha Search API Key": "Clau API de Bocha Search",
|
||||
"Boosting or penalizing specific tokens for constrained responses. Bias values will be clamped between -100 and 100 (inclusive). (Default: none)": "Potenciar o penalitzar tokens específics per a respostes limitades. Els valors de biaix es fixaran entre -100 i 100 (inclosos). (Per defecte: cap)",
|
||||
"Both Docling OCR Engine and Language(s) must be provided or both left empty.": "",
|
||||
"Brave Search API Key": "Clau API de Brave Search",
|
||||
"By {{name}}": "Per {{name}}",
|
||||
"Bypass Embedding and Retrieval": "Desactivar l'Embedding i el Retrieval",
|
||||
@@ -153,6 +159,7 @@
|
||||
"Cancel": "Cancel·lar",
|
||||
"Capabilities": "Capacitats",
|
||||
"Capture": "Captura",
|
||||
"Capture Audio": "",
|
||||
"Certificate Path": "Camí del certificat",
|
||||
"Change Password": "Canviar la contrasenya",
|
||||
"Channel Name": "Nom del canal",
|
||||
@@ -245,7 +252,7 @@
|
||||
"Copied shared chat URL to clipboard!": "S'ha copiat l'URL compartida al porta-retalls!",
|
||||
"Copied to clipboard": "Copiat al porta-retalls",
|
||||
"Copy": "Copiar",
|
||||
"Copy Formatted Text": "",
|
||||
"Copy Formatted Text": "Copiar el text formatat",
|
||||
"Copy last code block": "Copiar l'últim bloc de codi",
|
||||
"Copy last response": "Copiar l'última resposta",
|
||||
"Copy Link": "Copiar l'enllaç",
|
||||
@@ -262,6 +269,8 @@
|
||||
"Create Knowledge": "Crear Coneixement",
|
||||
"Create new key": "Crear una nova clau",
|
||||
"Create new secret key": "Crear una nova clau secreta",
|
||||
"Create Note": "",
|
||||
"Create your first note by clicking on the plus button below.": "",
|
||||
"Created at": "Creat el",
|
||||
"Created At": "Creat el",
|
||||
"Created by": "Creat per",
|
||||
@@ -299,6 +308,7 @@
|
||||
"Delete function?": "Eliminar funció?",
|
||||
"Delete Message": "Eleiminar el missatge",
|
||||
"Delete message?": "Eliminar el missatge?",
|
||||
"Delete note?": "",
|
||||
"Delete prompt?": "Eliminar indicació?",
|
||||
"delete this link": "Eliminar aquest enllaç",
|
||||
"Delete tool?": "Eliminar eina?",
|
||||
@@ -308,7 +318,7 @@
|
||||
"Deleted User": "Usuari eliminat",
|
||||
"Describe your knowledge base and objectives": "Descriu la teva base de coneixement i objectius",
|
||||
"Description": "Descripció",
|
||||
"Detect Artifacts Automatically": "",
|
||||
"Detect Artifacts Automatically": "Detectar automàticament els artefactes",
|
||||
"Didn't fully follow instructions": "No s'han seguit les instruccions completament",
|
||||
"Direct": "Directe",
|
||||
"Direct Connections": "Connexions directes",
|
||||
@@ -354,7 +364,7 @@
|
||||
"Download Database": "Descarregar la base de dades",
|
||||
"Drag and drop a file to upload or select a file to view": "Arrossegar un arxiu per pujar o escull un arxiu a veure",
|
||||
"Draw": "Dibuixar",
|
||||
"Drop any files here to add to the conversation": "Deixa qualsevol arxiu aquí per afegir-lo a la conversa",
|
||||
"Drop any files here to upload": "",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "p. ex. '30s','10m'. Les unitats de temps vàlides són 's', 'm', 'h'.",
|
||||
"e.g. \"json\" or a JSON schema": "p. ex. \"json\" o un esquema JSON",
|
||||
"e.g. 60": "p. ex. 60",
|
||||
@@ -364,7 +374,9 @@
|
||||
"e.g. my_filter": "p. ex. els_meus_filtres",
|
||||
"e.g. my_tools": "p. ex. les_meves_eines",
|
||||
"e.g. Tools for performing various operations": "p. ex. Eines per dur a terme operacions",
|
||||
"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
|
||||
"e.g., 3, 4, 5 (leave blank for default)": "",
|
||||
"e.g., en-US,ja-JP (leave blank for auto-detect)": "p. ex. en-US, ja-JP, ca-ES (deixa-ho en blanc per detecció automàtica)",
|
||||
"e.g., westus (leave blank for eastus)": "",
|
||||
"Edit": "Editar",
|
||||
"Edit Arena Model": "Editar model de l'Arena",
|
||||
"Edit Channel": "Editar el canal",
|
||||
@@ -392,7 +404,9 @@
|
||||
"Enable Mirostat sampling for controlling perplexity.": "Permetre el mostreig de Mirostat per controlar la perplexitat",
|
||||
"Enable New Sign Ups": "Permetre nous registres",
|
||||
"Enabled": "Habilitat",
|
||||
"Endpoint URL": "",
|
||||
"Enforce Temporary Chat": "Forçar els xats temporals",
|
||||
"Enhance": "",
|
||||
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Assegura't que els teus fitxers CSV inclouen 4 columnes en aquest ordre: Nom, Correu electrònic, Contrasenya, Rol.",
|
||||
"Enter {{role}} message here": "Introdueix aquí el missatge de {{role}}",
|
||||
"Enter a detail about yourself for your LLMs to recall": "Introdueix un detall sobre tu què els teus models de llenguatge puguin recordar",
|
||||
@@ -407,15 +421,21 @@
|
||||
"Enter CFG Scale (e.g. 7.0)": "Entra l'escala CFG (p.ex. 7.0)",
|
||||
"Enter Chunk Overlap": "Introdueix la mida de solapament de blocs",
|
||||
"Enter Chunk Size": "Introdueix la mida del bloc",
|
||||
"Enter comma-seperated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "Introdueix parelles de \"token:valor de biaix\" separats per comes (exemple: 5432:100, 413:-100)",
|
||||
"Enter comma-separated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "Introdueix parelles de \"token:valor de biaix\" separats per comes (exemple: 5432:100, 413:-100)",
|
||||
"Enter description": "Introdueix la descripció",
|
||||
"Enter Docling OCR Engine": "",
|
||||
"Enter Docling OCR Language(s)": "",
|
||||
"Enter Docling Server URL": "Introdueix la URL del servidor Docling",
|
||||
"Enter Document Intelligence Endpoint": "Introdueix el punt de connexió de Document Intelligence",
|
||||
"Enter Document Intelligence Key": "Introdueix la clau de Document Intelligence",
|
||||
"Enter domains separated by commas (e.g., example.com,site.org)": "Introdueix els dominis separats per comes (p. ex. example.com,site.org)",
|
||||
"Enter Exa API Key": "Introdueix la clau API de d'EXA",
|
||||
"Enter Firecrawl API Base URL": "",
|
||||
"Enter Firecrawl API Key": "",
|
||||
"Enter External Web Loader API Key": "Introdueix la clau API d'External Web Loader",
|
||||
"Enter External Web Loader URL": "Introdueix la URL d'External Web Loader",
|
||||
"Enter External Web Search API Key": "Introdueix la clau API d'External Web Search",
|
||||
"Enter External Web Search URL": "Introdueix la URL d'External Web Search",
|
||||
"Enter Firecrawl API Base URL": "Introdueix la URL base de Firecrawl API",
|
||||
"Enter Firecrawl API Key": "Introdueix la clau API de Firecrawl",
|
||||
"Enter Github Raw URL": "Introdueix l'URL en brut de Github",
|
||||
"Enter Google PSE API Key": "Introdueix la clau API de Google PSE",
|
||||
"Enter Google PSE Engine Id": "Introdueix l'identificador del motor PSE de Google",
|
||||
@@ -431,10 +451,11 @@
|
||||
"Enter Model ID": "Introdueix l'identificador del model",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Introdueix l'etiqueta del model (p. ex. {{modelTag}})",
|
||||
"Enter Mojeek Search API Key": "Introdueix la clau API de Mojeek Search",
|
||||
"Enter New Password": "",
|
||||
"Enter Number of Steps (e.g. 50)": "Introdueix el nombre de passos (p. ex. 50)",
|
||||
"Enter Perplexity API Key": "Introdueix la clau API de Perplexity",
|
||||
"Enter Playwright Timeout": "",
|
||||
"Enter Playwright WebSocket URL": "",
|
||||
"Enter Playwright Timeout": "Introdueix el timeout de Playwright",
|
||||
"Enter Playwright WebSocket URL": "Introdueix la URL de Playwright WebSocket",
|
||||
"Enter proxy URL (e.g. https://user:password@host:port)": "Entra l'URL (p. ex. https://user:password@host:port)",
|
||||
"Enter reasoning effort": "Introdueix l'esforç de raonament",
|
||||
"Enter Sampler (e.g. Euler a)": "Introdueix el mostrejador (p.ex. Euler a)",
|
||||
@@ -452,13 +473,13 @@
|
||||
"Enter server host": "Introdueix el servidor",
|
||||
"Enter server label": "Introdueix l'etiqueta del servidor",
|
||||
"Enter server port": "Introdueix el port del servidor",
|
||||
"Enter Sougou Search API sID": "",
|
||||
"Enter Sougou Search API SK": "",
|
||||
"Enter Sougou Search API sID": "Introdueix el sID de l'API de Sougou Search",
|
||||
"Enter Sougou Search API SK": "Introdueix l'SK de l'API de Sougou Search",
|
||||
"Enter stop sequence": "Introdueix la seqüència de parada",
|
||||
"Enter system prompt": "Introdueix la indicació de sistema",
|
||||
"Enter system prompt here": "Entra la indicació de sistema aquí",
|
||||
"Enter Tavily API Key": "Introdueix la clau API de Tavily",
|
||||
"Enter Tavily Extract Depth": "",
|
||||
"Enter Tavily Extract Depth": "Introdueix la profunditat d'extracció de Tavily",
|
||||
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Entra la URL pública de WebUI. Aquesta URL s'utilitzarà per generar els enllaços en les notificacions.",
|
||||
"Enter Tika Server URL": "Introdueix l'URL del servidor Tika",
|
||||
"Enter timeout in seconds": "Entra el temps màxim en segons",
|
||||
@@ -467,11 +488,15 @@
|
||||
"Enter Top K Reranker": "Introdueix el Top K Reranker",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "Introdueix l'URL (p. ex. http://127.0.0.1:7860/)",
|
||||
"Enter URL (e.g. http://localhost:11434)": "Introdueix l'URL (p. ex. http://localhost:11434)",
|
||||
"Enter Yacy Password": "",
|
||||
"Enter Yacy URL (e.g. http://yacy.example.com:8090)": "",
|
||||
"Enter Yacy Username": "",
|
||||
"Enter your current password": "Introdueix la teva contrasenya actual",
|
||||
"Enter Your Email": "Introdueix el teu correu electrònic",
|
||||
"Enter Your Full Name": "Introdueix el teu nom complet",
|
||||
"Enter your message": "Introdueix el teu missatge",
|
||||
"Enter your name": "Entra el teu nom",
|
||||
"Enter Your Name": "",
|
||||
"Enter your new password": "Introdueix la teva nova contrasenya",
|
||||
"Enter Your Password": "Introdueix la teva contrasenya",
|
||||
"Enter Your Role": "Introdueix el teu rol",
|
||||
@@ -480,6 +505,8 @@
|
||||
"Error": "Error",
|
||||
"ERROR": "ERROR",
|
||||
"Error accessing Google Drive: {{error}}": "Error en accedir a Google Drive: {{error}}",
|
||||
"Error accessing media devices.": "",
|
||||
"Error starting recording.": "",
|
||||
"Error uploading file: {{error}}": "Error en pujar l'arxiu: {{error}}",
|
||||
"Evaluations": "Avaluacions",
|
||||
"Exa API Key": "Clau API d'EXA",
|
||||
@@ -511,10 +538,16 @@
|
||||
"Export Tools": "Exportar les eines",
|
||||
"External": "Extern",
|
||||
"External Models": "Models externs",
|
||||
"External Web Loader API Key": "Clau API d'External Web Loader",
|
||||
"External Web Loader URL": "URL d'External Web Loader",
|
||||
"External Web Search API Key": "Clau API d'External Web Search",
|
||||
"External Web Search URL": "URL d'External Web Search",
|
||||
"Failed to add file.": "No s'ha pogut afegir l'arxiu.",
|
||||
"Failed to connect to {{URL}} OpenAPI tool server": "No s'ha pogut connecta al servidor d'eines OpenAPI {{URL}}",
|
||||
"Failed to create API Key.": "No s'ha pogut crear la clau API.",
|
||||
"Failed to delete note": "",
|
||||
"Failed to fetch models": "No s'han pogut obtenir els models",
|
||||
"Failed to load file content.": "No s'ha pogut carregar el contingut del fitxer",
|
||||
"Failed to read clipboard contents": "No s'ha pogut llegir el contingut del porta-retalls",
|
||||
"Failed to save connections": "No s'han pogut desar les connexions",
|
||||
"Failed to save models configuration": "No s'ha pogut desar la configuració dels models",
|
||||
@@ -539,12 +572,11 @@
|
||||
"Filter is now globally enabled": "El filtre ha estat activat globalment",
|
||||
"Filters": "Filtres",
|
||||
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "S'ha detectat la suplantació d'identitat de l'empremta digital: no es poden utilitzar les inicials com a avatar. S'estableix la imatge de perfil predeterminada.",
|
||||
"Firecrawl API Base URL": "",
|
||||
"Firecrawl API Key": "",
|
||||
"Firecrawl API Base URL": "URL de l'API de base de Firecrawl",
|
||||
"Firecrawl API Key": "Clau API de Firecrawl",
|
||||
"Fluidly stream large external response chunks": "Transmetre amb fluïdesa grans trossos de resposta externa",
|
||||
"Focus chat input": "Estableix el focus a l'entrada del xat",
|
||||
"Folder deleted successfully": "Carpeta eliminada correctament",
|
||||
"Folder name cannot be empty": "El nom de la carpeta no pot ser buit",
|
||||
"Folder name cannot be empty.": "El nom de la carpeta no pot ser buit.",
|
||||
"Folder name updated successfully": "Nom de la carpeta actualitzat correctament",
|
||||
"Followed instructions perfectly": "S'han seguit les instruccions perfectament",
|
||||
@@ -565,17 +597,18 @@
|
||||
"Function Name": "Nom de la funció",
|
||||
"Function updated successfully": "La funció s'ha actualitzat correctament",
|
||||
"Functions": "Funcions",
|
||||
"Functions allow arbitrary code execution": "Les funcions permeten l'execució de codi arbitrari",
|
||||
"Functions allow arbitrary code execution.": "Les funcions permeten l'execució de codi arbitrari.",
|
||||
"Functions imported successfully": "Les funcions s'han importat correctament",
|
||||
"Gemini": "Gemini",
|
||||
"Gemini API Config": "Configuració de Gemini API",
|
||||
"Gemini API Key is required.": "La clau API de Gemini és necessària",
|
||||
"General": "General",
|
||||
"Generate": "",
|
||||
"Generate an image": "Generar una imatge",
|
||||
"Generate Image": "Generar imatge",
|
||||
"Generate prompt pair": "Generar parella d'indicació",
|
||||
"Generating search query": "Generant consulta",
|
||||
"Generating...": "",
|
||||
"Get started": "Començar",
|
||||
"Get started with {{WEBUI_NAME}}": "Començar amb {{WEBUI_NAME}}",
|
||||
"Global": "Global",
|
||||
@@ -605,8 +638,8 @@
|
||||
"Hybrid Search": "Cerca híbrida",
|
||||
"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Afirmo que he llegit i entenc les implicacions de la meva acció. Soc conscient dels riscos associats a l'execució de codi arbitrari i he verificat la fiabilitat de la font.",
|
||||
"ID": "ID",
|
||||
"iframe Sandbox Allow Forms": "",
|
||||
"iframe Sandbox Allow Same Origin": "",
|
||||
"iframe Sandbox Allow Forms": "Permetre formularis sandbox iframe",
|
||||
"iframe Sandbox Allow Same Origin": "Permetre same-origin sandbox iframe",
|
||||
"Ignite curiosity": "Despertar la curiositat",
|
||||
"Image": "Imatge",
|
||||
"Image Compression": "Compressió d'imatges",
|
||||
@@ -622,6 +655,7 @@
|
||||
"Import Config from JSON File": "Importar la configuració des d'un arxiu JSON",
|
||||
"Import Functions": "Importar funcions",
|
||||
"Import Models": "Importar models",
|
||||
"Import Notes": "",
|
||||
"Import Presets": "Importar configuracions",
|
||||
"Import Prompts": "Importar indicacions",
|
||||
"Import Tools": "Importar eines",
|
||||
@@ -636,6 +670,7 @@
|
||||
"Instant Auto-Send After Voice Transcription": "Enviament automàtic després de la transcripció de veu",
|
||||
"Integration": "Integració",
|
||||
"Interface": "Interfície",
|
||||
"Invalid file content": "",
|
||||
"Invalid file format.": "Format d'arxiu no vàlid.",
|
||||
"Invalid JSON schema": "Esquema JSON no vàlid",
|
||||
"Invalid Tag": "Etiqueta no vàlida",
|
||||
@@ -667,7 +702,7 @@
|
||||
"Label": "Etiqueta",
|
||||
"Landing Page Mode": "Mode de la pàgina d'entrada",
|
||||
"Language": "Idioma",
|
||||
"Language Locales": "",
|
||||
"Language Locales": "Localització d'idiomes",
|
||||
"Last Active": "Activitat recent",
|
||||
"Last Modified": "Modificació",
|
||||
"Last reply": "Darrera resposta",
|
||||
@@ -706,6 +741,7 @@
|
||||
"Manage Pipelines": "Gestionar les Pipelines",
|
||||
"Manage Tool Servers": "Gestionar els servidors d'eines",
|
||||
"March": "Març",
|
||||
"Max Speakers": "",
|
||||
"Max Tokens (num_predict)": "Nombre màxim de Tokens (num_predict)",
|
||||
"Max Upload Count": "Nombre màxim de càrregues",
|
||||
"Max Upload Size": "Mida màxima de càrrega",
|
||||
@@ -718,8 +754,12 @@
|
||||
"Memory deleted successfully": "Memòria eliminada correctament",
|
||||
"Memory updated successfully": "Memòria actualitzada correctament",
|
||||
"Merge Responses": "Fusionar les respostes",
|
||||
"Merged Response": "Resposta combinada",
|
||||
"Message rating should be enabled to use this feature": "La classificació dels missatges s'hauria d'activar per utilitzar aquesta funció",
|
||||
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Els missatges enviats després de crear el teu enllaç no es compartiran. Els usuaris amb l'URL podran veure el xat compartit.",
|
||||
"Microsoft OneDrive": "Microsoft OneDrive",
|
||||
"Microsoft OneDrive (personal)": "Microsoft OneDrive (personal)",
|
||||
"Microsoft OneDrive (work/school)": "Microsoft OneDrive (feina/escola)",
|
||||
"Min P": "Min P",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Eta de Mirostat",
|
||||
@@ -753,19 +793,22 @@
|
||||
"Mojeek Search API Key": "Clau API de Mojeek Search",
|
||||
"more": "més",
|
||||
"More": "Més",
|
||||
"My Notes": "",
|
||||
"Name": "Nom",
|
||||
"Name your knowledge base": "Anomena la teva base de coneixement",
|
||||
"Native": "Natiu",
|
||||
"New Chat": "Nou xat",
|
||||
"New Folder": "Nova carpeta",
|
||||
"New Note": "",
|
||||
"New Password": "Nova contrasenya",
|
||||
"new-channel": "nou-canal",
|
||||
"No content": "",
|
||||
"No content found": "No s'ha trobat contingut",
|
||||
"No content found in file.": "No s'ha trobat contingut en el fitxer.",
|
||||
"No content to speak": "No hi ha contingut per parlar",
|
||||
"No distance available": "No hi ha distància disponible",
|
||||
"No feedbacks found": "No s'han trobat comentaris",
|
||||
"No file selected": "No s'ha escollit cap fitxer",
|
||||
"No files found.": "No s'han trobat arxius.",
|
||||
"No groups with access, add a group to grant access": "No hi ha cap grup amb accés, afegeix un grup per concedir accés",
|
||||
"No HTML, CSS, or JavaScript content found.": "No s'ha trobat contingut HTML, CSS o JavaScript.",
|
||||
"No inference engine with management support found": "No s'ha trobat un motor d'inferència amb suport de gestió",
|
||||
@@ -774,6 +817,7 @@
|
||||
"No model IDs": "No hi ha IDs de model",
|
||||
"No models found": "No s'han trobat models",
|
||||
"No models selected": "No s'ha seleccionat cap model",
|
||||
"No Notes": "",
|
||||
"No results found": "No s'han trobat resultats",
|
||||
"No search query generated": "No s'ha generat cap consulta",
|
||||
"No source available": "Sense font disponible",
|
||||
@@ -782,6 +826,7 @@
|
||||
"None": "Cap",
|
||||
"Not factually correct": "No és clarament correcte",
|
||||
"Not helpful": "No ajuda",
|
||||
"Note deleted successfully": "",
|
||||
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nota: Si s'estableix una puntuació mínima, la cerca només retornarà documents amb una puntuació major o igual a la puntuació mínima.",
|
||||
"Notes": "Notes",
|
||||
"Notification Sound": "So de la notificació",
|
||||
@@ -804,6 +849,7 @@
|
||||
"Only alphanumeric characters and hyphens are allowed": "Només es permeten caràcters alfanumèrics i guions",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "Només es permeten caràcters alfanumèrics i guions en la comanda.",
|
||||
"Only collections can be edited, create a new knowledge base to edit/add documents.": "Només es poden editar col·leccions, crea una nova base de coneixement per editar/afegir documents.",
|
||||
"Only markdown files are allowed": "",
|
||||
"Only select users and groups with permission can access": "Només hi poden accedir usuaris i grups seleccionats amb permís",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ui! Sembla que l'URL no és vàlida. Si us plau, revisa-la i torna-ho a provar.",
|
||||
"Oops! There are files still uploading. Please wait for the upload to complete.": "Ui! Encara hi ha fitxers pujant-se. Si us plau, espera que finalitzi la càrrega.",
|
||||
@@ -849,10 +895,11 @@
|
||||
"Pipelines": "Pipelines",
|
||||
"Pipelines Not Detected": "No s'ha detectat Pipelines",
|
||||
"Pipelines Valves": "Vàlvules de les Pipelines",
|
||||
"Plain text (.md)": "",
|
||||
"Plain text (.txt)": "Text pla (.txt)",
|
||||
"Playground": "Zona de jocs",
|
||||
"Playwright Timeout (ms)": "",
|
||||
"Playwright WebSocket URL": "",
|
||||
"Playwright Timeout (ms)": "Temps d'espera (ms) de Playwright",
|
||||
"Playwright WebSocket URL": "URL del WebSocket de Playwright",
|
||||
"Please carefully review the following warnings:": "Si us plau, revisa els següents avisos amb cura:",
|
||||
"Please do not close the settings page while loading the model.": "No tanquis la pàgina de configuració mentre carregues el model.",
|
||||
"Please enter a prompt": "Si us plau, entra una indicació",
|
||||
@@ -891,6 +938,7 @@
|
||||
"Read": "Llegit",
|
||||
"Read Aloud": "Llegir en veu alta",
|
||||
"Reasoning Effort": "Esforç de raonament",
|
||||
"Record": "",
|
||||
"Record voice": "Enregistrar la veu",
|
||||
"Redirecting you to Open WebUI Community": "Redirigint-te a la comunitat OpenWebUI",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative.": "Redueix la probabilitat de generar ximpleries. Un valor més alt (p. ex. 100) donarà respostes més diverses, mentre que un valor més baix (p. ex. 10) serà més conservador.",
|
||||
@@ -898,11 +946,11 @@
|
||||
"References from": "Referències de",
|
||||
"Refused when it shouldn't have": "Refusat quan no hauria d'haver estat",
|
||||
"Regenerate": "Regenerar",
|
||||
"Reindex": "",
|
||||
"Reindex Knowledge Base Vectors": "",
|
||||
"Reindex": "Reindexar",
|
||||
"Reindex Knowledge Base Vectors": "Reindexar els vector base del Coneixement",
|
||||
"Release Notes": "Notes de la versió",
|
||||
"Relevance": "Rellevància",
|
||||
"Relevance Threshold": "",
|
||||
"Relevance Threshold": "Límit de rellevància",
|
||||
"Remove": "Eliminar",
|
||||
"Remove Model": "Eliminar el model",
|
||||
"Rename": "Canviar el nom",
|
||||
@@ -911,9 +959,8 @@
|
||||
"Repeat Penalty (Ollama)": "Penalització per repetició (Ollama)",
|
||||
"Reply in Thread": "Respondre al fil",
|
||||
"Request Mode": "Mode de sol·licitud",
|
||||
"Reranking Engine": "",
|
||||
"Reranking Model": "Model de reavaluació",
|
||||
"Reranking model disabled": "Model de reavaluació desactivat",
|
||||
"Reranking model set to \"{{reranking_model}}\"": "Model de reavaluació establert a \"{{reranking_model}}\"",
|
||||
"Reset": "Restableix",
|
||||
"Reset All Models": "Restablir tots els models",
|
||||
"Reset Upload Directory": "Restableix el directori de pujades",
|
||||
@@ -939,7 +986,7 @@
|
||||
"Save Tag": "Desar l'etiqueta",
|
||||
"Saved": "Desat",
|
||||
"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": "Desar els registres de xat directament a l'emmagatzematge del teu navegador ja no està suportat. Si us plau, descarregr i elimina els registres de xat fent clic al botó de sota. No et preocupis, pots tornar a importar fàcilment els teus registres de xat al backend a través de",
|
||||
"Scroll to bottom when switching between branches": "Desplaçar a la part inferior quan es canviï de branca",
|
||||
"Scroll On Branch Change": "Fer scroll en canviar de branca",
|
||||
"Search": "Cercar",
|
||||
"Search a model": "Cercar un model",
|
||||
"Search Base": "Base de cerca",
|
||||
@@ -960,6 +1007,7 @@
|
||||
"Searched {{count}} sites": "S'han cercat {{count}} pàgines",
|
||||
"Searching \"{{searchQuery}}\"": "Cercant \"{{searchQuery}}\"",
|
||||
"Searching Knowledge for \"{{searchQuery}}\"": "Cercant \"{{searchQuery}}\" al coneixement",
|
||||
"Searching the web...": "",
|
||||
"Searxng Query URL": "URL de consulta de Searxng",
|
||||
"See readme.md for instructions": "Consulta l'arxiu readme.md per obtenir instruccions",
|
||||
"See what's new": "Veure què hi ha de nou",
|
||||
@@ -1020,6 +1068,8 @@
|
||||
"Show": "Mostrar",
|
||||
"Show \"What's New\" modal on login": "Veure 'Què hi ha de nou' a l'entrada",
|
||||
"Show Admin Details in Account Pending Overlay": "Mostrar els detalls de l'administrador a la superposició del compte pendent",
|
||||
"Show All": "",
|
||||
"Show Less": "",
|
||||
"Show Model": "Mostrar el model",
|
||||
"Show shortcuts": "Mostrar dreceres",
|
||||
"Show your support!": "Mostra el teu suport!",
|
||||
@@ -1032,8 +1082,8 @@
|
||||
"Sign up to {{WEBUI_NAME}}": "Registrar-se a {{WEBUI_NAME}}",
|
||||
"Signing in to {{WEBUI_NAME}}": "Iniciant sessió a {{WEBUI_NAME}}",
|
||||
"sk-1234": "sk-1234",
|
||||
"Sougou Search API sID": "",
|
||||
"Sougou Search API SK": "",
|
||||
"Sougou Search API sID": "sID de l'API de Sougou Search",
|
||||
"Sougou Search API SK": "SK de l'API de Sougou Search",
|
||||
"Source": "Font",
|
||||
"Speech Playback Speed": "Velocitat de la parla",
|
||||
"Speech recognition error: {{error}}": "Error de reconeixement de veu: {{error}}",
|
||||
@@ -1043,6 +1093,7 @@
|
||||
"Stream Chat Response": "Fer streaming de la resposta del xat",
|
||||
"STT Model": "Model SST",
|
||||
"STT Settings": "Preferències de STT",
|
||||
"Stylized PDF Export": "",
|
||||
"Subtitle (e.g. about the Roman Empire)": "Subtítol (per exemple, sobre l'Imperi Romà)",
|
||||
"Success": "Èxit",
|
||||
"Successfully updated.": "Actualitzat correctament.",
|
||||
@@ -1061,7 +1112,7 @@
|
||||
"Tap to interrupt": "Prem per interrompre",
|
||||
"Tasks": "Tasques",
|
||||
"Tavily API Key": "Clau API de Tavily",
|
||||
"Tavily Extract Depth": "",
|
||||
"Tavily Extract Depth": "Profunditat d'extracció de Tavily",
|
||||
"Tell us more:": "Dona'ns més informació:",
|
||||
"Temperature": "Temperatura",
|
||||
"Template": "Plantilla",
|
||||
@@ -1089,6 +1140,7 @@
|
||||
"This chat won’t appear in history and your messages will not be saved.": "Aquest xat no apareixerà a l'historial i els teus missatges no es desaran.",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Això assegura que les teves converses valuoses queden desades de manera segura a la teva base de dades. Gràcies!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Aquesta és una funció experimental, és possible que no funcioni com s'espera i està subjecta a canvis en qualsevol moment.",
|
||||
"This model is not publicly available. Please select another model.": "",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "Aquesta opció controla quants tokens es conserven en actualitzar el context. Per exemple, si s'estableix en 2, es conservaran els darrers 2 tokens del context de conversa. Preservar el context pot ajudar a mantenir la continuïtat d'una conversa, però pot reduir la capacitat de respondre a nous temes.",
|
||||
"This option sets the maximum number of tokens the model can generate in its response. Increasing this limit allows the model to provide longer answers, but it may also increase the likelihood of unhelpful or irrelevant content being generated.": "Aquesta opció estableix el nombre màxim de tokens que el model pot generar en la seva resposta. Augmentar aquest límit permet que el model proporcioni respostes més llargues, però també pot augmentar la probabilitat que es generi contingut poc útil o irrellevant.",
|
||||
"This option will delete all existing files in the collection and replace them with newly uploaded files.": "Aquesta opció eliminarà tots els fitxers existents de la col·lecció i els substituirà per fitxers recentment penjats.",
|
||||
@@ -1140,7 +1192,6 @@
|
||||
"Tools Access": "Accés a les eines",
|
||||
"Tools are a function calling system with arbitrary code execution": "Les eines són un sistema de crida a funcions amb execució de codi arbitrari",
|
||||
"Tools Function Calling Prompt": "Indicació per a la crida de funcions",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "Les eines disposen d'un sistema de crida a funcions que permet execució de codi arbitrari",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "Les eines disposen d'un sistema de crida a funcions que permet execució de codi arbitrari.",
|
||||
"Tools Public Sharing": "Compartició pública d'eines",
|
||||
"Top K": "Top K",
|
||||
@@ -1163,6 +1214,7 @@
|
||||
"Unpin": "Alliberar",
|
||||
"Unravel secrets": "Descobreix els secrets",
|
||||
"Untagged": "Sense etiquetes",
|
||||
"Untitled": "",
|
||||
"Update": "Actualitzar",
|
||||
"Update and Copy Link": "Actualitzar i copiar l'enllaç",
|
||||
"Update for the latest features and improvements.": "Actualitza per a les darreres característiques i millores.",
|
||||
@@ -1173,6 +1225,7 @@
|
||||
"Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.": "Actualitzar a un pla amb llicència per obtenir capacitats millorades, com ara la temàtica personalitzada i la marca, i assistència dedicada.",
|
||||
"Upload": "Pujar",
|
||||
"Upload a GGUF model": "Pujar un model GGUF",
|
||||
"Upload Audio": "",
|
||||
"Upload directory": "Pujar directori",
|
||||
"Upload files": "Pujar fitxers",
|
||||
"Upload Files": "Pujar fitxers",
|
||||
@@ -1184,8 +1237,8 @@
|
||||
"Use Gravatar": "Utilitzar Gravatar",
|
||||
"Use groups to group your users and assign permissions.": "Utilitza grups per agrupar els usuaris i assignar permisos.",
|
||||
"Use Initials": "Utilitzar inicials",
|
||||
"Use no proxy to fetch page contents.": "",
|
||||
"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
|
||||
"Use no proxy to fetch page contents.": "No utilitzis un proxy per obtenir contingut de la pàgina.",
|
||||
"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "Utilitza el proxy designat per les variables d'entorn http_proxy i https_proxy per obtenir el contingut de la pàgina.",
|
||||
"use_mlock (Ollama)": "use_mlock (Ollama)",
|
||||
"use_mmap (Ollama)": "use_mmap (Ollama)",
|
||||
"user": "usuari",
|
||||
@@ -1203,7 +1256,7 @@
|
||||
"variable": "variable",
|
||||
"variable to have them replaced with clipboard content.": "variable per tenir-les reemplaçades amb el contingut del porta-retalls.",
|
||||
"Verify Connection": "Verificar la connexió",
|
||||
"Verify SSL Certificate": "",
|
||||
"Verify SSL Certificate": "Verificar el certificat SSL",
|
||||
"Version": "Versió",
|
||||
"Version {{selectedVersion}} of {{totalVersions}}": "Versió {{selectedVersion}} de {{totalVersions}}",
|
||||
"View Replies": "Veure les respostes",
|
||||
@@ -1218,7 +1271,7 @@
|
||||
"Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Avís: l'execució de Jupyter permet l'execució de codi arbitrari, la qual cosa comporta greus riscos de seguretat; procediu amb extrema precaució.",
|
||||
"Web": "Web",
|
||||
"Web API": "Web API",
|
||||
"Web Loader Engine": "",
|
||||
"Web Loader Engine": "Motor de càrrega Web",
|
||||
"Web Search": "Cerca la web",
|
||||
"Web Search Engine": "Motor de cerca de la web",
|
||||
"Web Search in Chat": "Cerca a internet al xat",
|
||||
@@ -1246,13 +1299,15 @@
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "Escriu un resum en 50 paraules que resumeixi [tema o paraula clau].",
|
||||
"Write something...": "Escriu quelcom...",
|
||||
"Write your model template content here": "Introdueix el contingut de la plantilla del teu model aquí",
|
||||
"Yacy Instance URL": "",
|
||||
"Yacy Password": "",
|
||||
"Yacy Username": "",
|
||||
"Yesterday": "Ahir",
|
||||
"You": "Tu",
|
||||
"You are currently using a trial license. Please contact support to upgrade your license.": "Actualment esteu utilitzant una llicència de prova. Poseu-vos en contacte amb el servei d'assistència per actualitzar la vostra llicència.",
|
||||
"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Només pots xatejar amb un màxim de {{maxCount}} fitxers alhora.",
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Pots personalitzar les teves interaccions amb els models de llenguatge afegint memòries mitjançant el botó 'Gestiona' que hi ha a continuació, fent-les més útils i adaptades a tu.",
|
||||
"You cannot upload an empty file.": "No es pot pujar un ariux buit.",
|
||||
"You do not have permission to upload files": "No tens permisos per pujar arxius",
|
||||
"You do not have permission to upload files.": "No tens permisos per pujar arxius.",
|
||||
"You have no archived conversations.": "No tens converses arxivades.",
|
||||
"You have shared this chat": "Has compartit aquest xat",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"(e.g. `sh webui.sh --api --api-auth username_password`)": "",
|
||||
"(e.g. `sh webui.sh --api`)": "(pananglitan `sh webui.sh --api`)",
|
||||
"(latest)": "",
|
||||
"(leave blank for to use commercial endpoint)": "",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
@@ -62,6 +63,8 @@
|
||||
"Allow Chat Delete": "",
|
||||
"Allow Chat Deletion": "Tugoti nga mapapas ang mga chat",
|
||||
"Allow Chat Edit": "",
|
||||
"Allow Chat Export": "",
|
||||
"Allow Chat Share": "",
|
||||
"Allow File Upload": "",
|
||||
"Allow Multiple Models in Chat": "",
|
||||
"Allow non-local voices": "",
|
||||
@@ -76,6 +79,7 @@
|
||||
"Always": "",
|
||||
"Always Collapse Code Blocks": "",
|
||||
"Always Expand Details": "",
|
||||
"Always Play Notification Sound": "",
|
||||
"Amazing": "",
|
||||
"an assistant": "usa ka katabang",
|
||||
"Analyzed": "",
|
||||
@@ -101,6 +105,7 @@
|
||||
"Are you sure you want to delete this channel?": "",
|
||||
"Are you sure you want to delete this message?": "",
|
||||
"Are you sure you want to unarchive all archived chats?": "",
|
||||
"Are you sure you want to update this user's role to **{{ROLE}}**?": "",
|
||||
"Are you sure?": "Sigurado ka ?",
|
||||
"Arena Models": "",
|
||||
"Artifacts": "",
|
||||
@@ -143,6 +148,7 @@
|
||||
"Bing Search V7 Subscription Key": "",
|
||||
"Bocha Search API Key": "",
|
||||
"Boosting or penalizing specific tokens for constrained responses. Bias values will be clamped between -100 and 100 (inclusive). (Default: none)": "",
|
||||
"Both Docling OCR Engine and Language(s) must be provided or both left empty.": "",
|
||||
"Brave Search API Key": "",
|
||||
"By {{name}}": "",
|
||||
"Bypass Embedding and Retrieval": "",
|
||||
@@ -153,6 +159,7 @@
|
||||
"Cancel": "Pagkanselar",
|
||||
"Capabilities": "",
|
||||
"Capture": "",
|
||||
"Capture Audio": "",
|
||||
"Certificate Path": "",
|
||||
"Change Password": "Usba ang password",
|
||||
"Channel Name": "",
|
||||
@@ -262,6 +269,8 @@
|
||||
"Create Knowledge": "",
|
||||
"Create new key": "",
|
||||
"Create new secret key": "",
|
||||
"Create Note": "",
|
||||
"Create your first note by clicking on the plus button below.": "",
|
||||
"Created at": "Gihimo ang",
|
||||
"Created At": "",
|
||||
"Created by": "",
|
||||
@@ -299,6 +308,7 @@
|
||||
"Delete function?": "",
|
||||
"Delete Message": "",
|
||||
"Delete message?": "",
|
||||
"Delete note?": "",
|
||||
"Delete prompt?": "",
|
||||
"delete this link": "",
|
||||
"Delete tool?": "",
|
||||
@@ -354,7 +364,7 @@
|
||||
"Download Database": "I-download ang database",
|
||||
"Drag and drop a file to upload or select a file to view": "",
|
||||
"Draw": "",
|
||||
"Drop any files here to add to the conversation": "Ihulog ang bisan unsang file dinhi aron idugang kini sa panag-istoryahanay",
|
||||
"Drop any files here to upload": "",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "p. ",
|
||||
"e.g. \"json\" or a JSON schema": "",
|
||||
"e.g. 60": "",
|
||||
@@ -364,7 +374,9 @@
|
||||
"e.g. my_filter": "",
|
||||
"e.g. my_tools": "",
|
||||
"e.g. Tools for performing various operations": "",
|
||||
"e.g., 3, 4, 5 (leave blank for default)": "",
|
||||
"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
|
||||
"e.g., westus (leave blank for eastus)": "",
|
||||
"Edit": "",
|
||||
"Edit Arena Model": "",
|
||||
"Edit Channel": "",
|
||||
@@ -392,7 +404,9 @@
|
||||
"Enable Mirostat sampling for controlling perplexity.": "",
|
||||
"Enable New Sign Ups": "I-enable ang bag-ong mga rehistro",
|
||||
"Enabled": "",
|
||||
"Endpoint URL": "",
|
||||
"Enforce Temporary Chat": "",
|
||||
"Enhance": "",
|
||||
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "",
|
||||
"Enter {{role}} message here": "Pagsulod sa mensahe {{role}} dinhi",
|
||||
"Enter a detail about yourself for your LLMs to recall": "",
|
||||
@@ -407,13 +421,19 @@
|
||||
"Enter CFG Scale (e.g. 7.0)": "",
|
||||
"Enter Chunk Overlap": "Pagsulod sa block overlap",
|
||||
"Enter Chunk Size": "Isulod ang block size",
|
||||
"Enter comma-seperated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "",
|
||||
"Enter comma-separated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "",
|
||||
"Enter description": "",
|
||||
"Enter Docling OCR Engine": "",
|
||||
"Enter Docling OCR Language(s)": "",
|
||||
"Enter Docling Server URL": "",
|
||||
"Enter Document Intelligence Endpoint": "",
|
||||
"Enter Document Intelligence Key": "",
|
||||
"Enter domains separated by commas (e.g., example.com,site.org)": "",
|
||||
"Enter Exa API Key": "",
|
||||
"Enter External Web Loader API Key": "",
|
||||
"Enter External Web Loader URL": "",
|
||||
"Enter External Web Search API Key": "",
|
||||
"Enter External Web Search URL": "",
|
||||
"Enter Firecrawl API Base URL": "",
|
||||
"Enter Firecrawl API Key": "",
|
||||
"Enter Github Raw URL": "",
|
||||
@@ -431,6 +451,7 @@
|
||||
"Enter Model ID": "",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Pagsulod sa template tag (e.g. {{modelTag}})",
|
||||
"Enter Mojeek Search API Key": "",
|
||||
"Enter New Password": "",
|
||||
"Enter Number of Steps (e.g. 50)": "Pagsulod sa gidaghanon sa mga lakang (e.g. 50)",
|
||||
"Enter Perplexity API Key": "",
|
||||
"Enter Playwright Timeout": "",
|
||||
@@ -467,11 +488,15 @@
|
||||
"Enter Top K Reranker": "",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "Pagsulod sa URL (e.g. http://127.0.0.1:7860/)",
|
||||
"Enter URL (e.g. http://localhost:11434)": "",
|
||||
"Enter Yacy Password": "",
|
||||
"Enter Yacy URL (e.g. http://yacy.example.com:8090)": "",
|
||||
"Enter Yacy Username": "",
|
||||
"Enter your current password": "",
|
||||
"Enter Your Email": "Pagsulod sa imong e-mail address",
|
||||
"Enter Your Full Name": "Ibutang ang imong tibuok nga ngalan",
|
||||
"Enter your message": "",
|
||||
"Enter your name": "",
|
||||
"Enter Your Name": "",
|
||||
"Enter your new password": "",
|
||||
"Enter Your Password": "Ibutang ang imong password",
|
||||
"Enter Your Role": "",
|
||||
@@ -480,6 +505,8 @@
|
||||
"Error": "",
|
||||
"ERROR": "",
|
||||
"Error accessing Google Drive: {{error}}": "",
|
||||
"Error accessing media devices.": "",
|
||||
"Error starting recording.": "",
|
||||
"Error uploading file: {{error}}": "",
|
||||
"Evaluations": "",
|
||||
"Exa API Key": "",
|
||||
@@ -511,10 +538,16 @@
|
||||
"Export Tools": "",
|
||||
"External": "",
|
||||
"External Models": "",
|
||||
"External Web Loader API Key": "",
|
||||
"External Web Loader URL": "",
|
||||
"External Web Search API Key": "",
|
||||
"External Web Search URL": "",
|
||||
"Failed to add file.": "",
|
||||
"Failed to connect to {{URL}} OpenAPI tool server": "",
|
||||
"Failed to create API Key.": "",
|
||||
"Failed to delete note": "",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to load file content.": "",
|
||||
"Failed to read clipboard contents": "Napakyas sa pagbasa sa sulod sa clipboard",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "",
|
||||
@@ -544,7 +577,6 @@
|
||||
"Fluidly stream large external response chunks": "Hapsay nga paghatud sa daghang mga tipik sa eksternal nga mga tubag",
|
||||
"Focus chat input": "Pag-focus sa entry sa diskusyon",
|
||||
"Folder deleted successfully": "",
|
||||
"Folder name cannot be empty": "",
|
||||
"Folder name cannot be empty.": "",
|
||||
"Folder name updated successfully": "",
|
||||
"Followed instructions perfectly": "",
|
||||
@@ -565,17 +597,18 @@
|
||||
"Function Name": "",
|
||||
"Function updated successfully": "",
|
||||
"Functions": "",
|
||||
"Functions allow arbitrary code execution": "",
|
||||
"Functions allow arbitrary code execution.": "",
|
||||
"Functions imported successfully": "",
|
||||
"Gemini": "",
|
||||
"Gemini API Config": "",
|
||||
"Gemini API Key is required.": "",
|
||||
"General": "Heneral",
|
||||
"Generate": "",
|
||||
"Generate an image": "",
|
||||
"Generate Image": "",
|
||||
"Generate prompt pair": "",
|
||||
"Generating search query": "",
|
||||
"Generating...": "",
|
||||
"Get started": "",
|
||||
"Get started with {{WEBUI_NAME}}": "",
|
||||
"Global": "",
|
||||
@@ -622,6 +655,7 @@
|
||||
"Import Config from JSON File": "",
|
||||
"Import Functions": "",
|
||||
"Import Models": "",
|
||||
"Import Notes": "",
|
||||
"Import Presets": "",
|
||||
"Import Prompts": "Import prompt",
|
||||
"Import Tools": "",
|
||||
@@ -636,6 +670,7 @@
|
||||
"Instant Auto-Send After Voice Transcription": "",
|
||||
"Integration": "",
|
||||
"Interface": "Interface",
|
||||
"Invalid file content": "",
|
||||
"Invalid file format.": "",
|
||||
"Invalid JSON schema": "",
|
||||
"Invalid Tag": "",
|
||||
@@ -706,6 +741,7 @@
|
||||
"Manage Pipelines": "",
|
||||
"Manage Tool Servers": "",
|
||||
"March": "",
|
||||
"Max Speakers": "",
|
||||
"Max Tokens (num_predict)": "",
|
||||
"Max Upload Count": "",
|
||||
"Max Upload Size": "",
|
||||
@@ -718,8 +754,12 @@
|
||||
"Memory deleted successfully": "",
|
||||
"Memory updated successfully": "",
|
||||
"Merge Responses": "",
|
||||
"Merged Response": "Gihiusa nga Resulta sa Tubag",
|
||||
"Message rating should be enabled to use this feature": "",
|
||||
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "",
|
||||
"Microsoft OneDrive": "",
|
||||
"Microsoft OneDrive (personal)": "",
|
||||
"Microsoft OneDrive (work/school)": "",
|
||||
"Min P": "",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
@@ -753,19 +793,22 @@
|
||||
"Mojeek Search API Key": "",
|
||||
"more": "",
|
||||
"More": "",
|
||||
"My Notes": "",
|
||||
"Name": "Ngalan",
|
||||
"Name your knowledge base": "",
|
||||
"Native": "",
|
||||
"New Chat": "Bag-ong diskusyon",
|
||||
"New Folder": "",
|
||||
"New Note": "",
|
||||
"New Password": "Bag-ong Password",
|
||||
"new-channel": "",
|
||||
"No content": "",
|
||||
"No content found": "",
|
||||
"No content found in file.": "",
|
||||
"No content to speak": "",
|
||||
"No distance available": "",
|
||||
"No feedbacks found": "",
|
||||
"No file selected": "",
|
||||
"No files found.": "",
|
||||
"No groups with access, add a group to grant access": "",
|
||||
"No HTML, CSS, or JavaScript content found.": "",
|
||||
"No inference engine with management support found": "",
|
||||
@@ -774,6 +817,7 @@
|
||||
"No model IDs": "",
|
||||
"No models found": "",
|
||||
"No models selected": "",
|
||||
"No Notes": "",
|
||||
"No results found": "",
|
||||
"No search query generated": "",
|
||||
"No source available": "Walay tinubdan nga anaa",
|
||||
@@ -782,6 +826,7 @@
|
||||
"None": "",
|
||||
"Not factually correct": "",
|
||||
"Not helpful": "",
|
||||
"Note deleted successfully": "",
|
||||
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "",
|
||||
"Notes": "",
|
||||
"Notification Sound": "",
|
||||
@@ -804,6 +849,7 @@
|
||||
"Only alphanumeric characters and hyphens are allowed": "",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "Ang alphanumeric nga mga karakter ug hyphen lang ang gitugotan sa command string.",
|
||||
"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
|
||||
"Only markdown files are allowed": "",
|
||||
"Only select users and groups with permission can access": "",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oops! ",
|
||||
"Oops! There are files still uploading. Please wait for the upload to complete.": "",
|
||||
@@ -849,6 +895,7 @@
|
||||
"Pipelines": "",
|
||||
"Pipelines Not Detected": "",
|
||||
"Pipelines Valves": "",
|
||||
"Plain text (.md)": "",
|
||||
"Plain text (.txt)": "",
|
||||
"Playground": "Dulaanan",
|
||||
"Playwright Timeout (ms)": "",
|
||||
@@ -891,6 +938,7 @@
|
||||
"Read": "",
|
||||
"Read Aloud": "",
|
||||
"Reasoning Effort": "",
|
||||
"Record": "",
|
||||
"Record voice": "Irekord ang tingog",
|
||||
"Redirecting you to Open WebUI Community": "Gi-redirect ka sa komunidad sa OpenWebUI",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative.": "",
|
||||
@@ -911,9 +959,8 @@
|
||||
"Repeat Penalty (Ollama)": "",
|
||||
"Reply in Thread": "",
|
||||
"Request Mode": "Query mode",
|
||||
"Reranking Engine": "",
|
||||
"Reranking Model": "",
|
||||
"Reranking model disabled": "",
|
||||
"Reranking model set to \"{{reranking_model}}\"": "",
|
||||
"Reset": "",
|
||||
"Reset All Models": "",
|
||||
"Reset Upload Directory": "",
|
||||
@@ -939,7 +986,7 @@
|
||||
"Save Tag": "",
|
||||
"Saved": "",
|
||||
"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": "Ang pag-save sa mga chat log direkta sa imong browser storage dili na suportado. ",
|
||||
"Scroll to bottom when switching between branches": "",
|
||||
"Scroll On Branch Change": "",
|
||||
"Search": "Pagpanukiduki",
|
||||
"Search a model": "",
|
||||
"Search Base": "",
|
||||
@@ -960,6 +1007,7 @@
|
||||
"Searched {{count}} sites": "",
|
||||
"Searching \"{{searchQuery}}\"": "",
|
||||
"Searching Knowledge for \"{{searchQuery}}\"": "",
|
||||
"Searching the web...": "",
|
||||
"Searxng Query URL": "",
|
||||
"See readme.md for instructions": "Tan-awa ang readme.md alang sa mga panudlo",
|
||||
"See what's new": "Tan-awa unsay bag-o",
|
||||
@@ -1020,6 +1068,8 @@
|
||||
"Show": "Pagpakita",
|
||||
"Show \"What's New\" modal on login": "",
|
||||
"Show Admin Details in Account Pending Overlay": "",
|
||||
"Show All": "",
|
||||
"Show Less": "",
|
||||
"Show Model": "",
|
||||
"Show shortcuts": "Ipakita ang mga shortcut",
|
||||
"Show your support!": "",
|
||||
@@ -1043,6 +1093,7 @@
|
||||
"Stream Chat Response": "",
|
||||
"STT Model": "",
|
||||
"STT Settings": "Mga setting sa STT",
|
||||
"Stylized PDF Export": "",
|
||||
"Subtitle (e.g. about the Roman Empire)": "",
|
||||
"Success": "Kalampusan",
|
||||
"Successfully updated.": "Malampuson nga na-update.",
|
||||
@@ -1089,6 +1140,7 @@
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Kini nagsiguro nga ang imong bililhon nga mga panag-istoryahanay luwas nga natipig sa imong backend database. ",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
|
||||
"This model is not publicly available. Please select another model.": "",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
"This option sets the maximum number of tokens the model can generate in its response. Increasing this limit allows the model to provide longer answers, but it may also increase the likelihood of unhelpful or irrelevant content being generated.": "",
|
||||
"This option will delete all existing files in the collection and replace them with newly uploaded files.": "",
|
||||
@@ -1140,7 +1192,6 @@
|
||||
"Tools Access": "",
|
||||
"Tools are a function calling system with arbitrary code execution": "",
|
||||
"Tools Function Calling Prompt": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "",
|
||||
"Tools Public Sharing": "",
|
||||
"Top K": "Top K",
|
||||
@@ -1163,6 +1214,7 @@
|
||||
"Unpin": "",
|
||||
"Unravel secrets": "",
|
||||
"Untagged": "",
|
||||
"Untitled": "",
|
||||
"Update": "",
|
||||
"Update and Copy Link": "",
|
||||
"Update for the latest features and improvements.": "",
|
||||
@@ -1173,6 +1225,7 @@
|
||||
"Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.": "",
|
||||
"Upload": "",
|
||||
"Upload a GGUF model": "Pag-upload ug modelo sa GGUF",
|
||||
"Upload Audio": "",
|
||||
"Upload directory": "",
|
||||
"Upload files": "",
|
||||
"Upload Files": "",
|
||||
@@ -1246,13 +1299,15 @@
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "Pagsulat og 50 ka pulong nga summary nga nagsumaryo [topic o keyword].",
|
||||
"Write something...": "",
|
||||
"Write your model template content here": "",
|
||||
"Yacy Instance URL": "",
|
||||
"Yacy Password": "",
|
||||
"Yacy Username": "",
|
||||
"Yesterday": "",
|
||||
"You": "",
|
||||
"You are currently using a trial license. Please contact support to upgrade your license.": "",
|
||||
"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
|
||||
"You cannot upload an empty file.": "",
|
||||
"You do not have permission to upload files": "",
|
||||
"You do not have permission to upload files.": "",
|
||||
"You have no archived conversations.": "",
|
||||
"You have shared this chat": "",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"(e.g. `sh webui.sh --api --api-auth username_password`)": "(např. `sh webui.sh --api --api-auth username_password`)",
|
||||
"(e.g. `sh webui.sh --api`)": "(např. `sh webui.sh --api`)",
|
||||
"(latest)": "Nejnovější",
|
||||
"(leave blank for to use commercial endpoint)": "",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "{{ models }}",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
@@ -62,6 +63,8 @@
|
||||
"Allow Chat Delete": "Povolit odstranění chatu",
|
||||
"Allow Chat Deletion": "Povolit odstranění chatu",
|
||||
"Allow Chat Edit": "Povolit úpravu chatu",
|
||||
"Allow Chat Export": "",
|
||||
"Allow Chat Share": "",
|
||||
"Allow File Upload": "Povolit nahrávat soubory",
|
||||
"Allow Multiple Models in Chat": "",
|
||||
"Allow non-local voices": "Povolit ne-místní hlasy",
|
||||
@@ -76,6 +79,7 @@
|
||||
"Always": "",
|
||||
"Always Collapse Code Blocks": "",
|
||||
"Always Expand Details": "",
|
||||
"Always Play Notification Sound": "",
|
||||
"Amazing": "",
|
||||
"an assistant": "asistent",
|
||||
"Analyzed": "",
|
||||
@@ -101,6 +105,7 @@
|
||||
"Are you sure you want to delete this channel?": "",
|
||||
"Are you sure you want to delete this message?": "",
|
||||
"Are you sure you want to unarchive all archived chats?": "",
|
||||
"Are you sure you want to update this user's role to **{{ROLE}}**?": "",
|
||||
"Are you sure?": "Jste si jistý?",
|
||||
"Arena Models": "Arena modely",
|
||||
"Artifacts": "Artefakty",
|
||||
@@ -143,6 +148,7 @@
|
||||
"Bing Search V7 Subscription Key": "",
|
||||
"Bocha Search API Key": "",
|
||||
"Boosting or penalizing specific tokens for constrained responses. Bias values will be clamped between -100 and 100 (inclusive). (Default: none)": "",
|
||||
"Both Docling OCR Engine and Language(s) must be provided or both left empty.": "",
|
||||
"Brave Search API Key": "Klíč API pro Brave Search",
|
||||
"By {{name}}": "",
|
||||
"Bypass Embedding and Retrieval": "",
|
||||
@@ -153,6 +159,7 @@
|
||||
"Cancel": "Zrušit",
|
||||
"Capabilities": "Schopnosti",
|
||||
"Capture": "",
|
||||
"Capture Audio": "",
|
||||
"Certificate Path": "",
|
||||
"Change Password": "Změnit heslo",
|
||||
"Channel Name": "",
|
||||
@@ -262,6 +269,8 @@
|
||||
"Create Knowledge": "Vytvořit knowledge",
|
||||
"Create new key": "Vytvořit nový klíč",
|
||||
"Create new secret key": "Vytvořte nový tajný klíč",
|
||||
"Create Note": "",
|
||||
"Create your first note by clicking on the plus button below.": "",
|
||||
"Created at": "Vytvořeno dne",
|
||||
"Created At": "Vytvořeno dne",
|
||||
"Created by": "Vytvořeno uživatelem",
|
||||
@@ -299,6 +308,7 @@
|
||||
"Delete function?": "Funkce pro odstranění?",
|
||||
"Delete Message": "",
|
||||
"Delete message?": "",
|
||||
"Delete note?": "",
|
||||
"Delete prompt?": "Smazat prompt?",
|
||||
"delete this link": "smazat tento odkaz",
|
||||
"Delete tool?": "Odstranit nástroj?",
|
||||
@@ -354,7 +364,7 @@
|
||||
"Download Database": "Stáhnout databázi",
|
||||
"Drag and drop a file to upload or select a file to view": "",
|
||||
"Draw": "Namalovat",
|
||||
"Drop any files here to add to the conversation": "Sem přetáhněte libovolné soubory, které chcete přidat do konverzace",
|
||||
"Drop any files here to upload": "",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "např. '30s','10m'. Platné časové jednotky jsou 's', 'm', 'h'.",
|
||||
"e.g. \"json\" or a JSON schema": "",
|
||||
"e.g. 60": "",
|
||||
@@ -364,7 +374,9 @@
|
||||
"e.g. my_filter": "",
|
||||
"e.g. my_tools": "",
|
||||
"e.g. Tools for performing various operations": "",
|
||||
"e.g., 3, 4, 5 (leave blank for default)": "",
|
||||
"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
|
||||
"e.g., westus (leave blank for eastus)": "",
|
||||
"Edit": "Upravit",
|
||||
"Edit Arena Model": "Upravit Arena Model",
|
||||
"Edit Channel": "",
|
||||
@@ -392,7 +404,9 @@
|
||||
"Enable Mirostat sampling for controlling perplexity.": "",
|
||||
"Enable New Sign Ups": "Povolit nové registrace",
|
||||
"Enabled": "Povoleno",
|
||||
"Endpoint URL": "",
|
||||
"Enforce Temporary Chat": "",
|
||||
"Enhance": "",
|
||||
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Ujistěte se, že váš CSV soubor obsahuje 4 sloupce v tomto pořadí: Name, Email, Password, Role.",
|
||||
"Enter {{role}} message here": "Zadejte zprávu {{role}} sem",
|
||||
"Enter a detail about yourself for your LLMs to recall": "Zadejte podrobnost o sobě, kterou si vaše LLM mají pamatovat.",
|
||||
@@ -407,13 +421,19 @@
|
||||
"Enter CFG Scale (e.g. 7.0)": "Zadejte měřítko CFG (např. 7.0)",
|
||||
"Enter Chunk Overlap": "Zadejte překryv části",
|
||||
"Enter Chunk Size": "Zadejte velikost bloku",
|
||||
"Enter comma-seperated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "",
|
||||
"Enter comma-separated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "",
|
||||
"Enter description": "Zadejte popis",
|
||||
"Enter Docling OCR Engine": "",
|
||||
"Enter Docling OCR Language(s)": "",
|
||||
"Enter Docling Server URL": "",
|
||||
"Enter Document Intelligence Endpoint": "",
|
||||
"Enter Document Intelligence Key": "",
|
||||
"Enter domains separated by commas (e.g., example.com,site.org)": "",
|
||||
"Enter Exa API Key": "",
|
||||
"Enter External Web Loader API Key": "",
|
||||
"Enter External Web Loader URL": "",
|
||||
"Enter External Web Search API Key": "",
|
||||
"Enter External Web Search URL": "",
|
||||
"Enter Firecrawl API Base URL": "",
|
||||
"Enter Firecrawl API Key": "",
|
||||
"Enter Github Raw URL": "Zadejte URL adresu Github Raw",
|
||||
@@ -431,6 +451,7 @@
|
||||
"Enter Model ID": "Zadejte ID modelu",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Zadejte označení modelu (např. {{modelTag}})",
|
||||
"Enter Mojeek Search API Key": "",
|
||||
"Enter New Password": "",
|
||||
"Enter Number of Steps (e.g. 50)": "Zadejte počet kroků (např. 50)",
|
||||
"Enter Perplexity API Key": "",
|
||||
"Enter Playwright Timeout": "",
|
||||
@@ -467,11 +488,15 @@
|
||||
"Enter Top K Reranker": "",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "Zadejte URL (např. http://127.0.0.1:7860/)",
|
||||
"Enter URL (e.g. http://localhost:11434)": "Zadejte URL (např. http://localhost:11434)",
|
||||
"Enter Yacy Password": "",
|
||||
"Enter Yacy URL (e.g. http://yacy.example.com:8090)": "",
|
||||
"Enter Yacy Username": "",
|
||||
"Enter your current password": "",
|
||||
"Enter Your Email": "Zadejte svůj email",
|
||||
"Enter Your Full Name": "Zadejte své plné jméno",
|
||||
"Enter your message": "Zadejte svou zprávu",
|
||||
"Enter your name": "",
|
||||
"Enter Your Name": "",
|
||||
"Enter your new password": "",
|
||||
"Enter Your Password": "Zadejte své heslo",
|
||||
"Enter Your Role": "Zadejte svou roli",
|
||||
@@ -480,6 +505,8 @@
|
||||
"Error": "Chyba",
|
||||
"ERROR": "Chyba",
|
||||
"Error accessing Google Drive: {{error}}": "",
|
||||
"Error accessing media devices.": "",
|
||||
"Error starting recording.": "",
|
||||
"Error uploading file: {{error}}": "",
|
||||
"Evaluations": "Hodnocení",
|
||||
"Exa API Key": "",
|
||||
@@ -511,10 +538,16 @@
|
||||
"Export Tools": "Exportní nástroje",
|
||||
"External": "",
|
||||
"External Models": "Externí modely",
|
||||
"External Web Loader API Key": "",
|
||||
"External Web Loader URL": "",
|
||||
"External Web Search API Key": "",
|
||||
"External Web Search URL": "",
|
||||
"Failed to add file.": "Nepodařilo se přidat soubor.",
|
||||
"Failed to connect to {{URL}} OpenAPI tool server": "",
|
||||
"Failed to create API Key.": "Nepodařilo se vytvořit API klíč.",
|
||||
"Failed to delete note": "",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to load file content.": "",
|
||||
"Failed to read clipboard contents": "Nepodařilo se přečíst obsah schránky",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "",
|
||||
@@ -544,7 +577,6 @@
|
||||
"Fluidly stream large external response chunks": "Plynule streamujte velké externí části odpovědí",
|
||||
"Focus chat input": "Zaměřte se na vstup chatu",
|
||||
"Folder deleted successfully": "Složka byla úspěšně smazána",
|
||||
"Folder name cannot be empty": "Název složky nesmí být prázdný",
|
||||
"Folder name cannot be empty.": "Název složky nesmí být prázdný.",
|
||||
"Folder name updated successfully": "Název složky byl úspěšně aktualizován.",
|
||||
"Followed instructions perfectly": "Dodržel pokyny dokonale.",
|
||||
@@ -565,17 +597,18 @@
|
||||
"Function Name": "",
|
||||
"Function updated successfully": "Funkce byla úspěšně aktualizována.",
|
||||
"Functions": "Funkce",
|
||||
"Functions allow arbitrary code execution": "Funkce umožňují vykonávat libovolný kód.",
|
||||
"Functions allow arbitrary code execution.": "Funkce umožňují provádění libovolného kódu.",
|
||||
"Functions imported successfully": "Funkce byly úspěšně importovány",
|
||||
"Gemini": "",
|
||||
"Gemini API Config": "",
|
||||
"Gemini API Key is required.": "",
|
||||
"General": "Obecný",
|
||||
"Generate": "",
|
||||
"Generate an image": "",
|
||||
"Generate Image": "Vygenerovat obrázek",
|
||||
"Generate prompt pair": "",
|
||||
"Generating search query": "Generování vyhledávacího dotazu",
|
||||
"Generating...": "",
|
||||
"Get started": "",
|
||||
"Get started with {{WEBUI_NAME}}": "",
|
||||
"Global": "Globální",
|
||||
@@ -622,6 +655,7 @@
|
||||
"Import Config from JSON File": "Importování konfigurace z JSON souboru",
|
||||
"Import Functions": "Načítání funkcí",
|
||||
"Import Models": "Importování modelů",
|
||||
"Import Notes": "",
|
||||
"Import Presets": "",
|
||||
"Import Prompts": "Importovat Prompty",
|
||||
"Import Tools": "Importovat nástroje",
|
||||
@@ -636,6 +670,7 @@
|
||||
"Instant Auto-Send After Voice Transcription": "Okamžité automatické odeslání po přepisu hlasu",
|
||||
"Integration": "",
|
||||
"Interface": "Rozhraní",
|
||||
"Invalid file content": "",
|
||||
"Invalid file format.": "Neplatný formát souboru.",
|
||||
"Invalid JSON schema": "",
|
||||
"Invalid Tag": "Neplatný tag",
|
||||
@@ -706,6 +741,7 @@
|
||||
"Manage Pipelines": "Správa pipelines",
|
||||
"Manage Tool Servers": "",
|
||||
"March": "Březen",
|
||||
"Max Speakers": "",
|
||||
"Max Tokens (num_predict)": "Maximální počet tokenů (num_predict)",
|
||||
"Max Upload Count": "Maximální počet nahrání",
|
||||
"Max Upload Size": "Maximální velikost nahrávání",
|
||||
@@ -718,8 +754,12 @@
|
||||
"Memory deleted successfully": "Paměť byla úspěšně smazána",
|
||||
"Memory updated successfully": "Paměť úspěšně aktualizována",
|
||||
"Merge Responses": "Sloučit odpovědi",
|
||||
"Merged Response": "Sloučená odpověď",
|
||||
"Message rating should be enabled to use this feature": "Hodnocení zpráv musí být povoleno, aby bylo možné tuto funkci používat.",
|
||||
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Zprávy, které odešlete po vytvoření odkazu, nebudou sdíleny. Uživatelé s URL budou moci zobrazit sdílený chat.",
|
||||
"Microsoft OneDrive": "",
|
||||
"Microsoft OneDrive (personal)": "",
|
||||
"Microsoft OneDrive (work/school)": "",
|
||||
"Min P": "Min P",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
@@ -753,19 +793,22 @@
|
||||
"Mojeek Search API Key": "",
|
||||
"more": "více",
|
||||
"More": "Více",
|
||||
"My Notes": "",
|
||||
"Name": "Jméno",
|
||||
"Name your knowledge base": "",
|
||||
"Native": "",
|
||||
"New Chat": "Nový chat",
|
||||
"New Folder": "",
|
||||
"New Note": "",
|
||||
"New Password": "Nové heslo",
|
||||
"new-channel": "",
|
||||
"No content": "",
|
||||
"No content found": "Nebyly nalezeny žádné obsahové informace.",
|
||||
"No content found in file.": "",
|
||||
"No content to speak": "Žádný obsah k diskusi.",
|
||||
"No distance available": "Není dostupná žádná vzdálenost",
|
||||
"No feedbacks found": "Žádná zpětná vazba nenalezena",
|
||||
"No file selected": "Nebyl vybrán žádný soubor",
|
||||
"No files found.": "Nebyly nalezeny žádné soubory.",
|
||||
"No groups with access, add a group to grant access": "",
|
||||
"No HTML, CSS, or JavaScript content found.": "Nebyl nalezen žádný obsah HTML, CSS ani JavaScriptu.",
|
||||
"No inference engine with management support found": "",
|
||||
@@ -774,6 +817,7 @@
|
||||
"No model IDs": "",
|
||||
"No models found": "Nebyly nalezeny žádné modely",
|
||||
"No models selected": "",
|
||||
"No Notes": "",
|
||||
"No results found": "Nebyly nalezeny žádné výsledky",
|
||||
"No search query generated": "Nebyl vygenerován žádný vyhledávací dotaz.",
|
||||
"No source available": "Není k dispozici žádný zdroj.",
|
||||
@@ -782,6 +826,7 @@
|
||||
"None": "Žádný",
|
||||
"Not factually correct": "Není fakticky správné",
|
||||
"Not helpful": "Nepomocné",
|
||||
"Note deleted successfully": "",
|
||||
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Poznámka: Pokud nastavíte minimální skóre, vyhledávání vrátí pouze dokumenty s hodnocením, které je větší nebo rovno zadanému minimálnímu skóre.",
|
||||
"Notes": "Poznámky",
|
||||
"Notification Sound": "",
|
||||
@@ -804,6 +849,7 @@
|
||||
"Only alphanumeric characters and hyphens are allowed": "",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "Příkazový řetězec smí obsahovat pouze alfanumerické znaky a pomlčky.",
|
||||
"Only collections can be edited, create a new knowledge base to edit/add documents.": "Pouze kolekce mohou být upravovány, pro úpravu/přidání dokumentů vytvořte novou znalostní bázi.",
|
||||
"Only markdown files are allowed": "",
|
||||
"Only select users and groups with permission can access": "",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Jejda! Vypadá to, že URL adresa je neplatná. Prosím, zkontrolujte ji a zkuste to znovu.",
|
||||
"Oops! There are files still uploading. Please wait for the upload to complete.": "Jejda! Některé soubory se stále nahrávají. Prosím, počkejte, až bude nahrávání dokončeno.",
|
||||
@@ -849,6 +895,7 @@
|
||||
"Pipelines": "",
|
||||
"Pipelines Not Detected": "Přenosové kanály nebyly detekovány",
|
||||
"Pipelines Valves": "",
|
||||
"Plain text (.md)": "",
|
||||
"Plain text (.txt)": "Čistý text (.txt)",
|
||||
"Playground": "",
|
||||
"Playwright Timeout (ms)": "",
|
||||
@@ -891,6 +938,7 @@
|
||||
"Read": "",
|
||||
"Read Aloud": "Číst nahlas",
|
||||
"Reasoning Effort": "",
|
||||
"Record": "",
|
||||
"Record voice": "Nahrát hlas",
|
||||
"Redirecting you to Open WebUI Community": "Přesměrování na komunitu OpenWebUI",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative.": "",
|
||||
@@ -911,9 +959,8 @@
|
||||
"Repeat Penalty (Ollama)": "",
|
||||
"Reply in Thread": "",
|
||||
"Request Mode": "Režim žádosti",
|
||||
"Reranking Engine": "",
|
||||
"Reranking Model": "Model pro přehodnocení pořadí",
|
||||
"Reranking model disabled": "Přeřazovací model je deaktivován",
|
||||
"Reranking model set to \"{{reranking_model}}\"": "Model pro přeřazení nastaven na \"{{reranking_model}}\"",
|
||||
"Reset": "režim Reset",
|
||||
"Reset All Models": "",
|
||||
"Reset Upload Directory": "Resetovat adresář nahrávání",
|
||||
@@ -939,7 +986,7 @@
|
||||
"Save Tag": "Uložit štítek",
|
||||
"Saved": "Uloženo",
|
||||
"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": "Ukládání záznamů chatu přímo do úložiště vašeho prohlížeče již není podporováno. Věnujte prosím chvíli stažení a smazání svých záznamů chatu kliknutím na tlačítko níže. Nemějte obavy, můžete snadno znovu importovat své záznamy chatu na backend prostřednictvím",
|
||||
"Scroll to bottom when switching between branches": "Přejít na konec při přepínání mezi větvemi.",
|
||||
"Scroll On Branch Change": "",
|
||||
"Search": "Vyhledávání",
|
||||
"Search a model": "Vyhledat model",
|
||||
"Search Base": "",
|
||||
@@ -960,6 +1007,7 @@
|
||||
"Searched {{count}} sites": "",
|
||||
"Searching \"{{searchQuery}}\"": "Hledání \"{{searchQuery}}\"",
|
||||
"Searching Knowledge for \"{{searchQuery}}\"": "Vyhledávání znalostí pro \"{{searchQuery}}\"",
|
||||
"Searching the web...": "",
|
||||
"Searxng Query URL": "Adresa URL dotazu Searxng",
|
||||
"See readme.md for instructions": "Podívejte se do {{readme.md}} pro pokyny.",
|
||||
"See what's new": "Podívejte se, co je nového",
|
||||
@@ -1020,6 +1068,8 @@
|
||||
"Show": "Zobrazit",
|
||||
"Show \"What's New\" modal on login": "",
|
||||
"Show Admin Details in Account Pending Overlay": "Zobrazit podrobnosti administrátora v překryvném okně s čekajícím účtem",
|
||||
"Show All": "",
|
||||
"Show Less": "",
|
||||
"Show Model": "",
|
||||
"Show shortcuts": "Zobrazit klávesové zkratky",
|
||||
"Show your support!": "Vyjadřete svou podporu!",
|
||||
@@ -1043,6 +1093,7 @@
|
||||
"Stream Chat Response": "Odezva chatu Stream",
|
||||
"STT Model": "Model rozpoznávání řeči na text (STT)",
|
||||
"STT Settings": "Nastavení STT (Rozpoznávání řeči)",
|
||||
"Stylized PDF Export": "",
|
||||
"Subtitle (e.g. about the Roman Empire)": "Titulky (např. o Římské říši)",
|
||||
"Success": "Úspěch",
|
||||
"Successfully updated.": "Úspěšně aktualizováno.",
|
||||
@@ -1089,6 +1140,7 @@
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "To zajišťuje, že vaše cenné konverzace jsou bezpečně uloženy ve vaší backendové databázi. Děkujeme!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Jedná se o experimentální funkci, nemusí fungovat podle očekávání a může být kdykoliv změněna.",
|
||||
"This model is not publicly available. Please select another model.": "",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
"This option sets the maximum number of tokens the model can generate in its response. Increasing this limit allows the model to provide longer answers, but it may also increase the likelihood of unhelpful or irrelevant content being generated.": "",
|
||||
"This option will delete all existing files in the collection and replace them with newly uploaded files.": "Tato volba odstraní všechny existující soubory ve sbírce a nahradí je nově nahranými soubory.",
|
||||
@@ -1140,7 +1192,6 @@
|
||||
"Tools Access": "",
|
||||
"Tools are a function calling system with arbitrary code execution": "Nástroje jsou systémem pro volání funkcí s vykonáváním libovolného kódu.",
|
||||
"Tools Function Calling Prompt": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "Nástroje mají systém volání funkcí, který umožňuje libovolné spouštění kódu.",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "Nástroje mají systém volání funkcí, který umožňuje spuštění libovolného kódu.",
|
||||
"Tools Public Sharing": "",
|
||||
"Top K": "Top K",
|
||||
@@ -1163,6 +1214,7 @@
|
||||
"Unpin": "Odepnout",
|
||||
"Unravel secrets": "",
|
||||
"Untagged": "Nebyla označena",
|
||||
"Untitled": "",
|
||||
"Update": "Aktualizovat",
|
||||
"Update and Copy Link": "Aktualizovat a zkopírovat odkaz",
|
||||
"Update for the latest features and improvements.": "Aktualizace pro nejnovější funkce a vylepšení.",
|
||||
@@ -1173,6 +1225,7 @@
|
||||
"Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.": "",
|
||||
"Upload": "Nahrát",
|
||||
"Upload a GGUF model": "Nahrát model ve formátu GGUF",
|
||||
"Upload Audio": "",
|
||||
"Upload directory": "Nahrát adresář",
|
||||
"Upload files": "Nahrát soubory",
|
||||
"Upload Files": "Nahrát soubory",
|
||||
@@ -1246,13 +1299,15 @@
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "Napište shrnutí na 50 slov, které shrnuje [téma nebo klíčové slovo].",
|
||||
"Write something...": "Napište něco...",
|
||||
"Write your model template content here": "",
|
||||
"Yacy Instance URL": "",
|
||||
"Yacy Password": "",
|
||||
"Yacy Username": "",
|
||||
"Yesterday": "Včera",
|
||||
"You": "Vy",
|
||||
"You are currently using a trial license. Please contact support to upgrade your license.": "",
|
||||
"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Můžete komunikovat pouze s maximálně {{maxCount}} soubor(y) najednou.",
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Můžete personalizovat své interakce s LLM pomocí přidávání vzpomínek prostřednictvím tlačítka 'Spravovat' níže, což je učiní pro vás užitečnějšími a lépe přizpůsobenými.",
|
||||
"You cannot upload an empty file.": "Nemůžete nahrát prázdný soubor.",
|
||||
"You do not have permission to upload files": "",
|
||||
"You do not have permission to upload files.": "",
|
||||
"You have no archived conversations.": "Nemáte žádné archivované konverzace.",
|
||||
"You have shared this chat": "Sdíleli jste tento chat.",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@
|
||||
"(e.g. `sh webui.sh --api --api-auth username_password`)": "(z. B. `sh webui.sh --api --api-auth username_password`)",
|
||||
"(e.g. `sh webui.sh --api`)": "(z. B. `sh webui.sh --api`)",
|
||||
"(latest)": "(neueste)",
|
||||
"(leave blank for to use commercial endpoint)": "",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "{{ Modelle }}",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
@@ -57,17 +58,19 @@
|
||||
"All": "Alle",
|
||||
"All Documents": "Alle Dokumente",
|
||||
"All models deleted successfully": "Alle Modelle erfolgreich gelöscht",
|
||||
"Allow Call": "",
|
||||
"Allow Call": "Anruffunktion erlauben",
|
||||
"Allow Chat Controls": "Chat-Steuerung erlauben",
|
||||
"Allow Chat Delete": "Löschen von Chats erlauben",
|
||||
"Allow Chat Deletion": "Löschen von Chats erlauben",
|
||||
"Allow Chat Edit": "Bearbeiten von Chats erlauben",
|
||||
"Allow Chat Export": "Erlaube Chat Export",
|
||||
"Allow Chat Share": "Erlaube Chat teilen",
|
||||
"Allow File Upload": "Hochladen von Dateien erlauben",
|
||||
"Allow Multiple Models in Chat": "",
|
||||
"Allow Multiple Models in Chat": "Multiple Modelle in Chat erlauben",
|
||||
"Allow non-local voices": "Nicht-lokale Stimmen erlauben",
|
||||
"Allow Speech to Text": "",
|
||||
"Allow Speech to Text": "Sprache zu Text erlauben",
|
||||
"Allow Temporary Chat": "Temporäre Chats erlauben",
|
||||
"Allow Text to Speech": "",
|
||||
"Allow Text to Speech": "Text zu Sprache erlauben",
|
||||
"Allow User Location": "Standort freigeben",
|
||||
"Allow Voice Interruption in Call": "Unterbrechung durch Stimme im Anruf zulassen",
|
||||
"Allowed Endpoints": "Erlaubte Endpunkte",
|
||||
@@ -76,6 +79,7 @@
|
||||
"Always": "Immer",
|
||||
"Always Collapse Code Blocks": "Code-Blöcke immer zuklappen",
|
||||
"Always Expand Details": "Details immer aufklappen",
|
||||
"Always Play Notification Sound": "",
|
||||
"Amazing": "Fantastisch",
|
||||
"an assistant": "ein Assistent",
|
||||
"Analyzed": "Analysiert",
|
||||
@@ -101,6 +105,7 @@
|
||||
"Are you sure you want to delete this channel?": "Sind Sie sicher, dass Sie diesen Kanal löschen möchten?",
|
||||
"Are you sure you want to delete this message?": "Sind Sie sicher, dass Sie diese Nachricht löschen möchten?",
|
||||
"Are you sure you want to unarchive all archived chats?": "Sind Sie sicher, dass Sie alle archivierten Chats wiederherstellen möchten?",
|
||||
"Are you sure you want to update this user's role to **{{ROLE}}**?": "",
|
||||
"Are you sure?": "Sind Sie sicher?",
|
||||
"Arena Models": "Arena-Modelle",
|
||||
"Artifacts": "Artefakte",
|
||||
@@ -126,7 +131,7 @@
|
||||
"AUTOMATIC1111 Base URL": "AUTOMATIC1111-Basis-URL",
|
||||
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111-Basis-URL ist erforderlich.",
|
||||
"Available list": "Verfügbare Liste",
|
||||
"Available Tools": "",
|
||||
"Available Tools": "Verfügbare Werkzeuge",
|
||||
"available!": "Verfügbar!",
|
||||
"Awful": "Schrecklich",
|
||||
"Azure AI Speech": "Azure AI Speech",
|
||||
@@ -143,6 +148,7 @@
|
||||
"Bing Search V7 Subscription Key": "Bing Search V7-Abonnement-Schlüssel",
|
||||
"Bocha Search API Key": "",
|
||||
"Boosting or penalizing specific tokens for constrained responses. Bias values will be clamped between -100 and 100 (inclusive). (Default: none)": "",
|
||||
"Both Docling OCR Engine and Language(s) must be provided or both left empty.": "",
|
||||
"Brave Search API Key": "Brave Search API-Schlüssel",
|
||||
"By {{name}}": "Von {{name}}",
|
||||
"Bypass Embedding and Retrieval": "Embedding und Retrieval umgehen",
|
||||
@@ -153,6 +159,7 @@
|
||||
"Cancel": "Abbrechen",
|
||||
"Capabilities": "Fähigkeiten",
|
||||
"Capture": "Aufnehmen",
|
||||
"Capture Audio": "",
|
||||
"Certificate Path": "Zertifikatpfad",
|
||||
"Change Password": "Passwort ändern",
|
||||
"Channel Name": "Kanalname",
|
||||
@@ -223,9 +230,9 @@
|
||||
"Confirm your action": "Bestätigen Sie Ihre Aktion.",
|
||||
"Confirm your new password": "Neues Passwort bestätigen",
|
||||
"Connect to your own OpenAI compatible API endpoints.": "Verbinden Sie sich zu Ihren OpenAI-kompatiblen Endpunkten.",
|
||||
"Connect to your own OpenAPI compatible external tool servers.": "",
|
||||
"Connection failed": "",
|
||||
"Connection successful": "",
|
||||
"Connect to your own OpenAPI compatible external tool servers.": "Verbinden Sie sich zu Ihren OpenAPI-kompatiblen Werkzeug-Servern.",
|
||||
"Connection failed": "Verbindung fehlgeschlagen",
|
||||
"Connection successful": "Verbindung erfolgreich",
|
||||
"Connections": "Verbindungen",
|
||||
"Connections saved successfully": "",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
|
||||
@@ -245,7 +252,7 @@
|
||||
"Copied shared chat URL to clipboard!": "Freigabelink in die Zwischenablage kopiert!",
|
||||
"Copied to clipboard": "In die Zwischenablage kopiert",
|
||||
"Copy": "Kopieren",
|
||||
"Copy Formatted Text": "",
|
||||
"Copy Formatted Text": "Formatierten Text kopieren",
|
||||
"Copy last code block": "Letzten Codeblock kopieren",
|
||||
"Copy last response": "Letzte Antwort kopieren",
|
||||
"Copy Link": "Link kopieren",
|
||||
@@ -262,6 +269,8 @@
|
||||
"Create Knowledge": "Wissen erstellen",
|
||||
"Create new key": "Neuen Schlüssel erstellen",
|
||||
"Create new secret key": "Neuen API-Schlüssel erstellen",
|
||||
"Create Note": "Notiz erstellen",
|
||||
"Create your first note by clicking on the plus button below.": "",
|
||||
"Created at": "Erstellt am",
|
||||
"Created At": "Erstellt am",
|
||||
"Created by": "Erstellt von",
|
||||
@@ -299,6 +308,7 @@
|
||||
"Delete function?": "Funktion löschen?",
|
||||
"Delete Message": "Nachricht löschen",
|
||||
"Delete message?": "Nachricht löschen?",
|
||||
"Delete note?": "Notiz löschen?",
|
||||
"Delete prompt?": "Prompt löschen?",
|
||||
"delete this link": "diesen Link löschen",
|
||||
"Delete tool?": "Werkzeug löschen?",
|
||||
@@ -308,13 +318,13 @@
|
||||
"Deleted User": "Benutzer gelöscht",
|
||||
"Describe your knowledge base and objectives": "Beschreibe deinen Wissensspeicher und deine Ziele",
|
||||
"Description": "Beschreibung",
|
||||
"Detect Artifacts Automatically": "",
|
||||
"Detect Artifacts Automatically": "Artefakte automatisch erkennen",
|
||||
"Didn't fully follow instructions": "Nicht genau den Answeisungen gefolgt",
|
||||
"Direct": "Direkt",
|
||||
"Direct Connections": "Direktverbindungen",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Direktverbindungen ermöglichen es Benutzern, sich mit ihren eigenen OpenAI-kompatiblen API-Endpunkten zu verbinden.",
|
||||
"Direct Connections settings updated": "Direktverbindungs-Einstellungen aktualisiert",
|
||||
"Direct Tool Servers": "",
|
||||
"Direct Tool Servers": "Direkt verbundene Werkzeug-Server",
|
||||
"Disabled": "Deaktiviert",
|
||||
"Discover a function": "Entdecken Sie weitere Funktionen",
|
||||
"Discover a model": "Entdecken Sie weitere Modelle",
|
||||
@@ -346,7 +356,7 @@
|
||||
"Don't have an account?": "Haben Sie noch kein Benutzerkonto?",
|
||||
"don't install random functions from sources you don't trust.": "installieren Sie keine Funktionen aus Quellen, denen Sie nicht vertrauen.",
|
||||
"don't install random tools from sources you don't trust.": "installieren Sie keine Werkzeuge aus Quellen, denen Sie nicht vertrauen.",
|
||||
"Don't like the style": "schlechter Schreibstil",
|
||||
"Don't like the style": "Schlechter Schreibstil",
|
||||
"Done": "Erledigt",
|
||||
"Download": "Exportieren",
|
||||
"Download as SVG": "Exportieren als SVG",
|
||||
@@ -354,7 +364,7 @@
|
||||
"Download Database": "Datenbank exportieren",
|
||||
"Drag and drop a file to upload or select a file to view": "Ziehen Sie eine Datei zum Hochladen oder wählen Sie eine Datei zum Anzeigen aus",
|
||||
"Draw": "Zeichnen",
|
||||
"Drop any files here to add to the conversation": "Ziehen Sie beliebige Dateien hierher, um sie dem Chat hinzuzufügen",
|
||||
"Drop any files here to upload": "",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "z. B. '30s','10m'. Gültige Zeiteinheiten sind 's', 'm', 'h'.",
|
||||
"e.g. \"json\" or a JSON schema": "",
|
||||
"e.g. 60": "z. B. 60",
|
||||
@@ -364,7 +374,9 @@
|
||||
"e.g. my_filter": "z. B. mein_filter",
|
||||
"e.g. my_tools": "z. B. meine_werkzeuge",
|
||||
"e.g. Tools for performing various operations": "z. B. Werkzeuge für verschiedene Operationen",
|
||||
"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
|
||||
"e.g., 3, 4, 5 (leave blank for default)": "",
|
||||
"e.g., en-US,ja-JP (leave blank for auto-detect)": "z. B. en-US,de-DE (freilassen für automatische Erkennung)",
|
||||
"e.g., westus (leave blank for eastus)": "",
|
||||
"Edit": "Bearbeiten",
|
||||
"Edit Arena Model": "Arena-Modell bearbeiten",
|
||||
"Edit Channel": "Kanal bearbeiten",
|
||||
@@ -392,7 +404,9 @@
|
||||
"Enable Mirostat sampling for controlling perplexity.": "",
|
||||
"Enable New Sign Ups": "Registrierung erlauben",
|
||||
"Enabled": "Aktiviert",
|
||||
"Endpoint URL": "",
|
||||
"Enforce Temporary Chat": "Temporären Chat erzwingen",
|
||||
"Enhance": "",
|
||||
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Stellen Sie sicher, dass Ihre CSV-Datei 4 Spalten in dieser Reihenfolge enthält: Name, E-Mail, Passwort, Rolle.",
|
||||
"Enter {{role}} message here": "Geben Sie die {{role}}-Nachricht hier ein",
|
||||
"Enter a detail about yourself for your LLMs to recall": "Geben Sie ein Detail über sich selbst ein, das Ihre Sprachmodelle (LLMs) sich merken sollen",
|
||||
@@ -401,21 +415,27 @@
|
||||
"Enter Application DN Password": "Geben Sie das Anwendungs-DN-Passwort ein",
|
||||
"Enter Bing Search V7 Endpoint": "Geben Sie den Bing Search V7-Endpunkt ein",
|
||||
"Enter Bing Search V7 Subscription Key": "Geben Sie den Bing Search V7-Abonnement-Schlüssel ein",
|
||||
"Enter Bocha Search API Key": "",
|
||||
"Enter Bocha Search API Key": "Geben Sie den Bocha Search API-Schlüssel ein",
|
||||
"Enter Brave Search API Key": "Geben Sie den Brave Search API-Schlüssel ein",
|
||||
"Enter certificate path": "Geben Sie den Zertifikatpfad ein",
|
||||
"Enter CFG Scale (e.g. 7.0)": "Geben Sie die CFG-Skala ein (z. B. 7.0)",
|
||||
"Enter Chunk Overlap": "Geben Sie die Blocküberlappung ein",
|
||||
"Enter Chunk Size": "Geben Sie die Blockgröße ein",
|
||||
"Enter comma-seperated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "",
|
||||
"Enter comma-separated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "",
|
||||
"Enter description": "Geben Sie eine Beschreibung ein",
|
||||
"Enter Docling OCR Engine": "",
|
||||
"Enter Docling OCR Language(s)": "",
|
||||
"Enter Docling Server URL": "",
|
||||
"Enter Document Intelligence Endpoint": "",
|
||||
"Enter Document Intelligence Key": "",
|
||||
"Enter domains separated by commas (e.g., example.com,site.org)": "Geben Sie die Domains durch Kommas separiert ein (z.B. example.com,site.org)",
|
||||
"Enter Exa API Key": "Geben Sie den Exa-API-Schlüssel ein",
|
||||
"Enter Firecrawl API Base URL": "",
|
||||
"Enter Firecrawl API Key": "",
|
||||
"Enter External Web Loader API Key": "",
|
||||
"Enter External Web Loader URL": "",
|
||||
"Enter External Web Search API Key": "",
|
||||
"Enter External Web Search URL": "",
|
||||
"Enter Firecrawl API Base URL": "Geben Sie die Firecrawl Basis-URL ein",
|
||||
"Enter Firecrawl API Key": "Geben Sie den Firecrawl API-Schlüssel ein",
|
||||
"Enter Github Raw URL": "Geben Sie die Github Raw-URL ein",
|
||||
"Enter Google PSE API Key": "Geben Sie den Google PSE-API-Schlüssel ein",
|
||||
"Enter Google PSE Engine Id": "Geben Sie die Google PSE-Engine-ID ein",
|
||||
@@ -427,14 +447,15 @@
|
||||
"Enter Kagi Search API Key": "Geben Sie den Kagi Search API-Schlüssel ein",
|
||||
"Enter Key Behavior": "Verhalten von 'Enter'",
|
||||
"Enter language codes": "Geben Sie die Sprachcodes ein",
|
||||
"Enter Mistral API Key": "",
|
||||
"Enter Mistral API Key": "Geben Sie den Mistral API-Schlüssel ein",
|
||||
"Enter Model ID": "Geben Sie die Modell-ID ein",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Geben Sie den Model-Tag ein",
|
||||
"Enter Mojeek Search API Key": "Geben Sie den Mojeek Search API-Schlüssel ein",
|
||||
"Enter New Password": "",
|
||||
"Enter Number of Steps (e.g. 50)": "Geben Sie die Anzahl an Schritten ein (z. B. 50)",
|
||||
"Enter Perplexity API Key": "Geben Sie den Perplexity API-Key ein",
|
||||
"Enter Perplexity API Key": "Geben Sie den Perplexity API-Schlüssel ein",
|
||||
"Enter Playwright Timeout": "",
|
||||
"Enter Playwright WebSocket URL": "",
|
||||
"Enter Playwright WebSocket URL": "Geben Sie die Playwright WebSocket-URL ein",
|
||||
"Enter proxy URL (e.g. https://user:password@host:port)": "Geben sie die Proxy-URL ein (z. B. https://user:password@host:port)",
|
||||
"Enter reasoning effort": "Geben Sie den Schlussfolgerungsaufwand ein",
|
||||
"Enter Sampler (e.g. Euler a)": "Geben Sie den Sampler ein (z. B. Euler a)",
|
||||
@@ -444,7 +465,7 @@
|
||||
"Enter SearchApi Engine": "Geben Sie die SearchApi-Engine ein",
|
||||
"Enter Searxng Query URL": "Geben Sie die Searxng-Abfrage-URL ein",
|
||||
"Enter Seed": "Geben Sie den Seed ein",
|
||||
"Enter SerpApi API Key": "",
|
||||
"Enter SerpApi API Key": "Geben Sie den SerpApi API-Schlüssel ein",
|
||||
"Enter SerpApi Engine": "",
|
||||
"Enter Serper API Key": "Geben Sie den Serper-API-Schlüssel ein",
|
||||
"Enter Serply API Key": "Geben Sie den",
|
||||
@@ -456,7 +477,7 @@
|
||||
"Enter Sougou Search API SK": "",
|
||||
"Enter stop sequence": "Stop-Sequenz eingeben",
|
||||
"Enter system prompt": "Systemprompt eingeben",
|
||||
"Enter system prompt here": "",
|
||||
"Enter system prompt here": "Systemprompt hier eingeben",
|
||||
"Enter Tavily API Key": "Geben Sie den Tavily-API-Schlüssel ein",
|
||||
"Enter Tavily Extract Depth": "",
|
||||
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Geben sie die öffentliche URL Ihrer WebUI ein. Diese URL wird verwendet, um Links in den Benachrichtigungen zu generieren.",
|
||||
@@ -467,11 +488,15 @@
|
||||
"Enter Top K Reranker": "Geben Sie Top K für Reranker ein",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "Geben Sie die URL ein (z. B. http://127.0.0.1:7860/)",
|
||||
"Enter URL (e.g. http://localhost:11434)": "Geben Sie die URL ein (z. B. http://localhost:11434)",
|
||||
"Enter Yacy Password": "",
|
||||
"Enter Yacy URL (e.g. http://yacy.example.com:8090)": "",
|
||||
"Enter Yacy Username": "",
|
||||
"Enter your current password": "Geben Sie Ihr aktuelles Passwort ein",
|
||||
"Enter Your Email": "Geben Sie Ihre E-Mail-Adresse ein",
|
||||
"Enter Your Full Name": "Geben Sie Ihren vollständigen Namen ein",
|
||||
"Enter your message": "Geben Sie Ihre Nachricht ein",
|
||||
"Enter your name": "",
|
||||
"Enter your name": "Geben Sie Ihren Namen ein",
|
||||
"Enter Your Name": "",
|
||||
"Enter your new password": "Geben Sie Ihr neues Passwort ein",
|
||||
"Enter Your Password": "Geben Sie Ihr Passwort ein",
|
||||
"Enter Your Role": "Geben Sie Ihre Rolle ein",
|
||||
@@ -480,6 +505,8 @@
|
||||
"Error": "Fehler",
|
||||
"ERROR": "FEHLER",
|
||||
"Error accessing Google Drive: {{error}}": "Fehler beim Zugriff auf Google Drive: {{error}}",
|
||||
"Error accessing media devices.": "",
|
||||
"Error starting recording.": "",
|
||||
"Error uploading file: {{error}}": "Fehler beim Hochladen der Datei: {{error}}",
|
||||
"Evaluations": "Evaluationen",
|
||||
"Exa API Key": "Exa-API-Schlüssel",
|
||||
@@ -511,10 +538,16 @@
|
||||
"Export Tools": "Werkzeuge exportieren",
|
||||
"External": "Extern",
|
||||
"External Models": "Externe Modelle",
|
||||
"External Web Loader API Key": "",
|
||||
"External Web Loader URL": "",
|
||||
"External Web Search API Key": "",
|
||||
"External Web Search URL": "",
|
||||
"Failed to add file.": "Fehler beim Hinzufügen der Datei.",
|
||||
"Failed to connect to {{URL}} OpenAPI tool server": "",
|
||||
"Failed to create API Key.": "Fehler beim Erstellen des API-Schlüssels.",
|
||||
"Failed to delete note": "",
|
||||
"Failed to fetch models": "Fehler beim Abrufen der Modelle",
|
||||
"Failed to load file content.": "",
|
||||
"Failed to read clipboard contents": "Fehler beim Abruf der Zwischenablage",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "Fehler beim Speichern der Modellkonfiguration",
|
||||
@@ -539,12 +572,11 @@
|
||||
"Filter is now globally enabled": "Filter ist jetzt global aktiviert",
|
||||
"Filters": "Filter",
|
||||
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Fingerabdruck-Spoofing erkannt: Initialen können nicht als Avatar verwendet werden. Standard-Avatar wird verwendet.",
|
||||
"Firecrawl API Base URL": "",
|
||||
"Firecrawl API Key": "",
|
||||
"Firecrawl API Base URL": "Firecrawl API Basis-URL",
|
||||
"Firecrawl API Key": "Firecrawl API-Schlüssel",
|
||||
"Fluidly stream large external response chunks": "Nahtlose Übertragung großer externer Antwortabschnitte",
|
||||
"Focus chat input": "Chat-Eingabe fokussieren",
|
||||
"Folder deleted successfully": "Ordner erfolgreich gelöscht",
|
||||
"Folder name cannot be empty": "Ordnername darf nicht leer sein",
|
||||
"Folder name cannot be empty.": "Ordnername darf nicht leer sein.",
|
||||
"Folder name updated successfully": "Ordnername erfolgreich aktualisiert",
|
||||
"Followed instructions perfectly": "Anweisungen perfekt befolgt",
|
||||
@@ -565,17 +597,18 @@
|
||||
"Function Name": "Funktionsname",
|
||||
"Function updated successfully": "Funktion erfolgreich aktualisiert",
|
||||
"Functions": "Funktionen",
|
||||
"Functions allow arbitrary code execution": "Funktionen ermöglichen die Ausführung beliebigen Codes",
|
||||
"Functions allow arbitrary code execution.": "Funktionen ermöglichen die Ausführung beliebigen Codes.",
|
||||
"Functions imported successfully": "Funktionen erfolgreich importiert",
|
||||
"Gemini": "",
|
||||
"Gemini API Config": "",
|
||||
"Gemini API Key is required.": "",
|
||||
"Gemini": "Gemini",
|
||||
"Gemini API Config": "Gemini API-Konfiguration",
|
||||
"Gemini API Key is required.": "Gemini API-Schlüssel ist erforderlich.",
|
||||
"General": "Allgemein",
|
||||
"Generate": "",
|
||||
"Generate an image": "Bild erzeugen",
|
||||
"Generate Image": "Bild erzeugen",
|
||||
"Generate prompt pair": "",
|
||||
"Generating search query": "Suchanfrage wird erstellt",
|
||||
"Generating...": "Generiere...",
|
||||
"Get started": "Loslegen",
|
||||
"Get started with {{WEBUI_NAME}}": "Loslegen mit {{WEBUI_NAME}}",
|
||||
"Global": "Global",
|
||||
@@ -597,7 +630,7 @@
|
||||
"Hex Color": "Hex-Farbe",
|
||||
"Hex Color - Leave empty for default color": "Hex-Farbe - Leer lassen für Standardfarbe",
|
||||
"Hide": "Verbergen",
|
||||
"Hide Model": "",
|
||||
"Hide Model": "Modell verstecken",
|
||||
"Home": "",
|
||||
"Host": "Host",
|
||||
"How can I help you today?": "Wie kann ich Ihnen heute helfen?",
|
||||
@@ -622,6 +655,7 @@
|
||||
"Import Config from JSON File": "Konfiguration aus JSON-Datei importieren",
|
||||
"Import Functions": "Funktionen importieren",
|
||||
"Import Models": "Modelle importieren",
|
||||
"Import Notes": "",
|
||||
"Import Presets": "Voreinstellungen importieren",
|
||||
"Import Prompts": "Prompts importieren",
|
||||
"Import Tools": "Werkzeuge importieren",
|
||||
@@ -636,6 +670,7 @@
|
||||
"Instant Auto-Send After Voice Transcription": "Spracherkennung direkt absenden",
|
||||
"Integration": "",
|
||||
"Interface": "Oberfläche",
|
||||
"Invalid file content": "",
|
||||
"Invalid file format.": "Ungültiges Dateiformat.",
|
||||
"Invalid JSON schema": "",
|
||||
"Invalid Tag": "Ungültiger Tag",
|
||||
@@ -706,6 +741,7 @@
|
||||
"Manage Pipelines": "Pipelines verwalten",
|
||||
"Manage Tool Servers": "",
|
||||
"March": "März",
|
||||
"Max Speakers": "",
|
||||
"Max Tokens (num_predict)": "Maximale Tokenanzahl (num_predict)",
|
||||
"Max Upload Count": "Maximale Anzahl der Uploads",
|
||||
"Max Upload Size": "Maximale Uploadgröße",
|
||||
@@ -718,22 +754,26 @@
|
||||
"Memory deleted successfully": "Erinnerung erfolgreich gelöscht",
|
||||
"Memory updated successfully": "Erinnerung erfolgreich aktualisiert",
|
||||
"Merge Responses": "Antworten zusammenführen",
|
||||
"Merged Response": "Zusammengeführte Antwort",
|
||||
"Message rating should be enabled to use this feature": "Antwortbewertung muss aktiviert sein, um diese Funktion zu verwenden",
|
||||
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Nachrichten, die Sie nach der Erstellung Ihres Links senden, werden nicht geteilt. Nutzer mit der URL können den freigegebenen Chat einsehen.",
|
||||
"Microsoft OneDrive": "",
|
||||
"Microsoft OneDrive (personal)": "",
|
||||
"Microsoft OneDrive (work/school)": "",
|
||||
"Min P": "Min P",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"Mistral OCR": "",
|
||||
"Mistral OCR API Key required.": "",
|
||||
"Mistral OCR API Key required.": "Mistral OCR API-Schlüssel erforderlich.",
|
||||
"Model": "Modell",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "Modell '{{modelName}}' wurde erfolgreich heruntergeladen.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "Modell '{{modelTag}}' befindet sich bereits in der Warteschlange zum Herunterladen.",
|
||||
"Model {{modelId}} not found": "Modell {{modelId}} nicht gefunden",
|
||||
"Model {{modelName}} is not vision capable": "Das Modell {{modelName}} ist nicht für die Bildverarbeitung geeignet",
|
||||
"Model {{name}} is now {{status}}": "Modell {{name}} ist jetzt {{status}}",
|
||||
"Model {{name}} is now hidden": "",
|
||||
"Model {{name}} is now visible": "",
|
||||
"Model {{name}} is now hidden": "Modell {{name}} ist jetzt versteckt.",
|
||||
"Model {{name}} is now visible": "Modell {{name}} ist jetzt sichtbar.",
|
||||
"Model accepts image inputs": "Modell akzeptiert Bildeingaben",
|
||||
"Model created successfully!": "Modell erfolgreich erstellt!",
|
||||
"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Modell-Dateisystempfad erkannt. Modellkurzname ist für das Update erforderlich, Fortsetzung nicht möglich.",
|
||||
@@ -753,19 +793,22 @@
|
||||
"Mojeek Search API Key": "Mojeek Search API-Schlüssel",
|
||||
"more": "mehr",
|
||||
"More": "Mehr",
|
||||
"My Notes": "Meine Notizen",
|
||||
"Name": "Name",
|
||||
"Name your knowledge base": "Benennen Sie Ihren Wissensspeicher",
|
||||
"Native": "Nativ",
|
||||
"New Chat": "Neuer Chat",
|
||||
"New Folder": "Neuer Ordner",
|
||||
"New Note": "Neue Notiz",
|
||||
"New Password": "Neues Passwort",
|
||||
"new-channel": "neuer-kanal",
|
||||
"No content": "Kein Inhalt",
|
||||
"No content found": "Kein Inhalt gefunden",
|
||||
"No content found in file.": "",
|
||||
"No content to speak": "Kein Inhalt zum Vorlesen",
|
||||
"No distance available": "Keine Distanz verfügbar",
|
||||
"No feedbacks found": "Kein Feedback gefunden",
|
||||
"No file selected": "Keine Datei ausgewählt",
|
||||
"No files found.": "Keine Dateien gefunden.",
|
||||
"No groups with access, add a group to grant access": "Keine Gruppen mit Zugriff, fügen Sie eine Gruppe hinzu, um Zugriff zu gewähren",
|
||||
"No HTML, CSS, or JavaScript content found.": "Keine HTML-, CSS- oder JavaScript-Inhalte gefunden.",
|
||||
"No inference engine with management support found": "Keine Inferenz-Engine mit Management-Unterstützung gefunden",
|
||||
@@ -774,6 +817,7 @@
|
||||
"No model IDs": "Keine Modell-IDs",
|
||||
"No models found": "Keine Modelle gefunden",
|
||||
"No models selected": "Keine Modelle ausgewählt",
|
||||
"No Notes": "Keine Notizen",
|
||||
"No results found": "Keine Ergebnisse gefunden",
|
||||
"No search query generated": "Keine Suchanfrage generiert",
|
||||
"No source available": "Keine Quelle verfügbar",
|
||||
@@ -781,7 +825,8 @@
|
||||
"No valves to update": "Keine Valves zum Aktualisieren",
|
||||
"None": "Nichts",
|
||||
"Not factually correct": "Nicht sachlich korrekt",
|
||||
"Not helpful": "Nich hilfreich",
|
||||
"Not helpful": "Nicht hilfreich",
|
||||
"Note deleted successfully": "",
|
||||
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Hinweis: Wenn Sie eine Mindestpunktzahl festlegen, werden in der Suche nur Dokumente mit einer Punktzahl größer oder gleich der Mindestpunktzahl zurückgegeben.",
|
||||
"Notes": "Notizen",
|
||||
"Notification Sound": "Benachrichtigungston",
|
||||
@@ -804,6 +849,7 @@
|
||||
"Only alphanumeric characters and hyphens are allowed": "Nur alphanumerische Zeichen und Bindestriche sind erlaubt",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "In der Befehlszeichenfolge sind nur alphanumerische Zeichen und Bindestriche erlaubt.",
|
||||
"Only collections can be edited, create a new knowledge base to edit/add documents.": "Nur Sammlungen können bearbeitet werden. Erstellen Sie eine neue Wissensbasis, um Dokumente zu bearbeiten/hinzuzufügen.",
|
||||
"Only markdown files are allowed": "",
|
||||
"Only select users and groups with permission can access": "Nur ausgewählte Benutzer und Gruppen mit Berechtigung können darauf zugreifen",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Hoppla! Es scheint, dass die URL ungültig ist. Bitte überprüfen Sie diese und versuchen Sie es erneut.",
|
||||
"Oops! There are files still uploading. Please wait for the upload to complete.": "Hoppla! Es werden noch Dateien hochgeladen. Bitte warten Sie, bis der Upload abgeschlossen ist.",
|
||||
@@ -822,7 +868,7 @@
|
||||
"OpenAI API Key is required.": "OpenAI-API-Schlüssel erforderlich.",
|
||||
"OpenAI API settings updated": "OpenAI-API-Einstellungen aktualisiert",
|
||||
"OpenAI URL/Key required.": "OpenAI-URL/Schlüssel erforderlich.",
|
||||
"openapi.json Path": "",
|
||||
"openapi.json Path": "openapi.json Pfad",
|
||||
"or": "oder",
|
||||
"Organize your users": "Organisieren Sie Ihre Benutzer",
|
||||
"Other": "Andere",
|
||||
@@ -839,7 +885,7 @@
|
||||
"Permission denied when accessing microphone": "Zugriff auf das Mikrofon verweigert",
|
||||
"Permission denied when accessing microphone: {{error}}": "Zugriff auf das Mikrofon verweigert: {{error}}",
|
||||
"Permissions": "Berechtigungen",
|
||||
"Perplexity API Key": "",
|
||||
"Perplexity API Key": "Perplexity API-Schlüssel",
|
||||
"Personalization": "Personalisierung",
|
||||
"Pin": "Anheften",
|
||||
"Pinned": "Angeheftet",
|
||||
@@ -849,6 +895,7 @@
|
||||
"Pipelines": "Pipelines",
|
||||
"Pipelines Not Detected": "Pipelines nicht erkannt",
|
||||
"Pipelines Valves": "Pipeline Valves",
|
||||
"Plain text (.md)": "",
|
||||
"Plain text (.txt)": "Nur Text (.txt)",
|
||||
"Playground": "Testumgebung",
|
||||
"Playwright Timeout (ms)": "",
|
||||
@@ -856,8 +903,8 @@
|
||||
"Please carefully review the following warnings:": "Bitte überprüfen Sie die folgenden Warnungen sorgfältig:",
|
||||
"Please do not close the settings page while loading the model.": "Bitte schließen die Einstellungen-Seite nicht, während das Modell lädt.",
|
||||
"Please enter a prompt": "Bitte geben Sie einen Prompt ein",
|
||||
"Please enter a valid path": "",
|
||||
"Please enter a valid URL": "",
|
||||
"Please enter a valid path": "Bitte geben Sie einen gültigen Pfad ein",
|
||||
"Please enter a valid URL": "Bitte geben Sie eine gültige URL ein",
|
||||
"Please fill in all fields.": "Bitte füllen Sie alle Felder aus.",
|
||||
"Please select a model first.": "Bitte wählen Sie zuerst ein Modell aus.",
|
||||
"Please select a model.": "Bitte wählen Sie ein Modell aus.",
|
||||
@@ -891,6 +938,7 @@
|
||||
"Read": "Lesen",
|
||||
"Read Aloud": "Vorlesen",
|
||||
"Reasoning Effort": "Schlussfolgerungsaufwand",
|
||||
"Record": "Aufzeichnen",
|
||||
"Record voice": "Stimme aufnehmen",
|
||||
"Redirecting you to Open WebUI Community": "Sie werden zur OpenWebUI-Community weitergeleitet",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative.": "",
|
||||
@@ -898,11 +946,11 @@
|
||||
"References from": "Referenzen aus",
|
||||
"Refused when it shouldn't have": "Abgelehnt, obwohl es nicht hätte abgelehnt werden sollen",
|
||||
"Regenerate": "Neu generieren",
|
||||
"Reindex": "",
|
||||
"Reindex": "Neu indexieren",
|
||||
"Reindex Knowledge Base Vectors": "",
|
||||
"Release Notes": "Veröffentlichungshinweise",
|
||||
"Relevance": "Relevanz",
|
||||
"Relevance Threshold": "",
|
||||
"Relevance Threshold": "Relevanzschwelle",
|
||||
"Remove": "Entfernen",
|
||||
"Remove Model": "Modell entfernen",
|
||||
"Rename": "Umbenennen",
|
||||
@@ -911,9 +959,8 @@
|
||||
"Repeat Penalty (Ollama)": "Wiederholungsstrafe (Ollama)",
|
||||
"Reply in Thread": "Im Thread antworten",
|
||||
"Request Mode": "Anforderungsmodus",
|
||||
"Reranking Engine": "",
|
||||
"Reranking Model": "Reranking-Modell",
|
||||
"Reranking model disabled": "Reranking-Modell deaktiviert",
|
||||
"Reranking model set to \"{{reranking_model}}\"": "Reranking-Modell \"{{reranking_model}}\" fesgelegt",
|
||||
"Reset": "Zurücksetzen",
|
||||
"Reset All Models": "Alle Modelle zurücksetzen",
|
||||
"Reset Upload Directory": "Upload-Verzeichnis zurücksetzen",
|
||||
@@ -939,7 +986,7 @@
|
||||
"Save Tag": "Tag speichern",
|
||||
"Saved": "Gespeichert",
|
||||
"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": "Das direkte Speichern von Chats im Browser-Speicher wird nicht mehr unterstützt. Bitte nehmen Sie einen Moment Zeit, um Ihre Chats zu exportieren und zu löschen, indem Sie auf die Schaltfläche unten klicken. Keine Sorge, Sie können Ihre Chats problemlos über das Backend wieder importieren.",
|
||||
"Scroll to bottom when switching between branches": "Beim Wechsel zwischen Branches nach unten scrollen",
|
||||
"Scroll On Branch Change": "",
|
||||
"Search": "Suchen",
|
||||
"Search a model": "Modell suchen",
|
||||
"Search Base": "Suchbasis",
|
||||
@@ -960,6 +1007,7 @@
|
||||
"Searched {{count}} sites": "{{count}} Websites durchsucht",
|
||||
"Searching \"{{searchQuery}}\"": "Suche nach \"{{searchQuery}}\"",
|
||||
"Searching Knowledge for \"{{searchQuery}}\"": "Suche im Wissen nach \"{{searchQuery}}\"",
|
||||
"Searching the web...": "Durchsuche das Web...",
|
||||
"Searxng Query URL": "Searxng-Abfrage-URL",
|
||||
"See readme.md for instructions": "Anleitung in readme.md anzeigen",
|
||||
"See what's new": "Entdecken Sie die Neuigkeiten",
|
||||
@@ -1016,11 +1064,13 @@
|
||||
"Share": "Teilen",
|
||||
"Share Chat": "Chat teilen",
|
||||
"Share to Open WebUI Community": "Mit OpenWebUI Community teilen",
|
||||
"Sharing Permissions": "",
|
||||
"Sharing Permissions": "Berechtigungen teilen",
|
||||
"Show": "Anzeigen",
|
||||
"Show \"What's New\" modal on login": "\"Was gibt's Neues\"-Modal beim Anmelden anzeigen",
|
||||
"Show Admin Details in Account Pending Overlay": "Admin-Details im Account-Pending-Overlay anzeigen",
|
||||
"Show Model": "",
|
||||
"Show All": "",
|
||||
"Show Less": "",
|
||||
"Show Model": "Modell anzeigen",
|
||||
"Show shortcuts": "Verknüpfungen anzeigen",
|
||||
"Show your support!": "Zeigen Sie Ihre Unterstützung!",
|
||||
"Showcased creativity": "Kreativität gezeigt",
|
||||
@@ -1043,6 +1093,7 @@
|
||||
"Stream Chat Response": "Chat-Antwort streamen",
|
||||
"STT Model": "STT-Modell",
|
||||
"STT Settings": "STT-Einstellungen",
|
||||
"Stylized PDF Export": "",
|
||||
"Subtitle (e.g. about the Roman Empire)": "Untertitel (z. B. über das Römische Reich)",
|
||||
"Success": "Erfolg",
|
||||
"Successfully updated.": "Erfolgreich aktualisiert.",
|
||||
@@ -1089,6 +1140,7 @@
|
||||
"This chat won’t appear in history and your messages will not be saved.": "Diese Unterhaltung erscheint nicht in deinem Chat-Verlauf. Alle Nachrichten sind privat und werden nicht gespeichert.",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dies stellt sicher, dass Ihre wertvollen Chats sicher in Ihrer Backend-Datenbank gespeichert werden. Vielen Dank!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Dies ist eine experimentelle Funktion, sie funktioniert möglicherweise nicht wie erwartet und kann jederzeit geändert werden.",
|
||||
"This model is not publicly available. Please select another model.": "",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
"This option sets the maximum number of tokens the model can generate in its response. Increasing this limit allows the model to provide longer answers, but it may also increase the likelihood of unhelpful or irrelevant content being generated.": "",
|
||||
"This option will delete all existing files in the collection and replace them with newly uploaded files.": "Diese Option löscht alle vorhandenen Dateien in der Sammlung und ersetzt sie durch neu hochgeladene Dateien.",
|
||||
@@ -1140,7 +1192,6 @@
|
||||
"Tools Access": "Werkzeugzugriff",
|
||||
"Tools are a function calling system with arbitrary code execution": "Wekzeuge sind ein Funktionssystem mit beliebiger Codeausführung",
|
||||
"Tools Function Calling Prompt": "Prompt für Funktionssystemaufrufe",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "Werkezuge verfügen über ein Funktionssystem, das die Ausführung beliebigen Codes ermöglicht",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "Werkzeuge verfügen über ein Funktionssystem, das die Ausführung beliebigen Codes ermöglicht.",
|
||||
"Tools Public Sharing": "",
|
||||
"Top K": "Top K",
|
||||
@@ -1163,6 +1214,7 @@
|
||||
"Unpin": "Lösen",
|
||||
"Unravel secrets": "Geheimnisse lüften",
|
||||
"Untagged": "Ungetaggt",
|
||||
"Untitled": "Unbenannt",
|
||||
"Update": "Aktualisieren",
|
||||
"Update and Copy Link": "Aktualisieren und Link kopieren",
|
||||
"Update for the latest features and improvements.": "Aktualisieren Sie für die neuesten Funktionen und Verbesserungen.",
|
||||
@@ -1173,6 +1225,7 @@
|
||||
"Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.": "Upgrade auf einen lizenzierten Plan für erweiterte Funktionen wie individuelles Design, Branding und dedizierten Support.",
|
||||
"Upload": "Hochladen",
|
||||
"Upload a GGUF model": "GGUF-Model hochladen",
|
||||
"Upload Audio": "",
|
||||
"Upload directory": "Upload-Verzeichnis",
|
||||
"Upload files": "Dateien hochladen",
|
||||
"Upload Files": "Datei(en) hochladen",
|
||||
@@ -1191,7 +1244,7 @@
|
||||
"user": "Benutzer",
|
||||
"User": "Benutzer",
|
||||
"User location successfully retrieved.": "Benutzerstandort erfolgreich ermittelt.",
|
||||
"User Webhooks": "",
|
||||
"User Webhooks": "Benutzer Webhooks",
|
||||
"Username": "Benutzername",
|
||||
"Users": "Benutzer",
|
||||
"Using the default arena model with all models. Click the plus button to add custom models.": "Verwendung des Standard-Arena-Modells mit allen Modellen. Klicken Sie auf die Plus-Schaltfläche, um benutzerdefinierte Modelle hinzuzufügen.",
|
||||
@@ -1203,7 +1256,7 @@
|
||||
"variable": "Variable",
|
||||
"variable to have them replaced with clipboard content.": "Variable, um den Inhalt der Zwischenablage beim Nutzen des Prompts zu ersetzen.",
|
||||
"Verify Connection": "Verbindung verifizieren",
|
||||
"Verify SSL Certificate": "",
|
||||
"Verify SSL Certificate": "SSL Zertifikat prüfen",
|
||||
"Version": "Version",
|
||||
"Version {{selectedVersion}} of {{totalVersions}}": "Version {{selectedVersion}} von {{totalVersions}}",
|
||||
"View Replies": "Antworten anzeigen",
|
||||
@@ -1246,13 +1299,15 @@
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "Schreibe eine kurze Zusammenfassung in 50 Wörtern, die [Thema oder Schlüsselwort] zusammenfasst.",
|
||||
"Write something...": "Schreiben Sie etwas...",
|
||||
"Write your model template content here": "Schreiben Sie hier Ihren Modellvorlageninhalt",
|
||||
"Yacy Instance URL": "",
|
||||
"Yacy Password": "",
|
||||
"Yacy Username": "",
|
||||
"Yesterday": "Gestern",
|
||||
"You": "Sie",
|
||||
"You are currently using a trial license. Please contact support to upgrade your license.": "",
|
||||
"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Sie können nur mit maximal {{maxCount}} Datei(en) gleichzeitig chatten.",
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Personalisieren Sie Interaktionen mit LLMs, indem Sie über die Schaltfläche \"Verwalten\" Erinnerungen hinzufügen.",
|
||||
"You cannot upload an empty file.": "Sie können keine leere Datei hochladen.",
|
||||
"You do not have permission to upload files": "Sie haben keine Berechtigung, Dateien hochzuladen",
|
||||
"You do not have permission to upload files.": "Sie haben keine Berechtigung zum Hochladen von Dateien.",
|
||||
"You have no archived conversations.": "Du hast keine archivierten Chats.",
|
||||
"You have shared this chat": "Sie haben diesen Chat geteilt",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user