import { d as attr, o as derived, r as escape_html, p as ensure_array_like, e as attr_class, a8 as stringify, f as attr_style } from './dev-B6xUe35c.js';
import { o as onDestroy, g as goto } from './client-BVhtM1mG.js';
import { h as fetchEndpoints, u as setActiveEndpoint, C as voiceState, w as speakText, o as listSessions, m as getSessionMessages, c as createSession, x as startChatMessageTurn, t as sendChatMessage, q as replyChatPermission, s as replyChatQuestion, r as rejectChatQuestion, y as stopSpeaking, d as destroyVoice, A as themeService } from './theme-state.svelte-BsNoJAjT.js';
import { p as page } from './state2-boKye0ZJ.js';
import { S as Spinner } from './Spinner-Bk6e83RX.js';

//#region src/lib/format-date.ts
var TIME_FMT = new Intl.DateTimeFormat(void 0, {
	hour: "numeric",
	minute: "2-digit"
});
var DATE_TIME_FMT = new Intl.DateTimeFormat(void 0, {
	year: "numeric",
	month: "short",
	day: "numeric",
	hour: "numeric",
	minute: "2-digit"
});
/** Time of day, e.g. "3:42 PM" (locale-aware). Empty string for a falsy input. */
function formatTime(ts) {
	if (!ts) return "";
	return TIME_FMT.format(ts);
}
/**
* Absolute date + time, e.g. "Jun 7, 2026, 3:42 PM". Use for tooltips / full
* context where a relative label alone is ambiguous. Empty for a falsy input.
*/
function formatDateTime(ts) {
	if (!ts) return "";
	return DATE_TIME_FMT.format(ts);
}
/**
* Compact relative time, e.g. "just now", "5m ago", "yesterday", "3w ago".
* Empty string for a falsy input. No date-fns dependency — that would be ~30 KB
* for these few cases.
*/
function formatRelativeTime(ts) {
	if (!ts) return "";
	const diffSec = Math.max(0, (Date.now() - ts) / 1e3);
	if (diffSec < 60) return "just now";
	const min = Math.floor(diffSec / 60);
	if (min < 60) return `${min}m ago`;
	const hr = Math.floor(min / 60);
	if (hr < 24) return `${hr}h ago`;
	const day = Math.floor(hr / 24);
	if (day === 1) return "yesterday";
	if (day < 7) return `${day}d ago`;
	const wk = Math.floor(day / 7);
	if (wk < 5) return `${wk}w ago`;
	const mo = Math.floor(day / 30);
	if (mo < 12) return `${mo}mo ago`;
	return `${Math.floor(day / 365)}y ago`;
}
//#endregion
//#region src/lib/components/common/IconButton.svelte
function IconButton($$renderer, $$props) {
	/** Icon contents (an inline <svg> or spinner). */
	/** Optional visible label; when present the button grows from a square to a pill. */
	/** Render as an anchor instead of a button when a destination is given. */
	/** "On"/active look (used by ToggleButton and active states). */
	/** Colour of the selected state. */
	/** Mirrors the toggle state for assistive tech (set by ToggleButton). */
	let { icon, label, title, ariaLabel, disabled = false, href, onclick, type = "button", selected = false, tone = "primary", ariaPressed } = $$props;
	if (href) {
		$$renderer.push("<!--[0-->");
		$$renderer.push(`<a${attr_class("icon-btn svelte-vx1yib", void 0, {
			"has-label": label,
			"selected": selected,
			"tone-danger": tone === "danger"
		})}${attr("href", href)}${attr("title", title ?? ariaLabel ?? label)}${attr("aria-label", ariaLabel ?? label)}${attr("aria-disabled", disabled || void 0)}>`);
		icon($$renderer);
		$$renderer.push(`<!----> `);
		if (label) {
			$$renderer.push("<!--[0-->");
			$$renderer.push(`<span class="icon-btn-label svelte-vx1yib">${escape_html(label)}</span>`);
		} else $$renderer.push("<!--[-1-->");
		$$renderer.push(`<!--]--></a>`);
	} else {
		$$renderer.push("<!--[-1-->");
		$$renderer.push(`<button${attr_class("icon-btn svelte-vx1yib", void 0, {
			"has-label": label,
			"selected": selected,
			"tone-danger": tone === "danger"
		})}${attr("type", type)}${attr("disabled", disabled, true)}${attr("title", title ?? ariaLabel ?? label)}${attr("aria-label", ariaLabel ?? label)}${attr("aria-pressed", ariaPressed)}>`);
		icon($$renderer);
		$$renderer.push(`<!----> `);
		if (label) {
			$$renderer.push("<!--[0-->");
			$$renderer.push(`<span class="icon-btn-label svelte-vx1yib">${escape_html(label)}</span>`);
		} else $$renderer.push("<!--[-1-->");
		$$renderer.push(`<!--]--></button>`);
	}
	$$renderer.push(`<!--]-->`);
}
//#endregion
//#region src/lib/components/common/ToggleButton.svelte
function ToggleButton($$renderer, $$props) {
	/** Icon shown when off (and when on, if no selectedIcon is given). */
	/** Optional distinct icon shown when on. */
	/** Colour of the on state. */
	let { pressed, onToggle, icon, selectedIcon, label, title, ariaLabel, disabled = false, tone = "primary" } = $$props;
	IconButton($$renderer, {
		icon: pressed && selectedIcon ? selectedIcon : icon,
		label,
		title,
		ariaLabel,
		disabled,
		tone,
		selected: pressed,
		ariaPressed: pressed,
		onclick: onToggle
	});
}
//#endregion
//#region src/lib/advanced-mode-state.svelte.ts
var ADVANCED_MODE_STORAGE_KEY = "openpalm.chat.advanced";
var AdvancedModeService = class {
	enabled = false;
	initialized = false;
	init() {
		if (typeof window === "undefined" || this.initialized) return;
		this.enabled = this.#readStoredPreference();
		this.initialized = true;
	}
	setEnabled(value) {
		this.enabled = value;
		this.#writeStoredPreference(value);
	}
	toggle() {
		const next = !this.enabled;
		this.setEnabled(next);
		return next;
	}
	preferredChatPath() {
		return this.enabled ? "/advanced" : "/chat";
	}
	#readStoredPreference() {
		if (typeof window === "undefined") return false;
		try {
			return window.localStorage.getItem(ADVANCED_MODE_STORAGE_KEY) === "1";
		} catch {
			return false;
		}
	}
	#writeStoredPreference(value) {
		if (typeof window === "undefined") return;
		try {
			window.localStorage.setItem(ADVANCED_MODE_STORAGE_KEY, value ? "1" : "0");
		} catch {}
	}
};
var advancedModeService = new AdvancedModeService();
//#endregion
//#region src/lib/chat/oc-events.ts
function propStr(props, key) {
	const value = props?.[key];
	return typeof value === "string" ? value : void 0;
}
function partSnapshotType(event) {
	if (event.type !== "message.part.updated") return null;
	const part = event.properties?.part;
	if (typeof part?.id === "string" && typeof part.type === "string") return {
		partID: part.id,
		type: part.type
	};
	return null;
}
function extractTextDelta(event, sessionId, reasoningPartIds) {
	const props = event.properties ?? {};
	if (propStr(props, "sessionID") !== sessionId) return null;
	if (event.type === "session.next.text.delta") return propStr(props, "delta") ?? propStr(props, "text") ?? null;
	if (event.type === "message.part.delta") {
		const field = propStr(props, "field");
		if (field && field !== "text") return null;
		const partID = propStr(props, "partID");
		if (partID && reasoningPartIds?.has(partID)) return null;
		return propStr(props, "delta") ?? null;
	}
	return null;
}
function statusName(status) {
	if (typeof status === "string") return status;
	if (status && typeof status === "object" && typeof status.type === "string") return status.type;
}
function isTurnEnd(event, sessionId) {
	const props = event.properties ?? {};
	if (propStr(props, "sessionID") !== sessionId) return false;
	if (event.type === "session.idle") return true;
	if (event.type === "session.status") {
		const name = statusName(props.status);
		return name === "idle" || name === "completed" || name === "done";
	}
	return false;
}
function valueToText(value) {
	if (typeof value === "string") return value;
	if (value == null) return void 0;
	if (typeof value === "number" || typeof value === "boolean") return String(value);
	try {
		return JSON.stringify(value);
	} catch {
		return;
	}
}
function firstText(...values) {
	for (const value of values) {
		const text = valueToText(value);
		if (text) return text;
	}
}
function extractToolUpdate(event, sessionId) {
	const props = event.properties ?? {};
	if (propStr(props, "sessionID") !== sessionId) return null;
	const part = props.part ?? props.tool;
	if (event.type === "message.part.updated" && part && (part.type === "tool" || part.state)) {
		const state = part.state ?? {};
		return {
			callID: String(part.callID ?? part.id ?? ""),
			tool: String(part.tool ?? "tool"),
			status: String(state.status ?? "running"),
			title: typeof state.title === "string" ? state.title : void 0,
			detail: firstText(state.input, state.metadata, state.progress, state.output),
			output: valueToText(state.output),
			error: typeof state.error === "string" ? state.error : void 0
		};
	}
	if (event.type.startsWith("session.next.tool.")) {
		const type = event.type.replace("session.next.tool.", "");
		return {
			callID: propStr(props, "callID") ?? "",
			tool: propStr(props, "tool") ?? "tool",
			status: type === "completed" ? "completed" : type === "failed" ? "error" : type === "called" ? "running" : propStr(props, "status") ?? "running",
			title: propStr(props, "title") ?? propStr(props, "tool"),
			detail: firstText(props.message, props.delta, props.progress, props.input, props.metadata),
			output: firstText(props.output, props.result),
			error: firstText(props.error)
		};
	}
	return null;
}
function isSessionError(event, sessionId) {
	return event.type === "session.error" && propStr(event.properties, "sessionID") === sessionId;
}
function extractPermissionAsk(event, sessionId) {
	if (event.type !== "permission.asked") return null;
	if (propStr(event.properties, "sessionID") !== sessionId) return null;
	const id = propStr(event.properties, "id");
	if (!id) return null;
	const patterns = Array.isArray(event.properties?.patterns) ? event.properties.patterns.filter((item) => typeof item === "string") : [];
	const always = Array.isArray(event.properties?.always) ? event.properties.always.filter((item) => typeof item === "string") : [];
	const toolRecord = event.properties?.tool;
	return {
		requestID: id,
		permission: propStr(event.properties, "permission") ?? "tool",
		patterns,
		always,
		tool: propStr(toolRecord, "callID") ?? propStr(event.properties, "permission") ?? "tool",
		detail: firstText(event.properties?.metadata, event.properties?.message) ?? ""
	};
}
function extractQuestionAsk(event, sessionId) {
	if (event.type !== "question.asked") return null;
	if (propStr(event.properties, "sessionID") !== sessionId) return null;
	const id = propStr(event.properties, "id");
	if (!id) return null;
	const rawQuestions = Array.isArray(event.properties?.questions) ? event.properties.questions : [];
	const questions = [];
	for (const raw of rawQuestions) {
		const item = raw;
		const options = Array.isArray(item.options) ? item.options.map((option) => option).filter((option) => typeof option.label === "string").map((option) => ({
			label: option.label,
			description: typeof option.description === "string" ? option.description : ""
		})) : [];
		questions.push({
			question: typeof item.question === "string" ? item.question : "",
			header: typeof item.header === "string" ? item.header : "",
			options
		});
	}
	if (questions.length === 0) return null;
	return {
		requestID: id,
		questions
	};
}
function extractStepUpdate(event, sessionId) {
	const props = event.properties ?? {};
	if (propStr(props, "sessionID") !== sessionId) return null;
	if (event.type !== "session.next.step.started" && event.type !== "session.next.step.ended") return null;
	return {
		id: propStr(props, "stepID") ?? propStr(props, "id") ?? event.type,
		title: propStr(props, "title") ?? propStr(props, "step") ?? (event.type === "session.next.step.started" ? "Working" : "Step complete"),
		status: event.type === "session.next.step.ended" ? "completed" : "running",
		detail: firstText(props.detail, props.message)
	};
}
//#endregion
//#region src/lib/chat/session-events.ts
var INITIAL_BACKOFF_MS = 1e3;
var MAX_BACKOFF_MS = 3e4;
var STREAM_URL = "/proxy/assistant/event";
/**
* Parse one `\n\n`-delimited SSE frame. Multi-line `data:` fields are
* concatenated with `\n` per the SSE spec.
*/
function parseFrame(chunk) {
	const frame = {};
	const dataLines = [];
	for (const rawLine of chunk.split("\n")) {
		if (!rawLine || rawLine.startsWith(":")) continue;
		if (rawLine.startsWith("data:")) dataLines.push(rawLine.replace(/^data:\s?/, ""));
		else if (rawLine.startsWith("event:")) frame.event = rawLine.replace(/^event:\s?/, "");
		else if (rawLine.startsWith("id:")) frame.id = rawLine.replace(/^id:\s?/, "");
	}
	if (dataLines.length > 0) frame.data = dataLines.join("\n");
	return frame;
}
function dispatch(handlers, payload) {
	const info = payload.properties?.info;
	const id = info?.id;
	if (!id) return;
	switch (payload.type) {
		case "session.created":
			handlers.onCreated(id);
			return;
		case "session.updated":
			handlers.onUpdated(id, {
				title: info?.title,
				updatedAt: info?.time?.updated
			});
			return;
		case "session.deleted":
			handlers.onDeleted(id);
			return;
		default: return;
	}
}
/**
* Open an SSE connection to `/proxy/assistant/event` and dispatch
* session-scoped events. Returns an unsubscribe function that aborts the
* stream and prevents reconnection.
*/
function subscribeSessionEvents(handlers) {
	let stopped = false;
	let controller = new AbortController();
	let lastEventId;
	const sleep = (ms) => new Promise((resolve) => {
		const timer = setTimeout(resolve, ms);
		controller.signal.addEventListener("abort", () => {
			clearTimeout(timer);
			resolve();
		}, { once: true });
	});
	async function readStream() {
		const headers = { accept: "text/event-stream" };
		if (lastEventId !== void 0) headers["Last-Event-ID"] = lastEventId;
		const response = await fetch(STREAM_URL, {
			method: "GET",
			headers,
			credentials: "include",
			signal: controller.signal
		});
		if (!response.ok || !response.body) throw new Error(`SSE stream failed: ${response.status} ${response.statusText}`);
		handlers.onConnect?.();
		const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
		let buffer = "";
		try {
			while (true) {
				const { done, value } = await reader.read();
				if (done) break;
				buffer += value;
				const chunks = buffer.split("\n\n");
				buffer = chunks.pop() ?? "";
				for (const chunk of chunks) {
					if (!chunk) continue;
					const frame = parseFrame(chunk);
					if (frame.id !== void 0) lastEventId = frame.id;
					if (!frame.data) continue;
					let payload;
					try {
						payload = JSON.parse(frame.data);
					} catch (err) {
						console.warn("[session-events] Bad JSON in SSE frame", err);
						continue;
					}
					handlers.onEvent?.(payload);
					dispatch(handlers, payload);
				}
			}
		} finally {
			try {
				reader.releaseLock();
			} catch {}
		}
	}
	(async () => {
		let attempt = 0;
		while (!stopped) try {
			attempt++;
			await readStream();
			attempt = 0;
			if (!stopped) await sleep(500);
		} catch (err) {
			if (stopped) return;
			const error = err instanceof Error ? err : new Error(String(err));
			if (error.name !== "AbortError") {
				console.warn("[session-events] SSE error, reconnecting", error);
				handlers.onDisconnect?.(error);
			}
			await sleep(Math.min(INITIAL_BACKOFF_MS * 2 ** Math.max(0, attempt - 1), MAX_BACKOFF_MS));
			if (controller.signal.aborted && !stopped) controller = new AbortController();
		}
	})();
	return () => {
		stopped = true;
		try {
			controller.abort();
		} catch {}
	};
}
//#endregion
//#region src/lib/desktop-notifications.ts
var DESKTOP_NOTIFY_ENABLED_KEY = "openpalm.desktop.notify";
var DESKTOP_NOTIFY_PREVIEW_KEY = "openpalm.desktop.notify.preview";
function readFlag(key) {
	if (typeof window === "undefined") return false;
	try {
		return window.localStorage.getItem(key) === "1";
	} catch {
		return false;
	}
}
function desktopNotifyEnabled() {
	return readFlag(DESKTOP_NOTIFY_ENABLED_KEY);
}
function desktopReplyPreviewEnabled() {
	return readFlag(DESKTOP_NOTIFY_PREVIEW_KEY);
}
function shouldNotifyDesktop() {
	if (typeof document === "undefined") return false;
	if (document.hasFocus()) return false;
	if (!desktopNotifyEnabled()) return false;
	return typeof window !== "undefined" && typeof window.openpalm?.notify === "function";
}
function notifyAssistantReply(replyText) {
	if (!shouldNotifyDesktop()) return;
	const body = desktopReplyPreviewEnabled() && replyText.trim() ? replyText.trim() : "Assistant replied";
	window.openpalm?.notify?.("OpenPalm", body);
}
function notifyAssistantError() {
	if (!shouldNotifyDesktop()) return;
	window.openpalm?.notify?.("OpenPalm", "Assistant error");
}
//#endregion
//#region src/lib/chat/chat-state.svelte.ts
var STREAM_TURN_TIMEOUT_MS = 15e4;
function emptyEndpointState() {
	return {
		sessions: [],
		sessionsLoaded: false,
		sessionsLoading: false,
		sessionsError: "",
		activeSessionId: null
	};
}
var ChatService = class {
	/**
	* Per-endpoint session cache. Reassigned on every mutation so Svelte 5
	* picks up the change. Never mutate the existing Map in place.
	*/
	byEndpoint = /* @__PURE__ */ new Map();
	/**
	* Mirrored from `endpointsService.activeId` via `onEndpointChanged()`
	* so the chat layer doesn't have to import the endpoint store.
	*/
	activeEndpointId = "default";
	/** Messages for the currently rendered session only. */
	entries = [];
	entriesLoading = false;
	sending = false;
	error = "";
	pendingAssistantText = "";
	pendingToolStates = [];
	pendingPermission = null;
	pendingQuestion = null;
	/**
	* Set true while the SSE event stream is connected. Surfaced by the
	* SessionPicker as a tiny green/gray dot so the operator can see at a
	* glance whether live updates are flowing.
	*/
	liveConnected = false;
	/**
	* Active SSE subscription handle. Reassigned on every endpoint switch.
	* Plain field (not `$state`) — only the chat service touches it.
	*/
	_unsubscribeEvents = null;
	_pendingTurn = null;
	#activeSessionId = derived(() => this.byEndpoint.get(this.activeEndpointId)?.activeSessionId ?? null);
	get activeSessionId() {
		return this.#activeSessionId();
	}
	set activeSessionId($$value) {
		return this.#activeSessionId($$value);
	}
	setEndpointState(id, patch) {
		const next = {
			...this.byEndpoint.get(id) ?? emptyEndpointState(),
			...patch
		};
		this.byEndpoint = new Map(this.byEndpoint).set(id, next);
		return next;
	}
	/**
	* Handle an endpoint switch: load sessions, restore prior or pick
	* newest, fetch messages. Mid-generation switches are blocked.
	*/
	async onEndpointChanged(id) {
		if (this.sending) {
			this.error = "Wait for the current reply to finish before switching.";
			return;
		}
		this.activeEndpointId = id;
		this.entries = [];
		this.error = "";
		if (!this.byEndpoint.get(id)?.sessionsLoaded) await this.loadSessions();
		const state = this.byEndpoint.get(id) ?? emptyEndpointState();
		const sessions = state.sessions;
		const previous = state.activeSessionId;
		let nextSessionId = null;
		if (previous && sessions.some((s) => s.id === previous)) nextSessionId = previous;
		else if (sessions.length > 0) nextSessionId = sessions[0].id;
		if (nextSessionId !== state.activeSessionId) this.setEndpointState(id, { activeSessionId: nextSessionId });
		if (nextSessionId) await this.openSession(nextSessionId);
		this._resubscribeEvents();
	}
	/**
	* Tear down any prior SSE subscription and open a new one. Handlers
	* dispatch session.created / updated / deleted into the per-endpoint
	* cache, mirroring out-of-band changes (CLI, other clients).
	*/
	_resubscribeEvents() {
		if (this._unsubscribeEvents) {
			try {
				this._unsubscribeEvents();
			} catch (err) {
				console.warn("[chat] failed to unsubscribe from previous event stream", err);
			}
			this._unsubscribeEvents = null;
		}
		this.liveConnected = false;
		this._unsubscribeEvents = subscribeSessionEvents({
			onCreated: (id) => {
				this._onSessionCreated(id);
			},
			onUpdated: (id, info) => {
				this._onSessionUpdated(id, info);
			},
			onDeleted: (id) => {
				this._onSessionDeleted(id);
			},
			onEvent: (event) => {
				this._onLiveEvent(event);
			},
			onConnect: () => {
				this.liveConnected = true;
			},
			onDisconnect: () => {
				this.liveConnected = false;
				if (this._pendingTurn) this._failPendingTurn(/* @__PURE__ */ new Error("Assistant event stream disconnected."));
			}
		});
	}
	_toRawEvent(event) {
		return {
			type: event.type,
			properties: event.properties ?? {}
		};
	}
	_upsertPendingToolState(update) {
		const id = update.callID || `${update.tool}:${this.pendingToolStates.length}`;
		const next = {
			id,
			kind: "tool",
			tool: update.tool,
			status: update.status,
			title: update.title ?? update.tool,
			detail: update.detail ?? "",
			output: update.output ?? "",
			error: update.error ?? "",
			updatedAt: Date.now()
		};
		if (!this.pendingToolStates.find((item) => item.id === id)) {
			this.pendingToolStates = [...this.pendingToolStates, next];
			return;
		}
		this.pendingToolStates = this.pendingToolStates.map((item) => item.id === id ? {
			...item,
			...next
		} : item);
	}
	_upsertPendingStepState(update) {
		const next = {
			id: update.id,
			kind: "step",
			tool: "step",
			status: update.status,
			title: update.title,
			detail: update.detail ?? "",
			output: "",
			error: "",
			updatedAt: Date.now()
		};
		if (!this.pendingToolStates.find((item) => item.id === update.id)) {
			this.pendingToolStates = [...this.pendingToolStates, next];
			return;
		}
		this.pendingToolStates = this.pendingToolStates.map((item) => item.id === update.id ? {
			...item,
			...next
		} : item);
	}
	_resetPendingRenderState() {
		this.pendingAssistantText = "";
		this.pendingToolStates = [];
		this.pendingPermission = null;
		this.pendingQuestion = null;
	}
	_appendAssistantReply(text, toolStates) {
		const assistantEntry = {
			id: crypto.randomUUID(),
			role: "assistant",
			text,
			timestamp: Date.now(),
			...toolStates && toolStates.length > 0 ? { toolStates: [...toolStates] } : {}
		};
		this.entries = [...this.entries, assistantEntry];
	}
	_clearPendingTurn() {
		const pending = this._pendingTurn;
		if (!pending) return null;
		clearTimeout(pending.timeout);
		this._pendingTurn = null;
		return pending;
	}
	_finishPendingTurn(replyText) {
		const pending = this._clearPendingTurn();
		if (!pending) return;
		const text = (replyText ?? this.pendingAssistantText).trim() || "(no response)";
		if (this.pendingQuestion?.status === "answered" && this.pendingQuestion.message) {
			const questionNote = {
				id: crypto.randomUUID(),
				type: "note",
				label: "Question answered",
				text: this.pendingQuestion.message,
				timestamp: Date.now()
			};
			this.entries = [...this.entries, questionNote];
		}
		const capturedToolStates = this.pendingToolStates.length > 0 ? [...this.pendingToolStates] : void 0;
		this._appendAssistantReply(text, capturedToolStates);
		this._resetPendingRenderState();
		this._bumpSession(pending.sessionId);
		if (voiceState.ttsSupported && voiceState.ttsAutoEnabled && text && text !== "(no response)") speakText(text, {
			mode: "chat_reply",
			userText: pending.userText,
			assistantText: text
		});
		notifyAssistantReply(text === "(no response)" ? "" : text);
		pending.resolve();
	}
	_failPendingTurn(error) {
		const pending = this._clearPendingTurn();
		if (!pending) return;
		this._resetPendingRenderState();
		pending.reject(error);
	}
	_bumpSession(sessionId) {
		const endpointId = this.activeEndpointId;
		const prev = this.byEndpoint.get(endpointId);
		if (!prev) return;
		const now = Date.now();
		const existing = prev.sessions.find((s) => s.id === sessionId);
		const updated = existing ? {
			...existing,
			updatedAt: now
		} : {
			id: sessionId,
			title: "",
			createdAt: now,
			updatedAt: now
		};
		const rest = prev.sessions.filter((s) => s.id !== sessionId);
		this.setEndpointState(endpointId, { sessions: [updated, ...rest] });
	}
	_onLiveEvent(event) {
		const pending = this._pendingTurn;
		if (!pending) return;
		const raw = this._toRawEvent(event);
		if (pending.endpointId !== this.activeEndpointId) return;
		const snapshot = partSnapshotType(raw);
		if (snapshot?.type === "reasoning") pending.reasoningPartIds.add(snapshot.partID);
		const textDelta = extractTextDelta(raw, pending.sessionId, pending.reasoningPartIds);
		if (textDelta) this.pendingAssistantText += textDelta;
		const toolUpdate = extractToolUpdate(raw, pending.sessionId);
		if (toolUpdate) this._upsertPendingToolState(toolUpdate);
		const stepUpdate = extractStepUpdate(raw, pending.sessionId);
		if (stepUpdate) this._upsertPendingStepState(stepUpdate);
		const permissionAsk = extractPermissionAsk(raw, pending.sessionId);
		if (permissionAsk) this.pendingPermission = {
			...permissionAsk,
			status: "pending",
			decision: "",
			message: ""
		};
		const questionAsk = extractQuestionAsk(raw, pending.sessionId);
		if (questionAsk) this.pendingQuestion = {
			...questionAsk,
			status: "pending",
			answers: questionAsk.questions.map(() => ""),
			message: ""
		};
		if (isSessionError(raw, pending.sessionId)) {
			this._failPendingTurn(/* @__PURE__ */ new Error("Assistant session ended unexpectedly."));
			return;
		}
		if (isTurnEnd(raw, pending.sessionId)) this._finishPendingTurn();
	}
	/**
	* A session was created out-of-band — prepend to the active endpoint's
	* list if not already known. Do not auto-switch to it: the user owns
	* navigation.
	*/
	_onSessionCreated(sessionId) {
		const endpointId = this.activeEndpointId;
		const prev = this.byEndpoint.get(endpointId) ?? emptyEndpointState();
		if (prev.sessions.some((s) => s.id === sessionId)) return;
		const now = Date.now();
		const summary = {
			id: sessionId,
			title: "",
			createdAt: now,
			updatedAt: now
		};
		this.setEndpointState(endpointId, {
			sessions: [summary, ...prev.sessions],
			sessionsLoaded: true
		});
	}
	/**
	* A session was touched out-of-band — patch its updatedAt (and title if
	* the event carries one) and re-sort. Do NOT refetch messages: if the
	* user is viewing this session, leave the in-memory entries alone for
	* v1. A follow-up phase can reconcile message deltas via the assistant
	* event stream.
	*/
	_onSessionUpdated(sessionId, info) {
		const endpointId = this.activeEndpointId;
		const prev = this.byEndpoint.get(endpointId);
		if (!prev) return;
		const idx = prev.sessions.findIndex((s) => s.id === sessionId);
		if (idx === -1) return;
		const existing = prev.sessions[idx];
		const sessions = [{
			...existing,
			title: info?.title ?? existing.title,
			updatedAt: info?.updatedAt ?? Date.now()
		}, ...prev.sessions.filter((s) => s.id !== sessionId)];
		sessions.sort((a, b) => b.updatedAt - a.updatedAt);
		this.setEndpointState(endpointId, { sessions });
	}
	/**
	* A session was deleted out-of-band. Remove it from the list; if it was
	* the active session, fall back to the newest remaining session (or
	* null) and reload its messages.
	*/
	async _onSessionDeleted(sessionId) {
		const endpointId = this.activeEndpointId;
		const prev = this.byEndpoint.get(endpointId);
		if (!prev) return;
		if (!prev.sessions.some((s) => s.id === sessionId)) return;
		const sessions = prev.sessions.filter((s) => s.id !== sessionId);
		const wasActive = prev.activeSessionId === sessionId;
		const nextActive = wasActive ? sessions[0]?.id ?? null : prev.activeSessionId;
		this.setEndpointState(endpointId, {
			sessions,
			activeSessionId: nextActive
		});
		if (wasActive) {
			this.entries = [];
			if (nextActive) await this.openSession(nextActive);
		}
	}
	/** Fetch the session list for the active endpoint. */
	async loadSessions() {
		const id = this.activeEndpointId;
		this.setEndpointState(id, {
			sessionsLoading: true,
			sessionsError: ""
		});
		try {
			const sessions = await listSessions();
			this.setEndpointState(id, {
				sessions,
				sessionsLoaded: true,
				sessionsLoading: false,
				sessionsError: ""
			});
		} catch (e) {
			const err = e;
			const message = err.status === 503 || err.status === 502 ? "Assistant is not reachable." : err.message ?? "Failed to load sessions.";
			this.setEndpointState(id, {
				sessionsLoading: false,
				sessionsError: message
			});
		}
	}
	/** Select a session and render its messages. */
	async openSession(sessionId) {
		if (this.sending) {
			this.error = "Wait for the current reply to finish before switching.";
			return;
		}
		const endpointId = this.activeEndpointId;
		this.setEndpointState(endpointId, { activeSessionId: sessionId });
		this.entries = [];
		this._resetPendingRenderState();
		this.entriesLoading = true;
		this.error = "";
		try {
			const messages = await getSessionMessages(sessionId);
			if (this.activeEndpointId === endpointId && this.byEndpoint.get(endpointId)?.activeSessionId === sessionId) this.entries = messages;
		} catch (e) {
			const err = e;
			if (err.status === 503 || err.status === 502) this.error = "Assistant is not reachable. Try reconnecting.";
			else if (err.status === 401) this.error = "Sign-in required.";
			else this.error = err.message ?? "Failed to load messages.";
		} finally {
			this.entriesLoading = false;
		}
	}
	/** Create a new session on the active endpoint and select it. */
	async startNewSession() {
		if (this.sending) {
			this.error = "Wait for the current reply to finish before switching.";
			return null;
		}
		const endpointId = this.activeEndpointId;
		this.error = "";
		try {
			const { id } = await createSession();
			const now = Date.now();
			const summary = {
				id,
				title: "",
				createdAt: now,
				updatedAt: now
			};
			const prev = this.byEndpoint.get(endpointId) ?? emptyEndpointState();
			this.setEndpointState(endpointId, {
				sessions: [summary, ...prev.sessions.filter((s) => s.id !== id)],
				sessionsLoaded: true,
				activeSessionId: id
			});
			this.entries = [];
			this._resetPendingRenderState();
			return id;
		} catch (e) {
			const err = e;
			this.error = `Failed to start session: ${err.message ?? "unknown error"}`;
			return null;
		}
	}
	/**
	* Send a message in the active session. If none is active, create one
	* first (matches the "zero sessions" empty-state flow).
	*/
	async send(text) {
		const trimmed = text.trim();
		if (!trimmed) return;
		if (this.pendingQuestion && this.pendingQuestion.questions.length === 1 && this.sending) {
			await this.answerQuestion(trimmed);
			return;
		}
		if (this.pendingQuestion && this.sending) {
			this.pendingQuestion = {
				...this.pendingQuestion,
				status: "error",
				message: "This question has multiple parts and must be answered with the provided controls."
			};
			return;
		}
		if (this.sending) return;
		let sessionId = this.activeSessionId;
		if (!sessionId) {
			sessionId = await this.startNewSession();
			if (!sessionId) return;
		}
		const userEntry = {
			id: crypto.randomUUID(),
			role: "user",
			text: trimmed,
			timestamp: Date.now()
		};
		this.entries = [...this.entries, userEntry];
		this._resetPendingRenderState();
		this.error = "";
		this.sending = true;
		if (voiceState.ttsSupported && voiceState.ttsAutoEnabled) speakText("Working on it.", {
			mode: "chat_ack",
			userText: trimmed
		});
		try {
			if (this._unsubscribeEvents && this.liveConnected) await new Promise((resolve, reject) => {
				const timeout = setTimeout(() => {
					this._failPendingTurn(/* @__PURE__ */ new Error("Timed out waiting for the assistant response."));
				}, STREAM_TURN_TIMEOUT_MS);
				this._pendingTurn = {
					endpointId: this.activeEndpointId,
					sessionId,
					userText: trimmed,
					reasoningPartIds: /* @__PURE__ */ new Set(),
					resolve,
					reject,
					timeout
				};
				startChatMessageTurn(sessionId, trimmed).catch((error) => {
					this._failPendingTurn(error instanceof Error ? error : new Error(String(error)));
				});
			});
			else {
				const text = (await sendChatMessage(sessionId, trimmed)).parts.filter((p) => p.type === "text" && p.text).map((p) => p.text ?? "").join("").trim() || "(no response)";
				const capturedTools = this.pendingToolStates.length > 0 ? [...this.pendingToolStates] : void 0;
				this._appendAssistantReply(text, capturedTools);
				this._resetPendingRenderState();
				this._bumpSession(sessionId);
				if (voiceState.ttsSupported && voiceState.ttsAutoEnabled && text !== "(no response)") speakText(text, {
					mode: "chat_reply",
					userText: trimmed,
					assistantText: text
				});
				notifyAssistantReply(text === "(no response)" ? "" : text);
			}
		} catch (e) {
			const err = e;
			this._resetPendingRenderState();
			if (err.status === 503 || err.status === 502) {
				this.error = "Assistant is not reachable. Try reconnecting.";
				this.setEndpointState(this.activeEndpointId, { activeSessionId: null });
			} else if (err.status === 401) this.error = "Sign-in required.";
			else this.error = err.message ?? "Message failed.";
			notifyAssistantError();
		} finally {
			this.sending = false;
		}
	}
	async answerPermission(reply) {
		if (!this.pendingPermission || this.pendingPermission.status === "submitting") return;
		const current = this.pendingPermission;
		this.pendingPermission = {
			...current,
			status: "submitting",
			decision: reply,
			message: ""
		};
		try {
			await replyChatPermission(current.requestID, reply);
			this.pendingPermission = {
				...current,
				status: "resolved",
				decision: reply,
				message: reply === "once" ? `Allowed ${current.permission} once. Waiting for the assistant to continue...` : reply === "always" ? `Always allowed future matching ${current.permission} requests.` : `Denied ${current.permission}. Waiting for the assistant to continue...`
			};
		} catch (error) {
			const message = error instanceof Error ? error.message : "Failed to record permission reply.";
			this.pendingPermission = {
				...current,
				status: "error",
				decision: reply,
				message
			};
		}
	}
	setQuestionAnswer(index, answer) {
		if (!this.pendingQuestion) return;
		if (index < 0 || index >= this.pendingQuestion.questions.length) return;
		const answers = [...this.pendingQuestion.answers];
		answers[index] = answer;
		this.pendingQuestion = {
			...this.pendingQuestion,
			answers,
			message: ""
		};
	}
	async answerQuestion(answer) {
		if (!this.pendingQuestion || this.pendingQuestion.status === "submitting") return;
		const current = this.pendingQuestion;
		const answers = current.questions.length === 1 && typeof answer === "string" ? [answer.trim()] : current.answers.map((item) => item.trim());
		if (answers.some((item) => !item)) {
			this.pendingQuestion = {
				...current,
				status: "error",
				message: "Answer every question before submitting."
			};
			return;
		}
		this.pendingQuestion = {
			...current,
			status: "submitting",
			answers,
			message: ""
		};
		try {
			await replyChatQuestion(current.requestID, answers.map((item) => [item]));
			this.pendingQuestion = {
				...current,
				status: "answered",
				answers,
				message: "Answer sent."
			};
		} catch (error) {
			const message = error instanceof Error ? error.message : "Failed to send answer.";
			this.pendingQuestion = {
				...current,
				status: "error",
				answers,
				message
			};
		}
	}
	async rejectQuestion() {
		if (!this.pendingQuestion || this.pendingQuestion.status === "submitting") return;
		const current = this.pendingQuestion;
		this.pendingQuestion = {
			...current,
			status: "submitting",
			message: ""
		};
		try {
			await rejectChatQuestion(current.requestID);
			this.pendingQuestion = {
				...current,
				status: "rejected",
				message: "Question declined."
			};
		} catch (error) {
			const message = error instanceof Error ? error.message : "Failed to reject question.";
			this.pendingQuestion = {
				...current,
				status: "error",
				message
			};
		}
	}
	reset() {
		stopSpeaking();
		this._clearPendingTurn();
		this.entries = [];
		this.error = "";
		this._resetPendingRenderState();
		this.byEndpoint = /* @__PURE__ */ new Map();
		if (this._unsubscribeEvents) {
			try {
				this._unsubscribeEvents();
			} catch (err) {
				console.warn("[chat] failed to unsubscribe from event stream during reset", err);
			}
			this._unsubscribeEvents = null;
		}
		this.liveConnected = false;
	}
};
var chat = new ChatService();
//#endregion
//#region src/lib/chat/navigation.ts
function currentChatSessionId() {
	return chat.activeSessionId;
}
function buildAdvancedPath(sessionId) {
	const params = new URLSearchParams();
	if (sessionId) params.set("session", sessionId);
	const query = params.toString();
	return query ? `/advanced?${query}` : "/advanced";
}
function buildChatPath(sessionId, options) {
	const params = new URLSearchParams();
	if (sessionId) params.set("session", sessionId);
	const query = params.toString();
	return query ? `/chat?${query}` : "/chat";
}
//#endregion
//#region src/lib/components/chrome/ModeSwitch.svelte
function advancedIcon($$renderer) {
	$$renderer.push(`<svg aria-hidden="true" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><path d="m7 9 3 3-3 3"></path><line x1="13" y1="15" x2="17" y2="15"></line></svg>`);
}
function ModeSwitch($$renderer, $$props) {
	$$renderer.component(($$renderer) => {
		const pathname = derived(() => page.url?.pathname ?? "");
		const onAdmin = derived(() => pathname() === "/admin" || pathname().startsWith("/admin/"));
		function toggle() {
			const enabled = advancedModeService.toggle();
			if (onAdmin()) return;
			const sessionId = page.url.searchParams.get("session") ?? currentChatSessionId();
			goto(enabled ? buildAdvancedPath(sessionId) : buildChatPath(sessionId));
		}
		ToggleButton($$renderer, {
			pressed: advancedModeService.enabled,
			onToggle: toggle,
			ariaLabel: "Advanced mode",
			title: "Advanced mode (embedded OpenCode)",
			icon: advancedIcon
		});
	});
}
//#endregion
//#region src/lib/components/common/ThemeToggle.svelte
function currentIcon($$renderer) {
	if (themeService.preference === "system") {
		$$renderer.push("<!--[0-->");
		$$renderer.push(`<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"></rect><path d="M8 21h8"></path><path d="M12 17v4"></path></svg>`);
	} else if (themeService.preference === "light") {
		$$renderer.push("<!--[1-->");
		$$renderer.push(`<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2"></path><path d="M12 20v2"></path><path d="m4.93 4.93 1.41 1.41"></path><path d="m17.66 17.66 1.41 1.41"></path><path d="M2 12h2"></path><path d="M20 12h2"></path><path d="m6.34 17.66-1.41 1.41"></path><path d="m19.07 4.93-1.41 1.41"></path></svg>`);
	} else {
		$$renderer.push("<!--[-1-->");
		$$renderer.push(`<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9z"></path></svg>`);
	}
	$$renderer.push(`<!--]-->`);
}
function ThemeToggle($$renderer, $$props) {
	$$renderer.component(($$renderer) => {
		const CYCLE = [
			"system",
			"light",
			"dark"
		];
		function cycle() {
			const current = themeService.preference;
			const next = CYCLE[(CYCLE.indexOf(current) + 1) % CYCLE.length];
			themeService.setPreference(next);
		}
		const label = derived(() => themeService.preference === "system" ? "Theme: system (click for light)" : themeService.preference === "light" ? "Theme: light (click for dark)" : "Theme: dark (click for system)");
		IconButton($$renderer, {
			icon: currentIcon,
			ariaLabel: label(),
			title: label(),
			onclick: cycle
		});
	});
}
//#endregion
//#region src/lib/endpoints-state.svelte.ts
var EndpointsService = class {
	endpoints = [];
	activeId = "default";
	loading = false;
	loaded = false;
	error = "";
	#active = derived(() => this.endpoints.find((e) => e.id === this.activeId) ?? this.endpoints[0] ?? null);
	get active() {
		return this.#active();
	}
	set active($$value) {
		return this.#active($$value);
	}
	async load(force = false) {
		if (this.loading) return;
		if (this.loaded && !force) return;
		this.loading = true;
		this.error = "";
		try {
			const { endpoints, activeId } = await fetchEndpoints();
			this.endpoints = endpoints;
			this.activeId = activeId;
			this.loaded = true;
		} catch (e) {
			const err = e;
			if (err.status !== 401) this.error = err.message ?? "Failed to load endpoints";
		} finally {
			this.loading = false;
		}
	}
	async activate(id) {
		if (id === this.activeId) return;
		if (chat.sending) {
			this.error = "Wait for the current reply to finish before switching.";
			throw new Error(this.error);
		}
		const previous = this.activeId;
		this.activeId = id;
		try {
			await setActiveEndpoint(id);
			await chat.onEndpointChanged(id);
		} catch (e) {
			this.activeId = previous;
			const err = e;
			this.error = err.message ?? "Failed to switch endpoint";
			throw e;
		}
	}
};
var endpointsService = new EndpointsService();
//#endregion
//#region src/lib/components/common/Drawer.svelte
function Drawer($$renderer, $$props) {
	$$renderer.component(($$renderer) => {
		/** Optional content rendered at the start of the header (e.g. a back button). */
		/** Drawer width (CSS length). */
		let { open, title, onClose, children, footer, headerStart, width = "32rem" } = $$props;
		if (open) {
			$$renderer.push("<!--[0-->");
			$$renderer.push(`<div class="drawer-scrim svelte-1u2o1qj"></div> <div class="drawer svelte-1u2o1qj"${attr_style(`--drawer-width: ${stringify(width)}`)} role="dialog" aria-modal="true"${attr("aria-label", title)} tabindex="-1"><header class="drawer-header svelte-1u2o1qj">`);
			if (headerStart) {
				$$renderer.push("<!--[0-->");
				headerStart($$renderer);
				$$renderer.push(`<!---->`);
			} else $$renderer.push("<!--[-1-->");
			$$renderer.push(`<!--]--> <h3 class="drawer-title svelte-1u2o1qj">${escape_html(title)}</h3> <button class="drawer-close svelte-1u2o1qj" aria-label="Close"><svg aria-hidden="true" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg></button></header> <div class="drawer-body svelte-1u2o1qj">`);
			children($$renderer);
			$$renderer.push(`<!----></div> `);
			if (footer) {
				$$renderer.push("<!--[0-->");
				$$renderer.push(`<footer class="drawer-footer svelte-1u2o1qj">`);
				footer($$renderer);
				$$renderer.push(`<!----></footer>`);
			} else $$renderer.push("<!--[-1-->");
			$$renderer.push(`<!--]--></div>`);
		} else $$renderer.push("<!--[-1-->");
		$$renderer.push(`<!--]-->`);
	});
}
//#endregion
//#region src/lib/assistant-endpoint.ts
function isLocalAssistantUrl(url) {
	if (!url) return true;
	try {
		const host = new URL(url).hostname;
		return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "host.docker.internal";
	} catch {
		return true;
	}
}
//#endregion
//#region src/lib/components/chat/EndpointList.svelte
function EndpointList($$renderer, $$props) {
	$$renderer.component(($$renderer) => {
		/** Called after the active endpoint changes (e.g. to close the drawer). */
		let { onChosen } = $$props;
		const active = derived(() => endpointsService.active);
		const endpoints = derived(() => endpointsService.endpoints);
		const showManageAssistant = derived(() => isLocalAssistantUrl(active()?.url));
		let switching = false;
		$$renderer.push(`<div class="endpoint-list svelte-dlw00e">`);
		if (endpointsService.error) {
			$$renderer.push("<!--[0-->");
			$$renderer.push(`<p class="list-error svelte-dlw00e" role="alert">${escape_html(endpointsService.error)}</p>`);
		} else $$renderer.push("<!--[-1-->");
		$$renderer.push(`<!--]--> <div class="group" role="group" aria-label="Assistant endpoints"><!--[-->`);
		const each_array = ensure_array_like(endpoints());
		for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
			let ep = each_array[$$index];
			$$renderer.push(`<button type="button"${attr_class("list-item svelte-dlw00e", void 0, { "active": ep.id === active()?.id })}${attr("aria-current", ep.id === active()?.id ? "true" : void 0)}${attr("disabled", switching, true)}><span class="item-text svelte-dlw00e"><span class="item-label svelte-dlw00e">${escape_html(ep.label)}`);
			if (ep.id === active()?.id) {
				$$renderer.push("<!--[0-->");
				$$renderer.push(`<span class="sr-only svelte-dlw00e">(current)</span>`);
			} else $$renderer.push("<!--[-1-->");
			$$renderer.push(`<!--]--></span> <span class="item-url svelte-dlw00e">${escape_html(ep.url)}</span></span></button>`);
		}
		$$renderer.push(`<!--]--></div> <div class="divider svelte-dlw00e"></div> `);
		if (showManageAssistant()) {
			$$renderer.push("<!--[0-->");
			$$renderer.push(`<a class="list-item link svelte-dlw00e" href="/admin">Manage this assistant…</a>`);
		} else $$renderer.push("<!--[-1-->");
		$$renderer.push(`<!--]--> <a class="list-item link svelte-dlw00e" href="/admin/endpoints">Manage assistant connections…</a></div>`);
	});
}
//#endregion
//#region src/lib/components/chat/EndpointSwitcher.svelte
function EndpointSwitcher($$renderer, $$props) {
	$$renderer.component(($$renderer) => {
		let open = false;
		const active = derived(() => endpointsService.active);
		$$renderer.push(`<button type="button" class="trigger svelte-48xr05" aria-haspopup="dialog"${attr("aria-expanded", open)}${attr("aria-label", active() ? `Assistant endpoint: ${active().label}` : "Assistant endpoints")}${attr("title", active() ? `Connected to: ${active().label} (${active().url})` : "Assistant endpoints")}${attr("disabled", endpointsService.loading, true)}><svg class="trigger-icon svelte-48xr05" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect><rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect><line x1="6" y1="6" x2="6.01" y2="6"></line><line x1="6" y1="18" x2="6.01" y2="18"></line></svg> <span class="dot svelte-48xr05" aria-hidden="true"></span> <span class="label svelte-48xr05">${escape_html(active()?.label ?? "Endpoint…")}</span> <span class="caret svelte-48xr05" aria-hidden="true">▾</span></button> `);
		Drawer($$renderer, {
			open,
			title: "Assistant endpoint",
			onClose: () => open = false,
			width: "26rem",
			children: ($$renderer) => {
				EndpointList($$renderer, { onChosen: () => open = false });
			}});
		$$renderer.push(`<!---->`);
	});
}
//#endregion
//#region src/lib/session-title.ts
var DEFAULT_TITLE_RE = /^New session - (\d{4}-\d{2}-\d{2}T[0-9:.]+Z?)$/;
/** Resolve a session's display title. */
function resolveSessionTitle(title) {
	const trimmed = (title ?? "").trim();
	if (!trimmed) return "Untitled session";
	const match = DEFAULT_TITLE_RE.exec(trimmed);
	if (match) {
		const ts = Date.parse(match[1]);
		if (!Number.isNaN(ts)) return formatDateTime(ts);
	}
	return trimmed;
}
//#endregion
//#region src/lib/components/chat/SessionTitle.svelte
function SessionTitle($$renderer, $$props) {
	$$renderer.component(($$renderer) => {
		let { title } = $$props;
		const display = derived(() => resolveSessionTitle(title));
		$$renderer.push(`<!---->${escape_html(display())}`);
	});
}
//#endregion
//#region src/lib/components/chat/SessionList.svelte
function SessionList($$renderer, $$props) {
	$$renderer.component(($$renderer) => {
		/** Called after a session is opened or started (e.g. to close the drawer). */
		let { onChosen } = $$props;
		const SESSION_LIST_CAP = 50;
		const active = derived(() => endpointsService.active);
		const endpointState = derived(() => active() ? chat.byEndpoint.get(active().id) ?? null : null);
		const sessions = derived(() => endpointState()?.sessions ?? []);
		const loading = derived(() => endpointState()?.sessionsLoading ?? false);
		const error = derived(() => endpointState()?.sessionsError ?? "");
		const activeSessionId = derived(() => chat.activeSessionId);
		const visibleSessions = derived(() => sessions().slice(0, SESSION_LIST_CAP));
		const overflowCount = derived(() => Math.max(0, sessions().length - SESSION_LIST_CAP));
		$$renderer.push(`<div class="session-body svelte-1o6rxhb">`);
		if (chat.sending) {
			$$renderer.push("<!--[0-->");
			$$renderer.push(`<div class="notice svelte-1o6rxhb">Wait for the current reply to finish before switching.</div>`);
		} else $$renderer.push("<!--[-1-->");
		$$renderer.push(`<!--]--> `);
		if (loading()) {
			$$renderer.push("<!--[0-->");
			$$renderer.push(`<div class="empty svelte-1o6rxhb">`);
			Spinner($$renderer, { size: 12 });
			$$renderer.push(`<!----><span>Loading sessions…</span></div>`);
		} else if (error()) {
			$$renderer.push("<!--[1-->");
			$$renderer.push(`<div class="list-error svelte-1o6rxhb"><span class="svelte-1o6rxhb">${escape_html(error())}</span> <button type="button" class="retry-btn svelte-1o6rxhb">Retry</button></div>`);
		} else if (sessions().length === 0) {
			$$renderer.push("<!--[2-->");
			$$renderer.push(`<div class="empty svelte-1o6rxhb">No sessions yet. Start the first one.</div>`);
		} else {
			$$renderer.push("<!--[-1-->");
			$$renderer.push(`<div class="session-list svelte-1o6rxhb" role="group" aria-label="Sessions"><!--[-->`);
			const each_array = ensure_array_like(visibleSessions());
			for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
				let s = each_array[$$index];
				$$renderer.push(`<button type="button"${attr_class("list-item session-item svelte-1o6rxhb", void 0, { "active": s.id === activeSessionId() })}${attr("aria-current", s.id === activeSessionId() ? "true" : void 0)}${attr("disabled", chat.sending, true)}${attr("title", formatDateTime(s.updatedAt))}><span class="item-text svelte-1o6rxhb"><span class="item-label svelte-1o6rxhb">`);
				SessionTitle($$renderer, { title: s.title });
				$$renderer.push(`<!---->`);
				if (s.id === activeSessionId()) {
					$$renderer.push("<!--[0-->");
					$$renderer.push(`<span class="sr-only svelte-1o6rxhb">(current)</span>`);
				} else $$renderer.push("<!--[-1-->");
				$$renderer.push(`<!--]--></span> <span class="item-meta svelte-1o6rxhb">${escape_html(formatRelativeTime(s.updatedAt))}</span></span></button>`);
			}
			$$renderer.push(`<!--]--></div> `);
			if (overflowCount() > 0) {
				$$renderer.push("<!--[0-->");
				$$renderer.push(`<button type="button" class="show-all svelte-1o6rxhb">Show all (${escape_html(overflowCount())} more)</button>`);
			} else $$renderer.push("<!--[-1-->");
			$$renderer.push(`<!--]-->`);
		}
		$$renderer.push(`<!--]--> <div class="divider svelte-1o6rxhb"></div> <button type="button" class="list-item new-btn svelte-1o6rxhb"${attr("disabled", chat.sending, true)}><span class="check svelte-1o6rxhb" aria-hidden="true">+</span> <span class="item-text svelte-1o6rxhb"><span class="item-label svelte-1o6rxhb">New session</span></span></button></div>`);
	});
}
//#endregion
//#region src/lib/components/chat/SessionPicker.svelte
function SessionPicker($$renderer, $$props) {
	$$renderer.component(($$renderer) => {
		let open = false;
		const active = derived(() => endpointsService.active);
		const endpointState = derived(() => active() ? chat.byEndpoint.get(active().id) ?? null : null);
		const sessions = derived(() => endpointState()?.sessions ?? []);
		const activeSessionId = derived(() => chat.activeSessionId);
		const liveConnected = derived(() => chat.liveConnected);
		const activeSummary = derived(() => sessions().find((s) => s.id === activeSessionId()) ?? null);
		const triggerLabel = derived(() => activeSummary() ? resolveSessionTitle(activeSummary().title) : "New session");
		$$renderer.push(`<button type="button" class="trigger svelte-cihcnl" aria-haspopup="dialog"${attr("aria-expanded", open)} aria-label="Sessions"${attr("title", triggerLabel())}><svg class="trigger-icon svelte-cihcnl" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2z"></path><path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"></path></svg> <span${attr_class("dot svelte-cihcnl", void 0, { "connected": liveConnected() })} aria-hidden="true"${attr("title", liveConnected() ? "Live updates connected" : "Live updates disconnected")}></span> <span class="label svelte-cihcnl">${escape_html(triggerLabel())}</span> <span class="caret svelte-cihcnl" aria-hidden="true">▾</span></button> `);
		Drawer($$renderer, {
			open,
			title: `Sessions on ${stringify(active()?.label ?? "this endpoint")}`,
			onClose: () => open = false,
			width: "26rem",
			children: ($$renderer) => {
				SessionList($$renderer, { onChosen: () => open = false });
			}});
		$$renderer.push(`<!---->`);
	});
}
//#endregion
//#region src/lib/components/chat/VoiceControl.svelte
function VoiceControl($$renderer, $$props) {
	$$renderer.component(($$renderer) => {
		const MAX_INTERIM_CHARS = 48;
		let mounted = false;
		let removeGlobalMicToggle = null;
		onDestroy(() => {
			if (typeof window !== "undefined") window.openpalm?.setTrayMicRecording?.(false);
			removeGlobalMicToggle?.();
			removeGlobalMicToggle = null;
			destroyVoice();
		});
		let supported = derived(() => mounted);
		let ttsAvailable = derived(() => mounted);
		let isRecording = derived(() => voiceState.status === "recording");
		let isTranscribing = derived(() => voiceState.status === "transcribing");
		let isProcessing = derived(() => !isRecording() && !isTranscribing() && chat.sending);
		let isSpeaking = derived(() => voiceState.status === "speaking");
		$$renderer.push(`<div class="voice-control svelte-7elv0w" role="toolbar" aria-label="Voice controls">`);
		if (isRecording() && voiceState.interimTranscript) {
			$$renderer.push("<!--[0-->");
			$$renderer.push(`<span class="voice-interim svelte-7elv0w" aria-hidden="true"${attr("title", voiceState.interimTranscript)}>${escape_html(voiceState.interimTranscript.length > MAX_INTERIM_CHARS ? voiceState.interimTranscript.slice(0, MAX_INTERIM_CHARS) + "…" : voiceState.interimTranscript)}</span>`);
		} else $$renderer.push("<!--[-1-->");
		$$renderer.push(`<!--]--> `);
		if (voiceState.autoplayBlocked) {
			$$renderer.push("<!--[0-->");
			$$renderer.push(`<button type="button" class="voice-autoplay-banner svelte-7elv0w" aria-label="Resume paused audio" title="Audio was blocked by the browser. Click to resume."><svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svelte-7elv0w"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" class="svelte-7elv0w"></polygon><line x1="23" y1="9" x2="17" y2="15" class="svelte-7elv0w"></line><line x1="17" y1="9" x2="23" y2="15" class="svelte-7elv0w"></line></svg> <span class="svelte-7elv0w">Audio paused — click to resume</span></button>`);
		} else $$renderer.push("<!--[-1-->");
		$$renderer.push(`<!--]--> `);
		if (ttsAvailable()) {
			$$renderer.push("<!--[0-->");
			$$renderer.push(`<button${attr_class("voice-btn svelte-7elv0w", void 0, {
				"voice-btn-on": voiceState.ttsAutoEnabled,
				"voice-btn-speaking": isSpeaking()
			})}${attr("aria-label", voiceState.ttsAutoEnabled ? "Turn off spoken responses" : "Turn on spoken responses")}${attr("aria-pressed", voiceState.ttsAutoEnabled)}${attr("title", isSpeaking() ? "Speaking — click to turn off spoken responses" : voiceState.ttsAutoEnabled ? "Spoken responses are on — click to turn off" : "Spoken responses are off — click to turn on")}><svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svelte-7elv0w"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" class="svelte-7elv0w"></polygon>`);
			if (voiceState.ttsAutoEnabled) {
				$$renderer.push("<!--[0-->");
				$$renderer.push(`<path d="M15.54 8.46a5 5 0 0 1 0 7.07"${attr_class("svelte-7elv0w", void 0, { "wave-anim": isSpeaking() })}></path><path d="M19.07 4.93a10 10 0 0 1 0 14.14"${attr_class("svelte-7elv0w", void 0, { "wave-anim-2": isSpeaking() })}></path>`);
			} else {
				$$renderer.push("<!--[-1-->");
				$$renderer.push(`<line x1="23" y1="9" x2="17" y2="15" class="svelte-7elv0w"></line><line x1="17" y1="9" x2="23" y2="15" class="svelte-7elv0w"></line>`);
			}
			$$renderer.push(`<!--]--></svg></button>`);
		} else $$renderer.push("<!--[-1-->");
		$$renderer.push(`<!--]--> <button${attr_class("voice-btn svelte-7elv0w", void 0, {
			"voice-btn-active": isRecording(),
			"voice-btn-processing": isProcessing() || isTranscribing(),
			"voice-btn-disabled": !supported()
		})}${attr("disabled", !supported() || isProcessing(), true)}${attr("aria-label", !supported() ? "Voice input unavailable" : isRecording() ? "Stop recording" : isTranscribing() ? "Transcribing…" : isProcessing() ? "Sending message…" : "Start recording")}${attr("aria-pressed", isRecording())}${attr("title", !supported() ? "Voice input is unavailable — no speech-to-text engine is configured for this browser" : isRecording() ? "Stop recording" : isTranscribing() ? "Transcribing…" : isProcessing() ? "Sending message…" : "Speak — message will be sent to the selected assistant")}>`);
		if (!supported()) {
			$$renderer.push("<!--[0-->");
			$$renderer.push(`<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svelte-7elv0w"><line x1="1" y1="1" x2="23" y2="23" class="svelte-7elv0w"></line><path d="M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6" class="svelte-7elv0w"></path><path d="M17 16.95A7 7 0 0 1 5 12v-2m14 0v2a7 7 0 0 1-.11 1.23" class="svelte-7elv0w"></path><line x1="12" y1="19" x2="12" y2="23" class="svelte-7elv0w"></line><line x1="8" y1="23" x2="16" y2="23" class="svelte-7elv0w"></line></svg>`);
		} else if (isTranscribing() || isProcessing()) {
			$$renderer.push("<!--[1-->");
			$$renderer.push(`<span class="voice-spinner svelte-7elv0w" aria-hidden="true"></span>`);
		} else if (isRecording()) {
			$$renderer.push("<!--[2-->");
			$$renderer.push(`<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" class="svelte-7elv0w"><rect x="6" y="6" width="12" height="12" rx="1.5" class="svelte-7elv0w"></rect></svg> <span class="voice-pulse svelte-7elv0w" aria-hidden="true"></span>`);
		} else {
			$$renderer.push("<!--[-1-->");
			$$renderer.push(`<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svelte-7elv0w"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" class="svelte-7elv0w"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2" class="svelte-7elv0w"></path><line x1="12" y1="19" x2="12" y2="23" class="svelte-7elv0w"></line><line x1="8" y1="23" x2="16" y2="23" class="svelte-7elv0w"></line></svg>`);
		}
		$$renderer.push(`<!--]--></button> <span class="sr-only svelte-7elv0w" aria-live="polite">${escape_html(isRecording() && voiceState.interimTranscript ? voiceState.interimTranscript : isRecording() ? "Recording" : isTranscribing() ? "Transcribing" : isProcessing() ? "Sending message to assistant" : isSpeaking() ? "Assistant is speaking" : voiceState.ttsAutoEnabled ? "Spoken responses on" : "")}</span></div>`);
	});
}
//#endregion
//#region src/lib/components/chrome/Navbar.svelte
function chatIcon($$renderer) {
	$$renderer.push(`<svg aria-hidden="true" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>`);
}
function Navbar($$renderer, $$props) {
	$$renderer.component(($$renderer) => {
		const pathname = derived(() => page.url?.pathname ?? "");
		const onAdmin = derived(() => pathname() === "/admin" || pathname().startsWith("/admin/"));
		const onChatSurface = derived(() => pathname() === "/chat" || pathname().startsWith("/chat/"));
		const onAdvancedSurface = derived(() => pathname() === "/advanced" || pathname().startsWith("/advanced/"));
		const onConversationSurface = derived(() => onChatSurface() || onAdvancedSurface());
		const preferredChatHref = derived(() => {
			const sessionId = page.url.searchParams.get("session") ?? currentChatSessionId();
			return advancedModeService.enabled ? buildAdvancedPath(sessionId) : buildChatPath(sessionId);
		});
		$$renderer.push(`<header class="navbar svelte-66bgq1"><div class="navbar-inner svelte-66bgq1"><a class="navbar-brand svelte-66bgq1"${attr("href", preferredChatHref())} aria-label="OpenPalm — go to chat"><span class="brand-icon svelte-66bgq1" aria-hidden="true"><img src="/logo-128.png" alt="" class="svelte-66bgq1"/></span> <span class="brand-text svelte-66bgq1">OpenPalm</span></a> <div class="navbar-actions svelte-66bgq1">`);
		if (onAdmin()) {
			$$renderer.push("<!--[0-->");
			IconButton($$renderer, {
				href: preferredChatHref(),
				ariaLabel: "Back to chat",
				title: "Chat",
				icon: chatIcon
			});
		} else $$renderer.push("<!--[-1-->");
		$$renderer.push(`<!--]--> `);
		ThemeToggle($$renderer);
		$$renderer.push(`<!----> `);
		if (onConversationSurface()) {
			$$renderer.push("<!--[0-->");
			$$renderer.push(`<span class="chat-selectors svelte-66bgq1">`);
			EndpointSwitcher($$renderer);
			$$renderer.push(`<!----> `);
			SessionPicker($$renderer);
			$$renderer.push(`<!----></span>`);
		} else $$renderer.push("<!--[-1-->");
		$$renderer.push(`<!--]--> `);
		ModeSwitch($$renderer);
		$$renderer.push(`<!----> `);
		VoiceControl($$renderer);
		$$renderer.push(`<!----></div></div></header>`);
	});
}

export { Drawer as D, EndpointList as E, IconButton as I, Navbar as N, SessionList as S, chat as c, endpointsService as e, formatTime as f };
//# sourceMappingURL=Navbar-y0AyDpcy.js.map
