Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
96a004d4
Unverified
Commit
96a004d4
authored
Jun 09, 2024
by
Timothy Jaeryang Baek
Committed by
GitHub
Jun 09, 2024
Browse files
Merge pull request #2921 from open-webui/dev
0.3.0
parents
a8d80f93
1fa16d73
Changes
117
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
579 additions
and
493 deletions
+579
-493
src/lib/components/chat/MessageInput/PromptCommands.svelte
src/lib/components/chat/MessageInput/PromptCommands.svelte
+28
-1
src/lib/components/chat/MessageInput/VoiceRecording.svelte
src/lib/components/chat/MessageInput/VoiceRecording.svelte
+458
-0
src/lib/components/chat/Messages/CompareMessages.svelte
src/lib/components/chat/Messages/CompareMessages.svelte
+1
-1
src/lib/components/chat/Messages/ResponseMessage.svelte
src/lib/components/chat/Messages/ResponseMessage.svelte
+18
-7
src/lib/components/chat/ModelSelector/Selector.svelte
src/lib/components/chat/ModelSelector/Selector.svelte
+2
-2
src/lib/components/chat/Settings/About.svelte
src/lib/components/chat/Settings/About.svelte
+2
-2
src/lib/components/chat/Settings/Account.svelte
src/lib/components/chat/Settings/Account.svelte
+1
-1
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
...b/components/chat/Settings/Advanced/AdvancedParams.svelte
+1
-1
src/lib/components/chat/Settings/Audio.svelte
src/lib/components/chat/Settings/Audio.svelte
+38
-201
src/lib/components/chat/Settings/Chats.svelte
src/lib/components/chat/Settings/Chats.svelte
+2
-2
src/lib/components/chat/Settings/General.svelte
src/lib/components/chat/Settings/General.svelte
+2
-2
src/lib/components/chat/Settings/Interface.svelte
src/lib/components/chat/Settings/Interface.svelte
+0
-181
src/lib/components/chat/Settings/Models.svelte
src/lib/components/chat/Settings/Models.svelte
+2
-2
src/lib/components/chat/Settings/Personalization.svelte
src/lib/components/chat/Settings/Personalization.svelte
+7
-4
src/lib/components/chat/Settings/Personalization/AddMemoryModal.svelte
...nents/chat/Settings/Personalization/AddMemoryModal.svelte
+1
-1
src/lib/components/chat/Settings/Personalization/ManageModal.svelte
...mponents/chat/Settings/Personalization/ManageModal.svelte
+2
-2
src/lib/components/chat/SettingsModal.svelte
src/lib/components/chat/SettingsModal.svelte
+10
-79
src/lib/components/common/Selector.svelte
src/lib/components/common/Selector.svelte
+1
-1
src/lib/components/documents/Settings/QueryParams.svelte
src/lib/components/documents/Settings/QueryParams.svelte
+1
-1
src/lib/components/documents/Settings/WebParams.svelte
src/lib/components/documents/Settings/WebParams.svelte
+2
-2
No files found.
src/lib/components/chat/MessageInput/PromptCommands.svelte
View file @
96a004d4
...
@@ -6,6 +6,7 @@
...
@@ -6,6 +6,7 @@
const i18n = getContext('i18n');
const i18n = getContext('i18n');
export let files;
export let prompt = '';
export let prompt = '';
let selectedCommandIdx = 0;
let selectedCommandIdx = 0;
let filteredPromptCommands = [];
let filteredPromptCommands = [];
...
@@ -35,6 +36,32 @@
...
@@ -35,6 +36,32 @@
return '{{CLIPBOARD}}';
return '{{CLIPBOARD}}';
});
});
console.log(clipboardText);
const clipboardItems = await navigator.clipboard.read();
let imageUrl = null;
for (const item of clipboardItems) {
// Check for known image types
for (const type of item.types) {
if (type.startsWith('image/')) {
const blob = await item.getType(type);
imageUrl = URL.createObjectURL(blob);
console.log(`Image URL (${type}): ${imageUrl}`);
}
}
}
if (imageUrl) {
files = [
...files,
{
type: 'image',
url: imageUrl
}
];
}
text = command.content.replaceAll('{{CLIPBOARD}}', clipboardText);
text = command.content.replaceAll('{{CLIPBOARD}}', clipboardText);
}
}
...
@@ -61,7 +88,7 @@
...
@@ -61,7 +88,7 @@
</script>
</script>
{#if filteredPromptCommands.length > 0}
{#if filteredPromptCommands.length > 0}
<div class="
md:px-
2 mb-3 text-left w-full absolute bottom-0 left-0 right-0">
<div class="
pl-1 pr-1
2 mb-3 text-left w-full absolute bottom-0 left-0 right-0">
<div class="flex w-full px-2">
<div class="flex w-full px-2">
<div class=" bg-gray-100 dark:bg-gray-700 w-10 rounded-l-xl text-center">
<div class=" bg-gray-100 dark:bg-gray-700 w-10 rounded-l-xl text-center">
<div class=" text-lg font-semibold mt-2">/</div>
<div class=" text-lg font-semibold mt-2">/</div>
...
...
src/lib/components/chat/MessageInput/VoiceRecording.svelte
0 → 100644
View file @
96a004d4
<script lang="ts">
import { toast } from 'svelte-sonner';
import { createEventDispatcher, tick, getContext } from 'svelte';
import { config, settings } from '$lib/stores';
import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
import { transcribeAudio } from '$lib/apis/audio';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
export let recording = false;
let loading = false;
let confirmed = false;
let durationSeconds = 0;
let durationCounter = null;
let transcription = '';
const startDurationCounter = () => {
durationCounter = setInterval(() => {
durationSeconds++;
}, 1000);
};
const stopDurationCounter = () => {
clearInterval(durationCounter);
durationSeconds = 0;
};
$: if (recording) {
startRecording();
} else {
stopRecording();
}
const formatSeconds = (seconds) => {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
const formattedSeconds = remainingSeconds < 10 ? `0${remainingSeconds}` : remainingSeconds;
return `${minutes}:${formattedSeconds}`;
};
let speechRecognition;
let mediaRecorder;
let audioChunks = [];
const MIN_DECIBELS = -45;
const VISUALIZER_BUFFER_LENGTH = 300;
let visualizerData = Array(VISUALIZER_BUFFER_LENGTH).fill(0);
// Function to calculate the RMS level from time domain data
const calculateRMS = (data: Uint8Array) => {
let sumSquares = 0;
for (let i = 0; i < data.length; i++) {
const normalizedValue = (data[i] - 128) / 128; // Normalize the data
sumSquares += normalizedValue * normalizedValue;
}
return Math.sqrt(sumSquares / data.length);
};
const normalizeRMS = (rms) => {
rms = rms * 10;
const exp = 1.5; // Adjust exponent value; values greater than 1 expand larger numbers more and compress smaller numbers more
const scaledRMS = Math.pow(rms, exp);
// Scale between 0.01 (1%) and 1.0 (100%)
return Math.min(1.0, Math.max(0.01, scaledRMS));
};
const analyseAudio = (stream) => {
const audioContext = new AudioContext();
const audioStreamSource = audioContext.createMediaStreamSource(stream);
const analyser = audioContext.createAnalyser();
analyser.minDecibels = MIN_DECIBELS;
audioStreamSource.connect(analyser);
const bufferLength = analyser.frequencyBinCount;
const domainData = new Uint8Array(bufferLength);
const timeDomainData = new Uint8Array(analyser.fftSize);
let lastSoundTime = Date.now();
const detectSound = () => {
const processFrame = () => {
if (!recording || loading) return;
if (recording && !loading) {
analyser.getByteTimeDomainData(timeDomainData);
analyser.getByteFrequencyData(domainData);
// Calculate RMS level from time domain data
const rmsLevel = calculateRMS(timeDomainData);
// Push the calculated decibel level to visualizerData
visualizerData.push(normalizeRMS(rmsLevel));
// Ensure visualizerData array stays within the buffer length
if (visualizerData.length >= VISUALIZER_BUFFER_LENGTH) {
visualizerData.shift();
}
visualizerData = visualizerData;
// if (domainData.some((value) => value > 0)) {
// lastSoundTime = Date.now();
// }
// if (recording && Date.now() - lastSoundTime > 3000) {
// if ($settings?.speechAutoSend ?? false) {
// confirmRecording();
// }
// }
}
window.requestAnimationFrame(processFrame);
};
window.requestAnimationFrame(processFrame);
};
detectSound();
};
const transcribeHandler = async (audioBlob) => {
// Create a blob from the audio chunks
await tick();
const file = blobToFile(audioBlob, 'recording.wav');
const res = await transcribeAudio(localStorage.token, file).catch((error) => {
toast.error(error);
return null;
});
if (res) {
console.log(res.text);
dispatch('confirm', res.text);
}
};
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 () => {
startDurationCounter();
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.onstart = () => {
console.log('Recording started');
audioChunks = [];
analyseAudio(stream);
};
mediaRecorder.ondataavailable = (event) => audioChunks.push(event.data);
mediaRecorder.onstop = async () => {
console.log('Recording stopped');
if (($settings?.audio?.stt?.engine ?? '') === 'web') {
audioChunks = [];
} else {
if (confirmed) {
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
await transcribeHandler(audioBlob);
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;
// Set the timeout for turning off the recognition after inactivity (in milliseconds)
const inactivityTimeout = 2000; // 3 seconds
let timeoutId;
// Start recognition
speechRecognition.start();
// Event triggered when speech is recognized
speechRecognition.onresult = async (event) => {
// Clear the inactivity timeout
clearTimeout(timeoutId);
// Handle recognized speech
console.log(event);
const transcript = event.results[Object.keys(event.results).length - 1][0].transcript;
transcription = `${transcription}${transcript}`;
await tick();
document.getElementById('chat-textarea')?.focus();
// Restart the inactivity timeout
timeoutId = setTimeout(() => {
console.log('Speech recognition turned off due to inactivity.');
speechRecognition.stop();
}, inactivityTimeout);
};
// Event triggered when recognition is ended
speechRecognition.onend = function () {
// Restart recognition after it ends
console.log('recognition ended');
confirmRecording();
dispatch('confirm', 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 }));
dispatch('cancel');
stopRecording();
};
}
}
};
const stopRecording = async () => {
if (recording && mediaRecorder) {
await mediaRecorder.stop();
}
stopDurationCounter();
audioChunks = [];
};
const confirmRecording = async () => {
loading = true;
confirmed = true;
if (recording && mediaRecorder) {
await mediaRecorder.stop();
}
clearInterval(durationCounter);
};
</script>
<div
class="{loading
? ' bg-gray-100/50 dark:bg-gray-850/50'
: 'bg-indigo-300/10 dark:bg-indigo-500/10 '} rounded-full flex p-2.5"
>
<div class="flex items-center mr-1">
<button
type="button"
class="p-1.5
{loading
? ' bg-gray-200 dark:bg-gray-700/50'
: 'bg-indigo-400/20 text-indigo-600 dark:text-indigo-300 '}
rounded-full"
on:click={async () => {
dispatch('cancel');
stopRecording();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="3"
stroke="currentColor"
class="size-4"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
</button>
</div>
<div
class="flex flex-1 self-center items-center justify-between ml-2 mx-1 overflow-hidden h-6"
dir="rtl"
>
<div class="flex-1 flex items-center gap-0.5 h-6">
{#each visualizerData.slice().reverse() as rms}
<div
class="w-[2px]
{loading
? ' bg-gray-500 dark:bg-gray-400 '
: 'bg-indigo-500 dark:bg-indigo-400 '}
inline-block h-full"
style="height: {Math.min(100, Math.max(14, rms * 100))}%;"
/>
{/each}
</div>
</div>
<div class=" mx-1.5 pr-1 flex justify-center items-center">
<div
class="text-sm
{loading ? ' text-gray-500 dark:text-gray-400 ' : ' text-indigo-400 '}
font-medium flex-1 mx-auto text-center"
>
{formatSeconds(durationSeconds)}
</div>
</div>
<div class="flex items-center mr-1">
{#if loading}
<div class=" text-gray-500 rounded-full cursor-not-allowed">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
><style>
.spinner_OSmW {
transform-origin: center;
animation: spinner_T6mA 0.75s step-end infinite;
}
@keyframes spinner_T6mA {
8.3% {
transform: rotate(30deg);
}
16.6% {
transform: rotate(60deg);
}
25% {
transform: rotate(90deg);
}
33.3% {
transform: rotate(120deg);
}
41.6% {
transform: rotate(150deg);
}
50% {
transform: rotate(180deg);
}
58.3% {
transform: rotate(210deg);
}
66.6% {
transform: rotate(240deg);
}
75% {
transform: rotate(270deg);
}
83.3% {
transform: rotate(300deg);
}
91.6% {
transform: rotate(330deg);
}
100% {
transform: rotate(360deg);
}
}
</style><g class="spinner_OSmW"
><rect x="11" y="1" width="2" height="5" opacity=".14" /><rect
x="11"
y="1"
width="2"
height="5"
transform="rotate(30 12 12)"
opacity=".29"
/><rect
x="11"
y="1"
width="2"
height="5"
transform="rotate(60 12 12)"
opacity=".43"
/><rect
x="11"
y="1"
width="2"
height="5"
transform="rotate(90 12 12)"
opacity=".57"
/><rect
x="11"
y="1"
width="2"
height="5"
transform="rotate(120 12 12)"
opacity=".71"
/><rect
x="11"
y="1"
width="2"
height="5"
transform="rotate(150 12 12)"
opacity=".86"
/><rect x="11" y="1" width="2" height="5" transform="rotate(180 12 12)" /></g
></svg
>
</div>
{:else}
<button
type="button"
class="p-1.5 bg-indigo-500 text-white dark:bg-indigo-500 dark:text-blue-950 rounded-full"
on:click={async () => {
await confirmRecording();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2.5"
stroke="currentColor"
class="size-4"
>
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg>
</button>
{/if}
</div>
</div>
<style>
.visualizer {
display: flex;
height: 100%;
}
.visualizer-bar {
width: 2px;
background-color: #4a5aba; /* or whatever color you need */
}
</style>
src/lib/components/chat/Messages/CompareMessages.svelte
View file @
96a004d4
...
@@ -109,7 +109,7 @@
...
@@ -109,7 +109,7 @@
class=" snap-center min-w-80 w-full max-w-full m-1 border {history.messages[
class=" snap-center min-w-80 w-full max-w-full m-1 border {history.messages[
currentMessageId
currentMessageId
].model === model
].model === model
? 'border-gray-100 dark:border-gray-
70
0 border-[1.5px]'
? 'border-gray-100 dark:border-gray-
85
0 border-[1.5px]'
: 'border-gray-50 dark:border-gray-850 '} transition p-5 rounded-3xl"
: 'border-gray-50 dark:border-gray-850 '} transition p-5 rounded-3xl"
on:click={() => {
on:click={() => {
currentMessageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id;
currentMessageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id;
...
...
src/lib/components/chat/Messages/ResponseMessage.svelte
View file @
96a004d4
...
@@ -213,7 +213,7 @@
...
@@ -213,7 +213,7 @@
} else {
} else {
speaking = true;
speaking = true;
if ($
settings?.audio?.TTSE
ngine === 'openai') {
if ($
config.audio.tts.e
ngine === 'openai') {
loadingSpeech = true;
loadingSpeech = true;
const sentences = extractSentences(message.content).reduce((mergedTexts, currentText) => {
const sentences = extractSentences(message.content).reduce((mergedTexts, currentText) => {
...
@@ -244,9 +244,8 @@
...
@@ -244,9 +244,8 @@
for (const [idx, sentence] of sentences.entries()) {
for (const [idx, sentence] of sentences.entries()) {
const res = await synthesizeOpenAISpeech(
const res = await synthesizeOpenAISpeech(
localStorage.token,
localStorage.token,
$settings?.audio?.speaker,
$settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice,
sentence,
sentence
$settings?.audio?.model
).catch((error) => {
).catch((error) => {
toast.error(error);
toast.error(error);
...
@@ -273,17 +272,29 @@
...
@@ -273,17 +272,29 @@
clearInterval(getVoicesLoop);
clearInterval(getVoicesLoop);
const voice =
const voice =
voices?.filter((v) => v.name === $settings?.audio?.speaker)?.at(0) ?? undefined;
voices
?.filter(
(v) => v.voiceURI === ($settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice)
)
?.at(0) ?? undefined;
console.log(voice);
const speak = new SpeechSynthesisUtterance(message.content);
const speak = new SpeechSynthesisUtterance(message.content);
console.log(speak);
speak.onend = () => {
speak.onend = () => {
speaking = null;
speaking = null;
if ($settings.conversationMode) {
if ($settings.conversationMode) {
document.getElementById('voice-input-button')?.click();
document.getElementById('voice-input-button')?.click();
}
}
};
};
speak.voice = voice;
if (voice) {
speak.voice = voice;
}
speechSynthesis.speak(speak);
speechSynthesis.speak(speak);
}
}
}, 100);
}, 100);
...
@@ -757,7 +768,7 @@
...
@@ -757,7 +768,7 @@
</Tooltip>
</Tooltip>
{#if $config?.features.enable_image_generation && !readOnly}
{#if $config?.features.enable_image_generation && !readOnly}
<Tooltip content=
"
Generate Image
"
placement="bottom">
<Tooltip content=
{$i18n.t('
Generate Image
')}
placement="bottom">
<button
<button
class="{isLastMessage
class="{isLastMessage
? 'visible'
? 'visible'
...
...
src/lib/components/chat/ModelSelector/Selector.svelte
View file @
96a004d4
...
@@ -219,7 +219,7 @@
...
@@ -219,7 +219,7 @@
<DropdownMenu.Content
<DropdownMenu.Content
class=" z-40 {$mobile
class=" z-40 {$mobile
? `w-full`
? `w-full`
: `${className}`} max-w-[calc(100vw-1rem)] justify-start rounded-xl bg-white dark:bg-gray-850 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-
70
0/50 outline-none "
: `${className}`} max-w-[calc(100vw-1rem)] justify-start rounded-xl bg-white dark:bg-gray-850 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-
85
0/50 outline-none "
transition={flyAndScale}
transition={flyAndScale}
side={$mobile ? 'bottom' : 'bottom-start'}
side={$mobile ? 'bottom' : 'bottom-start'}
sideOffset={4}
sideOffset={4}
...
@@ -265,7 +265,7 @@
...
@@ -265,7 +265,7 @@
</div>
</div>
{/if}
{/if}
<div class="flex items-center gap-2">
<div class="flex items-center gap-2">
<div class="flex items-center">
<div class="flex items-center
min-w-fit
">
<div class="line-clamp-1">
<div class="line-clamp-1">
{item.label}
{item.label}
</div>
</div>
...
...
src/lib/components/chat/Settings/About.svelte
View file @
96a004d4
...
@@ -92,7 +92,7 @@
...
@@ -92,7 +92,7 @@
</div>
</div>
{#if ollamaVersion}
{#if ollamaVersion}
<hr class=" dark:border-gray-
70
0" />
<hr class=" dark:border-gray-
85
0" />
<div>
<div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Ollama Version')}</div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Ollama Version')}</div>
...
@@ -104,7 +104,7 @@
...
@@ -104,7 +104,7 @@
</div>
</div>
{/if}
{/if}
<hr class=" dark:border-gray-
70
0" />
<hr class=" dark:border-gray-
85
0" />
<div class="flex space-x-1">
<div class="flex space-x-1">
<a href="https://discord.gg/5rJgQTnV4s" target="_blank">
<a href="https://discord.gg/5rJgQTnV4s" target="_blank">
...
...
src/lib/components/chat/Settings/Account.svelte
View file @
96a004d4
...
@@ -234,7 +234,7 @@
...
@@ -234,7 +234,7 @@
<UpdatePassword />
<UpdatePassword />
</div>
</div>
<hr class=" dark:border-gray-
70
0 my-4" />
<hr class=" dark:border-gray-
85
0 my-4" />
<div class="flex justify-between items-center text-sm">
<div class="flex justify-between items-center text-sm">
<div class=" font-medium">{$i18n.t('API keys')}</div>
<div class=" font-medium">{$i18n.t('API keys')}</div>
...
...
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
View file @
96a004d4
...
@@ -556,7 +556,7 @@
...
@@ -556,7 +556,7 @@
type="number"
type="number"
class=" bg-transparent text-center w-14"
class=" bg-transparent text-center w-14"
min="-1"
min="-1"
step="1
0
"
step="1"
/>
/>
</div>
</div>
</div>
</div>
...
...
src/lib/components/chat/Settings/Audio.svelte
View file @
96a004d4
<script lang="ts">
<script lang="ts">
import
{
getAudioConfig
,
updateAudioConfig
}
from
'$lib/apis/audio'
;
import { user, settings, config } from '$lib/stores';
import
{
user
,
settings
}
from
'$lib/stores'
;
import { createEventDispatcher, onMount, getContext } from 'svelte';
import { createEventDispatcher, onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import { toast } from 'svelte-sonner';
import Switch from '$lib/components/common/Switch.svelte';
import Switch from '$lib/components/common/Switch.svelte';
...
@@ -11,26 +10,15 @@
...
@@ -11,26 +10,15 @@
export let saveSettings: Function;
export let saveSettings: Function;
// Audio
// Audio
let
OpenAIUrl
=
''
;
let
OpenAIKey
=
''
;
let
OpenAISpeaker
=
''
;
let
STTEngines
=
[
''
,
'openai'
];
let
STTEngine
=
''
;
let conversationMode = false;
let conversationMode = false;
let speechAutoSend = false;
let speechAutoSend = false;
let responseAutoPlayback = false;
let responseAutoPlayback = false;
let nonLocalVoices = false;
let nonLocalVoices = false;
let
TTSEngines
=
[
''
,
'openai'
];
let STTEngine = '';
let
TTSEngine
=
''
;
let voices = [];
let voices = [];
let
speaker
=
''
;
let voice = '';
let
models
=
[];
let
model
=
''
;
const getOpenAIVoices = () => {
const getOpenAIVoices = () => {
voices = [
voices = [
...
@@ -43,10 +31,6 @@
...
@@ -43,10 +31,6 @@
];
];
};
};
const
getOpenAIVoicesModel
=
()
=>
{
models
=
[{
name
:
'tts-1'
},
{
name
:
'tts-1-hd'
}];
};
const getWebAPIVoices = () => {
const getWebAPIVoices = () => {
const getVoicesLoop = setInterval(async () => {
const getVoicesLoop = setInterval(async () => {
voices = await speechSynthesis.getVoices();
voices = await speechSynthesis.getVoices();
...
@@ -58,21 +42,6 @@
...
@@ -58,21 +42,6 @@
}, 100);
}, 100);
};
};
const
toggleConversationMode
=
async
()
=>
{
conversationMode
=
!conversationMode;
if
(
conversationMode
)
{
responseAutoPlayback
=
true
;
speechAutoSend
=
true
;
}
saveSettings
({
conversationMode
:
conversationMode
,
responseAutoPlayback
:
responseAutoPlayback
,
speechAutoSend
:
speechAutoSend
});
};
const toggleResponseAutoPlayback = async () => {
const toggleResponseAutoPlayback = async () => {
responseAutoPlayback = !responseAutoPlayback;
responseAutoPlayback = !responseAutoPlayback;
saveSettings({ responseAutoPlayback: responseAutoPlayback });
saveSettings({ responseAutoPlayback: responseAutoPlayback });
...
@@ -83,76 +52,35 @@
...
@@ -83,76 +52,35 @@
saveSettings({ speechAutoSend: speechAutoSend });
saveSettings({ speechAutoSend: speechAutoSend });
};
};
const
updateConfigHandler
=
async
()
=>
{
if
(
TTSEngine
===
'openai'
)
{
const
res
=
await
updateAudioConfig
(
localStorage
.
token
,
{
url
:
OpenAIUrl
,
key
:
OpenAIKey
,
model
:
model
,
speaker
:
OpenAISpeaker
});
if
(
res
)
{
OpenAIUrl
=
res
.
OPENAI_API_BASE_URL
;
OpenAIKey
=
res
.
OPENAI_API_KEY
;
model
=
res
.
OPENAI_API_MODEL
;
OpenAISpeaker
=
res
.
OPENAI_API_VOICE
;
}
}
};
onMount(async () => {
onMount(async () => {
conversationMode = $settings.conversationMode ?? false;
conversationMode = $settings.conversationMode ?? false;
speechAutoSend = $settings.speechAutoSend ?? false;
speechAutoSend = $settings.speechAutoSend ?? false;
responseAutoPlayback = $settings.responseAutoPlayback ?? false;
responseAutoPlayback = $settings.responseAutoPlayback ?? false;
STTEngine
=
$
settings
?.
audio
?.
STTEngine
??
''
;
STTEngine = $settings?.audio?.stt?.engine ?? '';
TTSEngine
=
$
settings
?.
audio
?.
TTSEngine
??
''
;
voice = $settings?.audio?.tts?.voice ?? $config.audio.tts.voice ?? '';
nonLocalVoices
=
$
settings
.
audio
?.
nonLocalVoices
??
false
;
nonLocalVoices = $settings.audio?.tts?.nonLocalVoices ?? false;
speaker
=
$
settings
?.
audio
?.
speaker
??
''
;
model
=
$
settings
?.
audio
?.
model
??
''
;
if
(
TTSE
ngine
===
'openai'
)
{
if (
$config.audio.tts.e
ngine === 'openai') {
getOpenAIVoices();
getOpenAIVoices();
getOpenAIVoicesModel
();
} else {
} else {
getWebAPIVoices();
getWebAPIVoices();
}
}
if
($
user
.
role
===
'admin'
)
{
const
res
=
await
getAudioConfig
(
localStorage
.
token
);
if
(
res
)
{
OpenAIUrl
=
res
.
OPENAI_API_BASE_URL
;
OpenAIKey
=
res
.
OPENAI_API_KEY
;
model
=
res
.
OPENAI_API_MODEL
;
OpenAISpeaker
=
res
.
OPENAI_API_VOICE
;
if
(
TTSEngine
===
'openai'
)
{
speaker
=
OpenAISpeaker
;
}
}
}
});
});
</script>
</script>
<form
<form
class="flex flex-col h-full justify-between space-y-3 text-sm"
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={async () => {
on:submit|preventDefault={async () => {
if
($
user
.
role
===
'admin'
)
{
await
updateConfigHandler
();
}
saveSettings({
saveSettings({
audio: {
audio: {
STTEngine
:
STTEngine
!== '' ? STTEngine : undefined,
stt: {
TTSEngine
:
TTSEngine
!== '' ? TTSEngine : undefined,
engine: STTEngine !== '' ? STTEngine : undefined
speaker
:
},
(
TTSEngine
===
'openai'
?
OpenAISpeaker
:
speaker
)
!== ''
tts: {
?
TTSEngine
===
'openai'
voice: voice !== '' ? voice : undefined,
?
OpenAISpeaker
nonLocalVoices: $config.audio.tts.engine === '' ? nonLocalVoices : undefined
:
speaker
}
:
undefined
,
model
:
model
!== '' ? model : undefined,
nonLocalVoices
:
nonLocalVoices
}
}
});
});
dispatch('save');
dispatch('save');
...
@@ -162,53 +90,25 @@
...
@@ -162,53 +90,25 @@
<div>
<div>
<div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
<div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
<
div
class
=
" py-0.5 flex w-full justify-between"
>
{#if $config.audio.stt.engine !== 'web'}
<
div
class
=
" self-center text-xs font-medium"
>{$
i18n
.
t
(
'Speech-to-Text Engine'
)}</
div
>
<div class=" py-0.5 flex w-full justify-between">
<
div
class
=
"flex items-center relative"
>
<div class=" self-center text-xs font-medium">{$i18n.t('Speech-to-Text Engine')}</div>
<
select
<div class="flex items-center relative">
class
=
"dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
<select
bind
:
value
={
STTEngine
}
class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
placeholder
=
"Select a mode"
bind:value={STTEngine}
on
:
change
={(
e
)
=>
{
placeholder="Select an engine"
if
(
e
.
target
.
value
!== '') {
>
navigator
.
mediaDevices
.
getUserMedia
({
audio
:
true
}).
catch
(
function
(
err
)
{
<option value="">{$i18n.t('Default')}</option>
toast
.
error
(
<option value="web">{$i18n.t('Web API')}</option>
$
i18n
.
t
(`
Permission
denied
when
accessing
microphone
:
{{
error
}}`,
{
</select>
error
:
err
</div>
})
);
STTEngine
=
''
;
});
}
}}
>
<
option
value
=
""
>{$
i18n
.
t
(
'Default (Web API)'
)}</
option
>
<
option
value
=
"whisper-local"
>{$
i18n
.
t
(
'Whisper (Local)'
)}</
option
>
</
select
>
</div>
</div>
</
div
>
{/if}
<
div
class
=
" py-0.5 flex w-full justify-between"
>
<
div
class
=
" self-center text-xs font-medium"
>{$
i18n
.
t
(
'Conversation Mode'
)}</
div
>
<
button
class
=
"p-1 px-3 text-xs flex rounded transition"
on
:
click
={()
=>
{
toggleConversationMode
();
}}
type
=
"button"
>
{#
if
conversationMode
===
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 class=" py-0.5 flex w-full justify-between">
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
<div class=" self-center text-xs font-medium">
{$
i18n
.
t
(
'Auto-
s
end
input after 3 sec.
'
)}
{$i18n.t('
Instant
Auto-
S
end
After Voice Transcription
')}
</div>
</div>
<button
<button
...
@@ -230,50 +130,6 @@
...
@@ -230,50 +130,6 @@
<div>
<div>
<div class=" mb-1 text-sm font-medium">{$i18n.t('TTS Settings')}</div>
<div class=" mb-1 text-sm font-medium">{$i18n.t('TTS Settings')}</div>
<
div
class
=
" py-0.5 flex w-full justify-between"
>
<
div
class
=
" self-center text-xs font-medium"
>{$
i18n
.
t
(
'Text-to-Speech Engine'
)}</
div
>
<
div
class
=
"flex items-center relative"
>
<
select
class
=
" dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
bind
:
value
={
TTSEngine
}
placeholder
=
"Select a mode"
on
:
change
={(
e
)
=>
{
if
(
e
.
target
.
value
===
'openai'
)
{
getOpenAIVoices
();
OpenAISpeaker
=
'alloy'
;
model
=
'tts-1'
;
}
else
{
getWebAPIVoices
();
speaker
=
''
;
}
}}
>
<
option
value
=
""
>{$
i18n
.
t
(
'Default (Web API)'
)}</
option
>
<
option
value
=
"openai"
>{$
i18n
.
t
(
'Open AI'
)}</
option
>
</
select
>
</
div
>
</
div
>
{#
if
$
user
.
role
===
'admin'
}
{#
if
TTSEngine
===
'openai'
}
<
div
class
=
"mt-1 flex gap-2 mb-1"
>
<
input
class
=
"w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder
={$
i18n
.
t
(
'API Base URL'
)}
bind
:
value
={
OpenAIUrl
}
required
/>
<
input
class
=
"w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder
={$
i18n
.
t
(
'API Key'
)}
bind
:
value
={
OpenAIKey
}
required
/>
</
div
>
{/
if
}
{/
if
}
<div class=" py-0.5 flex w-full justify-between">
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Auto-playback response')}</div>
<div class=" self-center text-xs font-medium">{$i18n.t('Auto-playback response')}</div>
...
@@ -293,23 +149,23 @@
...
@@ -293,23 +149,23 @@
</div>
</div>
</div>
</div>
<
hr
class
=
" dark:border-gray-
70
0"
/>
<hr class=" dark:border-gray-
85
0" />
{#
if
TTSE
ngine
===
''
}
{#if
$config.audio.tts.e
ngine === ''}
<div>
<div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1">
<div class="flex-1">
<select
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind
:
value
={
speaker
}
bind:value={
voice
}
>
>
<
option
value
=
""
selected
={
speaker
!== ''}>{$i18n.t('Default')}</option>
<option value="" selected={
voice
!== ''}>{$i18n.t('Default')}</option>
{#
each
voices
.
filter
((
v
)
=>
nonLocalVoices
||
v
.
localService
===
true
)
as
voice
}
{#each voices.filter((v) => nonLocalVoices || v.localService === true) as
_
voice}
<option
<option
value
={
voice
.
name
}
value={
_
voice.name}
class="bg-gray-100 dark:bg-gray-700"
class="bg-gray-100 dark:bg-gray-700"
selected
={
speaker
===
voice
.
name
}>{
voice
.
name
}</
option
selected={
voice
===
_
voice.name}>{
_
voice.name}</option
>
>
{/each}
{/each}
</select>
</select>
...
@@ -325,7 +181,7 @@
...
@@ -325,7 +181,7 @@
</div>
</div>
</div>
</div>
</div>
</div>
{:
else
if
TTSE
ngine
===
'openai'
}
{:else if
$config.audio.tts.e
ngine === 'openai'}
<div>
<div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
<div class="flex w-full">
<div class="flex w-full">
...
@@ -333,7 +189,7 @@
...
@@ -333,7 +189,7 @@
<input
<input
list="voice-list"
list="voice-list"
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind
:
value
={
OpenAISpeaker
}
bind:value={
voice
}
placeholder="Select a voice"
placeholder="Select a voice"
/>
/>
...
@@ -345,25 +201,6 @@
...
@@ -345,25 +201,6 @@
</div>
</div>
</div>
</div>
</div>
</div>
<
div
>
<
div
class
=
" mb-2.5 text-sm font-medium"
>{$
i18n
.
t
(
'Set Model'
)}</
div
>
<
div
class
=
"flex w-full"
>
<
div
class
=
"flex-1"
>
<
input
list
=
"model-list"
class
=
"w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind
:
value
={
model
}
placeholder
=
"Select a model"
/>
<
datalist
id
=
"model-list"
>
{#
each
models
as
model
}
<
option
value
={
model
.
name
}
/>
{/
each
}
</
datalist
>
</
div
>
</
div
>
</
div
>
{/if}
{/if}
</div>
</div>
...
...
src/lib/components/chat/Settings/Chats.svelte
View file @
96a004d4
...
@@ -161,7 +161,7 @@
...
@@ -161,7 +161,7 @@
</div>
</div>
</div>
</div>
<hr class=" dark:border-gray-
70
0" />
<hr class=" dark:border-gray-
85
0" />
<div class="flex flex-col">
<div class="flex flex-col">
<input
<input
...
@@ -218,7 +218,7 @@
...
@@ -218,7 +218,7 @@
</button>
</button>
</div>
</div>
<hr class=" dark:border-gray-
70
0" />
<hr class=" dark:border-gray-
85
0" />
<div class="flex flex-col">
<div class="flex flex-col">
{#if showArchiveConfirm}
{#if showArchiveConfirm}
...
...
src/lib/components/chat/Settings/General.svelte
View file @
96a004d4
...
@@ -203,7 +203,7 @@
...
@@ -203,7 +203,7 @@
</div>
</div>
</div>
</div>
<hr class=" dark:border-gray-
70
0 my-3" />
<hr class=" dark:border-gray-
85
0 my-3" />
<div>
<div>
<div class=" my-2.5 text-sm font-medium">{$i18n.t('System Prompt')}</div>
<div class=" my-2.5 text-sm font-medium">{$i18n.t('System Prompt')}</div>
...
@@ -228,7 +228,7 @@
...
@@ -228,7 +228,7 @@
{#if showAdvanced}
{#if showAdvanced}
<AdvancedParams bind:params />
<AdvancedParams bind:params />
<hr class=" dark:border-gray-
70
0" />
<hr class=" dark:border-gray-
85
0" />
<div class=" py-1 w-full justify-between">
<div class=" py-1 w-full justify-between">
<div class="flex w-full justify-between">
<div class="flex w-full justify-between">
...
...
src/lib/components/chat/Settings/Interface.svelte
View file @
96a004d4
...
@@ -14,15 +14,11 @@
...
@@ -14,15 +14,11 @@
// Addons
// Addons
let titleAutoGenerate = true;
let titleAutoGenerate = true;
let responseAutoCopy = false;
let responseAutoCopy = false;
let titleAutoGenerateModel = '';
let titleAutoGenerateModelExternal = '';
let widescreenMode = false;
let widescreenMode = false;
let titleGenerationPrompt = '';
let splitLargeChunks = false;
let splitLargeChunks = false;
// Interface
// Interface
let defaultModelId = '';
let defaultModelId = '';
let promptSuggestions = [];
let showUsername = false;
let showUsername = false;
let chatBubble = true;
let chatBubble = true;
let chatDirection: 'LTR' | 'RTL' = 'LTR';
let chatDirection: 'LTR' | 'RTL' = 'LTR';
...
@@ -85,34 +81,13 @@
...
@@ -85,34 +81,13 @@
};
};
const updateInterfaceHandler = async () => {
const updateInterfaceHandler = async () => {
if ($user.role === 'admin') {
promptSuggestions = await setDefaultPromptSuggestions(localStorage.token, promptSuggestions);
await config.set(await getBackendConfig());
}
saveSettings({
saveSettings({
title: {
...$settings.title,
model: titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined,
modelExternal:
titleAutoGenerateModelExternal !== '' ? titleAutoGenerateModelExternal : undefined,
prompt: titleGenerationPrompt ? titleGenerationPrompt : undefined
},
models: [defaultModelId]
models: [defaultModelId]
});
});
};
};
onMount(async () => {
onMount(async () => {
if ($user.role === 'admin') {
promptSuggestions = $config?.default_prompt_suggestions;
}
titleAutoGenerate = $settings?.title?.auto ?? true;
titleAutoGenerate = $settings?.title?.auto ?? true;
titleAutoGenerateModel = $settings?.title?.model ?? '';
titleAutoGenerateModelExternal = $settings?.title?.modelExternal ?? '';
titleGenerationPrompt =
$settings?.title?.prompt ??
`Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title': {{prompt}}`;
responseAutoCopy = $settings.responseAutoCopy ?? false;
responseAutoCopy = $settings.responseAutoCopy ?? false;
showUsername = $settings.showUsername ?? false;
showUsername = $settings.showUsername ?? false;
chatBubble = $settings.chatBubble ?? true;
chatBubble = $settings.chatBubble ?? true;
...
@@ -304,162 +279,6 @@
...
@@ -304,162 +279,6 @@
</select>
</select>
</div>
</div>
</div>
</div>
<hr class=" dark:border-gray-850" />
<div>
<div class=" mb-2.5 text-sm font-medium flex">
<div class=" mr-1">{$i18n.t('Set Task Model')}</div>
<Tooltip
content={$i18n.t(
'A task model is used when performing tasks such as generating titles for chats and web search queries'
)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
/>
</svg>
</Tooltip>
</div>
<div class="flex w-full gap-2 pr-2">
<div class="flex-1">
<div class=" text-xs mb-1">Local Models</div>
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={titleAutoGenerateModel}
placeholder={$i18n.t('Select a model')}
>
<option value="" selected>{$i18n.t('Current Model')}</option>
{#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>
{/each}
</select>
</div>
<div class="flex-1">
<div class=" text-xs mb-1">External Models</div>
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={titleAutoGenerateModelExternal}
placeholder={$i18n.t('Select a model')}
>
<option value="" selected>{$i18n.t('Current Model')}</option>
{#each $models as model}
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">
{model.name}
</option>
{/each}
</select>
</div>
</div>
<div class="mt-3 mr-2">
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Title Generation Prompt')}</div>
<textarea
bind:value={titleGenerationPrompt}
class="w-full rounded-lg p-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
rows="3"
/>
</div>
</div>
{#if $user.role === 'admin'}
<hr class=" dark:border-gray-700" />
<div class=" space-y-3 pr-1.5">
<div class="flex w-full justify-between mb-2">
<div class=" self-center text-sm font-semibold">
{$i18n.t('Default Prompt Suggestions')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
if (promptSuggestions.length === 0 || promptSuggestions.at(-1).content !== '') {
promptSuggestions = [...promptSuggestions, { content: '', title: ['', ''] }];
}
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
/>
</svg>
</button>
</div>
<div class="flex flex-col space-y-1">
{#each promptSuggestions as prompt, promptIdx}
<div class=" flex border dark:border-gray-600 rounded-lg">
<div class="flex flex-col flex-1">
<div class="flex border-b dark:border-gray-600 w-full">
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
placeholder={$i18n.t('Title (e.g. Tell me a fun fact)')}
bind:value={prompt.title[0]}
/>
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
placeholder={$i18n.t('Subtitle (e.g. about the Roman Empire)')}
bind:value={prompt.title[1]}
/>
</div>
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
placeholder={$i18n.t('Prompt (e.g. Tell me a fun fact about the Roman Empire)')}
bind:value={prompt.content}
/>
</div>
<button
class="px-2"
type="button"
on:click={() => {
promptSuggestions.splice(promptIdx, 1);
promptSuggestions = promptSuggestions;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
{/each}
</div>
{#if promptSuggestions.length > 0}
<div class="text-xs text-left w-full mt-2">
{$i18n.t('Adjusting these settings will apply changes universally to all users.')}
</div>
{/if}
</div>
{/if}
</div>
</div>
<div class="flex justify-end text-sm font-medium">
<div class="flex justify-end text-sm font-medium">
...
...
src/lib/components/chat/Settings/Models.svelte
View file @
96a004d4
...
@@ -541,7 +541,7 @@
...
@@ -541,7 +541,7 @@
]);
]);
} else {
} else {
ollamaEnabled = false;
ollamaEnabled = false;
toast.error('Ollama API is disabled');
toast.error(
$i18n.t(
'Ollama API is disabled')
)
;
}
}
});
});
</script>
</script>
...
@@ -1063,7 +1063,7 @@
...
@@ -1063,7 +1063,7 @@
</div>
</div>
{/if}
{/if}
{:else if ollamaEnabled === false}
{:else if ollamaEnabled === false}
<div>Ollama API is disabled</div>
<div>
{$i18n.t('
Ollama API is disabled
')}
</div>
{:else}
{:else}
<div class="flex h-full justify-center">
<div class="flex h-full justify-center">
<div class="my-auto">
<div class="my-auto">
...
...
src/lib/components/chat/Settings/Personalization.svelte
View file @
96a004d4
...
@@ -35,7 +35,9 @@
...
@@ -35,7 +35,9 @@
<div>
<div>
<div class="flex items-center justify-between mb-1">
<div class="flex items-center justify-between mb-1">
<Tooltip
<Tooltip
content="This is an experimental feature, it may not function as expected and is subject to change at any time."
content={$i18n.t(
'This is an experimental feature, it may not function as expected and is subject to change at any time.'
)}
>
>
<div class="text-sm font-medium">
<div class="text-sm font-medium">
{$i18n.t('Memory')}
{$i18n.t('Memory')}
...
@@ -57,8 +59,9 @@
...
@@ -57,8 +59,9 @@
<div class="text-xs text-gray-600 dark:text-gray-400">
<div class="text-xs text-gray-600 dark:text-gray-400">
<div>
<div>
You can personalize your interactions with LLMs by adding memories through the 'Manage'
{$i18n.t(
button below, making them more helpful and tailored to you.
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you."
)}
</div>
</div>
<!-- <div class="mt-3">
<!-- <div class="mt-3">
...
@@ -79,7 +82,7 @@
...
@@ -79,7 +82,7 @@
showManageModal = true;
showManageModal = true;
}}
}}
>
>
Manage
{$i18n.t('
Manage
')}
</button>
</button>
</div>
</div>
</div>
</div>
...
...
src/lib/components/chat/Settings/Personalization/AddMemoryModal.svelte
View file @
96a004d4
...
@@ -75,7 +75,7 @@
...
@@ -75,7 +75,7 @@
/>
/>
<div class="text-xs text-gray-500">
<div class="text-xs text-gray-500">
ⓘ Refer to yourself as "User" (e.g., "User is learning Spanish")
ⓘ
{$i18n.t('
Refer to yourself as "User" (e.g., "User is learning Spanish")
')}
</div>
</div>
</div>
</div>
...
...
src/lib/components/chat/Settings/Personalization/ManageModal.svelte
View file @
96a004d4
...
@@ -136,7 +136,7 @@
...
@@ -136,7 +136,7 @@
class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-300 dark:outline-gray-800 rounded-3xl"
class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-300 dark:outline-gray-800 rounded-3xl"
on:click={() => {
on:click={() => {
showAddMemoryModal = true;
showAddMemoryModal = true;
}}>Add
m
emory</button
}}>
{$i18n.t('
Add
M
emory
')}
</button
>
>
<button
<button
class=" px-3.5 py-1.5 font-medium text-red-500 hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-red-300 dark:outline-red-800 rounded-3xl"
class=" px-3.5 py-1.5 font-medium text-red-500 hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-red-300 dark:outline-red-800 rounded-3xl"
...
@@ -150,7 +150,7 @@
...
@@ -150,7 +150,7 @@
toast.success('Memory cleared successfully');
toast.success('Memory cleared successfully');
memories = [];
memories = [];
}
}
}}>Clear memory</button
}}>
{$i18n.t('
Clear memory
')}
</button
>
>
</div>
</div>
</div>
</div>
...
...
src/lib/components/chat/SettingsModal.svelte
View file @
96a004d4
...
@@ -8,16 +8,14 @@
...
@@ -8,16 +8,14 @@
import Modal from '../common/Modal.svelte';
import Modal from '../common/Modal.svelte';
import Account from './Settings/Account.svelte';
import Account from './Settings/Account.svelte';
import About from './Settings/About.svelte';
import About from './Settings/About.svelte';
import Models from './Settings/Models.svelte';
import General from './Settings/General.svelte';
import General from './Settings/General.svelte';
import Interface from './Settings/Interface.svelte';
import Interface from './Settings/Interface.svelte';
import Audio from './Settings/Audio.svelte';
import Audio from './Settings/Audio.svelte';
import Chats from './Settings/Chats.svelte';
import Chats from './Settings/Chats.svelte';
import Connections from './Settings/Connections.svelte';
import Images from './Settings/Images.svelte';
import User from '../icons/User.svelte';
import User from '../icons/User.svelte';
import Personalization from './Settings/Personalization.svelte';
import Personalization from './Settings/Personalization.svelte';
import { updateUserSettings } from '$lib/apis/users';
import { updateUserSettings } from '$lib/apis/users';
import { goto } from '$app/navigation';
const i18n = getContext('i18n');
const i18n = getContext('i18n');
...
@@ -90,55 +88,32 @@
...
@@ -90,55 +88,32 @@
<div class=" self-center">{$i18n.t('General')}</div>
<div class=" self-center">{$i18n.t('General')}</div>
</button>
</button>
{#if $user?.role === 'admin'}
{#if $user.role === 'admin'}
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'connections'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'connections';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M1 9.5A3.5 3.5 0 0 0 4.5 13H12a3 3 0 0 0 .917-5.857 2.503 2.503 0 0 0-3.198-3.019 3.5 3.5 0 0 0-6.628 2.171A3.5 3.5 0 0 0 1 9.5Z"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Connections')}</div>
</button>
<button
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'
models
'
'
admin
'
? 'bg-gray-200 dark:bg-gray-700'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
on:click={async () => {
selectedTab = 'models';
await goto('/admin/settings');
show = false;
}}
}}
>
>
<div class=" self-center mr-2">
<div class=" self-center mr-2">
<svg
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 2
0
2
0
"
viewBox="0 0 2
4
2
4
"
fill="currentColor"
fill="currentColor"
class="
w-4 h
-4"
class="
size
-4"
>
>
<path
<path
fill-rule="evenodd"
fill-rule="evenodd"
d="M
10 1c3.866 0 7 1.79 7 4s-3.134 4-7 4-7-1.79-7-4 3.134-4 7-4zm5.694 8.13c.464-.
264.
91-.583 1.306-.952V10c0 2.21-3.134 4-7 4s-7-1.79-7-4V8.178c.396.37.842.688 1.306.953C5.838 10.006 7.854 10.5 10 10.5s4.162-.494 5.694-1.37zM3 13.179V15c0 2.21 3.134 4 7 4s7-1.79 7-4v-1.822c-.396.37-.842.688-1.306.953-1.532.875-3.548 1.369-5.694 1.369s-4.162-.494-5.694-1.37A7.009 7.009 0 013 13.179z
"
d="M
4.5 3.75a3 3 0 0 0-3 3v10.5a3 3 0 0 0 3 3h15a3 3 0 0 0 3-3V6.75a3 3 0 0 0-3-3h-15Zm4.125 3a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Zm-3.873 8.703a4.1
26
4.
126 0 0 1 7.746 0 .75.75 0 0 1-.351.92 7.47 7.47 0 0 1-3.522.877 7.47 7.47 0 0 1-3.522-.877.75.75 0 0 1-.351-.92ZM15 8.25a.75.75 0 0 0 0 1.5h3.75a.75.75 0 0 0 0-1.5H15ZM14.25 12a.75.75 0 0 1 .75-.75h3.75a.75.75 0 0 1 0 1.5H15a.75.75 0 0 1-.75-.75Zm.75 2.25a.75.75 0 0 0 0 1.5h3.75a.75.75 0 0 0 0-1.5H15Z
"
clip-rule="evenodd"
clip-rule="evenodd"
/>
/>
</svg>
</svg>
</div>
</div>
<div class=" self-center">{$i18n.t('
Model
s')}</div>
<div class=" self-center">{$i18n.t('
Admin Setting
s')}</div>
</button>
</button>
{/if}
{/if}
...
@@ -210,34 +185,6 @@
...
@@ -210,34 +185,6 @@
<div class=" self-center">{$i18n.t('Audio')}</div>
<div class=" self-center">{$i18n.t('Audio')}</div>
</button>
</button>
{#if $user.role === 'admin'}
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'images'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'images';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M2 4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4Zm10.5 5.707a.5.5 0 0 0-.146-.353l-1-1a.5.5 0 0 0-.708 0L9.354 9.646a.5.5 0 0 1-.708 0L6.354 7.354a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0-.146.353V12a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V9.707ZM12 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Images')}</div>
</button>
{/if}
<button
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'chats'
'chats'
...
@@ -325,15 +272,6 @@
...
@@ -325,15 +272,6 @@
toast.success($i18n.t('Settings saved successfully!'));
toast.success($i18n.t('Settings saved successfully!'));
}}
}}
/>
/>
{:else if selectedTab === 'models'}
<Models {getModels} />
{:else if selectedTab === 'connections'}
<Connections
{getModels}
on:save={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'interface'}
{:else if selectedTab === 'interface'}
<Interface
<Interface
{saveSettings}
{saveSettings}
...
@@ -355,13 +293,6 @@
...
@@ -355,13 +293,6 @@
toast.success($i18n.t('Settings saved successfully!'));
toast.success($i18n.t('Settings saved successfully!'));
}}
}}
/>
/>
{:else if selectedTab === 'images'}
<Images
{saveSettings}
on:save={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'chats'}
{:else if selectedTab === 'chats'}
<Chats {saveSettings} />
<Chats {saveSettings} />
{:else if selectedTab === 'account'}
{:else if selectedTab === 'account'}
...
...
src/lib/components/common/Selector.svelte
View file @
96a004d4
...
@@ -48,7 +48,7 @@
...
@@ -48,7 +48,7 @@
<ChevronDown className="absolute end-2 top-1/2 -translate-y-[45%] size-3.5" strokeWidth="2.5" />
<ChevronDown className="absolute end-2 top-1/2 -translate-y-[45%] size-3.5" strokeWidth="2.5" />
</Select.Trigger>
</Select.Trigger>
<Select.Content
<Select.Content
class="w-full rounded-lg bg-white dark:bg-gray-900 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-
70
0/50 outline-none"
class="w-full rounded-lg bg-white dark:bg-gray-900 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-
85
0/50 outline-none"
transition={flyAndScale}
transition={flyAndScale}
sideOffset={4}
sideOffset={4}
>
>
...
...
src/lib/components/documents/Settings/QueryParams.svelte
View file @
96a004d4
...
@@ -95,7 +95,7 @@
...
@@ -95,7 +95,7 @@
)}
)}
</div>
</div>
<hr class=" dark:border-gray-
70
0 my-3" />
<hr class=" dark:border-gray-
85
0 my-3" />
{/if}
{/if}
<div>
<div>
...
...
src/lib/components/documents/Settings/WebParams.svelte
View file @
96a004d4
...
@@ -68,10 +68,10 @@
...
@@ -68,10 +68,10 @@
<select
<select
class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
bind:value={webConfig.search.engine}
bind:value={webConfig.search.engine}
placeholder=
"
Select a engine
"
placeholder=
{$i18n.t('
Select a engine
')}
required
required
>
>
<option disabled selected value="">Select a engine</option>
<option disabled selected value="">
{$i18n.t('
Select a engine
')}
</option>
{#each webSearchEngines as engine}
{#each webSearchEngines as engine}
<option value={engine}>{engine}</option>
<option value={engine}>{engine}</option>
{/each}
{/each}
...
...
Prev
1
2
3
4
5
6
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment