Full Code of lencx/ChatGPT for AI

v2-dev a6de9a8b6107 cached
55 files
56.4 KB
17.5k tokens
59 symbols
1 requests
Download .txt
Repository: lencx/ChatGPT
Branch: v2-dev
Commit: a6de9a8b6107
Files: 55
Total size: 56.4 KB

Directory structure:
gitextract_vymxshi2/

├── .gitignore
├── .vscode/
│   └── extensions.json
├── Cargo.toml
├── README.md
├── index.html
├── package.json
├── postcss.config.js
├── rustfmt.toml
├── src/
│   ├── App.tsx
│   ├── base.css
│   ├── components/
│   │   └── WinTitlebar.tsx
│   ├── hooks/
│   │   ├── useInfo.tsx
│   │   └── useTheme.tsx
│   ├── icons/
│   │   ├── ArrowLeft.tsx
│   │   ├── Ask.tsx
│   │   ├── Link.tsx
│   │   ├── Pin.tsx
│   │   ├── Reload.tsx
│   │   ├── SVGWrap.tsx
│   │   ├── Send.tsx
│   │   ├── Setting.tsx
│   │   ├── ThemeDark.tsx
│   │   ├── ThemeLight.tsx
│   │   ├── ThemeSystem.tsx
│   │   ├── UnPin.tsx
│   │   ├── WindowClose.tsx
│   │   ├── WindowMaximize.tsx
│   │   ├── WindowMinimize.tsx
│   │   └── WindowRestore.tsx
│   ├── main.tsx
│   ├── types.d.ts
│   ├── view/
│   │   ├── Ask.tsx
│   │   ├── Settings.tsx
│   │   └── Titlebar.tsx
│   └── vite-env.d.ts
├── src-tauri/
│   ├── .gitignore
│   ├── Cargo.toml
│   ├── Info.plist
│   ├── build.rs
│   ├── capabilities/
│   │   └── desktop.json
│   ├── icons/
│   │   └── icon.icns
│   ├── scripts/
│   │   └── ask.js
│   ├── src/
│   │   ├── core/
│   │   │   ├── cmd.rs
│   │   │   ├── conf.rs
│   │   │   ├── constant.rs
│   │   │   ├── mod.rs
│   │   │   ├── setup.rs
│   │   │   ├── template.rs
│   │   │   └── window.rs
│   │   └── main.rs
│   └── tauri.conf.json
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# rust
target/

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?


================================================
FILE: .vscode/extensions.json
================================================
{
  "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
}


================================================
FILE: Cargo.toml
================================================
[workspace]
resolver = "2"
members = ["src-tauri"]

# https://v2.tauri.app/test/webdriver/example/#adding-tauri-to-the-cargo-project
[profile.release]
incremental = false
codegen-units = 1
panic = "abort"
opt-level = "s"
lto = true

================================================
FILE: README.md
================================================
<p align="center">
  <img width="180" src="./public/ChatGPT.png" alt="ChatGPT">
  <p align="center">ChatGPT Desktop Application (Available on Mac, Windows, and Linux)</p>
</p>

[![ChatGPT downloads](https://img.shields.io/github/downloads/lencx/ChatGPT/total.svg?style=flat-square)](https://github.com/lencx/ChatGPT/releases)
[![chat](https://img.shields.io/badge/chat-discord-blue?style=flat&logo=discord)](https://discord.gg/aPhCRf4zZr)
[![twitter](https://img.shields.io/badge/follow-lencx__-blue?style=flat&logo=Twitter)](https://twitter.com/lencx_)
[![youtube](https://img.shields.io/youtube/channel/subscribers/UC__gTZL-OZKDPic7s_6Ntgg?style=social)](https://www.youtube.com/@lencx)

<a href="https://www.buymeacoffee.com/lencx" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png" alt="Buy Me A Coffee" style="height: 40px !important;width: 145px !important;" ></a>

---

> [!NOTE]
> **If you want to experience a more powerful AI wrapper application, you can try the Noi (https://github.com/lencx/Noi), which is a successor to the ChatGPT desktop application concept.**

Thank you very much for your interest in this project. OpenAI has now released the macOS version of the application, and a Windows version will be available later ([Introducing GPT-4o and more tools to ChatGPT free users](https://openai.com/index/gpt-4o-and-more-tools-to-chatgpt-free/)). If you prefer the official application, you can stay updated with the latest information from OpenAI.

If you want to learn about or download the previous version (v1.1.0), please click [here](https://github.com/lencx/ChatGPT/tree/release-v1.1.0).

I am currently looking for some differentiating features to develop version 2.0. If you are interested in this, please stay tuned.

![](./docs/static/chatgpt-v2.gif)

================================================
FILE: index.html
================================================
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ChatGPT</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


================================================
FILE: package.json
================================================
{
  "name": "chatgpt",
  "private": true,
  "version": "2.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "tauri": "tauri"
  },
  "dependencies": {
    "@tauri-apps/api": "2.0.0-beta.15",
    "@tauri-apps/plugin-os": "2.0.0-beta.7",
    "@tauri-apps/plugin-shell": "2.0.0-beta.8",
    "clsx": "^2.1.1",
    "lodash": "^4.17.21",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-hotkeys-hook": "^4.5.0",
    "react-router-dom": "^6.23.0"
  },
  "devDependencies": {
    "@tailwindcss/typography": "^0.5.13",
    "@tauri-apps/cli": "2.0.0-beta.22",
    "@types/lodash": "^4.17.1",
    "@types/node": "^20.12.12",
    "@types/react": "^18.3.1",
    "@types/react-dom": "^18.3.0",
    "@vitejs/plugin-react": "^4.2.1",
    "autoprefixer": "^10.4.19",
    "postcss": "^8.4.38",
    "tailwindcss": "^3.4.3",
    "typescript": "^5.4.5",
    "vite": "^5.2.11",
    "vite-tsconfig-paths": "^4.3.2"
  }
}


================================================
FILE: postcss.config.js
================================================
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

================================================
FILE: rustfmt.toml
================================================
max_width = 100
hard_tabs = false
tab_spaces = 4
newline_style = "Auto"
use_small_heuristics = "Default"
reorder_imports = true
reorder_modules = true
remove_nested_parens = true
edition = "2021"
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
force_explicit_abi = true
# imports_granularity = "Crate"

================================================
FILE: src/App.tsx
================================================
import { getCurrentWebview } from '@tauri-apps/api/webview';

import Titlebar from '~view/Titlebar';
import Ask from '~view/Ask';
import Settings from '~view/Settings';

const viewMap = {
  titlebar: <Titlebar />,
  ask: <Ask />,
  settings: <Settings />,
};

export default function App() {
  const webview = getCurrentWebview();
  return viewMap[webview.label as keyof typeof viewMap] || null;
}


================================================
FILE: src/base.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;

html, body, #root {
  margin: 0;
  bottom: 0;
  height: 100%;
  overflow: hidden;
}

================================================
FILE: src/components/WinTitlebar.tsx
================================================
import { useEffect, useState } from 'react';
import { Window } from '@tauri-apps/api/window';

import WindowClose from '~icons/WindowClose';
import WindowMaximize from '~icons/WindowMaximize';
import WindowMinimize from '~icons/WindowMinimize';
import WindowRestore from '~/icons/WindowRestore';

export default function WinTitlebar() {
  const [isMax, setMax] = useState(false);

  useEffect(() => {
    (async () => {
      const win = Window.getByLabel('core');
      const max = await win?.isMaximized();
      setMax(!!max);
    })()
  }, [])

  const handleToggle = async () => {
    const win = Window.getByLabel('core');
    await win?.toggleMaximize();
    setMax(!isMax);
  }

  const handleMinimize = () => {
    const win = Window.getByLabel('core');
    win?.minimize();
  };

  const handleClose = () => {
    const win = Window.getByLabel('core');
    win?.close();
  }

  return (
    <div className="flex items-center gap-1">
      <WindowMinimize size={20} className="p-0" onClick={handleMinimize} />
      {isMax
        ? <WindowRestore size={20} className="p-0" onClick={handleToggle} />
        : <WindowMaximize size={20} className="p-0" onClick={handleToggle} />}
      <WindowClose size={20} className="p-0" onClick={handleClose} />
    </div>
  )
}

================================================
FILE: src/hooks/useInfo.tsx
================================================
import { useEffect, useState } from 'react';
import { platform as TauriPlatform } from '@tauri-apps/plugin-os';

export default function useInfo() {
  const [platform, setPlatform] = useState('');
  const [isMac, setMac] = useState(false);

  const handleInfo = async () => {
    const p = await TauriPlatform();
    setPlatform(await TauriPlatform());
    setMac(p === 'macos');
  }

  useEffect(() => {
    handleInfo();
  }, []);

  return { platform, isMac };
}

================================================
FILE: src/hooks/useTheme.tsx
================================================
import { useState, useEffect } from 'react';
import { getCurrentWindow } from '@tauri-apps/api/window';

export default function useTheme() {
  const [theme, setTheme] = useState<string | null>('light'); // ['light', 'dark']

  useEffect(() => {
    let unlisten: Function;
    (async () => {
      let win = getCurrentWindow();
      setTheme(await win.theme() || '');
      unlisten = await win.onThemeChanged(({ payload: newTheme }) => {
        setTheme(newTheme);
      });
    })()

    return () => {
      unlisten?.();
    };
  }, [])

  return theme;
}

================================================
FILE: src/icons/ArrowLeft.tsx
================================================
import SVGWrap from './SVGWrap';

export default function ArrowLeft(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 24 24">
      <path fill="currentColor" d="m7.85 13l2.85 2.85q.3.3.288.7t-.288.7q-.3.3-.712.313t-.713-.288L4.7 12.7q-.3-.3-.3-.7t.3-.7l4.575-4.575q.3-.3.713-.287t.712.312q.275.3.288.7t-.288.7L7.85 11H19q.425 0 .713.288T20 12t-.288.713T19 13z"/>
    </SVGWrap>
  );
}


================================================
FILE: src/icons/Ask.tsx
================================================
import SVGWrap from './SVGWrap';

export default function Ask(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 24 24">
      <path fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.038 19.927A9.93 9.93 0 0 1 7.7 19L3 20l1.3-3.9C1.976 12.663 2.874 8.228 6.4 5.726c3.526-2.501 8.59-2.296 11.845.48c1.993 1.7 2.93 4.043 2.746 6.346M19 16l-2 3h4l-2 3"/>
    </SVGWrap>
  );
}


================================================
FILE: src/icons/Link.tsx
================================================
import SVGWrap from './SVGWrap';

export default function Link(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 256 256">
      <path fill="currentColor" d="M117.18 188.74a12 12 0 0 1 0 17l-5.12 5.12A58.26 58.26 0 0 1 70.6 228a58.62 58.62 0 0 1-41.46-100.08l34.75-34.75a58.64 58.64 0 0 1 98.56 28.11a12 12 0 1 1-23.37 5.44a34.65 34.65 0 0 0-58.22-16.58l-34.75 34.75A34.62 34.62 0 0 0 70.57 204a34.41 34.41 0 0 0 24.49-10.14l5.11-5.12a12 12 0 0 1 17.01 0M226.83 45.17a58.65 58.65 0 0 0-82.93 0l-5.11 5.11a12 12 0 0 0 17 17l5.12-5.12a34.63 34.63 0 1 1 49 49l-34.81 34.7A34.39 34.39 0 0 1 150.61 156a34.63 34.63 0 0 1-33.69-26.72a12 12 0 0 0-23.38 5.44A58.64 58.64 0 0 0 150.56 180h.05a58.28 58.28 0 0 0 41.47-17.17l34.75-34.75a58.62 58.62 0 0 0 0-82.91"/>
    </SVGWrap>
  );
}


================================================
FILE: src/icons/Pin.tsx
================================================
import SVGWrap from './SVGWrap';

export default function Pin(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 24 24">
      <path fill="currentColor" fillRule="evenodd" d="M16 9V4h1c.55 0 1-.45 1-1s-.45-1-1-1H7c-.55 0-1 .45-1 1s.45 1 1 1h1v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1l1-1v-7H19v-2c-1.66 0-3-1.34-3-3"/>
    </SVGWrap>
  );
}


================================================
FILE: src/icons/Reload.tsx
================================================
import SVGWrap from './SVGWrap';

export default function Reload(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 24 24">
      <path fill="currentColor" d="M12 20q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4q1.725 0 3.3.712T18 6.75V5q0-.425.288-.712T19 4t.713.288T20 5v5q0 .425-.288.713T19 11h-5q-.425 0-.712-.288T13 10t.288-.712T14 9h3.2q-.8-1.4-2.187-2.2T12 6Q9.5 6 7.75 7.75T6 12t1.75 4.25T12 18q1.7 0 3.113-.862t2.187-2.313q.2-.35.563-.487t.737-.013q.4.125.575.525t-.025.75q-1.025 2-2.925 3.2T12 20"/>
    </SVGWrap>
  );
}


================================================
FILE: src/icons/SVGWrap.tsx
================================================
import React from 'react';
import clsx from 'clsx';

export default function SVGWrap({ size = 18, children, type, className, title, onClick, action = false, ...props }: I.SVG) {
  const handleClick = (e: React.MouseEvent) => {
    onClick && onClick(e);
  };

  return (
    <i
      style={{
        width: size,
        height: size,
      }}
      title={title}
      onClick={handleClick}
      className={clsx('inline-flex items-center justify-center rounded-sm p-[2px] text-slate-500 dark:text-slate-500 transition-all', {
        'cursor-pointer hover:bg-slate-300/50 hover:dark:bg-white/10': action,
      }, className)}
    >
      <svg
        xmlns="http://www.w3.org/2000/svg"
        style={{ widows: '100%', height: '100%' }}
        {...props}
      >
        {children}
      </svg>
    </i>
  );
}


================================================
FILE: src/icons/Send.tsx
================================================
import SVGWrap from './SVGWrap';

export default function Send(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 24 24">
      <g fill="none">
        <path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022m-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/>
        <path fill="currentColor" d="m21.433 4.861l-6 15.5a1 1 0 0 1-1.624.362l-3.382-3.235l-2.074 2.073a.5.5 0 0 1-.853-.354v-4.519L2.309 9.723a1 1 0 0 1 .442-1.691l17.5-4.5a1 1 0 0 1 1.181 1.329ZM19 6.001L8.032 13.152l1.735 1.66L19 6Z"/>
      </g>
    </SVGWrap>
  );
}


================================================
FILE: src/icons/Setting.tsx
================================================
import SVGWrap from './SVGWrap';

export default function Setting(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 24 24">
      <g fill="none" stroke="currentColor" strokeWidth="2">
        <path d="M3.082 13.945c-.529-.95-.793-1.426-.793-1.945c0-.519.264-.994.793-1.944L4.43 7.63l1.426-2.381c.559-.933.838-1.4 1.287-1.66c.45-.259.993-.267 2.08-.285L12 3.26l2.775.044c1.088.018 1.631.026 2.08.286c.45.26.73.726 1.288 1.659L19.57 7.63l1.35 2.426c.528.95.792 1.425.792 1.944c0 .519-.264.994-.793 1.944L19.57 16.37l-1.426 2.381c-.559.933-.838 1.4-1.287 1.66c-.45.259-.993.267-2.08.285L12 20.74l-2.775-.044c-1.088-.018-1.631-.026-2.08-.286c-.45-.26-.73-.726-1.288-1.659L4.43 16.37z"/>
        <circle cx="12" cy="12" r="3"/>
      </g>
    </SVGWrap>
  );
}


================================================
FILE: src/icons/ThemeDark.tsx
================================================
import SVGWrap from './SVGWrap';

export default function ThemeDark(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 24 24">
      <path fill="currentColor" d="M12 21q-3.775 0-6.387-2.613T3 12q0-3.45 2.25-5.988T11 3.05q.325-.05.575.088t.4.362t.163.525t-.188.575q-.425.65-.638 1.375T11.1 7.5q0 2.25 1.575 3.825T16.5 12.9q.775 0 1.538-.225t1.362-.625q.275-.175.563-.162t.512.137q.25.125.388.375t.087.6q-.35 3.45-2.937 5.725T12 21m0-2q2.2 0 3.95-1.213t2.55-3.162q-.5.125-1 .2t-1 .075q-3.075 0-5.238-2.163T9.1 7.5q0-.5.075-1t.2-1q-1.95.8-3.163 2.55T5 12q0 2.9 2.05 4.95T12 19m-.25-6.75"/>
    </SVGWrap>
  );
}


================================================
FILE: src/icons/ThemeLight.tsx
================================================
import SVGWrap from './SVGWrap';

export default function ThemeLight(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 24 24">
      <path fill="currentColor" d="M12 15q1.25 0 2.125-.875T15 12t-.875-2.125T12 9t-2.125.875T9 12t.875 2.125T12 15m0 2q-2.075 0-3.537-1.463T7 12t1.463-3.537T12 7t3.538 1.463T17 12t-1.463 3.538T12 17M2 13q-.425 0-.712-.288T1 12t.288-.712T2 11h2q.425 0 .713.288T5 12t-.288.713T4 13zm18 0q-.425 0-.712-.288T19 12t.288-.712T20 11h2q.425 0 .713.288T23 12t-.288.713T22 13zm-8-8q-.425 0-.712-.288T11 4V2q0-.425.288-.712T12 1t.713.288T13 2v2q0 .425-.288.713T12 5m0 18q-.425 0-.712-.288T11 22v-2q0-.425.288-.712T12 19t.713.288T13 20v2q0 .425-.288.713T12 23M5.65 7.05L4.575 6q-.3-.275-.288-.7t.288-.725q.3-.3.725-.3t.7.3L7.05 5.65q.275.3.275.7t-.275.7t-.687.288t-.713-.288M18 19.425l-1.05-1.075q-.275-.3-.275-.712t.275-.688q.275-.3.688-.287t.712.287L19.425 18q.3.275.288.7t-.288.725q-.3.3-.725.3t-.7-.3M16.95 7.05q-.3-.275-.288-.687t.288-.713L18 4.575q.275-.3.7-.288t.725.288q.3.3.3.725t-.3.7L18.35 7.05q-.3.275-.7.275t-.7-.275M4.575 19.425q-.3-.3-.3-.725t.3-.7l1.075-1.05q.3-.275.712-.275t.688.275q.3.275.288.688t-.288.712L6 19.425q-.275.3-.7.288t-.725-.288M12 12"/>
    </SVGWrap>
  );
}


================================================
FILE: src/icons/ThemeSystem.tsx
================================================
import SVGWrap from './SVGWrap';

export default function Ask(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 24 24">
      <path fill="currentColor" d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2S2 6.477 2 12s4.477 10 10 10m0-2V4a8 8 0 1 1 0 16"/>
    </SVGWrap>
  );
}


================================================
FILE: src/icons/UnPin.tsx
================================================
import SVGWrap from './SVGWrap';

export default function UnPin(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 24 24">
      <path fill="currentColor" d="M14 4v5c0 1.12.37 2.16 1 3H9c.65-.86 1-1.9 1-3V4zm3-2H7c-.55 0-1 .45-1 1s.45 1 1 1h1v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1l1-1v-7H19v-2c-1.66 0-3-1.34-3-3V4h1c.55 0 1-.45 1-1s-.45-1-1-1"/>
    </SVGWrap>
  );
}


================================================
FILE: src/icons/WindowClose.tsx
================================================
import SVGWrap from './SVGWrap';

export default function WindowClose(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 16 16">
      <path fill="currentColor" d="m12.96 4.46l-1.42-1.42L8 6.59L4.46 3.04L3.04 4.46L6.59 8l-3.55 3.54l1.42 1.42L8 9.41l3.54 3.55l1.42-1.42L9.41 8z"/>
    </SVGWrap>
  );
}


================================================
FILE: src/icons/WindowMaximize.tsx
================================================
import SVGWrap from './SVGWrap';

export default function WindowMaximize(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 24 24">
      <path fill="currentColor" d="M4 4h16v16H4zm2 4v10h12V8z"/>
    </SVGWrap>
  );
}


================================================
FILE: src/icons/WindowMinimize.tsx
================================================
import SVGWrap from './SVGWrap';

export default function WindowMinimize(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 24 24">
      <path fill="currentColor" d="M20 14H4v-4h16"/>
    </SVGWrap>
  );
}


================================================
FILE: src/icons/WindowRestore.tsx
================================================
import SVGWrap from './SVGWrap';

export default function WindowRestore(props: I.SVG) {
  return (
    <SVGWrap {...props} viewBox="0 0 24 24">
      <path fill="currentColor" d="M4 8h4V4h12v12h-4v4H4zm12 0v6h2V6h-8v2zM6 12v6h8v-6z"/>
    </SVGWrap>
  );
}


================================================
FILE: src/main.tsx
================================================
import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';

// import Routes from './routes';
import App from './App';
import './base.css';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <StrictMode>
    <App />
  </StrictMode>
);


================================================
FILE: src/types.d.ts
================================================
declare namespace I {
  export type AppConf = {
    theme: 'light' | 'dark' | 'system';
    stay_on_top: boolean;
    ask_mode: boolean;
    mac_titlebar_hidden: boolean;
  }

  export interface SVG extends React.SVGProps<SVGSVGElement> {
    children?: React.ReactNode;
    size?: number;
    title?: string;
    action?: boolean;
    onClick?: (e: React.MouseEvent) => void;
  }
}

================================================
FILE: src/view/Ask.tsx
================================================
import { useState, useEffect, useRef } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { useHotkeys } from 'react-hotkeys-hook';
import useInfo from '~hooks/useInfo';
import SendIcon from '~icons/Send';
import debounce from 'lodash/debounce';

export default function ChatInput() {
  const inputRef = useRef<HTMLTextAreaElement>(null);
  const [message, setMessage] = useState('');
  const { isMac } = useInfo();

  useEffect(() => {
    const syncMessage = debounce(async () => {
      try {
        await invoke('ask_sync', { message: JSON.stringify(message) });
      } catch (error) {
        console.error('Error syncing message:', error);
      }
    }, 300); // Debounce by 300ms

    syncMessage();
    return () => syncMessage.cancel(); // Cleanup debounce on unmount
  }, [message]);

  useHotkeys(isMac ? 'meta+enter' : 'ctrl+enter', async (event: KeyboardEvent) => {
    event.preventDefault();
    await handleSend();
  }, {
    enableOnFormTags: true,
  }, [message]);

  const handleInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    setMessage(e.target.value);
  };

  const handleSend = async () => {
    if (!message) return;
    try {
      await invoke('ask_send', { message: JSON.stringify(message) });
    } catch (error) {
      console.error('Error sending message:', error);
    }
    setMessage('');
    if (inputRef.current) {
      inputRef.current.value = '';
      inputRef.current.focus();
    }
  };

  return (
    <div className="relative flex h-full dark:bg-app-gray-2/[0.98] bg-gray-100 dark:text-slate-200 items-center gap-1">
      <textarea
        ref={inputRef}
        onChange={handleInput}
        spellCheck="false"
        autoFocus
        className="w-full h-full pl-3 pr-[40px] py-2 outline-none resize-none bg-transparent"
        placeholder="Type your message here..."
      />
      <SendIcon
        size={30}
        className="absolute right-2 text-gray-400/80 dark:text-gray-600 cursor-pointer"
        onClick={handleSend}
        title={`Send message (${isMac ? '⌘⏎' : '⌃⏎'})`}
        aria-label="Send message"
      />
    </div>
  );
}


================================================
FILE: src/view/Settings.tsx
================================================
export default function Settings() {
  return (
    <div>Settings</div>
  )
}

================================================
FILE: src/view/Titlebar.tsx
================================================
import { useEffect, useState, useMemo } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { open } from '@tauri-apps/plugin-shell';
import { debounce } from 'lodash';
import clsx from 'clsx';

import useInfo from '~hooks/useInfo';
import ReloadIcon from '~icons/Reload';
import PinIcon from '~icons/Pin';
import UnPinIcon from '~icons/UnPin';
import LinkIcon from '~icons/Link';
import AskIcon from '~icons/Ask';
import SettingIcon from '~icons/Setting';
import ThemeSystem from '~icons/ThemeSystem';
import ThemeLight from '~icons/ThemeLight';
import ThemeDark from '~icons/ThemeDark';
import ArrowLeftIcon from '~icons/ArrowLeft';

export default function Titlebar() {
  const info = useInfo();
  const [url, setUrl] = useState('');
  const [hostname, setHostname] = useState('');
  const [theme, setTheme] = useState('system');
  const [enableAsk, setEnableAsk] = useState(false);
  const [fullScreen, setFullScreen] = useState(false);
  const [isPin, setPin] = useState(false);
  const [isTitlebarHidden, setTitlebarHidden] = useState(false);

  const titlebarHidden = info.isMac && isTitlebarHidden;

  useEffect(() => {
    const win = getCurrentWindow();
    let winResize: Function;
    let changeUrl: Function;

    invoke<I.AppConf>('get_app_conf')
      .then((v) => {
        setEnableAsk(v.ask_mode);
        setPin(v.stay_on_top);
        setTheme(v.theme);
        setTitlebarHidden(v.mac_titlebar_hidden);
      });

    (async () => {
      const full = await win.isFullscreen();
      setFullScreen(full);
      winResize = await win.listen('tauri://resize', debounce(async () => {
        const full = await win.isFullscreen();
        setFullScreen(full);
      }, 50))

      changeUrl = await getCurrentWindow().listen('navigation:change', (event: any) => {
        const { url } = event.payload;
        setUrl(url);

        try {
          const { hostname } = new URL(url);
          setHostname(hostname);
        } catch (error) {
          setHostname(url);
        }
      })
    })();

    return () => {
      winResize && winResize();
      changeUrl && changeUrl();
    }
  }, [])

  const handleRefresh = () => {
    invoke('view_reload');
  };

  const handleGoForward = () => {
    invoke('view_go_forward');
  };

  const handleGoBack = () => {
    invoke('view_go_back');
  };

  const handlePin = (isPin: boolean) => {
    setPin(isPin);
    invoke('window_pin', { pin: isPin });
  };

  const handleAsk = () => {
    setEnableAsk(!enableAsk);
    invoke('set_view_ask', { enabled: !enableAsk });
  };

  const handleTheme = (theme: string) => {
    invoke('set_theme', { theme });
  };

  const themeIcon = useMemo(() => {
    switch (theme) {
      case 'system':
        return <ThemeSystem title="Light" action onClick={() => handleTheme('light')} />
      case 'light':
        return <ThemeLight title="Dark" action onClick={() => handleTheme('dark')} />
      case 'dark':
        return <ThemeDark title="System" action onClick={() => handleTheme('system')} />
      default:
        return <ThemeSystem title="System" action onClick={() => handleTheme('system')} />
    }
  }, [theme]);

  const handleOpenUrl = () => {
    open(url);
  };

  const handleSetting = () => {
    invoke('open_settings');
  };

  const renderSettings = useMemo(() => {
    return (
      <div className={clsx('items-center gap-1', {
        'hidden group-hover:flex': titlebarHidden,
        'flex': !titlebarHidden,
      })}>
        {themeIcon}
        {isPin
          ? <PinIcon action onClick={() => handlePin(false)} />
          : <UnPinIcon action onClick={() => handlePin(true)} />}
        <SettingIcon action onClick={handleSetting} />
      </div>
    )
  }, [titlebarHidden, themeIcon, isPin])

  return (
    <div data-tauri-drag-region className={clsx('flex group pr-2 h-full cursor-default select-none dark:bg-app-gray-2 justify-between', {
      'pl-[80px]': !fullScreen && info.isMac,
      'pl-[10px]': fullScreen || !info.isMac,
    })}>
      <div data-tauri-drag-region className={clsx('items-center gap-0.5', {
        'hidden tablet:group-hover:flex group-hover:hidden': titlebarHidden,
        'tablet:flex hidden': !titlebarHidden,
      })}>
        {hostname && (
          <span
            className="flex items-center bg-slate-300/50 dark:bg-slate-100/10 dark:text-gray-500 rounded-full pl-[4px] pr-[8px] h-[14px] text-[10px] gap-1 text-slate-700 mr-1"
            onClick={handleOpenUrl}
            title={url}
          >
            <LinkIcon size={14} />
            {hostname}
          </span>
        )}
        <ArrowLeftIcon
          action
          onClick={handleGoBack}
        />
        <ArrowLeftIcon
          action
          onClick={handleGoForward}
          className="rotate-180"
        />
        <ReloadIcon action onClick={handleRefresh} />
        <AskIcon
          action
          onClick={handleAsk}
          className={clsx({
            '!text-app-active': enableAsk,
          })}
        />
      </div>
      <div className={clsx({
        'hidden group-hover:flex': titlebarHidden,
        'flex': !titlebarHidden,
      })} />
      {renderSettings}
    </div>
  );
}

================================================
FILE: src/vite-env.d.ts
================================================
/// <reference types="vite/client" />


================================================
FILE: src-tauri/.gitignore
================================================
# Generated by Cargo
# will have compiled files and executables
/target/

# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas


================================================
FILE: src-tauri/Cargo.toml
================================================
[package]
name = "chatgpt"
version = "0.0.0"
description = "ChatGPT Desktop Application (Unofficial)"
authors = ["lencx <cxin1314@gmail.com>"]
repository = "https://github.com/lencx/ChatGPT"
license = "AGPL-3.0"
edition = "2021"
rust-version = "1.77.1"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[build-dependencies]
tauri-build = { version = "2.0.0-beta", features = [] }

[dependencies]
tauri = { version = "2.0.0-beta", features = ["unstable", "devtools"] }
tokio = { version = "1.37.0", features = ["macros"] }
tauri-plugin-shell = "2.0.0-beta"
tauri-plugin-dialog = "2.0.0-beta"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
once_cell = "1.19.0"
log = "0.4.21"
anyhow = "1.0.83"
dark-light = "1.1.1"
regex = "1.10.4"
semver = "1.0.23"
tauri-plugin-os = "2.0.0-beta.4"


================================================
FILE: src-tauri/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>NSAppTransportSecurity</key>
    <dict>
      <key>NSExceptionDomains</key>
      <dict>
        <key>chatgpt.com</key>
        <dict>
          <key>NSExceptionAllowsInsecureHTTPLoads</key>
          <true />
          <key>NSIncludesSubdomains</key>
          <true />
        </dict>
      </dict>
    </dict>
  </dict>
</plist>

================================================
FILE: src-tauri/build.rs
================================================
// src-tauri/build.rs

fn main() {
    println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.13");
    tauri_build::build()
}


================================================
FILE: src-tauri/capabilities/desktop.json
================================================
{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "desktop-capability",
  "windows": [
    "*"
  ],
  "remote": {
    "urls": [
      "https://chatgpt.com/*"
    ]
  },
  "platforms": [
    "linux",
    "macOS",
    "windows"
  ],
  "permissions": [
    "window:default",
    "window:allow-create",
    "window:allow-start-dragging",
    "window:allow-toggle-maximize",
    "window:allow-minimize",
    "window:allow-close",
    "webview:default",
    "webview:allow-internal-toggle-devtools",
    "webview:allow-set-webview-zoom",
    "webview:allow-create-webview",
    "webview:allow-create-webview-window",
    "webview:allow-set-webview-focus",
    "event:default",
    "event:allow-emit",
    "event:allow-emit-to",
    "event:allow-listen",
    "event:allow-unlisten",
    "shell:default",
    "shell:allow-execute",
    "shell:allow-open",
    "os:allow-arch",
    "os:allow-platform",
    "os:allow-version",
    "os:allow-os-type",
    "os:default",
    "shell:default"
  ]
}

================================================
FILE: src-tauri/scripts/ask.js
================================================
/**
 * @name ask.js
 * @version 0.1.0
 * @url https://github.com/lencx/ChatGPT/tree/main/scripts/ask.js
 */

class ChatAsk {
  static sync(message) {
    const inputElement = document.querySelector('textarea');
    if (inputElement) {
      const nativeTextareaSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
      nativeTextareaSetter.call(inputElement, message);
      const inputEvent = new InputEvent('input', {
        bubbles: true,
        cancelable: true,
      });
      inputElement.dispatchEvent(inputEvent);
    }
  }

  static submit() {
    const btns = document.querySelectorAll('main form button');
    const btn = btns[btns.length - 1];

    if (btn) {
      btn.focus();
      btn.disabled = false;
      btn.click();
    }
  }
}

window.ChatAsk = ChatAsk;

================================================
FILE: src-tauri/src/core/cmd.rs
================================================
use tauri::{command, AppHandle, LogicalPosition, Manager, PhysicalSize};

use crate::core::{
    conf::AppConf,
    constant::{ASK_HEIGHT, TITLEBAR_HEIGHT},
};

#[command]
pub fn view_reload(app: AppHandle) {
    app.get_window("core")
        .unwrap()
        .get_webview("main")
        .unwrap()
        .eval("window.location.reload()")
        .unwrap();
}

#[command]
pub fn view_url(app: AppHandle) -> tauri::Url {
    app.get_window("core")
        .unwrap()
        .get_webview("main")
        .unwrap()
        .url()
        .unwrap()
}

#[command]
pub fn view_go_forward(app: AppHandle) {
    app.get_window("core")
        .unwrap()
        .get_webview("main")
        .unwrap()
        .eval("window.history.forward()")
        .unwrap();
}

#[command]
pub fn view_go_back(app: AppHandle) {
    app.get_window("core")
        .unwrap()
        .get_webview("main")
        .unwrap()
        .eval("window.history.back()")
        .unwrap();
}

#[command]
pub fn window_pin(app: AppHandle, pin: bool) {
    let conf = AppConf::load(&app).unwrap();
    conf.amend(serde_json::json!({"stay_on_top": pin}))
        .unwrap()
        .save(&app)
        .unwrap();

    app.get_window("core")
        .unwrap()
        .set_always_on_top(pin)
        .unwrap();
}

#[command]
pub fn ask_sync(app: AppHandle, message: String) {
    app.get_window("core")
        .unwrap()
        .get_webview("main")
        .unwrap()
        .eval(&format!("ChatAsk.sync({})", message))
        .unwrap();
}

#[command]
pub fn ask_send(app: AppHandle) {
    let win = app.get_window("core").unwrap();

    win.get_webview("main")
        .unwrap()
        .eval(
            r#"
        ChatAsk.submit();
        setTimeout(() => {
            __TAURI__.webview.Webview.getByLabel('ask')?.setFocus();
        }, 500);
        "#,
        )
        .unwrap();
}

#[command]
pub fn set_theme(app: AppHandle, theme: String) {
    let conf = AppConf::load(&app).unwrap();
    conf.amend(serde_json::json!({"theme": theme}))
        .unwrap()
        .save(&app)
        .unwrap();

    app.restart();
}

#[command]
pub fn get_app_conf(app: AppHandle) -> AppConf {
    AppConf::load(&app).unwrap()
}

#[command]
pub fn set_view_ask(app: AppHandle, enabled: bool) {
    let conf = AppConf::load(&app).unwrap();
    conf.amend(serde_json::json!({"ask_mode": enabled}))
        .unwrap()
        .save(&app)
        .unwrap();

    let core_window = app.get_window("core").unwrap();
    let ask_mode_height = if enabled { ASK_HEIGHT } else { 0.0 };
    let scale_factor = core_window.scale_factor().unwrap();
    let titlebar_height = (scale_factor * TITLEBAR_HEIGHT).round() as u32;
    let win_size = core_window.inner_size().unwrap();
    let ask_height = (scale_factor * ask_mode_height).round() as u32;

    let main_view = core_window.get_webview("main").unwrap();
    let titlebar_view = core_window.get_webview("titlebar").unwrap();
    let ask_view = core_window.get_webview("ask").unwrap();

    if enabled {
        ask_view.set_focus().unwrap();
    } else {
        main_view.set_focus().unwrap();
    }

    let set_view_properties =
        |view: &tauri::Webview, position: LogicalPosition<f64>, size: PhysicalSize<u32>| {
            if let Err(e) = view.set_position(position) {
                eprintln!("[cmd:view:position] Failed to set view position: {}", e);
            }
            if let Err(e) = view.set_size(size) {
                eprintln!("[cmd:view:size] Failed to set view size: {}", e);
            }
        };

    #[cfg(target_os = "macos")]
    {
        set_view_properties(
            &main_view,
            LogicalPosition::new(0.0, TITLEBAR_HEIGHT),
            PhysicalSize::new(
                win_size.width,
                win_size.height - (titlebar_height + ask_height),
            ),
        );
        set_view_properties(
            &titlebar_view,
            LogicalPosition::new(0.0, 0.0),
            PhysicalSize::new(win_size.width, titlebar_height),
        );
        set_view_properties(
            &ask_view,
            LogicalPosition::new(
                0.0,
                (win_size.height as f64 / scale_factor) - ask_mode_height,
            ),
            PhysicalSize::new(win_size.width, ask_height),
        );
    }

    #[cfg(not(target_os = "macos"))]
    {
        set_view_properties(
            &main_view,
            LogicalPosition::new(0.0, 0.0),
            PhysicalSize::new(
                win_size.width,
                win_size.height - (ask_height + titlebar_height),
            ),
        );
        set_view_properties(
            &titlebar_view,
            LogicalPosition::new(
                0.0,
                (win_size.height as f64 / scale_factor) - TITLEBAR_HEIGHT,
            ),
            PhysicalSize::new(win_size.width, titlebar_height),
        );
        set_view_properties(
            &ask_view,
            LogicalPosition::new(
                0.0,
                (win_size.height as f64 / scale_factor) - ask_mode_height - TITLEBAR_HEIGHT,
            ),
            PhysicalSize::new(win_size.width, ask_height),
        );
    }
}


================================================
FILE: src-tauri/src/core/conf.rs
================================================
use log::error;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{
    collections::BTreeMap,
    fs::{self, File},
    io::{Read, Write},
    path::PathBuf,
};
use tauri::{AppHandle, Manager, Theme};

#[derive(Serialize, Deserialize, Debug)]
pub struct AppConf {
    pub theme: String,
    pub stay_on_top: bool,
    pub ask_mode: bool,
    pub mac_titlebar_hidden: bool,
}

impl AppConf {
    pub fn new() -> Self {
        Self {
            theme: "system".to_string(),
            stay_on_top: false,
            ask_mode: false,
            #[cfg(target_os = "macos")]
            mac_titlebar_hidden: true,
            #[cfg(not(target_os = "macos"))]
            mac_titlebar_hidden: false,
        }
    }

    pub fn get_conf_path(app: &AppHandle) -> Result<PathBuf, Box<dyn std::error::Error>> {
        let config_dir = app
            .path()
            .config_dir()?
            .join("com.nofwl.chatgpt")
            .join("config.json");
        Ok(config_dir)
    }

    pub fn get_scripts_path(app: &AppHandle) -> Result<PathBuf, Box<dyn std::error::Error>> {
        let scripts_dir = app
            .path()
            .config_dir()?
            .join("com.nofwl.chatgpt")
            .join("scripts");
        Ok(scripts_dir)
    }

    pub fn load_script(app: &AppHandle, filename: &str) -> String {
        let script_file = Self::get_scripts_path(app).unwrap().join(filename);
        fs::read_to_string(script_file).unwrap_or_else(|_| "".to_string())
    }

    pub fn load(app: &AppHandle) -> Result<Self, Box<dyn std::error::Error>> {
        let path = Self::get_conf_path(app)?;

        if !path.exists() {
            let config = Self::new();
            config.save(app)?;
            return Ok(config);
        }

        let mut file = File::open(path)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        let config: Result<AppConf, _> = serde_json::from_str(&contents);

        // Handle conditional fields and fallback to defaults if necessary
        if let Err(e) = &config {
            error!("[conf::load] {}", e);
            let mut default_config = Self::new();
            default_config = default_config.amend(serde_json::from_str(&contents)?)?;
            default_config.save(app)?;
            return Ok(default_config);
        }

        Ok(config?)
    }

    pub fn save(&self, app: &AppHandle) -> Result<(), Box<dyn std::error::Error>> {
        let path = Self::get_conf_path(app)?;

        if let Some(dir) = path.parent() {
            fs::create_dir_all(dir)?;
        }

        let mut file = File::create(path)?;
        let contents = serde_json::to_string_pretty(self)?;
        // dbg!(&contents);
        file.write_all(contents.as_bytes())?;
        Ok(())
    }

    pub fn amend(self, json: Value) -> Result<Self, serde_json::Error> {
        let val = serde_json::to_value(self)?;
        let mut config: BTreeMap<String, Value> = serde_json::from_value(val)?;
        let new_json: BTreeMap<String, Value> = serde_json::from_value(json)?;

        for (k, v) in new_json {
            config.insert(k, v);
        }

        let config_str = serde_json::to_string_pretty(&config)?;
        serde_json::from_str::<AppConf>(&config_str).map_err(|err| {
            error!("[conf::amend] {}", err);
            err
        })
    }

    pub fn get_theme(app: &AppHandle) -> Theme {
        let theme = Self::load(app).unwrap().theme;
        match theme.as_str() {
            "system" => match dark_light::detect() {
                dark_light::Mode::Dark => Theme::Dark,
                dark_light::Mode::Light => Theme::Light,
                dark_light::Mode::Default => Theme::Light,
            },
            "dark" => Theme::Dark,
            _ => Theme::Light,
        }
    }
}


================================================
FILE: src-tauri/src/core/constant.rs
================================================
pub static TITLEBAR_HEIGHT: f64 = 28.0;
pub static ASK_HEIGHT: f64 = 120.0;

pub static WINDOW_SETTINGS: &str = "settings";

pub static INIT_SCRIPT: &str = r#"
window.addEventListener('DOMContentLoaded', function() {
    function handleUrlChange() {
        const url = window.location.href;
        if (url !== 'about:blank') {
            console.log('URL changed:', url);
            window.__TAURI__.webviewWindow.WebviewWindow.getByLabel('titlebar').emit('navigation:change', { url });
        }
    }

    function handleLinkClick(event) {
        const target = event.target;
        if (target.tagName === 'A' && target.target && target.target !== '_blank') {
            target.target = '_blank';
        }
    }

    document.addEventListener('click', handleLinkClick, true);
    window.addEventListener('popstate', handleUrlChange);
    window.addEventListener('pushState', handleUrlChange);
    window.addEventListener('replaceState', handleUrlChange);

    const originalPushState = history.pushState;
    const originalReplaceState = history.replaceState;

    history.pushState = function() {
        originalPushState.apply(this, arguments);
        console.log('pushState called');
        handleUrlChange();
    };

    history.replaceState = function() {
        originalReplaceState.apply(this, arguments);
        console.log('replaceState called');
        handleUrlChange();
    };

    handleUrlChange();
});
"#;


================================================
FILE: src-tauri/src/core/mod.rs
================================================
pub mod cmd;
pub mod conf;
pub mod constant;
pub mod setup;
pub mod template;
pub mod window;


================================================
FILE: src-tauri/src/core/setup.rs
================================================
use std::{
    path::PathBuf,
    sync::{Arc, Mutex},
};
use tauri::{
    webview::DownloadEvent, App, LogicalPosition, Manager, PhysicalSize, WebviewBuilder,
    WebviewUrl, WindowBuilder, WindowEvent,
};
use tauri_plugin_shell::ShellExt;

#[cfg(target_os = "macos")]
use tauri::TitleBarStyle;

use crate::core::{
    conf::AppConf,
    constant::{ASK_HEIGHT, INIT_SCRIPT, TITLEBAR_HEIGHT},
    template,
};

pub fn init(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
    let handle = app.handle();

    let conf = &AppConf::load(handle)?;
    let ask_mode_height = if conf.ask_mode { ASK_HEIGHT } else { 0.0 };

    template::Template::new(AppConf::get_scripts_path(handle)?);

    tauri::async_runtime::spawn({
        let handle = handle.clone();
        async move {
            let mut core_window = WindowBuilder::new(&handle, "core").title("ChatGPT");

            #[cfg(target_os = "macos")]
            {
                core_window = core_window
                    .title_bar_style(TitleBarStyle::Overlay)
                    .hidden_title(true);
            }

            core_window = core_window
                .resizable(true)
                .inner_size(800.0, 600.0)
                .min_inner_size(300.0, 200.0)
                .theme(Some(AppConf::get_theme(&handle)));

            let core_window = core_window
                .build()
                .expect("[core:window] Failed to build window");

            let win_size = core_window
                .inner_size()
                .expect("[core:window] Failed to get window size");
            // Wrap the window in Arc<Mutex<_>> to manage ownership across threads
            let window = Arc::new(Mutex::new(core_window));

            let main_view =
                WebviewBuilder::new("main", WebviewUrl::App("https://chatgpt.com".into()))
                    .auto_resize()
                    .on_download({
                        let app_handle = handle.clone();
                        let download_path = Arc::new(Mutex::new(PathBuf::new()));
                        move |_, event| {
                            match event {
                                DownloadEvent::Requested { destination, .. } => {
                                    let download_dir = app_handle
                                        .path()
                                        .download_dir()
                                        .expect("[view:download] Failed to get download directory");
                                    let mut locked_path = download_path
                                        .lock()
                                        .expect("[view:download] Failed to lock download path");
                                    *locked_path = download_dir.join(&destination);
                                    *destination = locked_path.clone();
                                }
                                DownloadEvent::Finished { success, .. } => {
                                    let final_path = download_path
                                        .lock()
                                        .expect("[view:download] Failed to lock download path")
                                        .clone();

                                    if success {
                                        app_handle
                                            .shell()
                                            .open(final_path.to_string_lossy(), None)
                                            .expect("[view:download] Failed to open file");
                                    }
                                }
                                _ => (),
                            }
                            true
                        }
                    })
                    .initialization_script(&AppConf::load_script(&handle, "ask.js"))
                    .initialization_script(INIT_SCRIPT);

            let titlebar_view = WebviewBuilder::new(
                "titlebar",
                WebviewUrl::App("index.html".into()),
            )
            .auto_resize();

            let ask_view =
                WebviewBuilder::new("ask", WebviewUrl::App("index.html".into()))
                    .auto_resize();

            let win = window.lock().unwrap();
            let scale_factor = win.scale_factor().unwrap();
            let titlebar_height = (scale_factor * TITLEBAR_HEIGHT).round() as u32;
            let ask_height = (scale_factor * ask_mode_height).round() as u32;

            #[cfg(target_os = "macos")]
            {
                let main_area_height = win_size.height - titlebar_height;

                win.add_child(
                    titlebar_view,
                    LogicalPosition::new(0, 0),
                    PhysicalSize::new(win_size.width, titlebar_height),
                )
                .unwrap();
                win.add_child(
                    ask_view,
                    LogicalPosition::new(
                        0.0,
                        (win_size.height as f64 / scale_factor) - ask_mode_height,
                    ),
                    PhysicalSize::new(win_size.width, ask_height),
                )
                .unwrap();
                win.add_child(
                    main_view,
                    LogicalPosition::new(0.0, TITLEBAR_HEIGHT),
                    PhysicalSize::new(win_size.width, main_area_height - ask_height),
                )
                .unwrap();
            }

            #[cfg(not(target_os = "macos"))]
            {
                win.add_child(
                    ask_view,
                    LogicalPosition::new(
                        0.0,
                        (win_size.height as f64 / scale_factor) - ask_mode_height,
                    ),
                    PhysicalSize::new(win_size.width, ask_height),
                )
                .unwrap();
                win.add_child(
                    titlebar_view,
                    LogicalPosition::new(
                        0.0,
                        (win_size.height as f64 / scale_factor) - ask_mode_height - TITLEBAR_HEIGHT,
                    ),
                    PhysicalSize::new(win_size.width, titlebar_height),
                )
                .unwrap();
                win.add_child(
                    main_view,
                    LogicalPosition::new(0.0, 0.0),
                    PhysicalSize::new(
                        win_size.width,
                        win_size.height - (ask_height + titlebar_height),
                    ),
                )
                .unwrap();
            }

            let window_clone = Arc::clone(&window);
            let set_view_properties =
                |view: &tauri::Webview, position: LogicalPosition<f64>, size: PhysicalSize<u32>| {
                    if let Err(e) = view.set_position(position) {
                        eprintln!("[view:position] Failed to set view position: {}", e);
                    }
                    if let Err(e) = view.set_size(size) {
                        eprintln!("[view:size] Failed to set view size: {}", e);
                    }
                };

            win.on_window_event(move |event| {
                let conf = &AppConf::load(&handle).unwrap();
                let ask_mode_height = if conf.ask_mode { ASK_HEIGHT } else { 0.0 };
                let ask_height = (scale_factor * ask_mode_height).round() as u32;

                if let WindowEvent::Resized(size) = event {
                    let win = window_clone.lock().unwrap();

                    let main_view = win
                        .get_webview("main")
                        .expect("[view:main] Failed to get webview window");
                    let titlebar_view = win
                        .get_webview("titlebar")
                        .expect("[view:titlebar] Failed to get webview window");
                    let ask_view = win
                        .get_webview("ask")
                        .expect("[view:ask] Failed to get webview window");

                    #[cfg(target_os = "macos")]
                    {
                        set_view_properties(
                            &main_view,
                            LogicalPosition::new(0.0, TITLEBAR_HEIGHT),
                            PhysicalSize::new(
                                size.width,
                                size.height - (titlebar_height + ask_height),
                            ),
                        );
                        set_view_properties(
                            &titlebar_view,
                            LogicalPosition::new(0.0, 0.0),
                            PhysicalSize::new(size.width, titlebar_height),
                        );
                        set_view_properties(
                            &ask_view,
                            LogicalPosition::new(
                                0.0,
                                (size.height as f64 / scale_factor) - ask_mode_height,
                            ),
                            PhysicalSize::new(size.width, ask_height),
                        );
                    }

                    #[cfg(not(target_os = "macos"))]
                    {
                        set_view_properties(
                            &main_view,
                            LogicalPosition::new(0.0, 0.0),
                            PhysicalSize::new(
                                size.width,
                                size.height - (ask_height + titlebar_height),
                            ),
                        );
                        set_view_properties(
                            &titlebar_view,
                            LogicalPosition::new(
                                0.0,
                                (size.height as f64 / scale_factor) - TITLEBAR_HEIGHT,
                            ),
                            PhysicalSize::new(size.width, titlebar_height),
                        );
                        set_view_properties(
                            &ask_view,
                            LogicalPosition::new(
                                0.0,
                                (size.height as f64 / scale_factor)
                                    - ask_mode_height
                                    - TITLEBAR_HEIGHT,
                            ),
                            PhysicalSize::new(size.width, ask_height),
                        );
                    }
                }
            });
        }
    });

    Ok(())
}


================================================
FILE: src-tauri/src/core/template.rs
================================================
use anyhow::{Context, Result};
use log::{error, info};
use regex::Regex;
use semver::Version;
use serde_json::json;
use std::{
    fs::{self, File},
    io::{Read, Write},
    path::Path,
};

pub static SCRIPT_ASK: &[u8] = include_bytes!("../../scripts/ask.js");

/// Struct representing the template with the script data.
#[derive(Debug)]
pub struct Template {
    pub ask: Vec<u8>,
}

impl Template {
    /// Creates a new Template instance, initializing it with the script data.
    pub fn new<P: AsRef<Path>>(template_dir: P) -> Self {
        let template_dir = template_dir.as_ref();
        let mut template = Template::default();

        let files = vec![(template_dir.join("ask.js"), &mut template.ask)];

        for (filename, _) in files {
            match update_or_create_file(&filename, SCRIPT_ASK) {
                Ok(updated) => {
                    if updated {
                        info!("Script updated or created: {}", filename.display());
                    } else {
                        info!("Script is up-to-date: {}", filename.display());
                    }
                }
                Err(e) => {
                    error!("Failed to process script, {}: {}", filename.display(), e);
                }
            }
        }

        template
    }
}

impl Default for Template {
    fn default() -> Template {
        Template {
            ask: Vec::from(SCRIPT_ASK),
        }
    }
}

/// Reads the version information from the given data.
fn read_version_info(data: &[u8]) -> Result<serde_json::Value> {
    let content = String::from_utf8_lossy(data);
    let re_name = Regex::new(r"@name\s+(.*?)\n").context("Failed to compile name regex")?;
    let re_version =
        Regex::new(r"@version\s+(.*?)\n").context("Failed to compile version regex")?;
    let re_url = Regex::new(r"@url\s+(.*?)\n").context("Failed to compile url regex")?;

    let name = re_name
        .captures(&content)
        .and_then(|cap| cap.get(1))
        .map_or(String::new(), |m| m.as_str().trim().to_string());

    let version = re_version
        .captures(&content)
        .and_then(|cap| cap.get(1))
        .map_or(String::new(), |m| m.as_str().trim().to_string());

    let url = re_url
        .captures(&content)
        .and_then(|cap| cap.get(1))
        .map_or(String::new(), |m| m.as_str().trim().to_string());

    let json_data = json!({
        "name": name,
        "version": version,
        "url": url,
    });

    Ok(json_data)
}

/// Reads the contents of the given file.
fn read_file_contents<P: AsRef<Path>>(filename: P) -> Result<Vec<u8>> {
    let filename = filename.as_ref();
    let mut file = File::open(filename)?;
    let mut contents = Vec::new();
    file.read_to_end(&mut contents)?;
    Ok(contents)
}

/// Writes the given data to the specified file.
fn write_file_contents<P: AsRef<Path>>(filename: P, data: &[u8]) -> Result<()> {
    let filename = filename.as_ref();
    let mut file = File::create(filename)?;
    file.write_all(data)?;
    Ok(())
}

/// Creates the necessary directories for the specified file path.
fn create_dir<P: AsRef<Path>>(filename: P) -> Result<()> {
    let filename = filename.as_ref();
    if let Some(parent) = filename.parent() {
        if !parent.exists() {
            fs::create_dir_all(parent)?;
        }
    }
    Ok(())
}

/// Updates the file if the new data has a newer version or if version info is missing,
/// or creates the file if it doesn't exist.
fn update_or_create_file<P: AsRef<Path>>(filename: P, new_data: &[u8]) -> Result<bool> {
    let filename = filename.as_ref();

    // Ensure directory exists
    create_dir(filename)?;

    let current_data = read_file_contents(filename);

    match current_data {
        Ok(current_data) => {
            let new_info = read_version_info(new_data)?;
            let current_info = read_version_info(&current_data);

            match (
                new_info.get("version").and_then(|v| v.as_str()),
                current_info,
            ) {
                (Some(new_version), Ok(current_info)) => {
                    let current_version = current_info
                        .get("version")
                        .and_then(|v| v.as_str())
                        .unwrap_or("");

                    if current_version.is_empty()
                        || Version::parse(new_version)? > Version::parse(current_version)?
                    {
                        write_file_contents(filename, new_data)?;
                        info!("{} → {}", current_version, new_version);
                        Ok(true)
                    } else {
                        Ok(false)
                    }
                }
                // If there is an error reading current version info, update the file
                (Some(_), Err(_)) => {
                    write_file_contents(filename, new_data)?;
                    Ok(true)
                }
                (None, _) => {
                    // If there is an error reading new version info, don't update the file
                    Ok(false)
                }
            }
        }
        Err(_) => {
            // If there is an error reading the current file, create a new file
            write_file_contents(filename, new_data)?;
            Ok(true)
        }
    }
}


================================================
FILE: src-tauri/src/core/window.rs
================================================
use tauri::{command, AppHandle, Manager, WebviewUrl, WebviewWindowBuilder};

use crate::core::constant::WINDOW_SETTINGS;

#[command]
pub fn open_settings(app: AppHandle) {
  match app.get_webview_window(WINDOW_SETTINGS) {
    Some(window) => {
      window.show().unwrap();
    }
    None => {
      WebviewWindowBuilder::new(&app, WINDOW_SETTINGS, WebviewUrl::App("index.html".into()))
        .build()
        .unwrap();
    }
  }
}


================================================
FILE: src-tauri/src/main.rs
================================================
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod core;
use core::{cmd, setup, window};

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_os::init())
        .plugin(tauri_plugin_shell::init())
        .plugin(tauri_plugin_dialog::init())
        .invoke_handler(tauri::generate_handler![
            cmd::view_reload,
            cmd::view_url,
            cmd::view_go_forward,
            cmd::view_go_back,
            cmd::set_view_ask,
            cmd::get_app_conf,
            cmd::window_pin,
            cmd::ask_sync,
            cmd::ask_send,
            cmd::set_theme,
            window::open_settings,
        ])
        .setup(setup::init)
        .run(tauri::generate_context!())
        .expect("error while running lencx/ChatGPT application");
}


================================================
FILE: src-tauri/tauri.conf.json
================================================
{
  "productName": "ChatGPT",
  "version": "../package.json",
  "identifier": "com.nofwl.chatgpt",
  "build": {
    "beforeDevCommand": "pnpm dev",
    "devUrl": "http://localhost:1420",
    "beforeBuildCommand": "pnpm build",
    "frontendDist": "../dist"
  },
  "app": {
    "withGlobalTauri": true,
    "windows": [],
    "security": {
      "csp": null
    }
  },
  "bundle": {
    "active": true,
    "targets": "all",
    "icon": [
      "icons/32x32.png",
      "icons/128x128.png",
      "icons/128x128@2x.png",
      "icons/icon.icns",
      "icons/icon.ico"
    ]
  }
}


================================================
FILE: tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
export default {
  content: [
    './index.html',
    './src/**/*.{js,jsx,ts,tsx}',
  ],
  theme: {
    screens: {
      tablet: '480px',
    },
    extend: {
      colors: {
        'app-gray-1': '#171717',
        'app-gray-2': '#212121',
        'app-gray-3': '#2f2f2f;',
        'app-active': '#10a37f',
      }
    },
  },
  plugins: [
    require('@tailwindcss/typography'),
    require('autoprefixer'),
  ],
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,

    "baseUrl": ".",
    "paths": {
      "~/*": ["src/*"],
      "~components/*": ["src/components/*"],
      "~view/*": ["src/view/*"],
      "~hooks/*": ["src/hooks/*"],
      "~utils/*": ["src/utils/*"],
      "~icons/*": ["src/icons/*"],
      "~layout/*": ["src/layout/*"]
    }
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}


================================================
FILE: tsconfig.node.json
================================================
{
  "compilerOptions": {
    "composite": true,
    "skipLibCheck": true,
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}


================================================
FILE: vite.config.ts
================================================
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';

// https://vitejs.dev/config/
export default defineConfig(async () => ({
  plugins: [tsconfigPaths(), react()],

  // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
  //
  // 1. prevent vite from obscuring rust errors
  clearScreen: false,
  // 2. tauri expects a fixed port, fail if that port is not available
  server: {
    port: 1420,
    strictPort: true,
    watch: {
      // 3. tell vite to ignore watching `src-tauri`
      ignored: ['**/src-tauri/**'],
    },
  },
}));
Download .txt
gitextract_vymxshi2/

├── .gitignore
├── .vscode/
│   └── extensions.json
├── Cargo.toml
├── README.md
├── index.html
├── package.json
├── postcss.config.js
├── rustfmt.toml
├── src/
│   ├── App.tsx
│   ├── base.css
│   ├── components/
│   │   └── WinTitlebar.tsx
│   ├── hooks/
│   │   ├── useInfo.tsx
│   │   └── useTheme.tsx
│   ├── icons/
│   │   ├── ArrowLeft.tsx
│   │   ├── Ask.tsx
│   │   ├── Link.tsx
│   │   ├── Pin.tsx
│   │   ├── Reload.tsx
│   │   ├── SVGWrap.tsx
│   │   ├── Send.tsx
│   │   ├── Setting.tsx
│   │   ├── ThemeDark.tsx
│   │   ├── ThemeLight.tsx
│   │   ├── ThemeSystem.tsx
│   │   ├── UnPin.tsx
│   │   ├── WindowClose.tsx
│   │   ├── WindowMaximize.tsx
│   │   ├── WindowMinimize.tsx
│   │   └── WindowRestore.tsx
│   ├── main.tsx
│   ├── types.d.ts
│   ├── view/
│   │   ├── Ask.tsx
│   │   ├── Settings.tsx
│   │   └── Titlebar.tsx
│   └── vite-env.d.ts
├── src-tauri/
│   ├── .gitignore
│   ├── Cargo.toml
│   ├── Info.plist
│   ├── build.rs
│   ├── capabilities/
│   │   └── desktop.json
│   ├── icons/
│   │   └── icon.icns
│   ├── scripts/
│   │   └── ask.js
│   ├── src/
│   │   ├── core/
│   │   │   ├── cmd.rs
│   │   │   ├── conf.rs
│   │   │   ├── constant.rs
│   │   │   ├── mod.rs
│   │   │   ├── setup.rs
│   │   │   ├── template.rs
│   │   │   └── window.rs
│   │   └── main.rs
│   └── tauri.conf.json
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
Download .txt
SYMBOL INDEX (59 symbols across 32 files)

FILE: src-tauri/build.rs
  function main (line 3) | fn main() {

FILE: src-tauri/scripts/ask.js
  class ChatAsk (line 7) | class ChatAsk {
    method sync (line 8) | static sync(message) {
    method submit (line 21) | static submit() {

FILE: src-tauri/src/core/cmd.rs
  function view_reload (line 9) | pub fn view_reload(app: AppHandle) {
  function view_url (line 19) | pub fn view_url(app: AppHandle) -> tauri::Url {
  function view_go_forward (line 29) | pub fn view_go_forward(app: AppHandle) {
  function view_go_back (line 39) | pub fn view_go_back(app: AppHandle) {
  function window_pin (line 49) | pub fn window_pin(app: AppHandle, pin: bool) {
  function ask_sync (line 63) | pub fn ask_sync(app: AppHandle, message: String) {
  function ask_send (line 73) | pub fn ask_send(app: AppHandle) {
  function set_theme (line 90) | pub fn set_theme(app: AppHandle, theme: String) {
  function get_app_conf (line 101) | pub fn get_app_conf(app: AppHandle) -> AppConf {
  function set_view_ask (line 106) | pub fn set_view_ask(app: AppHandle, enabled: bool) {

FILE: src-tauri/src/core/conf.rs
  type AppConf (line 13) | pub struct AppConf {
    method new (line 21) | pub fn new() -> Self {
    method get_conf_path (line 33) | pub fn get_conf_path(app: &AppHandle) -> Result<PathBuf, Box<dyn std::...
    method get_scripts_path (line 42) | pub fn get_scripts_path(app: &AppHandle) -> Result<PathBuf, Box<dyn st...
    method load_script (line 51) | pub fn load_script(app: &AppHandle, filename: &str) -> String {
    method load (line 56) | pub fn load(app: &AppHandle) -> Result<Self, Box<dyn std::error::Error...
    method save (line 82) | pub fn save(&self, app: &AppHandle) -> Result<(), Box<dyn std::error::...
    method amend (line 96) | pub fn amend(self, json: Value) -> Result<Self, serde_json::Error> {
    method get_theme (line 112) | pub fn get_theme(app: &AppHandle) -> Theme {

FILE: src-tauri/src/core/setup.rs
  function init (line 20) | pub fn init(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {

FILE: src-tauri/src/core/template.rs
  type Template (line 16) | pub struct Template {
    method new (line 22) | pub fn new<P: AsRef<Path>>(template_dir: P) -> Self {
  method default (line 48) | fn default() -> Template {
  function read_version_info (line 56) | fn read_version_info(data: &[u8]) -> Result<serde_json::Value> {
  function read_file_contents (line 88) | fn read_file_contents<P: AsRef<Path>>(filename: P) -> Result<Vec<u8>> {
  function write_file_contents (line 97) | fn write_file_contents<P: AsRef<Path>>(filename: P, data: &[u8]) -> Resu...
  function create_dir (line 105) | fn create_dir<P: AsRef<Path>>(filename: P) -> Result<()> {
  function update_or_create_file (line 117) | fn update_or_create_file<P: AsRef<Path>>(filename: P, new_data: &[u8]) -...

FILE: src-tauri/src/core/window.rs
  function open_settings (line 6) | pub fn open_settings(app: AppHandle) {

FILE: src-tauri/src/main.rs
  function main (line 7) | fn main() {

FILE: src/App.tsx
  function App (line 13) | function App() {

FILE: src/components/WinTitlebar.tsx
  function WinTitlebar (line 9) | function WinTitlebar() {

FILE: src/hooks/useInfo.tsx
  function useInfo (line 4) | function useInfo() {

FILE: src/hooks/useTheme.tsx
  function useTheme (line 4) | function useTheme() {

FILE: src/icons/ArrowLeft.tsx
  function ArrowLeft (line 3) | function ArrowLeft(props: I.SVG) {

FILE: src/icons/Ask.tsx
  function Ask (line 3) | function Ask(props: I.SVG) {

FILE: src/icons/Link.tsx
  function Link (line 3) | function Link(props: I.SVG) {

FILE: src/icons/Pin.tsx
  function Pin (line 3) | function Pin(props: I.SVG) {

FILE: src/icons/Reload.tsx
  function Reload (line 3) | function Reload(props: I.SVG) {

FILE: src/icons/SVGWrap.tsx
  function SVGWrap (line 4) | function SVGWrap({ size = 18, children, type, className, title, onClick,...

FILE: src/icons/Send.tsx
  function Send (line 3) | function Send(props: I.SVG) {

FILE: src/icons/Setting.tsx
  function Setting (line 3) | function Setting(props: I.SVG) {

FILE: src/icons/ThemeDark.tsx
  function ThemeDark (line 3) | function ThemeDark(props: I.SVG) {

FILE: src/icons/ThemeLight.tsx
  function ThemeLight (line 3) | function ThemeLight(props: I.SVG) {

FILE: src/icons/ThemeSystem.tsx
  function Ask (line 3) | function Ask(props: I.SVG) {

FILE: src/icons/UnPin.tsx
  function UnPin (line 3) | function UnPin(props: I.SVG) {

FILE: src/icons/WindowClose.tsx
  function WindowClose (line 3) | function WindowClose(props: I.SVG) {

FILE: src/icons/WindowMaximize.tsx
  function WindowMaximize (line 3) | function WindowMaximize(props: I.SVG) {

FILE: src/icons/WindowMinimize.tsx
  function WindowMinimize (line 3) | function WindowMinimize(props: I.SVG) {

FILE: src/icons/WindowRestore.tsx
  function WindowRestore (line 3) | function WindowRestore(props: I.SVG) {

FILE: src/types.d.ts
  type AppConf (line 2) | type AppConf = {
  type SVG (line 9) | interface SVG extends React.SVGProps<SVGSVGElement> {

FILE: src/view/Ask.tsx
  function ChatInput (line 8) | function ChatInput() {

FILE: src/view/Settings.tsx
  function Settings (line 1) | function Settings() {

FILE: src/view/Titlebar.tsx
  function Titlebar (line 20) | function Titlebar() {
Condensed preview — 55 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (63K chars).
[
  {
    "path": ".gitignore",
    "chars": 269,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\n# rust\ntarget/\n\nnode_"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 80,
    "preview": "{\n  \"recommendations\": [\"tauri-apps.tauri-vscode\", \"rust-lang.rust-analyzer\"]\n}\n"
  },
  {
    "path": "Cargo.toml",
    "chars": 231,
    "preview": "[workspace]\nresolver = \"2\"\nmembers = [\"src-tauri\"]\n\n# https://v2.tauri.app/test/webdriver/example/#adding-tauri-to-the-c"
  },
  {
    "path": "README.md",
    "chars": 1813,
    "preview": "<p align=\"center\">\n  <img width=\"180\" src=\"./public/ChatGPT.png\" alt=\"ChatGPT\">\n  <p align=\"center\">ChatGPT Desktop Appl"
  },
  {
    "path": "index.html",
    "chars": 294,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-w"
  },
  {
    "path": "package.json",
    "chars": 990,
    "preview": "{\n  \"name\": \"chatgpt\",\n  \"private\": true,\n  \"version\": \"2.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n  "
  },
  {
    "path": "postcss.config.js",
    "chars": 79,
    "preview": "export default {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n}"
  },
  {
    "path": "rustfmt.toml",
    "chars": 333,
    "preview": "max_width = 100\nhard_tabs = false\ntab_spaces = 4\nnewline_style = \"Auto\"\nuse_small_heuristics = \"Default\"\nreorder_imports"
  },
  {
    "path": "src/App.tsx",
    "chars": 398,
    "preview": "import { getCurrentWebview } from '@tauri-apps/api/webview';\n\nimport Titlebar from '~view/Titlebar';\nimport Ask from '~v"
  },
  {
    "path": "src/base.css",
    "chars": 143,
    "preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nhtml, body, #root {\n  margin: 0;\n  bottom: 0;\n  height: 100%"
  },
  {
    "path": "src/components/WinTitlebar.tsx",
    "chars": 1274,
    "preview": "import { useEffect, useState } from 'react';\nimport { Window } from '@tauri-apps/api/window';\n\nimport WindowClose from '"
  },
  {
    "path": "src/hooks/useInfo.tsx",
    "chars": 465,
    "preview": "import { useEffect, useState } from 'react';\nimport { platform as TauriPlatform } from '@tauri-apps/plugin-os';\n\nexport "
  },
  {
    "path": "src/hooks/useTheme.tsx",
    "chars": 562,
    "preview": "import { useState, useEffect } from 'react';\nimport { getCurrentWindow } from '@tauri-apps/api/window';\n\nexport default "
  },
  {
    "path": "src/icons/ArrowLeft.tsx",
    "chars": 399,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function ArrowLeft(props: I.SVG) {\n  return (\n    <SVGWrap {...props} v"
  },
  {
    "path": "src/icons/Ask.tsx",
    "chars": 439,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function Ask(props: I.SVG) {\n  return (\n    <SVGWrap {...props} viewBox"
  },
  {
    "path": "src/icons/Link.tsx",
    "chars": 791,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function Link(props: I.SVG) {\n  return (\n    <SVGWrap {...props} viewBo"
  },
  {
    "path": "src/icons/Pin.tsx",
    "chars": 346,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function Pin(props: I.SVG) {\n  return (\n    <SVGWrap {...props} viewBox"
  },
  {
    "path": "src/icons/Reload.tsx",
    "chars": 541,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function Reload(props: I.SVG) {\n  return (\n    <SVGWrap {...props} view"
  },
  {
    "path": "src/icons/SVGWrap.tsx",
    "chars": 815,
    "preview": "import React from 'react';\nimport clsx from 'clsx';\n\nexport default function SVGWrap({ size = 18, children, type, classN"
  },
  {
    "path": "src/icons/Send.tsx",
    "chars": 994,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function Send(props: I.SVG) {\n  return (\n    <SVGWrap {...props} viewBo"
  },
  {
    "path": "src/icons/Setting.tsx",
    "chars": 770,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function Setting(props: I.SVG) {\n  return (\n    <SVGWrap {...props} vie"
  },
  {
    "path": "src/icons/ThemeDark.tsx",
    "chars": 622,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function ThemeDark(props: I.SVG) {\n  return (\n    <SVGWrap {...props} v"
  },
  {
    "path": "src/icons/ThemeLight.tsx",
    "chars": 1222,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function ThemeLight(props: I.SVG) {\n  return (\n    <SVGWrap {...props} "
  },
  {
    "path": "src/icons/ThemeSystem.tsx",
    "chars": 287,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function Ask(props: I.SVG) {\n  return (\n    <SVGWrap {...props} viewBox"
  },
  {
    "path": "src/icons/UnPin.tsx",
    "chars": 377,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function UnPin(props: I.SVG) {\n  return (\n    <SVGWrap {...props} viewB"
  },
  {
    "path": "src/icons/WindowClose.tsx",
    "chars": 315,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function WindowClose(props: I.SVG) {\n  return (\n    <SVGWrap {...props}"
  },
  {
    "path": "src/icons/WindowMaximize.tsx",
    "chars": 232,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function WindowMaximize(props: I.SVG) {\n  return (\n    <SVGWrap {...pro"
  },
  {
    "path": "src/icons/WindowMinimize.tsx",
    "chars": 220,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function WindowMinimize(props: I.SVG) {\n  return (\n    <SVGWrap {...pro"
  },
  {
    "path": "src/icons/WindowRestore.tsx",
    "chars": 257,
    "preview": "import SVGWrap from './SVGWrap';\n\nexport default function WindowRestore(props: I.SVG) {\n  return (\n    <SVGWrap {...prop"
  },
  {
    "path": "src/main.tsx",
    "chars": 281,
    "preview": "import { StrictMode } from 'react';\nimport ReactDOM from 'react-dom/client';\n\n// import Routes from './routes';\nimport A"
  },
  {
    "path": "src/types.d.ts",
    "chars": 382,
    "preview": "declare namespace I {\n  export type AppConf = {\n    theme: 'light' | 'dark' | 'system';\n    stay_on_top: boolean;\n    as"
  },
  {
    "path": "src/view/Ask.tsx",
    "chars": 2128,
    "preview": "import { useState, useEffect, useRef } from 'react';\nimport { invoke } from '@tauri-apps/api/core';\nimport { useHotkeys "
  },
  {
    "path": "src/view/Settings.tsx",
    "chars": 77,
    "preview": "export default function Settings() {\n  return (\n    <div>Settings</div>\n  )\n}"
  },
  {
    "path": "src/view/Titlebar.tsx",
    "chars": 5247,
    "preview": "import { useEffect, useState, useMemo } from 'react';\nimport { invoke } from '@tauri-apps/api/core';\nimport { getCurrent"
  },
  {
    "path": "src/vite-env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "src-tauri/.gitignore",
    "chars": 166,
    "preview": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n\n# Generated by Tauri\n# will have schema files "
  },
  {
    "path": "src-tauri/Cargo.toml",
    "chars": 849,
    "preview": "[package]\nname = \"chatgpt\"\nversion = \"0.0.0\"\ndescription = \"ChatGPT Desktop Application (Unofficial)\"\nauthors = [\"lencx "
  },
  {
    "path": "src-tauri/Info.plist",
    "chars": 513,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "src-tauri/build.rs",
    "chars": 126,
    "preview": "// src-tauri/build.rs\n\nfn main() {\n    println!(\"cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.13\");\n    tauri_build::buil"
  },
  {
    "path": "src-tauri/capabilities/desktop.json",
    "chars": 1004,
    "preview": "{\n  \"$schema\": \"../gen/schemas/desktop-schema.json\",\n  \"identifier\": \"desktop-capability\",\n  \"windows\": [\n    \"*\"\n  ],\n "
  },
  {
    "path": "src-tauri/scripts/ask.js",
    "chars": 820,
    "preview": "/**\n * @name ask.js\n * @version 0.1.0\n * @url https://github.com/lencx/ChatGPT/tree/main/scripts/ask.js\n */\n\nclass ChatA"
  },
  {
    "path": "src-tauri/src/core/cmd.rs",
    "chars": 5156,
    "preview": "use tauri::{command, AppHandle, LogicalPosition, Manager, PhysicalSize};\n\nuse crate::core::{\n    conf::AppConf,\n    cons"
  },
  {
    "path": "src-tauri/src/core/conf.rs",
    "chars": 3826,
    "preview": "use log::error;\nuse serde::{Deserialize, Serialize};\nuse serde_json::Value;\nuse std::{\n    collections::BTreeMap,\n    fs"
  },
  {
    "path": "src-tauri/src/core/constant.rs",
    "chars": 1437,
    "preview": "pub static TITLEBAR_HEIGHT: f64 = 28.0;\npub static ASK_HEIGHT: f64 = 120.0;\n\npub static WINDOW_SETTINGS: &str = \"setting"
  },
  {
    "path": "src-tauri/src/core/mod.rs",
    "chars": 94,
    "preview": "pub mod cmd;\npub mod conf;\npub mod constant;\npub mod setup;\npub mod template;\npub mod window;\n"
  },
  {
    "path": "src-tauri/src/core/setup.rs",
    "chars": 10603,
    "preview": "use std::{\n    path::PathBuf,\n    sync::{Arc, Mutex},\n};\nuse tauri::{\n    webview::DownloadEvent, App, LogicalPosition, "
  },
  {
    "path": "src-tauri/src/core/template.rs",
    "chars": 5330,
    "preview": "use anyhow::{Context, Result};\nuse log::{error, info};\nuse regex::Regex;\nuse semver::Version;\nuse serde_json::json;\nuse "
  },
  {
    "path": "src-tauri/src/core/window.rs",
    "chars": 435,
    "preview": "use tauri::{command, AppHandle, Manager, WebviewUrl, WebviewWindowBuilder};\n\nuse crate::core::constant::WINDOW_SETTINGS;"
  },
  {
    "path": "src-tauri/src/main.rs",
    "chars": 888,
    "preview": "// Prevents additional console window on Windows in release, DO NOT REMOVE!!\n#![cfg_attr(not(debug_assertions), windows_"
  },
  {
    "path": "src-tauri/tauri.conf.json",
    "chars": 580,
    "preview": "{\n  \"productName\": \"ChatGPT\",\n  \"version\": \"../package.json\",\n  \"identifier\": \"com.nofwl.chatgpt\",\n  \"build\": {\n    \"bef"
  },
  {
    "path": "tailwind.config.js",
    "chars": 461,
    "preview": "/** @type {import('tailwindcss').Config} */\nexport default {\n  content: [\n    './index.html',\n    './src/**/*.{js,jsx,ts"
  },
  {
    "path": "tsconfig.json",
    "chars": 891,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM."
  },
  {
    "path": "tsconfig.node.json",
    "chars": 213,
    "preview": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\""
  },
  {
    "path": "vite.config.ts",
    "chars": 655,
    "preview": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport tsconfigPaths from 'vite-tsconfig-"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the lencx/ChatGPT GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 55 files (56.4 KB), approximately 17.5k tokens, and a symbol index with 59 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!