From af172388958fe599b9f3755651b650d607199ef8 Mon Sep 17 00:00:00 2001 From: "xhunmon@126.com" Date: Thu, 6 Apr 2023 09:40:53 +0800 Subject: [PATCH] ChatzGPT-UI --- 008-ChatGPT-UI/.gitignore | 129 ++++++++++++++++++ 008-ChatGPT-UI/README.md | 41 ++++++ 008-ChatGPT-UI/config.ini | 10 ++ 008-ChatGPT-UI/config.json | 10 ++ 008-ChatGPT-UI/doc/1.jpg | Bin 0 -> 50559 bytes 008-ChatGPT-UI/doc/config.json | 10 ++ 008-ChatGPT-UI/doc/pyinstaller.sh | 7 + 008-ChatGPT-UI/gpt.py | 138 +++++++++++++++++++ 008-ChatGPT-UI/logo.ico | Bin 0 -> 38078 bytes 008-ChatGPT-UI/main.py | 211 ++++++++++++++++++++++++++++++ 008-ChatGPT-UI/requirements.txt | 3 + 008-ChatGPT-UI/utils.py | 149 +++++++++++++++++++++ README.md | 6 +- 13 files changed, 712 insertions(+), 2 deletions(-) create mode 100644 008-ChatGPT-UI/.gitignore create mode 100644 008-ChatGPT-UI/README.md create mode 100644 008-ChatGPT-UI/config.ini create mode 100644 008-ChatGPT-UI/config.json create mode 100644 008-ChatGPT-UI/doc/1.jpg create mode 100644 008-ChatGPT-UI/doc/config.json create mode 100644 008-ChatGPT-UI/doc/pyinstaller.sh create mode 100755 008-ChatGPT-UI/gpt.py create mode 100644 008-ChatGPT-UI/logo.ico create mode 100644 008-ChatGPT-UI/main.py create mode 100644 008-ChatGPT-UI/requirements.txt create mode 100644 008-ChatGPT-UI/utils.py diff --git a/008-ChatGPT-UI/.gitignore b/008-ChatGPT-UI/.gitignore new file mode 100644 index 0000000..53eb059 --- /dev/null +++ b/008-ChatGPT-UI/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ +.idea/ diff --git a/008-ChatGPT-UI/README.md b/008-ChatGPT-UI/README.md new file mode 100644 index 0000000..f156109 --- /dev/null +++ b/008-ChatGPT-UI/README.md @@ -0,0 +1,41 @@ +Build your ChatGPT app. + +![GPT-UI](doc/1.jpg) + + +# Run +- 1.clone this repo +- 2.edit your config.json +```json +{ + "key": "your api key", //create at https://platform.openai.com/account/api-keys + "api_base": "", + "model": "gpt-3.5-turbo", //support: gpt-3.5-turbo/gpt-4/gpt-4-32k + "stream": true, + "response": true, + "folder": "/Users/Qincji/Desktop/gptfileout/", //directory where chat logs are saved + "repeat": true, //if false: all chats in one file. if true: each chat is in a new file + "proxy": "socks5://127.0.0.1:7890" //support proxy: HTTP/HTTPS/SOCKS4A/SOCKS5 +} + +``` +- 3.run main.py + + +# Build App +- 4.install pyinstaller +- 5.cd `GPT-UI` +- 6.run `pyinstaller --windowed --name GPT-UI --add-data "config.ini:." --icon logo.ico main.py gpt.py utils.py` + + +# Feature +- [x] Support model: gpt-3.5-turbo/gpt-4/gpt-4-32k +- [x] Support for exporting chat logs to files +- [x] Support proxy: HTTP/HTTPS/SOCKS4A/SOCKS5 +- [x] Build MacOS App +- [x] Build Window exe +- [ ] Support for generating images + +# Link +- API key generated: [https://platform.openai.com/account/api-keys](https://platform.openai.com/account/api-keys) +- [https://github.com/evilpan/gptcli](https://github.com/evilpan/gptcli) diff --git a/008-ChatGPT-UI/config.ini b/008-ChatGPT-UI/config.ini new file mode 100644 index 0000000..dc1f1ba --- /dev/null +++ b/008-ChatGPT-UI/config.ini @@ -0,0 +1,10 @@ +[common] +expired_time=2025/12/15 23:59:59 + +title=GPT-UI + +version_name=v1.0.1--github/xhunmon + +version_code=1010 + +email=xhunmon@126.com \ No newline at end of file diff --git a/008-ChatGPT-UI/config.json b/008-ChatGPT-UI/config.json new file mode 100644 index 0000000..061df14 --- /dev/null +++ b/008-ChatGPT-UI/config.json @@ -0,0 +1,10 @@ +{ + "key": "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", + "api_base": "", + "model": "gpt-3.5-turbo", + "stream": true, + "response": true, + "folder": "", + "repeat": true, + "proxy": "socks5://127.0.0.1:7890" +} \ No newline at end of file diff --git a/008-ChatGPT-UI/doc/1.jpg b/008-ChatGPT-UI/doc/1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3307c708d13c28a9e3d49c0daa854e0e97f4b32e GIT binary patch literal 50559 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 literal 0 HcmV?d00001 diff --git a/008-ChatGPT-UI/doc/config.json b/008-ChatGPT-UI/doc/config.json new file mode 100644 index 0000000..76b70cd --- /dev/null +++ b/008-ChatGPT-UI/doc/config.json @@ -0,0 +1,10 @@ +{ + "api_base": "", + "key": "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", + "model": "gpt-3.5-turbo", + "stream": true, + "response": true, + "folder": "/Users/Qincji/Desktop/develop/py/opengpt/gptcli/doc/", + "repeat": true, + "proxy": "socks5://127.0.0.1:7890" +} \ No newline at end of file diff --git a/008-ChatGPT-UI/doc/pyinstaller.sh b/008-ChatGPT-UI/doc/pyinstaller.sh new file mode 100644 index 0000000..6a2cf98 --- /dev/null +++ b/008-ChatGPT-UI/doc/pyinstaller.sh @@ -0,0 +1,7 @@ +#!/bin/bash + + +pyinstaller --windowed --name GPT-UI --add-data "config.ini:." --icon logo.ico main.py gpt.py utils.py + +#if use --onefile, the build file is small, but star very slow. +#pyinstaller --onefile --windowed --name GPT-UI --add-data "config.ini:." --icon logo.ico main.py gpt.py utils.py diff --git a/008-ChatGPT-UI/gpt.py b/008-ChatGPT-UI/gpt.py new file mode 100755 index 0000000..feb6212 --- /dev/null +++ b/008-ChatGPT-UI/gpt.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +@Description:gpt.py +@Date :2023/03/31 +@Author :xhunmon +@Mail :xhunmon@126.com +""" + +import time +from datetime import datetime + +from utils import * + + +class Gpt(object): + func_ui_print = None + + def __init__(self, config: Config): + self.session = [] + self.api_prompt = [] + self.update_config(config) + self.content = "" + self.is_change = False + self.is_finish = True + gpt_t = threading.Thread(target=self.start) + gpt_t.setDaemon(True) + gpt_t.start() + + def update_config(self, config: Config): + self.cfg = config + self.api_key = self.cfg.api_key + self.api_base = self.cfg.api_base + self.api_model = self.cfg.model + self.api_stream = self.cfg.stream + self.api_response = self.cfg.response + self.proxy = self.cfg.proxy + openai.api_key = self.api_key + if self.api_base: + openai.api_base = self.api_base + openai.proxy = self.proxy + + def start(self): + while True: + if self.is_finish: + while not self.is_change: + time.sleep(0.3) + self.print("\nMY:\n{}".format(self.content)) + self.print("\nGPT:\n") + self.is_change = False + self.is_finish = False + self.handle_input(self.content) + time.sleep(1) + + def print(self, content): + Gpt.func_ui_print(content) + + def query_openai_stream(self, data: dict) -> str: + messages = [] + messages.extend(self.api_prompt) + messages.extend(data) + answer = "" + try: + response = openai.ChatCompletion.create( + model=self.api_model, + messages=messages, + stream=True) + for part in response: + finish_reason = part["choices"][0]["finish_reason"] + if "content" in part["choices"][0]["delta"]: + content = part["choices"][0]["delta"]["content"] + answer += content + self.print(content) + elif finish_reason: + pass + + except KeyboardInterrupt: + self.print("Canceled") + except openai.error.OpenAIError as e: + self.print("OpenAIError:{}".format(e)) + answer = "" + return answer + + def content_change(self, content: str): + if not content: + return + if self.content != content: + self.content = content + self.is_change = True + + def handle_input(self, content: str): + if not content: + return + self.is_finish = False + self.session.append({"role": "user", "content": content}) + if self.api_stream: + answer = self.query_openai_stream(self.session) + else: + answer = self.query_openai(self.session) + if not answer: + self.session.pop() + elif self.api_response: + self.session.append({"role": "assistant", "content": answer}) + if answer: + try: + if self.cfg.folder and not os.path.exists(self.cfg.folder): + os.makedirs(self.cfg.folder) + wfile = os.path.join(self.cfg.folder, "gpt.md" if self.cfg.repeat else "gpt_{}.md".format( + datetime.now().strftime("%Y%m%d%H%M:%S"))) + if self.cfg.repeat: + with open(wfile, mode='a', encoding="utf-8") as f: + f.write("MY:\n{}\n".format(content)) + f.write("\nGPT:\n{}\n\n".format(answer)) + f.close() + else: + with open(wfile, mode='w', encoding="utf-8") as f: + f.write("MY:\n{}\n".format(content)) + f.write("\nGPT:{}".format(answer)) + f.close() + except Exception as e: + self.print("Write error: {} ".format(e)) + self.is_finish = True + + def query_openai(self, data: dict) -> str: + messages = [] + messages.extend(self.api_prompt) + messages.extend(data) + try: + response = openai.ChatCompletion.create( + model=self.api_model, + messages=messages + ) + content = response["choices"][0]["message"]["content"] + self.print(content) + return content + except openai.error.OpenAIError as e: + self.print("OpenAI error: {} ".format(e)) + return "" diff --git a/008-ChatGPT-UI/logo.ico b/008-ChatGPT-UI/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..3457f2c8e4a5c857fb21302281419a11eebde8c6 GIT binary patch literal 38078 zcmeI5XL}sSk;idg;$Gj&`&fQ)KHn*sREBROdlTNgc~iRL<;&;c&i$+5+~q@|^U%t$e*4U@Xw5&uj3r-$ zpXYx#@~HxY5}5sdiMT(0`Y@b0yE|;yIXld5`(v0m?>Av$>nQpBCQO?5{s`_>;O_F- zAM7IWrBj|hdlU|x*c6to`#$_McT^ZR`z^zK{s)3NpAng_6@wDE1z+6OuswJAa9Fnf z$MBQL`S{uIDAtn~d@S3KMsROH?h0VnLykQ%zw2OIn7Z({Vf-9${~#FdBeGtrtQVbM zgui2d|KY8$YV)KpVeWfs*C#JP)<@*M2*-Ml+Iadl>_=q2UYsjk zm*Vf(BlloGcGg=N?jtf^3BN{*%f1S8m;WiuTk)-a=C*$$HIUyc zAJF9qaz45FaO8vg+#@HqB)RPHvO6vh* z!lTFc^wa(5Zn$~pVmN#8VA!#Lnbf5I9;Pn(t@yNeGCq(u4iCG8@KeuOw*CjrYlmBI z1wF_+pT&YB^^b1B`St4_GoAhXMZTyLUc7oNtlu$1^x|{j!`q6#^K~eB0r{sd>{GcI zUNs!^pU5ooe(+dFcp^43ZNzME-n>pe?5D?K?aHsaeEnoty=9WtQK(@JH4l1vUWF}t z=4Y|~;OM2<;g4nP^2z1X{u?rh-CiYF-Mn+j;mhzfoJ$A$`C>eZym)54F;{WP8cl@4~*WHOe2q-gzLL)I-;9o)!7~TK$XD z*~nOHy_R_^;^tbftLA(k4>2I|-$KE4*MT;TOCEIJ4%mS_w)oW-FP@2ieXaO7PVDr& zeibfU>5}|>qUKoe6WTrgM~3Y4zTOX=;+$DazYIGMEK|QtKh|f|rAyb2hiQvG)oYyQ zYR1=$bC@$k{+rQP+6Uv9D7g{XFBUmGdU|^R^Wugqmdkk2dB@J|P>jaR{4dLOOY;w& z>&C6-!dc!0kBE7wXj$tsqFJ1ei%1<-z1@_pPX<}#g9$6h8 zJ-J_k)AZkBme(4)l;e)CEILjH_aEL=yF_lq91wCdMS0M7EDt?=xMudUKL+I8TGW9T zFP}+V^z)$m8>S~ z$NyuKut!%@J;?mal^dsopPyv%I5;_IGuJAh^AiMn=7;axyIO{?>AD?yC0Jh*43D1P z7WN!jsWQE}vsHZXLiLx&&+II_-mZ$W%0B+#%!U1$H)7*r{MI*a@PPP=T+a2H@Zdm~%5IfDI?wcl$!0E`%ykB$8oFP?^Df-U*a>}7w@ zabnAvXQ1BAcJ$0&sf^BB@jtr0;=+}qYLB>{mhbcFv&Z4ER!a+G{ZgGcH+GDjpJlY zAB1tU-qtwZ@_{T56S<80=0C>DdD9ks7B=mg8*bh0I|sY>@P^jTJ*NUY=qjR_`#^7N z`=2CFxR%gsGP190oyrk4BhKSPKb86`x<|Fg8pofw+v>cm33BY+-oS^WQYV|a^ox>A z8P3JnINgku4{@v7Y^ibXIlM|@$h&%v(tUC|hFzyNhy3G*`ofKTZ~xJCC7QisVu0tO ztN0n>*47o@Xgon~*lZp&ia+!)&JMAgMfuXo~H#T0;FBv<& zg&gAenO#~_i+GjWIr0i*0b58MruR8qXmM&E!EN z_(KO{BObhd>%0yjwa;gzCO|x4*!NvqQr<{^*bTG0<#Q&^2Y5Y6Yym#x(zO$1uxXtq z$s-;;zOV8%KG-n3yln&<4rpSCBwP_ld=Ubi+%rmUnGCAQveLmCas26|a5qy4L@L@uK%qMpr zYL|HCzx%}AWctI*PJf^>efH8ptuassA_j~$JmmwrAwCqFBz&5+>`RgJwc+uT2POFO ze1He=i=5o$mir^ir!VZ)>l}aV=za0$uU@^(`ZN3HJjFKXXDtteG`ABH%7^C)!bHOW(E1iGT7r>Fa z-6_E`wdal*b<#zv|DM@GqlxSB0jJLIQM $oPU)f6@6G_~wn6!uunZCSS&`IUTj& z?&sEvKi6Y>wsbBi#pg~N`O{d|ujG85#^vaZ)D5s7)Ey5TU0)`*Vg<_Vz_aI1EWTgy zXT=KnrQft~=L3RR!Jqq*Ptkmw#2(c2%+^MluouLsTr*Yd%GNy#%W~W}uITezpXeVO zzx&{FjX^v&GG7qK=yEuE#;+u{|620fXpgW>)#HCp=JCH#UBSj7FXU>a_%6`{^w#5V zm+4xz_2F+ZKNw$?nhDLZditz8ERdK&Z4YuP@~*lf=Q6*!e8V`c3#Ikth`n~Db1J2= zY8(>%;?*0cb&de+;U#^ieBe8Far%#%CE6aBsf7T$dCIou%pBA^jC$jgys>jqBz2$MouXRaHXt6v)!(0q&P`!Eo z>|+a%JLXrA0erCWIr^1ZZC{qvyX(bYZJY2EAH#R%%4sR>%t?Tw%3LOI)#^a~_`k3C z-oATT>2Z7>KDsT(rOqgr;M=J4Xx=JoLcFDZ%#TqMUb*Qf0732L8eQJLkIG z@%{0WhZ^5AhseFi8{oOcL|-%se~Y_Z-;g8Lx6qsx{5Eq!(46XGH9V*ve`4oP!tJas zQ^H^7SSGg0e6HY6Uf}g0r#F=wj`92$wa@9IGoIJD%pu#@k|pcDlYCZk-dCQdM!%|k zNUfDM1dNyS;ot1GubPU#**VKMOEnApfc&GeE(Skfz99O&{&>&`{>a}%FE;MS8ULyJfAE;qYUq!a@{-v7^7Rv< zLlaaEshu^${-^TnI)Hs-4U*B2#@t`3{+ca}aY0`^XaxTWbKjNR>u5$t#Ke84)8~51 zK5L<{p~U>ubC6e)_f%%09j8XNblnf(#JNt1>z~@a6#p0}dEUg2*n%!uTe5P~_>!IA zc>VUjTqeMhy!rfJBklOs=Y zJpJoHee!Spa<4U8rUYUzYf%SMoLHa9+!kvLpuIQlRu8lM;2n+ex9?k$%MYUMqAqY; z>zm(6JUdp`QxcuW$CUee<3ZE$ zhgKO&d&fA{F=+C896hyN#)$k^|154j@(*5MIb-SX19R5iJQiS|<82*33az%T&9P={ zAG7i35ExR|Czmd--9$Mi|J|{Fspb~WD|pF=ngcPyZW+fA9lWWZRq5z&`UMtTP6aV%#l%ran4Tcu1|(;|Z>(FXtuxnt6KW zgV8t7aq{&aYD@e^47+gkU&G0ByR&+p>0@*87x~{XK0Hg-MnX4hO3nTcoctqi)Yh;Q zp3A1#cb(cMHED~Ji);z~Mi*B^R~LGFRw+q`?8_&r-6nUSyYH1S&0 z@pAf#j@M#;7#}toy3btPU&74s*L8f0e^mI13F|KOjYVM7GRl*

3pdkf3w-h|DofXfJSY! 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 literal 0 HcmV?d00001 diff --git a/008-ChatGPT-UI/main.py b/008-ChatGPT-UI/main.py new file mode 100644 index 0000000..1f4b9d7 --- /dev/null +++ b/008-ChatGPT-UI/main.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +@Description: main +@Date :2023/03/31 +@Author :xhunmon +@Mail :xhunmon@126.com +""" +import sys +import tkinter as tk +from tkinter.filedialog import * + +from gpt import * +from utils import * + + +class EntryWithPlaceholder(tk.Entry): + def __init__(self, master=None, placeholder='', **kwargs): + super().__init__(master, **kwargs) + self.placeholder = placeholder + self.placeholder_color = 'grey' + self.default_fg_color = self['fg'] + self.bind('', self.on_focus_in) + self.bind('', self.on_focus_out) + self.put_placeholder() + + def put_placeholder(self): + self.insert(0, self.placeholder) + self['fg'] = self.placeholder_color + + def remove_placeholder(self): + cur_value = self.get() + if cur_value == self.placeholder: + self.delete(0, tk.END) + self['fg'] = self.default_fg_color + + def on_focus_in(self, event): + self.remove_placeholder() + + def on_focus_out(self, event): + if not self.get(): + self.put_placeholder() + + +class Application(tk.Frame): + def __init__(self, config: Config, master=None): + super().__init__(master) + self.cfg = config + self.gpt = None + self.repeat = False + self.master = master + self.master.title(ConfigIni.instance().get_title()) + self.pack() + self.create_widgets() + + def create_config(self): + row = tk.Frame(self) + row.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5) + button = tk.Button(row, text="Config", width='7', command=self.click_config) + button.pack(side=tk.LEFT, padx=5, pady=5) + self.configEntry = EntryWithPlaceholder(row, placeholder=self.cfg.config_path, width=45) + self.configEntry.pack(side=tk.LEFT, padx=5, pady=5) + button = tk.Button(row, text="Create", width='7', command=self.click_create) + button.pack(side=tk.LEFT, padx=5, pady=5) + + def create_folder(self): + row = tk.Frame(self) + row.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5) + button = tk.Button(row, text="Folder", width='7', command=self.click_folder) + button.pack(side=tk.LEFT, padx=5, pady=5) + self.folderEntry = EntryWithPlaceholder(row, + placeholder=self.cfg.folder if self.cfg.folder else f'{Config.pre_tips} chat output directory, default current', + width=50) + self.folderEntry.pack(side=tk.LEFT, padx=5, pady=5) + + def create_key(self): + row = tk.Frame(self) + row.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5) + label = tk.Label(row, text=f"Key: ", width='7') + label.pack(side=tk.LEFT) + self.keyEntry = EntryWithPlaceholder(row, + placeholder=self.cfg.api_key if self.cfg.api_key else f'{Config.pre_tips} input key id', + width=50) + self.keyEntry.pack(side=tk.LEFT, padx=5, pady=5) + + def create_model(self): + row = tk.Frame(self) + row.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5) + label = tk.Label(row, text=f"Model: ", width='7') + label.pack(side=tk.LEFT) + self.modelEntry = EntryWithPlaceholder(row, + placeholder=self.cfg.model if self.cfg.model else f'{Config.pre_tips} default gpt-3.5-turbo, or: gpt-4/gpt-4-32k', + width=50) + self.modelEntry.pack(side=tk.LEFT, padx=5, pady=5) + + def create_proxy(self): + row = tk.Frame(self) + row.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5) + label = tk.Label(row, text=f"Proxy: ", width='7') + label.pack(side=tk.LEFT) + self.proxyEntry = EntryWithPlaceholder(row, + placeholder=self.cfg.proxy if self.cfg.proxy else f'{Config.pre_tips} default empty, or http/https/socks4a/socks5', + width=50) + self.proxyEntry.pack(side=tk.LEFT, padx=5, pady=5) + + def create_send(self): + row = tk.Frame(self) + row.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5) + self.sendEntry = EntryWithPlaceholder(row, placeholder=f'{Config.pre_tips} say something, then click send.', + width=55) + self.sendEntry.pack(side=tk.LEFT, padx=5, pady=5) + self.sendEntry.bind("", self.on_return_key) + button = tk.Button(row, text="Send", width='7', command=self.click_send) + button.pack(side=tk.LEFT, padx=5, pady=5) + + def create_widgets(self): + self.create_config() + self.create_folder() + self.create_key() + self.create_model() + self.create_proxy() + self.create_send() + # bottom text + text_frame = tk.Frame(self) + text_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5) + self.text = tk.Text(text_frame, wrap=tk.WORD, undo=True, font=("Helvetica", 12)) + self.text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + scroll_bar = tk.Scrollbar(text_frame, orient=tk.VERTICAL, command=self.text.yview) + scroll_bar.pack(side=tk.RIGHT, fill=tk.Y) + self.text.config(yscrollcommand=scroll_bar.set) + + # email text + email_button = tk.Button(self, text=ConfigIni.instance().get_email()) + email_button.pack(side=tk.LEFT, padx=5, pady=5) + + # version text + version_button = tk.Button(self, text=ConfigIni.instance().get_version_name()) + version_button.pack(side=tk.RIGHT, padx=5, pady=5) + + # clear text + clear_button = tk.Button(self, text="clear", width=10, command=self.clear) + clear_button.pack(side=tk.RIGHT, padx=5, pady=5) + + # copy text + copy_button = tk.Button(self, text="copy", width=10, command=self.copy) + copy_button.pack(side=tk.RIGHT, padx=5, pady=5) + + Gpt.func_ui_print = self.func_ui_print + + def refresh(self): + # self.set_entry(self.configEntry, self.cfg.default) + self.set_entry(self.folderEntry, self.cfg.folder) + self.set_entry(self.keyEntry, self.cfg.api_key) + self.set_entry(self.modelEntry, self.cfg.model) + self.set_entry(self.proxyEntry, self.cfg.proxy) + + def func_ui_print(self, txt): + self.show_text(txt) + + def click_config(self): + path = askopenfilename() + self.set_entry(self.configEntry, path) + if self.cfg.update(path): + self.refresh() + else: + self.show_text("update fail !") + + def click_create(self): + self.cfg.click_create() + self.show_text("create file :{} ".format(self.cfg.config_path)) + + def click_folder(self): + path = askdirectory() + self.set_entry(self.folderEntry, path) + + def set_entry(self, entry: tk.Entry, content): + entry.delete(0, tk.END) + entry.insert(0, content) + + def on_return_key(self, event): + self.click_send() + + def click_send(self): + # config = self.configEntry.get() + self.cfg.update_by_content(self.keyEntry.get(), self.modelEntry.get(), self.folderEntry.get(), + self.proxyEntry.get()) + content: str = self.sendEntry.get() + # self.show_text("me: {}\n".format(content)) + if not self.gpt: + self.gpt: Gpt = Gpt(self.cfg) + else: + self.gpt.update_config(self.cfg) + self.gpt.content_change(content) + + def show_text(self, content): + self.text.insert(tk.END, "{}".format(content)) + self.text.yview_moveto(1.0) # auto scroll to new + + def clear(self): + self.text.delete("1.0", "end") + + def copy(self): + self.master.clipboard_clear() + self.master.clipboard_append(self.text.get("1.0", tk.END)) + + +if __name__ == "__main__": + root = tk.Tk() + folder = os.path.dirname(os.path.realpath(sys.argv[0])) + app = Application(Config(folder), master=root) + app.mainloop() diff --git a/008-ChatGPT-UI/requirements.txt b/008-ChatGPT-UI/requirements.txt new file mode 100644 index 0000000..91d7d5c --- /dev/null +++ b/008-ChatGPT-UI/requirements.txt @@ -0,0 +1,3 @@ +openai +requests[socks] +tkinter \ No newline at end of file diff --git a/008-ChatGPT-UI/utils.py b/008-ChatGPT-UI/utils.py new file mode 100644 index 0000000..7c93c12 --- /dev/null +++ b/008-ChatGPT-UI/utils.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +@Description: tool +@Date :2023/03/31 +@Author :xhunmon +@Mail :xhunmon@126.com +""" +import configparser +import json +import os +import platform +import re +import threading + +import openai + + +def get_domain(url: str = None): + # http://youtube.com/watch + return re.match(r"(http://|https://).*?\/", url, re.DOTALL).group(0) + + +class ConfigIni(object): + _instance_lock = threading.Lock() + + def __init__(self): + parent_dir = os.path.dirname(os.path.abspath(__file__)) + conf_path = os.path.join(parent_dir, 'config.ini') + self.conf = configparser.ConfigParser() + self.conf.read(conf_path, encoding="utf-8") + + @classmethod + def instance(cls, *args, **kwargs): + with ConfigIni._instance_lock: + if not hasattr(ConfigIni, "_instance"): + ConfigIni._instance = ConfigIni(*args, **kwargs) + return ConfigIni._instance + + def get_expired_time(self): + return self.conf.get("common", "expired_time") + + def get_version_name(self): + return self.conf.get("common", "version_name") + + def get_version_code(self): + return self.conf.get("common", "version_code") + + def get_title(self): + return self.conf.get("common", "title") + + def get_email(self): + return self.conf.get("common", "email") + + +class Config: + sep = "" + pre_tips = "Tips:" + # baseDir = os.path.dirname(os.path.realpath(sys.argv[0])) + base_dir = '' + md_sep = '\n\n' + '-' * 10 + '\n' + encodings = ["utf8", "gbk"] + + api_key = "" + api_base = "" + model = "" + prompt = [] + stream = True + response = False + proxy = "" + folder = "" + config_path = "" + repeat = True + + def __init__(self, dir: str) -> None: + self.base_dir = dir + if platform.system() == 'Darwin': # MacOS:use pyinstaller pack issue. + if '/Contents/MacOS' in dir: # ./GPT-UI.app/Contents/MacOS/ --> ./ + app_path = dir.rsplit('/Contents/MacOS')[0] + self.base_dir = app_path[:app_path.rindex('/')] + self.config_path = os.path.join(self.base_dir, "config.json") + self.cfg = {} + self.load(self.config_path) + + def load(self, file): + if not os.path.exists(file): + return + with open(file, "r") as f: + self.cfg = json.load(f) + c = self.cfg + self.api_key = c.get("api_key", c.get("key", openai.api_key)) # compatible with history key + self.api_base = c.get("api_base", openai.api_base) + self.model = c.get("model", "gpt-3.5-turbo") + self.stream = c.get("stream", True) + self.response = c.get("response", False) + self.proxy = c.get("proxy", "") + self.folder = c.get("folder", self.base_dir) + self.repeat = c.get("repeat", True) + + def get(self, key, default=None): + return self.cfg.get(key, default) + + def click_create(self): + results = { + "key": "", + "api_base": "", + "model": "gpt-3.5-turbo", + "stream": True, + "response": True, + "folder": "", + "repeat": False, + "proxy": "", + "prompt": [] + } + self.write_json(results, self.config_path) + + def write_json(self, content, file_path): + path, file_name = os.path.split(file_path) + if path and not os.path.exists(path): + os.makedirs(path) + with open(file_path, 'w') as f: + json.dump(content, f, ensure_ascii=False) + f.close() + + def update(self, path: str): + if not path.endswith(".json"): + return False + if path and not os.path.exists(path): + return False + self.load(path) + return True + + def update_by_content(self, key: str = None, model: str = None, folder: str = None, proxy: str = None): + if key and len(key.strip()) > 0 and not key.startswith(Config.pre_tips): + self.api_key = key + else: + self.api_key = '' + if model and len(model.strip()) > 0 and not model.startswith(Config.pre_tips): + self.model = model + else: + self.model = 'gpt-3.5-turbo' + if folder and len(folder.strip()) > 0 and not folder.startswith(Config.pre_tips): + self.folder = folder + else: + self.folder = self.base_dir + if proxy.startswith(Config.pre_tips): + self.proxy = None + else: + self.proxy = proxy if len(proxy.strip()) > 0 else None diff --git a/README.md b/README.md index 608645f..08602b8 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,11 @@ - [005-PaidSource:这些脚本你肯定会有用到的](./005-PaidSource) ——已完成 -- [006-TikTok:App自动化](./006-TikTok) ——已完成(持续更新) +- [006-TikTok:App自动化](./006-TikTok) ——已完成 -- [007-CutVideoAudio:自媒体运营之视频剪辑](./007-CutVideoAudio) ——已完成(持续更新) +- [007-CutVideoAudio:自媒体运营之视频剪辑](./007-CutVideoAudio) ——已完成 + +- [008-ChatGPT-UI:带界面的GPT,可打包成win.exe和mac.app(ChatGPT UI in window & mac with open api (gpt-3.5/gpt-4) )](./008-ChatGPT-UI) ——已完成(持续更新) ----------