Merge remote-tracking branch 'origin/main' into pr58-sync

# Conflicts:
#	packages/server/src/shared/messages.ts
This commit is contained in:
Mohamed Boudra
2026-02-18 18:19:41 +07:00
39 changed files with 2179 additions and 257 deletions
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { AttemptCancelledError, AttemptGuard } from "@/utils/attempt-guard";
import { getTauri } from "@/utils/tauri";
export interface AudioCaptureConfig {
sampleRate?: number;
@@ -156,16 +157,33 @@ export function useAudioRecorder(config?: AudioCaptureConfig) {
: true;
const currentOrigin =
typeof window !== "undefined" && window.location ? window.location.origin : "unknown";
const isTauri = getTauri() !== null;
if (missingNavigator) {
throw new Error("Microphone capture is not supported in this environment");
}
if (!secureContext) {
console.log("[AudioRecorder][Web] Microphone preflight", {
secureContext,
currentOrigin,
isTauri,
hasMediaDevices:
typeof navigator !== "undefined" &&
!!navigator.mediaDevices &&
typeof navigator.mediaDevices.getUserMedia === "function",
});
if (!secureContext && !isTauri) {
throw new Error(
`Microphone access requires HTTPS or localhost. Current origin: ${currentOrigin}`
);
}
if (!secureContext && isTauri) {
console.warn(
"[AudioRecorder][Web] Insecure context reported under Tauri; attempting getUserMedia anyway",
{ currentOrigin }
);
}
const options = configRef.current;
const constraints: MediaStreamConstraints = {
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { getTauri } from "@/utils/tauri";
import type { DictationAudioSource, DictationAudioSourceConfig } from "./use-dictation-audio-source.types";
@@ -148,13 +149,29 @@ export function useDictationAudioSource(config: DictationAudioSourceConfig): Dic
: true;
const currentOrigin =
typeof window !== "undefined" && window.location ? window.location.origin : "unknown";
const isTauri = getTauri() !== null;
if (missingNavigator) {
throw new Error("Microphone capture is not supported in this environment");
}
if (!secureContext) {
console.log("[DictationAudio][Web] Microphone preflight", {
secureContext,
currentOrigin,
isTauri,
hasMediaDevices:
typeof navigator !== "undefined" &&
!!navigator.mediaDevices &&
typeof navigator.mediaDevices.getUserMedia === "function",
});
if (!secureContext && !isTauri) {
throw new Error(`Microphone access requires HTTPS or localhost. Current origin: ${currentOrigin}`);
}
if (!secureContext && isTauri) {
console.warn(
"[DictationAudio][Web] Insecure context reported under Tauri; attempting getUserMedia anyway",
{ currentOrigin }
);
}
const AudioContextCtor = getAudioContextCtor();
if (!AudioContextCtor) {
+3 -2
View File
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { Platform } from "react-native";
import { getIsTauriMac } from "@/constants/layout";
import { useAggregatedAgents } from "./use-aggregated-agents";
import { getCurrentTauriWindow } from "@/utils/tauri";
type FaviconStatus = "none" | "running" | "attention";
type ColorScheme = "dark" | "light";
@@ -90,9 +91,9 @@ function getSystemColorScheme(): ColorScheme {
}
async function updateMacDockBadge(count?: number) {
if (Platform.OS !== "web" || typeof window === "undefined" || !getIsTauriMac()) return;
if (Platform.OS !== "web" || !getIsTauriMac()) return;
const tauriWindow = (window as any).__TAURI__?.window?.getCurrentWindow?.();
const tauriWindow = getCurrentTauriWindow();
if (!tauriWindow || typeof tauriWindow.setBadgeCount !== "function") {
return;
}
+4 -7
View File
@@ -1,6 +1,7 @@
import { useState, useRef, useEffect } from "react";
import { Platform } from "react-native";
import type { ImageAttachment } from "@/components/message-input";
import { getCurrentTauriWindow, getTauri } from "@/utils/tauri";
interface UseFileDropZoneOptions {
onFilesDropped: (files: ImageAttachment[]) => void;
@@ -52,10 +53,6 @@ function isImageFile(file: File): boolean {
return file.type.startsWith("image/");
}
function isTauriEnvironment(): boolean {
return typeof window !== "undefined" && (window as any).__TAURI__ !== undefined;
}
function getFileExtension(path: string): string {
const normalizedPath = path.split("#", 1)[0]?.split("?", 1)[0] ?? path;
const extensionIndex = normalizedPath.lastIndexOf(".");
@@ -72,7 +69,7 @@ function isImagePath(path: string): boolean {
function filePathToImageAttachment(path: string): ImageAttachment {
const extension = getFileExtension(path);
const mimeType = IMAGE_MIME_BY_EXTENSION[extension] ?? "image/jpeg";
const convertFileSrc = (window as any).__TAURI__?.core?.convertFileSrc;
const convertFileSrc = getTauri()?.core?.convertFileSrc;
const uri =
typeof convertFileSrc === "function" ? convertFileSrc(path) : path;
@@ -145,11 +142,11 @@ export function useFileDropZone({
}
async function setupTauriDragDrop(): Promise<boolean> {
if (!isTauriEnvironment()) {
if (getTauri() === null) {
return false;
}
const tauriWindow = (window as any).__TAURI__?.window?.getCurrentWindow?.();
const tauriWindow = getCurrentTauriWindow();
if (!tauriWindow || typeof tauriWindow.onDragDropEvent !== "function") {
return false;
}
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { SpeechSegmenter } from "@/voice/speech-segmenter";
import { getTauri } from "@/utils/tauri";
export interface SpeechmaticsAudioConfig {
onAudioSegment?: (segment: { audioData: string; isLast: boolean }) => void;
@@ -245,14 +246,30 @@ export function useSpeechmaticsAudio(config: SpeechmaticsAudioConfig): Speechmat
? window.isSecureContext
: true;
const currentOrigin = typeof window !== "undefined" && window.location ? window.location.origin : "unknown";
const isTauri = getTauri() !== null;
try {
if (missingNavigator) {
throw new Error("Microphone capture is not supported in this environment");
}
if (!secureContext) {
console.log("[Voice][Web] Microphone preflight", {
secureContext,
currentOrigin,
isTauri,
hasMediaDevices:
typeof navigator !== "undefined" &&
!!navigator.mediaDevices &&
typeof navigator.mediaDevices.getUserMedia === "function",
});
if (!secureContext && !isTauri) {
throw new Error(`Microphone access requires HTTPS or localhost. Current origin: ${currentOrigin}`);
}
if (!secureContext && isTauri) {
console.warn(
"[Voice][Web] Insecure context reported under Tauri; attempting getUserMedia anyway",
{ currentOrigin }
);
}
const AudioContextCtor = getAudioContextCtor();
if (!AudioContextCtor) {