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 ================================================

ChatGPT

ChatGPT Desktop Application (Available on Mac, Windows, and Linux)

[![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) Buy Me A Coffee --- > [!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 ================================================ ChatGPT
================================================ 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: , ask: , 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 (
{isMax ? : }
) } ================================================ 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('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 ( ); } ================================================ FILE: src/icons/Ask.tsx ================================================ import SVGWrap from './SVGWrap'; export default function Ask(props: I.SVG) { return ( ); } ================================================ FILE: src/icons/Link.tsx ================================================ import SVGWrap from './SVGWrap'; export default function Link(props: I.SVG) { return ( ); } ================================================ FILE: src/icons/Pin.tsx ================================================ import SVGWrap from './SVGWrap'; export default function Pin(props: I.SVG) { return ( ); } ================================================ FILE: src/icons/Reload.tsx ================================================ import SVGWrap from './SVGWrap'; export default function Reload(props: I.SVG) { return ( ); } ================================================ 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 ( {children} ); } ================================================ FILE: src/icons/Send.tsx ================================================ import SVGWrap from './SVGWrap'; export default function Send(props: I.SVG) { return ( ); } ================================================ FILE: src/icons/Setting.tsx ================================================ import SVGWrap from './SVGWrap'; export default function Setting(props: I.SVG) { return ( ); } ================================================ FILE: src/icons/ThemeDark.tsx ================================================ import SVGWrap from './SVGWrap'; export default function ThemeDark(props: I.SVG) { return ( ); } ================================================ FILE: src/icons/ThemeLight.tsx ================================================ import SVGWrap from './SVGWrap'; export default function ThemeLight(props: I.SVG) { return ( ); } ================================================ FILE: src/icons/ThemeSystem.tsx ================================================ import SVGWrap from './SVGWrap'; export default function Ask(props: I.SVG) { return ( ); } ================================================ FILE: src/icons/UnPin.tsx ================================================ import SVGWrap from './SVGWrap'; export default function UnPin(props: I.SVG) { return ( ); } ================================================ FILE: src/icons/WindowClose.tsx ================================================ import SVGWrap from './SVGWrap'; export default function WindowClose(props: I.SVG) { return ( ); } ================================================ FILE: src/icons/WindowMaximize.tsx ================================================ import SVGWrap from './SVGWrap'; export default function WindowMaximize(props: I.SVG) { return ( ); } ================================================ FILE: src/icons/WindowMinimize.tsx ================================================ import SVGWrap from './SVGWrap'; export default function WindowMinimize(props: I.SVG) { return ( ); } ================================================ FILE: src/icons/WindowRestore.tsx ================================================ import SVGWrap from './SVGWrap'; export default function WindowRestore(props: I.SVG) { return ( ); } ================================================ 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( ); ================================================ 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 { 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(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) => { 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 (