Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
797cc145c3 | ||
|
|
6d9cdf9128 | ||
|
|
a7534dc223 | ||
|
|
109ac1c549 | ||
|
|
01e0d3a320 |
No files matched your search
+1
-1
@@ -72,12 +72,12 @@ Status: beta.
|
||||
- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).
|
||||
|
||||
### Fixes
|
||||
- CLI: initialize plugins before registering the pairing CLI to fix `pairing approve/list` commands. (#3272) Thanks @araa47.
|
||||
- Discord: restore username directory lookup in target resolution. (#3131) Thanks @bonald.
|
||||
- Agents: align MiniMax base URL test expectation with default provider config. (#3131) Thanks @bonald.
|
||||
- Agents: prevent retries on oversized image errors and surface size limits. (#2871) Thanks @Suksham-sharma.
|
||||
- Agents: inherit provider baseUrl/api for inline models. (#2740) Thanks @lploc94.
|
||||
- Memory Search: keep auto provider model defaults and only include remote when configured. (#2576) Thanks @papago2355.
|
||||
- TTS: read OPENAI_TTS_BASE_URL at runtime instead of module load to honor config.env. (#3341) Thanks @hclsys.
|
||||
- macOS: auto-scroll to bottom when sending a new message while scrolled up. (#2471) Thanks @kennyklee.
|
||||
- Web UI: auto-expand the chat compose textarea while typing (with sensible max height). (#2950) Thanks @shivamraut101.
|
||||
- Gateway: prevent crashes on transient network errors (fetch failures, timeouts, DNS). Added fatal error detection to only exit on truly critical errors. Fixes #2895, #2879, #2873. (#2980) Thanks @elliotsecops.
|
||||
|
||||
@@ -71,6 +71,7 @@ const LOBSTER_ASCII = [
|
||||
"██░███░██░▀▀▀░██░▀▀░███░████░▀▀░██░▀▀▀░███░████",
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
|
||||
" 🦞 FRESH DAILY 🦞 ",
|
||||
" ",
|
||||
];
|
||||
|
||||
export function formatCliBannerArt(options: BannerOptions = {}): string {
|
||||
|
||||
@@ -69,7 +69,8 @@ export function printWizardHeader(runtime: RuntimeEnv) {
|
||||
"██░█░█░██░███░██░██████░████░▄▄▀██░███░███░████",
|
||||
"██░███░██░▀▀▀░██░▀▀░███░████░▀▀░██░▀▀▀░███░████",
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
|
||||
" 🦞 FRESH DAILY 🦞 ",
|
||||
" 🦞 FRESH DAILY 🦞 ",
|
||||
" ",
|
||||
].join("\n");
|
||||
runtime.log(header);
|
||||
}
|
||||
|
||||
+15
-7
@@ -757,11 +757,19 @@ export const OPENAI_TTS_MODELS = ["gpt-4o-mini-tts", "tts-1", "tts-1-hd"] as con
|
||||
* Custom OpenAI-compatible TTS endpoint.
|
||||
* When set, model/voice validation is relaxed to allow non-OpenAI models.
|
||||
* Example: OPENAI_TTS_BASE_URL=http://localhost:8880/v1
|
||||
*
|
||||
* Note: Read at runtime (not module load) to support config.env loading.
|
||||
*/
|
||||
const OPENAI_TTS_BASE_URL = (
|
||||
process.env.OPENAI_TTS_BASE_URL?.trim() || "https://api.openai.com/v1"
|
||||
).replace(/\/+$/, "");
|
||||
const isCustomOpenAIEndpoint = OPENAI_TTS_BASE_URL !== "https://api.openai.com/v1";
|
||||
function getOpenAITtsBaseUrl(): string {
|
||||
return (process.env.OPENAI_TTS_BASE_URL?.trim() || "https://api.openai.com/v1").replace(
|
||||
/\/+$/,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
function isCustomOpenAIEndpoint(): boolean {
|
||||
return getOpenAITtsBaseUrl() !== "https://api.openai.com/v1";
|
||||
}
|
||||
export const OPENAI_TTS_VOICES = [
|
||||
"alloy",
|
||||
"ash",
|
||||
@@ -778,13 +786,13 @@ type OpenAiTtsVoice = (typeof OPENAI_TTS_VOICES)[number];
|
||||
|
||||
function isValidOpenAIModel(model: string): boolean {
|
||||
// Allow any model when using custom endpoint (e.g., Kokoro, LocalAI)
|
||||
if (isCustomOpenAIEndpoint) return true;
|
||||
if (isCustomOpenAIEndpoint()) return true;
|
||||
return OPENAI_TTS_MODELS.includes(model as (typeof OPENAI_TTS_MODELS)[number]);
|
||||
}
|
||||
|
||||
function isValidOpenAIVoice(voice: string): voice is OpenAiTtsVoice {
|
||||
// Allow any voice when using custom endpoint (e.g., Kokoro Chinese voices)
|
||||
if (isCustomOpenAIEndpoint) return true;
|
||||
if (isCustomOpenAIEndpoint()) return true;
|
||||
return OPENAI_TTS_VOICES.includes(voice as OpenAiTtsVoice);
|
||||
}
|
||||
|
||||
@@ -1011,7 +1019,7 @@ async function openaiTTS(params: {
|
||||
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${OPENAI_TTS_BASE_URL}/audio/speech`, {
|
||||
const response = await fetch(`${getOpenAITtsBaseUrl()}/audio/speech`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
|
||||
@@ -42,6 +42,7 @@ import { renderNodes } from "./views/nodes";
|
||||
import { renderOverview } from "./views/overview";
|
||||
import { renderSessions } from "./views/sessions";
|
||||
import { renderExecApprovalPrompt } from "./views/exec-approval";
|
||||
import { renderGatewayUrlConfirmation } from "./views/gateway-url-confirmation";
|
||||
import {
|
||||
approveDevicePairing,
|
||||
loadDevices,
|
||||
@@ -578,6 +579,7 @@ export function renderApp(state: AppViewState) {
|
||||
: nothing}
|
||||
</main>
|
||||
${renderExecApprovalPrompt(state)}
|
||||
${renderGatewayUrlConfirmation(state)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -33,6 +33,7 @@ type SettingsHost = {
|
||||
basePath: string;
|
||||
themeMedia: MediaQueryList | null;
|
||||
themeMediaHandler: ((event: MediaQueryListEvent) => void) | null;
|
||||
pendingGatewayUrl?: string | null;
|
||||
};
|
||||
|
||||
export function applySettings(host: SettingsHost, next: UiSettings) {
|
||||
@@ -98,7 +99,7 @@ export function applySettingsFromUrl(host: SettingsHost) {
|
||||
if (gatewayUrlRaw != null) {
|
||||
const gatewayUrl = gatewayUrlRaw.trim();
|
||||
if (gatewayUrl && gatewayUrl !== host.settings.gatewayUrl) {
|
||||
applySettings(host, { ...host.settings, gatewayUrl });
|
||||
host.pendingGatewayUrl = gatewayUrl;
|
||||
}
|
||||
params.delete("gatewayUrl");
|
||||
shouldCleanUrl = true;
|
||||
|
||||
@@ -73,6 +73,7 @@ export type AppViewState = {
|
||||
execApprovalQueue: ExecApprovalRequest[];
|
||||
execApprovalBusy: boolean;
|
||||
execApprovalError: string | null;
|
||||
pendingGatewayUrl: string | null;
|
||||
configLoading: boolean;
|
||||
configRaw: string;
|
||||
configRawOriginal: string;
|
||||
@@ -165,6 +166,8 @@ export type AppViewState = {
|
||||
handleNostrProfileImport: () => Promise<void>;
|
||||
handleNostrProfileToggleAdvanced: () => void;
|
||||
handleExecApprovalDecision: (decision: "allow-once" | "allow-always" | "deny") => Promise<void>;
|
||||
handleGatewayUrlConfirm: () => void;
|
||||
handleGatewayUrlCancel: () => void;
|
||||
handleConfigLoad: () => Promise<void>;
|
||||
handleConfigSave: () => Promise<void>;
|
||||
handleConfigApply: () => Promise<void>;
|
||||
|
||||
@@ -152,6 +152,7 @@ export class MoltbotApp extends LitElement {
|
||||
@state() execApprovalQueue: ExecApprovalRequest[] = [];
|
||||
@state() execApprovalBusy = false;
|
||||
@state() execApprovalError: string | null = null;
|
||||
@state() pendingGatewayUrl: string | null = null;
|
||||
|
||||
@state() configLoading = false;
|
||||
@state() configRaw = "{\n}\n";
|
||||
@@ -448,6 +449,21 @@ export class MoltbotApp extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
handleGatewayUrlConfirm() {
|
||||
const nextGatewayUrl = this.pendingGatewayUrl;
|
||||
if (!nextGatewayUrl) return;
|
||||
this.pendingGatewayUrl = null;
|
||||
applySettingsInternal(
|
||||
this as unknown as Parameters<typeof applySettingsInternal>[0],
|
||||
{ ...this.settings, gatewayUrl: nextGatewayUrl },
|
||||
);
|
||||
this.connect();
|
||||
}
|
||||
|
||||
handleGatewayUrlCancel() {
|
||||
this.pendingGatewayUrl = null;
|
||||
}
|
||||
|
||||
// Sidebar handlers for tool output viewing
|
||||
handleOpenSidebar(content: string) {
|
||||
if (this.sidebarCloseTimer != null) {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { html, nothing } from "lit";
|
||||
|
||||
import type { AppViewState } from "../app-view-state";
|
||||
|
||||
export function renderGatewayUrlConfirmation(state: AppViewState) {
|
||||
const { pendingGatewayUrl } = state;
|
||||
if (!pendingGatewayUrl) return nothing;
|
||||
|
||||
return html`
|
||||
<div class="exec-approval-overlay" role="dialog" aria-modal="true" aria-live="polite">
|
||||
<div class="exec-approval-card">
|
||||
<div class="exec-approval-header">
|
||||
<div>
|
||||
<div class="exec-approval-title">Change Gateway URL</div>
|
||||
<div class="exec-approval-sub">This will reconnect to a different gateway server</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="exec-approval-command mono">${pendingGatewayUrl}</div>
|
||||
<div class="callout danger" style="margin-top: 12px;">
|
||||
Only confirm if you trust this URL. Malicious URLs can compromise your system.
|
||||
</div>
|
||||
<div class="exec-approval-actions">
|
||||
<button
|
||||
class="btn primary"
|
||||
@click=${() => state.handleGatewayUrlConfirm()}
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
<button
|
||||
class="btn"
|
||||
@click=${() => state.handleGatewayUrlCancel()}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
Reference in new issue
Block a user