repo_description: ✨ Light and Fast AI Assistant. Support: Web | iOS | MacOS | Android |  Linux | Windows
repo_topics: calclaude chatgpt claude cross-platform desktop fe gemini gemini-pro gemini-server gemini-ultra gpt-4o groq nextjs ollama react tauri tauri-app vercel webui
code: // using tauri command to send request
  // see src-tauri/src/stream.rs, and src-tauri/src/main.rs
  // 1. invoke('stream_fetch', {url, method, headers, body}), get response with headers.
  // 2. listen event: `stream-response` multi times to get body
  
  type ResponseEvent = {
    id: number;
    payload: {
      request_id: number;
      status?: number;
      chunk?: number[];
    };
  };
  
  type StreamResponse = {
    request_id: number;
    status: number;
    status_text: string;
    headers: Record<string, string>;
  };
  
  export function fetch(url: string, options?: RequestInit): Promise<Response> {
    if (window.__TAURI__) {
      const {
        signal,
        method = "GET",
        headers: _headers = {},
        body = [],
      } = options || {};
      let unlisten: Function | undefined;
      let setRequestId: Function | undefined;
      const requestIdPromise = new Promise((resolve) => (setRequestId = resolve));
      const ts = new TransformStream();
      const writer = ts.writable.getWriter();
  
      let closed = false;
      const close = () => {
        if (closed) return;
        closed = true;
        unlisten && unlisten();
        writer.ready.then(() => {
          writer.close().catch((e) => console.error(e));
        });
      };
  
      if (signal) {
        signal.addEventListener("abort", () => close());
      }
      // @ts-ignore 2. listen response multi times, and write to Response.body
      window.__TAURI__.event
        .listen("stream-response", (e: ResponseEvent) =>
          requestIdPromise.then((request_id) => {
            const { request_id: rid, chunk, status } = e?.payload || {};
            if (request_id != rid) {
              return;
            }
            if (chunk) {
              writer.ready.then(() => {
                writer.write(new Uint8Array(chunk));
              });
            } else if (status === 0) {
              // end of body
              close();
            }
          }),
        )
        .then((u: Function) => (unlisten = u));
  
      const headers: Record<string, string> = {
        Accept: "application/json, text/plain, */*",
        "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
        "User-Agent": navigator.userAgent,
      };
      for (const item of new Headers(_headers || {})) {
        headers[item[0]] = item[1];
      }
      return window.__TAURI__
        .invoke("stream_fetch", {
          method: method.toUpperCase(),
          url,
          headers,
          // TODO FormData
          body:
            typeof body === "string"
              ? Array.from(new TextEncoder().encode(body))
              : [],
        })
        .then((res: StreamResponse) => {
          const { request_id, status, status_text: statusText, headers } = res;
          setRequestId?.(request_id);
          const response = new Response(ts.readable, {
            status,
            statusText,
            headers,
          });
          if (status >= 300) {
            setTimeout(close, 100);
          }
          return response;
        })
        .catch((e) => {
          console.error("stream error", e);
          // throw e;
          return new Response("", { status: 599 });
        });
    }
    return window.fetch(url, options);
  }
  