Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/lib/common/spinners/Loader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@
<style>
/* Viewport-fixed frosted overlay that flex-centers the spinner so the
loader always sits in the middle of the main content area, regardless
of any positioned ancestor. The left edge tracks the vertical menu's
width so the sidebar stays uncovered */
of any positioned ancestor. By default it covers the whole viewport. */
.loader {
position: fixed;
inset: 0;
Expand All @@ -75,12 +74,16 @@
background-color: rgb(17 24 39 / 0.55);
}

/* Only the vertical layout renders a fixed sidebar, and there the left edge
tracks its width so the menu stays uncovered. Standalone routes such as
/chat/[agentId]/[conversationId] have no menu, so the overlay must span the
full viewport there instead of leaving a dead strip on the left. */
@media (min-width: 1024px) {
.loader {
:global(body:has(.vertical-menu)) .loader {
left: var(--sidebar-width);
}

:global(body.vertical-collpsed) .loader {
:global(body.vertical-collpsed:has(.vertical-menu)) .loader {
left: var(--sidebar-collapsed-width);
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/lib/helpers/enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@ const llmModelType = {
Embedding: "Embedding",
Audio: "Audio",
Realtime: "Realtime",
Web: "Web"
Web: "Web",
Live: "Live"
};
export const LlmModelType = Object.freeze(llmModelType);

Expand All @@ -246,7 +247,8 @@ const llmModelCapability = {
AudioGeneration: "AudioGeneration",
Realtime: "Realtime",
WebSearch: "WebSearch",
PdfReading: "PdfReading"
PdfReading: "PdfReading",
Live: "Live"
};
export const LlmModelCapability = Object.freeze(llmModelCapability);

Expand Down
2 changes: 1 addition & 1 deletion src/lib/helpers/types/agentTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
* @property {string?} [reasoning_effort_level]
* @property {string?} [response_format]
* @property {any} [image_composition]
* @property {any} [audio_transcription]
* @property {any} [realtime]
* @property {any} [live]
*/

/**
Expand Down
18 changes: 17 additions & 1 deletion src/lib/services/realtime-chat-service.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PUBLIC_SERVICE_URL } from "$env/static/public";
import { buildConversationUserStates } from "$lib/helpers/conversation";
import { getUserStore } from "$lib/helpers/store";
import { AudioRecordingWorklet } from "$lib/helpers/realtime/pcmProcessor";

// @ts-ignore
Expand Down Expand Up @@ -37,7 +38,7 @@ export const realtimeChat = {
start(agentId, conversationId) {
reset();
const wsUrl = buildWebsocketUrl();
socket = new WebSocket(`${wsUrl}/chat/stream/${agentId}/${conversationId}`);
socket = new WebSocket(`${wsUrl}/chat/stream/${agentId}/${conversationId}${buildUserQuery()}`);

socket.onopen = async () => {
console.log("WebSocket connected");
Expand Down Expand Up @@ -131,6 +132,21 @@ export const realtimeChat = {
};


/**
* Identifies the signed-in user on the handshake.
*
* A browser cannot set headers on a WebSocket, so no bearer token reaches /chat/stream and the
* server would otherwise see nobody for the whole conversation. It goes in the query string
* rather than as a path segment because the server reads agentId and conversationId from the END
* of the path, so an extra segment would shift both.
*
* @returns {string}
*/
function buildUserQuery() {
const user = getUserStore();
return user?.id ? `?user-id=${encodeURIComponent(user.id)}` : '';
}

function buildWebsocketUrl() {
let url = '';
const host = PUBLIC_SERVICE_URL.split('://');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
export function fetchLlmConfig() {
const chatConfig = chatConfigCmp?.fetchConfig();
const imageCompositionConfig = imageCompositionConfigCmp?.fetchConfig();
const audioTranscriptionConfig = audioTranscriptionConfigCmp?.fetchConfig();
const liveConfig = liveConfigCmp?.fetchConfig();
const realtimeConfig = realtimeConfigCmp?.fetchConfig();
return {
...chatConfig,
image_composition: imageCompositionConfig ? {...imageCompositionConfig} : null,
audio_transcription: audioTranscriptionConfig ? {...audioTranscriptionConfig} : null,
live: liveConfig ? {...liveConfig} : null,
realtime: realtimeConfig ? {...realtimeConfig} : null
};
}
Expand All @@ -35,7 +35,7 @@
/** @type {any} */
let imageCompositionConfigCmp = $state(null);
/** @type {any} */
let audioTranscriptionConfigCmp = $state(null);
let liveConfigCmp = $state(null);
/** @type {any} */
let realtimeConfigCmp = $state(null);

Expand Down Expand Up @@ -71,12 +71,12 @@
{handleAgentChange}
/>
<LlmBasicConfig
title="Audio Transcription"
bind:this={audioTranscriptionConfigCmp}
title="Live"
bind:this={liveConfigCmp}
llmConfigOptions={llmConfigs}
llmConfig={agent.llm_config?.audio_transcription}
modelType={LlmModelType.Audio}
modelCapability={LlmModelCapability.AudioTranscription}
llmConfig={agent.llm_config?.live}
modelType={LlmModelType.Live}
modelCapability={LlmModelCapability.Live}
{handleAgentChange}
/>
<RealtimeConfig
Expand Down
Loading