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
55dc6c1b
Commit
55dc6c1b
authored
Jun 07, 2024
by
Timothy J. Baek
Browse files
refac: audio
parent
da47c2df
Changes
20
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
772 additions
and
467 deletions
+772
-467
backend/apps/audio/main.py
backend/apps/audio/main.py
+78
-33
backend/config.py
backend/config.py
+53
-19
backend/main.py
backend/main.py
+9
-0
src/lib/components/admin/Settings.svelte
src/lib/components/admin/Settings.svelte
+227
-0
src/lib/components/admin/Settings/Audio.svelte
src/lib/components/admin/Settings/Audio.svelte
+310
-0
src/lib/components/admin/Settings/Banners.svelte
src/lib/components/admin/Settings/Banners.svelte
+1
-1
src/lib/components/admin/Settings/Database.svelte
src/lib/components/admin/Settings/Database.svelte
+1
-1
src/lib/components/admin/Settings/General.svelte
src/lib/components/admin/Settings/General.svelte
+1
-1
src/lib/components/admin/Settings/Pipelines.svelte
src/lib/components/admin/Settings/Pipelines.svelte
+1
-1
src/lib/components/admin/Settings/Users.svelte
src/lib/components/admin/Settings/Users.svelte
+1
-1
src/lib/components/admin/SettingsModal.svelte
src/lib/components/admin/SettingsModal.svelte
+0
-176
src/lib/components/chat/MessageInput/CallOverlay.svelte
src/lib/components/chat/MessageInput/CallOverlay.svelte
+28
-9
src/lib/components/chat/MessageInput/VoiceRecording.svelte
src/lib/components/chat/MessageInput/VoiceRecording.svelte
+2
-2
src/lib/components/chat/Messages/ResponseMessage.svelte
src/lib/components/chat/Messages/ResponseMessage.svelte
+8
-4
src/lib/components/chat/Settings/Audio.svelte
src/lib/components/chat/Settings/Audio.svelte
+36
-181
src/routes/(app)/+layout.svelte
src/routes/(app)/+layout.svelte
+0
-1
src/routes/(app)/+page.svelte
src/routes/(app)/+page.svelte
+2
-0
src/routes/(app)/admin/+layout.svelte
src/routes/(app)/admin/+layout.svelte
+9
-9
src/routes/(app)/admin/+page.svelte
src/routes/(app)/admin/+page.svelte
+0
-28
src/routes/(app)/admin/settings/+page.svelte
src/routes/(app)/admin/settings/+page.svelte
+5
-0
No files found.
backend/apps/audio/main.py
View file @
55dc6c1b
...
@@ -41,10 +41,15 @@ from config import (
...
@@ -41,10 +41,15 @@ from config import (
WHISPER_MODEL_DIR
,
WHISPER_MODEL_DIR
,
WHISPER_MODEL_AUTO_UPDATE
,
WHISPER_MODEL_AUTO_UPDATE
,
DEVICE_TYPE
,
DEVICE_TYPE
,
AUDIO_OPENAI_API_BASE_URL
,
AUDIO_STT_OPENAI_API_BASE_URL
,
AUDIO_OPENAI_API_KEY
,
AUDIO_STT_OPENAI_API_KEY
,
AUDIO_OPENAI_API_MODEL
,
AUDIO_TTS_OPENAI_API_BASE_URL
,
AUDIO_OPENAI_API_VOICE
,
AUDIO_TTS_OPENAI_API_KEY
,
AUDIO_STT_ENGINE
,
AUDIO_STT_MODEL
,
AUDIO_TTS_ENGINE
,
AUDIO_TTS_MODEL
,
AUDIO_TTS_VOICE
,
AppConfig
,
AppConfig
,
)
)
...
@@ -61,10 +66,17 @@ app.add_middleware(
...
@@ -61,10 +66,17 @@ app.add_middleware(
)
)
app
.
state
.
config
=
AppConfig
()
app
.
state
.
config
=
AppConfig
()
app
.
state
.
config
.
OPENAI_API_BASE_URL
=
AUDIO_OPENAI_API_BASE_URL
app
.
state
.
config
.
OPENAI_API_KEY
=
AUDIO_OPENAI_API_KEY
app
.
state
.
config
.
STT_OPENAI_API_BASE_URL
=
AUDIO_STT_OPENAI_API_BASE_URL
app
.
state
.
config
.
OPENAI_API_MODEL
=
AUDIO_OPENAI_API_MODEL
app
.
state
.
config
.
STT_OPENAI_API_KEY
=
AUDIO_STT_OPENAI_API_KEY
app
.
state
.
config
.
OPENAI_API_VOICE
=
AUDIO_OPENAI_API_VOICE
app
.
state
.
config
.
STT_ENGINE
=
AUDIO_STT_ENGINE
app
.
state
.
config
.
STT_MODEL
=
AUDIO_STT_MODEL
app
.
state
.
config
.
TTS_OPENAI_API_BASE_URL
=
AUDIO_TTS_OPENAI_API_BASE_URL
app
.
state
.
config
.
TTS_OPENAI_API_KEY
=
AUDIO_TTS_OPENAI_API_KEY
app
.
state
.
config
.
TTS_ENGINE
=
AUDIO_TTS_ENGINE
app
.
state
.
config
.
TTS_MODEL
=
AUDIO_TTS_MODEL
app
.
state
.
config
.
TTS_VOICE
=
AUDIO_TTS_VOICE
# setting device type for whisper model
# setting device type for whisper model
whisper_device_type
=
DEVICE_TYPE
if
DEVICE_TYPE
and
DEVICE_TYPE
==
"cuda"
else
"cpu"
whisper_device_type
=
DEVICE_TYPE
if
DEVICE_TYPE
and
DEVICE_TYPE
==
"cuda"
else
"cpu"
...
@@ -74,41 +86,74 @@ SPEECH_CACHE_DIR = Path(CACHE_DIR).joinpath("./audio/speech/")
...
@@ -74,41 +86,74 @@ SPEECH_CACHE_DIR = Path(CACHE_DIR).joinpath("./audio/speech/")
SPEECH_CACHE_DIR
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
SPEECH_CACHE_DIR
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
class
OpenAIConfigUpdateForm
(
BaseModel
):
class
TTSConfigForm
(
BaseModel
):
url
:
str
OPENAI_API_BASE_URL
:
str
key
:
str
OPENAI_API_KEY
:
str
model
:
str
ENGINE
:
str
speaker
:
str
MODEL
:
str
VOICE
:
str
class
STTConfigForm
(
BaseModel
):
OPENAI_API_BASE_URL
:
str
OPENAI_API_KEY
:
str
ENGINE
:
str
MODEL
:
str
class
AudioConfigUpdateForm
(
BaseModel
):
tts
:
TTSConfigForm
stt
:
STTConfigForm
@
app
.
get
(
"/config"
)
@
app
.
get
(
"/config"
)
async
def
get_
openai
_config
(
user
=
Depends
(
get_admin_user
)):
async
def
get_
audio
_config
(
user
=
Depends
(
get_admin_user
)):
return
{
return
{
"OPENAI_API_BASE_URL"
:
app
.
state
.
config
.
OPENAI_API_BASE_URL
,
"tts"
:
{
"OPENAI_API_KEY"
:
app
.
state
.
config
.
OPENAI_API_KEY
,
"OPENAI_API_BASE_URL"
:
app
.
state
.
config
.
TTS_OPENAI_API_BASE_URL
,
"OPENAI_API_MODEL"
:
app
.
state
.
config
.
OPENAI_API_MODEL
,
"OPENAI_API_KEY"
:
app
.
state
.
config
.
TTS_OPENAI_API_KEY
,
"OPENAI_API_VOICE"
:
app
.
state
.
config
.
OPENAI_API_VOICE
,
"ENGINE"
:
app
.
state
.
config
.
TTS_ENGINE
,
"MODEL"
:
app
.
state
.
config
.
TTS_MODEL
,
"VOICE"
:
app
.
state
.
config
.
TTS_VOICE
,
},
"stt"
:
{
"OPENAI_API_BASE_URL"
:
app
.
state
.
config
.
STT_OPENAI_API_BASE_URL
,
"OPENAI_API_KEY"
:
app
.
state
.
config
.
STT_OPENAI_API_KEY
,
"ENGINE"
:
app
.
state
.
config
.
STT_ENGINE
,
"MODEL"
:
app
.
state
.
config
.
STT_MODEL
,
},
}
}
@
app
.
post
(
"/config/update"
)
@
app
.
post
(
"/config/update"
)
async
def
update_
openai
_config
(
async
def
update_
audio
_config
(
form_data
:
OpenAI
ConfigUpdateForm
,
user
=
Depends
(
get_admin_user
)
form_data
:
Audio
ConfigUpdateForm
,
user
=
Depends
(
get_admin_user
)
):
):
if
form_data
.
key
==
""
:
app
.
state
.
config
.
TTS_OPENAI_API_BASE_URL
=
form_data
.
tts
.
OPENAI_API_BASE_URL
raise
HTTPException
(
status_code
=
400
,
detail
=
ERROR_MESSAGES
.
API_KEY_NOT_FOUND
)
app
.
state
.
config
.
TTS_OPENAI_API_KEY
=
form_data
.
tts
.
OPENAI_API_KEY
app
.
state
.
config
.
TTS_ENGINE
=
form_data
.
tts
.
ENGINE
app
.
state
.
config
.
TTS_MODEL
=
form_data
.
tts
.
MODEL
app
.
state
.
config
.
TTS_VOICE
=
form_data
.
tts
.
VOICE
app
.
state
.
config
.
OPENAI_API_BASE_URL
=
form_data
.
url
app
.
state
.
config
.
STT_
OPENAI_API_BASE_URL
=
form_data
.
stt
.
OPENAI_API_BASE_URL
app
.
state
.
config
.
OPENAI_API_KEY
=
form_data
.
key
app
.
state
.
config
.
STT_
OPENAI_API_KEY
=
form_data
.
stt
.
OPENAI_API_KEY
app
.
state
.
config
.
OPENAI_API_MODEL
=
form_data
.
model
app
.
state
.
config
.
STT_ENGINE
=
form_data
.
stt
.
ENGINE
app
.
state
.
config
.
OPENAI_API_VOICE
=
form_data
.
s
peaker
app
.
state
.
config
.
STT_MODEL
=
form_data
.
s
tt
.
MODEL
return
{
return
{
"status"
:
True
,
"tts"
:
{
"OPENAI_API_BASE_URL"
:
app
.
state
.
config
.
OPENAI_API_BASE_URL
,
"OPENAI_API_BASE_URL"
:
app
.
state
.
config
.
TTS_OPENAI_API_BASE_URL
,
"OPENAI_API_KEY"
:
app
.
state
.
config
.
OPENAI_API_KEY
,
"OPENAI_API_KEY"
:
app
.
state
.
config
.
TTS_OPENAI_API_KEY
,
"OPENAI_API_MODEL"
:
app
.
state
.
config
.
OPENAI_API_MODEL
,
"ENGINE"
:
app
.
state
.
config
.
TTS_ENGINE
,
"OPENAI_API_VOICE"
:
app
.
state
.
config
.
OPENAI_API_VOICE
,
"MODEL"
:
app
.
state
.
config
.
TTS_MODEL
,
"VOICE"
:
app
.
state
.
config
.
TTS_VOICE
,
},
"stt"
:
{
"OPENAI_API_BASE_URL"
:
app
.
state
.
config
.
STT_OPENAI_API_BASE_URL
,
"OPENAI_API_KEY"
:
app
.
state
.
config
.
STT_OPENAI_API_KEY
,
"ENGINE"
:
app
.
state
.
config
.
STT_ENGINE
,
"MODEL"
:
app
.
state
.
config
.
STT_MODEL
,
},
}
}
...
@@ -125,13 +170,13 @@ async def speech(request: Request, user=Depends(get_verified_user)):
...
@@ -125,13 +170,13 @@ async def speech(request: Request, user=Depends(get_verified_user)):
return
FileResponse
(
file_path
)
return
FileResponse
(
file_path
)
headers
=
{}
headers
=
{}
headers
[
"Authorization"
]
=
f
"Bearer
{
app
.
state
.
config
.
OPENAI_API_KEY
}
"
headers
[
"Authorization"
]
=
f
"Bearer
{
app
.
state
.
config
.
TTS_
OPENAI_API_KEY
}
"
headers
[
"Content-Type"
]
=
"application/json"
headers
[
"Content-Type"
]
=
"application/json"
r
=
None
r
=
None
try
:
try
:
r
=
requests
.
post
(
r
=
requests
.
post
(
url
=
f
"
{
app
.
state
.
config
.
OPENAI_API_BASE_URL
}
/audio/speech"
,
url
=
f
"
{
app
.
state
.
config
.
TTS_
OPENAI_API_BASE_URL
}
/audio/speech"
,
data
=
body
,
data
=
body
,
headers
=
headers
,
headers
=
headers
,
stream
=
True
,
stream
=
True
,
...
...
backend/config.py
View file @
55dc6c1b
...
@@ -933,25 +933,59 @@ IMAGE_GENERATION_MODEL = PersistentConfig(
...
@@ -933,25 +933,59 @@ IMAGE_GENERATION_MODEL = PersistentConfig(
# Audio
# Audio
####################################
####################################
AUDIO_OPENAI_API_BASE_URL
=
PersistentConfig
(
AUDIO_STT_OPENAI_API_BASE_URL
=
PersistentConfig
(
"AUDIO_OPENAI_API_BASE_URL"
,
"AUDIO_STT_OPENAI_API_BASE_URL"
,
"audio.openai.api_base_url"
,
"audio.stt.openai.api_base_url"
,
os
.
getenv
(
"AUDIO_OPENAI_API_BASE_URL"
,
OPENAI_API_BASE_URL
),
os
.
getenv
(
"AUDIO_STT_OPENAI_API_BASE_URL"
,
OPENAI_API_BASE_URL
),
)
)
AUDIO_OPENAI_API_KEY
=
PersistentConfig
(
"AUDIO_OPENAI_API_KEY"
,
AUDIO_STT_OPENAI_API_KEY
=
PersistentConfig
(
"audio.openai.api_key"
,
"AUDIO_STT_OPENAI_API_KEY"
,
os
.
getenv
(
"AUDIO_OPENAI_API_KEY"
,
OPENAI_API_KEY
),
"audio.stt.openai.api_key"
,
)
os
.
getenv
(
"AUDIO_STT_OPENAI_API_KEY"
,
OPENAI_API_KEY
),
AUDIO_OPENAI_API_MODEL
=
PersistentConfig
(
)
"AUDIO_OPENAI_API_MODEL"
,
"audio.openai.api_model"
,
AUDIO_STT_ENGINE
=
PersistentConfig
(
os
.
getenv
(
"AUDIO_OPENAI_API_MODEL"
,
"tts-1"
),
"AUDIO_STT_ENGINE"
,
)
"audio.stt.engine"
,
AUDIO_OPENAI_API_VOICE
=
PersistentConfig
(
os
.
getenv
(
"AUDIO_STT_ENGINE"
,
""
),
"AUDIO_OPENAI_API_VOICE"
,
)
"audio.openai.api_voice"
,
os
.
getenv
(
"AUDIO_OPENAI_API_VOICE"
,
"alloy"
),
AUDIO_STT_MODEL
=
PersistentConfig
(
"AUDIO_STT_MODEL"
,
"audio.stt.model"
,
os
.
getenv
(
"AUDIO_STT_MODEL"
,
"whisper-1"
),
)
AUDIO_TTS_OPENAI_API_BASE_URL
=
PersistentConfig
(
"AUDIO_TTS_OPENAI_API_BASE_URL"
,
"audio.tts.openai.api_base_url"
,
os
.
getenv
(
"AUDIO_TTS_OPENAI_API_BASE_URL"
,
OPENAI_API_BASE_URL
),
)
AUDIO_TTS_OPENAI_API_KEY
=
PersistentConfig
(
"AUDIO_TTS_OPENAI_API_KEY"
,
"audio.tts.openai.api_key"
,
os
.
getenv
(
"AUDIO_TTS_OPENAI_API_KEY"
,
OPENAI_API_KEY
),
)
AUDIO_TTS_ENGINE
=
PersistentConfig
(
"AUDIO_TTS_ENGINE"
,
"audio.tts.engine"
,
os
.
getenv
(
"AUDIO_TTS_ENGINE"
,
""
),
)
AUDIO_TTS_MODEL
=
PersistentConfig
(
"AUDIO_TTS_MODEL"
,
"audio.tts.model"
,
os
.
getenv
(
"AUDIO_TTS_MODEL"
,
"tts-1"
),
)
AUDIO_TTS_VOICE
=
PersistentConfig
(
"AUDIO_TTS_VOICE"
,
"audio.tts.voice"
,
os
.
getenv
(
"AUDIO_TTS_VOICE"
,
"alloy"
),
)
)
...
...
backend/main.py
View file @
55dc6c1b
...
@@ -900,6 +900,15 @@ async def get_app_config():
...
@@ -900,6 +900,15 @@ async def get_app_config():
"enable_community_sharing"
:
webui_app
.
state
.
config
.
ENABLE_COMMUNITY_SHARING
,
"enable_community_sharing"
:
webui_app
.
state
.
config
.
ENABLE_COMMUNITY_SHARING
,
"enable_admin_export"
:
ENABLE_ADMIN_EXPORT
,
"enable_admin_export"
:
ENABLE_ADMIN_EXPORT
,
},
},
"audio"
:
{
"tts"
:
{
"engine"
:
audio_app
.
state
.
config
.
TTS_ENGINE
,
"voice"
:
audio_app
.
state
.
config
.
TTS_VOICE
,
},
"stt"
:
{
"engine"
:
audio_app
.
state
.
config
.
STT_ENGINE
,
},
},
}
}
...
...
src/lib/components/admin/Settings.svelte
0 → 100644
View file @
55dc6c1b
<script>
import { getContext } from 'svelte';
import Modal from '../common/Modal.svelte';
import Database from './Settings/Database.svelte';
import General from './Settings/General.svelte';
import Users from './Settings/Users.svelte';
import Banners from '$lib/components/admin/Settings/Banners.svelte';
import { toast } from 'svelte-sonner';
import Pipelines from './Settings/Pipelines.svelte';
import Audio from './Settings/Audio.svelte';
const i18n = getContext('i18n');
let selectedTab = 'general';
</script>
<div class="flex flex-col md:flex-row w-full h-full py-3 md:space-x-4">
<div
class="tabs flex flex-row overflow-x-auto space-x-1 md:space-x-0 md:space-y-1 md:flex-col md:flex-none md:w-40 dark:text-gray-200 text-xs text-left mb-3 md:mb-0 scrollbar-hidden"
>
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'general'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'general';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('General')}</div>
</button>
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'users'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'users';
}}
>
<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="M8 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5ZM3.156 11.763c.16-.629.44-1.21.813-1.72a2.5 2.5 0 0 0-2.725 1.377c-.136.287.102.58.418.58h1.449c.01-.077.025-.156.045-.237ZM12.847 11.763c.02.08.036.16.046.237h1.446c.316 0 .554-.293.417-.579a2.5 2.5 0 0 0-2.722-1.378c.374.51.653 1.09.813 1.72ZM14 7.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0ZM3.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM5 13c-.552 0-1.013-.455-.876-.99a4.002 4.002 0 0 1 7.753 0c.136.535-.324.99-.877.99H5Z"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Users')}</div>
</button>
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'audio'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'audio';
}}
>
<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="M7.557 2.066A.75.75 0 0 1 8 2.75v10.5a.75.75 0 0 1-1.248.56L3.59 11H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h1.59l3.162-2.81a.75.75 0 0 1 .805-.124ZM12.95 3.05a.75.75 0 1 0-1.06 1.06 5.5 5.5 0 0 1 0 7.78.75.75 0 1 0 1.06 1.06 7 7 0 0 0 0-9.9Z"
/>
<path
d="M10.828 5.172a.75.75 0 1 0-1.06 1.06 2.5 2.5 0 0 1 0 3.536.75.75 0 1 0 1.06 1.06 4 4 0 0 0 0-5.656Z"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Audio')}</div>
</button>
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'banners'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'banners';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-4"
>
<path
d="M5.85 3.5a.75.75 0 0 0-1.117-1 9.719 9.719 0 0 0-2.348 4.876.75.75 0 0 0 1.479.248A8.219 8.219 0 0 1 5.85 3.5ZM19.267 2.5a.75.75 0 1 0-1.118 1 8.22 8.22 0 0 1 1.987 4.124.75.75 0 0 0 1.48-.248A9.72 9.72 0 0 0 19.266 2.5Z"
/>
<path
fill-rule="evenodd"
d="M12 2.25A6.75 6.75 0 0 0 5.25 9v.75a8.217 8.217 0 0 1-2.119 5.52.75.75 0 0 0 .298 1.206c1.544.57 3.16.99 4.831 1.243a3.75 3.75 0 1 0 7.48 0 24.583 24.583 0 0 0 4.83-1.244.75.75 0 0 0 .298-1.205 8.217 8.217 0 0 1-2.118-5.52V9A6.75 6.75 0 0 0 12 2.25ZM9.75 18c0-.034 0-.067.002-.1a25.05 25.05 0 0 0 4.496 0l.002.1a2.25 2.25 0 1 1-4.5 0Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Banners')}</div>
</button>
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'pipelines'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'pipelines';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-4"
>
<path
d="M11.644 1.59a.75.75 0 0 1 .712 0l9.75 5.25a.75.75 0 0 1 0 1.32l-9.75 5.25a.75.75 0 0 1-.712 0l-9.75-5.25a.75.75 0 0 1 0-1.32l9.75-5.25Z"
/>
<path
d="m3.265 10.602 7.668 4.129a2.25 2.25 0 0 0 2.134 0l7.668-4.13 1.37.739a.75.75 0 0 1 0 1.32l-9.75 5.25a.75.75 0 0 1-.71 0l-9.75-5.25a.75.75 0 0 1 0-1.32l1.37-.738Z"
/>
<path
d="m10.933 19.231-7.668-4.13-1.37.739a.75.75 0 0 0 0 1.32l9.75 5.25c.221.12.489.12.71 0l9.75-5.25a.75.75 0 0 0 0-1.32l-1.37-.738-7.668 4.13a2.25 2.25 0 0 1-2.134-.001Z"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Pipelines')}</div>
</button>
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'db'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'db';
}}
>
<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="M8 7c3.314 0 6-1.343 6-3s-2.686-3-6-3-6 1.343-6 3 2.686 3 6 3Z" />
<path
d="M8 8.5c1.84 0 3.579-.37 4.914-1.037A6.33 6.33 0 0 0 14 6.78V8c0 1.657-2.686 3-6 3S2 9.657 2 8V6.78c.346.273.72.5 1.087.683C4.42 8.131 6.16 8.5 8 8.5Z"
/>
<path
d="M8 12.5c1.84 0 3.579-.37 4.914-1.037.366-.183.74-.41 1.086-.684V12c0 1.657-2.686 3-6 3s-6-1.343-6-3v-1.22c.346.273.72.5 1.087.683C4.42 12.131 6.16 12.5 8 12.5Z"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Database')}</div>
</button>
</div>
<div class="flex-1">
{#if selectedTab === 'general'}
<General
saveHandler={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'users'}
<Users
saveHandler={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'audio'}
<Audio
saveHandler={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'db'}
<Database
saveHandler={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'banners'}
<Banners
saveHandler={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'pipelines'}
<Pipelines
saveHandler={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{/if}
</div>
</div>
src/lib/components/admin/Settings/Audio.svelte
0 → 100644
View file @
55dc6c1b
<script lang="ts">
import { getAudioConfig, updateAudioConfig } from '$lib/apis/audio';
import { user, settings, config } from '$lib/stores';
import { createEventDispatcher, onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import Switch from '$lib/components/common/Switch.svelte';
import { getBackendConfig } from '$lib/apis';
const dispatch = createEventDispatcher();
const i18n = getContext('i18n');
export let saveHandler: Function;
// Audio
let TTS_OPENAI_API_BASE_URL = '';
let TTS_OPENAI_API_KEY = '';
let TTS_ENGINE = '';
let TTS_MODEL = '';
let TTS_VOICE = '';
let STT_OPENAI_API_BASE_URL = '';
let STT_OPENAI_API_KEY = '';
let STT_ENGINE = '';
let STT_MODEL = '';
let voices = [];
let models = [];
let nonLocalVoices = false;
const getOpenAIVoices = () => {
voices = [
{ name: 'alloy' },
{ name: 'echo' },
{ name: 'fable' },
{ name: 'onyx' },
{ name: 'nova' },
{ name: 'shimmer' }
];
};
const getOpenAIModels = () => {
models = [{ name: 'tts-1' }, { name: 'tts-1-hd' }];
};
const getWebAPIVoices = () => {
const getVoicesLoop = setInterval(async () => {
voices = await speechSynthesis.getVoices();
// do your loop
if (voices.length > 0) {
clearInterval(getVoicesLoop);
}
}, 100);
};
const updateConfigHandler = async () => {
const res = await updateAudioConfig(localStorage.token, {
tts: {
OPENAI_API_BASE_URL: TTS_OPENAI_API_BASE_URL,
OPENAI_API_KEY: TTS_OPENAI_API_KEY,
ENGINE: TTS_ENGINE,
MODEL: TTS_MODEL,
VOICE: TTS_VOICE
},
stt: {
OPENAI_API_BASE_URL: STT_OPENAI_API_BASE_URL,
OPENAI_API_KEY: STT_OPENAI_API_KEY,
ENGINE: STT_ENGINE,
MODEL: STT_MODEL
}
});
if (res) {
toast.success('Audio settings updated successfully');
config.set(await getBackendConfig());
}
};
onMount(async () => {
const res = await getAudioConfig(localStorage.token);
if (res) {
console.log(res);
TTS_OPENAI_API_BASE_URL = res.tts.OPENAI_API_BASE_URL;
TTS_OPENAI_API_KEY = res.tts.OPENAI_API_KEY;
TTS_ENGINE = res.tts.ENGINE;
TTS_MODEL = res.tts.MODEL;
TTS_VOICE = res.tts.VOICE;
STT_OPENAI_API_BASE_URL = res.stt.OPENAI_API_BASE_URL;
STT_OPENAI_API_KEY = res.stt.OPENAI_API_KEY;
STT_ENGINE = res.stt.ENGINE;
STT_MODEL = res.stt.MODEL;
}
if (TTS_ENGINE === 'openai') {
getOpenAIVoices();
getOpenAIModels();
} else {
getWebAPIVoices();
}
});
</script>
<form
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={async () => {
await updateConfigHandler();
dispatch('save');
}}
>
<div class="flex flex-col gap-3">
<div>
<div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Speech-to-Text 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={STT_ENGINE}
placeholder="Select an engine"
>
<option value="">{$i18n.t('Local Whisper')}</option>
<option value="openai">OpenAI</option>
<option value="web">{$i18n.t('Web API')}</option>
</select>
</div>
</div>
{#if STT_ENGINE === 'openai'}
<div>
<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={STT_OPENAI_API_BASE_URL}
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={STT_OPENAI_API_KEY}
required
/>
</div>
</div>
<hr class=" dark:border-gray-850 my-2" />
<div>
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('STT 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={STT_MODEL}
placeholder="Select a model"
/>
<datalist id="model-list">
<option value="whisper-1" />
</datalist>
</div>
</div>
</div>
{/if}
</div>
<hr class=" dark:border-gray-800" />
<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={TTS_ENGINE}
placeholder="Select a mode"
on:change={(e) => {
if (e.target.value === 'openai') {
getOpenAIVoices();
TTS_VOICE = 'alloy';
TTS_MODEL = 'tts-1';
} else {
getWebAPIVoices();
TTS_VOICE = '';
}
}}
>
<option value="">{$i18n.t('Web API')}</option>
<option value="openai">{$i18n.t('Open AI')}</option>
</select>
</div>
</div>
{#if TTS_ENGINE === 'openai'}
<div>
<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={TTS_OPENAI_API_BASE_URL}
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={TTS_OPENAI_API_KEY}
required
/>
</div>
</div>
{/if}
<hr class=" dark:border-gray-850 my-2" />
{#if TTS_ENGINE === ''}
<div>
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Voice')}</div>
<div class="flex w-full">
<div class="flex-1">
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={TTS_VOICE}
>
<option value="" selected={TTS_VOICE !== ''}>{$i18n.t('Default')}</option>
{#each voices.filter((v) => nonLocalVoices || v.localService === true) as voice}
<option
value={voice.voiceURI}
class="bg-gray-100 dark:bg-gray-700"
selected={TTS_VOICE === voice.voiceURI}>{voice.name}</option
>
{/each}
</select>
</div>
</div>
<div class="flex items-center justify-between my-1.5">
<div class="text-xs">
{$i18n.t('Allow non-local voices')}
</div>
<div class="mt-1">
<Switch bind:state={nonLocalVoices} />
</div>
</div>
</div>
{:else if TTS_ENGINE === 'openai'}
<div class=" flex gap-2">
<div class="w-full">
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Voice')}</div>
<div class="flex w-full">
<div class="flex-1">
<input
list="voice-list"
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={TTS_VOICE}
placeholder="Select a voice"
/>
<datalist id="voice-list">
{#each voices as voice}
<option value={voice.name} />
{/each}
</datalist>
</div>
</div>
</div>
<div class="w-full">
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('TTS 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={TTS_MODEL}
placeholder="Select a model"
/>
<datalist id="model-list">
{#each models as model}
<option value={model.name} />
{/each}
</datalist>
</div>
</div>
</div>
</div>
{/if}
</div>
</div>
<div class="flex justify-end 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>
src/lib/components/admin/Settings/Banners.svelte
View file @
55dc6c1b
...
@@ -33,7 +33,7 @@
...
@@ -33,7 +33,7 @@
saveHandler();
saveHandler();
}}
}}
>
>
<div class=" space-y-3 pr-1.5 overflow-y-scroll
max-h-80
h-full">
<div class=" space-y-3 pr-1.5 overflow-y-scroll h-full">
<div class=" space-y-3 pr-1.5">
<div class=" space-y-3 pr-1.5">
<div class="flex w-full justify-between mb-2">
<div class="flex w-full justify-between mb-2">
<div class=" self-center text-sm font-semibold">
<div class=" self-center text-sm font-semibold">
...
...
src/lib/components/admin/Settings/Database.svelte
View file @
55dc6c1b
...
@@ -30,7 +30,7 @@
...
@@ -30,7 +30,7 @@
saveHandler();
saveHandler();
}}
}}
>
>
<div class=" space-y-3 pr-1.5 overflow-y-scroll
max-h-80
">
<div class=" space-y-3 pr-1.5 overflow-y-scroll
h-full
">
<div>
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('Database')}</div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('Database')}</div>
...
...
src/lib/components/admin/Settings/General.svelte
View file @
55dc6c1b
...
@@ -56,7 +56,7 @@
...
@@ -56,7 +56,7 @@
saveHandler();
saveHandler();
}}
}}
>
>
<div class=" space-y-3 pr-1.5 overflow-y-scroll
max-h-[22rem]
">
<div class=" space-y-3 pr-1.5 overflow-y-scroll
h-full
">
{#if adminConfig !== null}
{#if adminConfig !== null}
<div>
<div>
<div class=" mb-3 text-sm font-medium">{$i18n.t('General Settings')}</div>
<div class=" mb-3 text-sm font-medium">{$i18n.t('General Settings')}</div>
...
...
src/lib/components/admin/Settings/Pipelines.svelte
View file @
55dc6c1b
...
@@ -200,7 +200,7 @@
...
@@ -200,7 +200,7 @@
updateHandler();
updateHandler();
}}
}}
>
>
<div class=" pr-1.5 overflow-y-scroll
max-h-80
h-full">
<div class=" pr-1.5 overflow-y-scroll h-full">
{#if PIPELINES_LIST !== null}
{#if PIPELINES_LIST !== null}
<div class="flex w-full justify-between mb-2">
<div class="flex w-full justify-between mb-2">
<div class=" self-center text-sm font-semibold">
<div class=" self-center text-sm font-semibold">
...
...
src/lib/components/admin/Settings/Users.svelte
View file @
55dc6c1b
...
@@ -48,7 +48,7 @@
...
@@ -48,7 +48,7 @@
await config.set(await getBackendConfig());
await config.set(await getBackendConfig());
}}
}}
>
>
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-
80
">
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-
full
">
<div>
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('User Permissions')}</div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('User Permissions')}</div>
...
...
src/lib/components/admin/SettingsModal.svelte
View file @
55dc6c1b
...
@@ -39,181 +39,5 @@
...
@@ -39,181 +39,5 @@
</svg>
</svg>
</button>
</button>
</div>
</div>
<div class="flex flex-col md:flex-row w-full p-4 md:space-x-4">
<div
class="tabs flex flex-row overflow-x-auto space-x-1 md:space-x-0 md:space-y-1 md:flex-col flex-1 md:flex-none md:w-40 dark:text-gray-200 text-xs text-left mb-3 md:mb-0"
>
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'general'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'general';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('General')}</div>
</button>
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'users'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'users';
}}
>
<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="M8 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5ZM3.156 11.763c.16-.629.44-1.21.813-1.72a2.5 2.5 0 0 0-2.725 1.377c-.136.287.102.58.418.58h1.449c.01-.077.025-.156.045-.237ZM12.847 11.763c.02.08.036.16.046.237h1.446c.316 0 .554-.293.417-.579a2.5 2.5 0 0 0-2.722-1.378c.374.51.653 1.09.813 1.72ZM14 7.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0ZM3.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM5 13c-.552 0-1.013-.455-.876-.99a4.002 4.002 0 0 1 7.753 0c.136.535-.324.99-.877.99H5Z"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Users')}</div>
</button>
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'db'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'db';
}}
>
<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="M8 7c3.314 0 6-1.343 6-3s-2.686-3-6-3-6 1.343-6 3 2.686 3 6 3Z" />
<path
d="M8 8.5c1.84 0 3.579-.37 4.914-1.037A6.33 6.33 0 0 0 14 6.78V8c0 1.657-2.686 3-6 3S2 9.657 2 8V6.78c.346.273.72.5 1.087.683C4.42 8.131 6.16 8.5 8 8.5Z"
/>
<path
d="M8 12.5c1.84 0 3.579-.37 4.914-1.037.366-.183.74-.41 1.086-.684V12c0 1.657-2.686 3-6 3s-6-1.343-6-3v-1.22c.346.273.72.5 1.087.683C4.42 12.131 6.16 12.5 8 12.5Z"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Database')}</div>
</button>
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'banners'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'banners';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-4"
>
<path
d="M5.85 3.5a.75.75 0 0 0-1.117-1 9.719 9.719 0 0 0-2.348 4.876.75.75 0 0 0 1.479.248A8.219 8.219 0 0 1 5.85 3.5ZM19.267 2.5a.75.75 0 1 0-1.118 1 8.22 8.22 0 0 1 1.987 4.124.75.75 0 0 0 1.48-.248A9.72 9.72 0 0 0 19.266 2.5Z"
/>
<path
fill-rule="evenodd"
d="M12 2.25A6.75 6.75 0 0 0 5.25 9v.75a8.217 8.217 0 0 1-2.119 5.52.75.75 0 0 0 .298 1.206c1.544.57 3.16.99 4.831 1.243a3.75 3.75 0 1 0 7.48 0 24.583 24.583 0 0 0 4.83-1.244.75.75 0 0 0 .298-1.205 8.217 8.217 0 0 1-2.118-5.52V9A6.75 6.75 0 0 0 12 2.25ZM9.75 18c0-.034 0-.067.002-.1a25.05 25.05 0 0 0 4.496 0l.002.1a2.25 2.25 0 1 1-4.5 0Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Banners')}</div>
</button>
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'pipelines'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'pipelines';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-4"
>
<path
d="M11.644 1.59a.75.75 0 0 1 .712 0l9.75 5.25a.75.75 0 0 1 0 1.32l-9.75 5.25a.75.75 0 0 1-.712 0l-9.75-5.25a.75.75 0 0 1 0-1.32l9.75-5.25Z"
/>
<path
d="m3.265 10.602 7.668 4.129a2.25 2.25 0 0 0 2.134 0l7.668-4.13 1.37.739a.75.75 0 0 1 0 1.32l-9.75 5.25a.75.75 0 0 1-.71 0l-9.75-5.25a.75.75 0 0 1 0-1.32l1.37-.738Z"
/>
<path
d="m10.933 19.231-7.668-4.13-1.37.739a.75.75 0 0 0 0 1.32l9.75 5.25c.221.12.489.12.71 0l9.75-5.25a.75.75 0 0 0 0-1.32l-1.37-.738-7.668 4.13a2.25 2.25 0 0 1-2.134-.001Z"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Pipelines')}</div>
</button>
</div>
<div class="flex-1 md:min-h-[380px]">
{#if selectedTab === 'general'}
<General
saveHandler={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'users'}
<Users
saveHandler={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'db'}
<Database
saveHandler={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'banners'}
<Banners
saveHandler={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'pipelines'}
<Pipelines
saveHandler={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{/if}
</div>
</div>
</div>
</div>
</Modal>
</Modal>
src/lib/components/chat/MessageInput/CallOverlay.svelte
View file @
55dc6c1b
<script lang="ts">
<script lang="ts">
import { settings, showCallOverlay } from '$lib/stores';
import {
config,
settings, showCallOverlay } from '$lib/stores';
import { onMount, tick, getContext } from 'svelte';
import { onMount, tick, getContext } from 'svelte';
import { blobToFile, calculateSHA256, extractSentences, findWordIndices } from '$lib/utils';
import { blobToFile, calculateSHA256, extractSentences, findWordIndices } from '$lib/utils';
...
@@ -159,9 +159,9 @@
...
@@ -159,9 +159,9 @@
const getOpenAISpeech = async (text) => {
const getOpenAISpeech = async (text) => {
const res = await synthesizeOpenAISpeech(
const res = await synthesizeOpenAISpeech(
localStorage.token,
localStorage.token,
$settings?.audio?.
speaker ?? 'alloy'
,
$settings?.audio?.
tts?.voice ?? $config?.audio?.tts?.voice
,
text,
text,
$settings?.audio?.model ??
'tts-1'
$settings?.audio?.
tts?.
model ??
$config?.audio?.tts?.model
).catch((error) => {
).catch((error) => {
toast.error(error);
toast.error(error);
assistantSpeaking = false;
assistantSpeaking = false;
...
@@ -207,10 +207,29 @@
...
@@ -207,10 +207,29 @@
const assistantSpeakingHandler = async (content) => {
const assistantSpeakingHandler = async (content) => {
assistantSpeaking = true;
assistantSpeaking = true;
if (($settings?.audio?.TTSEngine ?? '') == '') {
if (($config.audio.tts.engine ?? '') == '') {
currentUtterance = new SpeechSynthesisUtterance(content);
let voices = [];
speechSynthesis.speak(currentUtterance);
const getVoicesLoop = setInterval(async () => {
} else if ($settings?.audio?.TTSEngine === 'openai') {
voices = await speechSynthesis.getVoices();
if (voices.length > 0) {
clearInterval(getVoicesLoop);
const voice =
voices
?.filter(
(v) => v.voiceURI === ($settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice)
)
?.at(0) ?? undefined;
console.log($settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice);
console.log(voices);
currentUtterance = new SpeechSynthesisUtterance(content);
currentUtterance.voice = voice;
speechSynthesis.speak(currentUtterance);
}
}, 100);
} else if ($config.audio.tts.engine === 'openai') {
console.log('openai');
console.log('openai');
const sentences = extractSentences(content).reduce((mergedTexts, currentText) => {
const sentences = extractSentences(content).reduce((mergedTexts, currentText) => {
...
@@ -236,9 +255,9 @@
...
@@ -236,9 +255,9 @@
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
$settings?.audio?.
tts?.model ?? $config?.audio?.tts?.
model
).catch((error) => {
).catch((error) => {
toast.error(error);
toast.error(error);
...
...
src/lib/components/chat/MessageInput/VoiceRecording.svelte
View file @
55dc6c1b
...
@@ -169,7 +169,7 @@
...
@@ -169,7 +169,7 @@
mediaRecorder.ondataavailable = (event) => audioChunks.push(event.data);
mediaRecorder.ondataavailable = (event) => audioChunks.push(event.data);
mediaRecorder.onstop = async () => {
mediaRecorder.onstop = async () => {
console.log('Recording stopped');
console.log('Recording stopped');
if (($settings?.audio?.
STTE
ngine ?? '') === 'web') {
if (($settings?.audio?.
stt?.e
ngine ?? '') === 'web') {
audioChunks = [];
audioChunks = [];
} else {
} else {
if (confirmed) {
if (confirmed) {
...
@@ -186,7 +186,7 @@
...
@@ -186,7 +186,7 @@
};
};
mediaRecorder.start();
mediaRecorder.start();
if (($settings?.audio?.
STTE
ngine ?? '') === 'web') {
if (($settings?.audio?.
stt?.e
ngine ?? '') === 'web') {
if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
// Create a SpeechRecognition object
// Create a SpeechRecognition object
speechRecognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
speechRecognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
...
...
src/lib/components/chat/Messages/ResponseMessage.svelte
View file @
55dc6c1b
...
@@ -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,9 @@
...
@@ -244,9 +244,9 @@
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
$settings?.audio?.
tts?.model ?? $config?.audio?.tts?.
model
).catch((error) => {
).catch((error) => {
toast.error(error);
toast.error(error);
...
@@ -273,7 +273,11 @@
...
@@ -273,7 +273,11 @@
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;
const speak = new SpeechSynthesisUtterance(message.content);
const speak = new SpeechSynthesisUtterance(message.content);
...
...
src/lib/components/chat/Settings/Audio.svelte
View file @
55dc6c1b
<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: $config.audio.tts.engine === '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,31 +90,21 @@
...
@@ -162,31 +90,21 @@
<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 an engine"
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 (Whisper)'
)}</
option
>
<
option
value
=
"web"
>{$
i18n
.
t
(
'Web API'
)}</
option
>
</
select
>
</div>
</div>
</
div
>
{/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">
<div class=" self-center text-xs font-medium">
...
@@ -212,50 +130,6 @@
...
@@ -212,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>
...
@@ -277,21 +151,21 @@
...
@@ -277,21 +151,21 @@
<hr class=" dark:border-gray-700" />
<hr class=" dark:border-gray-700" />
{#
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>
...
@@ -307,7 +181,7 @@
...
@@ -307,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">
...
@@ -315,7 +189,7 @@
...
@@ -315,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"
/>
/>
...
@@ -327,25 +201,6 @@
...
@@ -327,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/routes/(app)/+layout.svelte
View file @
55dc6c1b
...
@@ -183,7 +183,6 @@
...
@@ -183,7 +183,6 @@
});
});
</script>
</script>
<Help />
<SettingsModal bind:show={$showSettings} />
<SettingsModal bind:show={$showSettings} />
<ChangelogModal bind:show={$showChangelog} />
<ChangelogModal bind:show={$showChangelog} />
...
...
src/routes/(app)/+page.svelte
View file @
55dc6c1b
<script lang="ts">
<script lang="ts">
import Chat from '$lib/components/chat/Chat.svelte';
import Chat from '$lib/components/chat/Chat.svelte';
import Help from '$lib/components/layout/Help.svelte';
</script>
</script>
<Help />
<Chat />
<Chat />
src/routes/(app)/admin/+layout.svelte
View file @
55dc6c1b
...
@@ -30,29 +30,29 @@
...
@@ -30,29 +30,29 @@
</div>
</div>
</button>
</button>
</div>
</div>
<div class="flex items-center text-xl font-semibold">{$i18n.t('
Workspace
')}</div>
<div class="flex items-center text-xl font-semibold">{$i18n.t('
Admin Panel
')}</div>
</div>
</div>
</div>
</div>
<!--
<div class="px-4 my-1">
<div class="px-4 my-1">
<div
<div
class="flex scrollbar-none overflow-x-auto w-fit text-center text-sm font-medium rounded-xl bg-transparent/10 p-1"
class="flex scrollbar-none overflow-x-auto w-fit text-center text-sm font-medium rounded-xl bg-transparent/10 p-1"
>
>
<a
<a
class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname
.includes('/workspace/models')
class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname
=== '/admin'
? 'bg-gray-50 dark:bg-gray-850'
? 'bg-gray-50 dark:bg-gray-850'
: ''} transition"
: ''} transition"
href="/
workspace/models
">{$i18n.t('
Models
')}</a
href="/
admin
">{$i18n.t('
Dashboard
')}</a
>
>
<a
<a
class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname.includes('/
workspace/prompt
s')
class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname.includes('/
admin/setting
s')
? 'bg-gray-50 dark:bg-gray-850'
? 'bg-gray-50 dark:bg-gray-850'
: ''} transition"
: ''} transition"
href="/
workspace/prompt
s">{$i18n.t('
Prompt
s')}</a
href="/
admin/setting
s">{$i18n.t('
Setting
s')}</a
>
>
<a
<!--
<a
class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname.includes('/workspace/documents')
class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname.includes('/workspace/documents')
? 'bg-gray-50 dark:bg-gray-850'
? 'bg-gray-50 dark:bg-gray-850'
: ''} transition"
: ''} transition"
...
@@ -66,9 +66,9 @@
...
@@ -66,9 +66,9 @@
? 'bg-gray-50 dark:bg-gray-850'
? 'bg-gray-50 dark:bg-gray-850'
: ''} transition"
: ''} transition"
href="/workspace/playground">{$i18n.t('Playground')}</a
href="/workspace/playground">{$i18n.t('Playground')}</a
>
>
-->
</div>
</div>
</div>
-->
</div>
<hr class=" my-2 dark:border-gray-850" />
<hr class=" my-2 dark:border-gray-850" />
...
...
src/routes/(app)/admin/+page.svelte
View file @
55dc6c1b
...
@@ -11,12 +11,8 @@
...
@@ -11,12 +11,8 @@
import { toast } from 'svelte-sonner';
import { toast } from 'svelte-sonner';
import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users';
import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users';
import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
import MenuLines from '$lib/components/icons/MenuLines.svelte';
import EditUserModal from '$lib/components/admin/EditUserModal.svelte';
import EditUserModal from '$lib/components/admin/EditUserModal.svelte';
import SettingsModal from '$lib/components/admin/SettingsModal.svelte';
import Pagination from '$lib/components/common/Pagination.svelte';
import Pagination from '$lib/components/common/Pagination.svelte';
import ChatBubbles from '$lib/components/icons/ChatBubbles.svelte';
import ChatBubbles from '$lib/components/icons/ChatBubbles.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
...
@@ -34,7 +30,6 @@
...
@@ -34,7 +30,6 @@
let page = 1;
let page = 1;
let showSettingsModal = false;
let showAddUserModal = false;
let showAddUserModal = false;
let showUserChatsModal = false;
let showUserChatsModal = false;
...
@@ -100,7 +95,6 @@
...
@@ -100,7 +95,6 @@
}}
}}
/>
/>
<UserChatsModal bind:show={showUserChatsModal} user={selectedUser} />
<UserChatsModal bind:show={showUserChatsModal} user={selectedUser} />
<SettingsModal bind:show={showSettingsModal} />
{#if loaded}
{#if loaded}
<div class="mt-0.5 mb-3 gap-1 flex flex-col md:flex-row justify-between">
<div class="mt-0.5 mb-3 gap-1 flex flex-col md:flex-row justify-between">
...
@@ -137,28 +131,6 @@
...
@@ -137,28 +131,6 @@
</svg>
</svg>
</button>
</button>
</Tooltip>
</Tooltip>
<Tooltip content={$i18n.t('Admin Settings')}>
<button
class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition font-medium text-sm flex items-center space-x-1"
on:click={() => {
showSettingsModal = !showSettingsModal;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
clip-rule="evenodd"
/>
</svg>
</button>
</Tooltip>
</div>
</div>
</div>
</div>
</div>
</div>
...
...
src/routes/(app)/admin/settings/+page.svelte
0 → 100644
View file @
55dc6c1b
<script>
import Settings from '$lib/components/admin/Settings.svelte';
</script>
<Settings />
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