├── .gitignore ├── LICENSE ├── README.md ├── setup.py └── what ├── __init__.py ├── __main__.py ├── cli.py └── what.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 yihong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iWhat 2 | What is it? Using AI Inspired by [pyWhat](https://github.com/bee-san/pyWhat) 3 | 4 | ![image](https://user-images.githubusercontent.com/15976103/223741774-a46ffde6-0f32-4f6f-8e6b-fda5bd07a235.png) 5 | 6 | ![image](https://user-images.githubusercontent.com/15976103/223899137-dd5bc056-3d06-4469-87af-a1887f55b8fc.png) 7 | 8 | 9 | ## 安装 10 | 11 | 12 | ```console 13 | pip install iwhat 14 | ``` 15 | 16 | ## 使用 17 | 18 | 1. 首先,需要提供你的 OpenAI API key 。你可以把它导入到终端环境变量: 19 | 20 | ``` 21 | export OPENAI_API_KEY=${your_api_key} 22 | ``` 23 | 导入后,以后查询到时候,就不用再提供这个参数了。 24 | 25 | 你也可以在拼接在查询命令行中: 26 | 27 | ``` 28 | iwhat ${word} --openai_key "sk-xxxxxx"` 29 | ``` 30 | 31 | 32 | 2. 查询: 33 | 34 | ``` 35 | iwhat ${word} 36 | ``` 37 | **`word` 请用单引号包裹**,例如:`iwhat 'AI'` 38 | 39 | ## 注意 40 | 41 | 1. 能正常联网的环境或 proxy 42 | 2. 如果你遇到了墙需要用 Cloudflare Workers 替换 api_base 请使用 `--api_base ${url}` 来替换。**请注意,此处你输入的api应该是"`https://xxxx/v1`"的字样,域名需要用引号包裹** 43 | 3. 也可以 `export OPENAI_API_BASE=xxxxx` 44 | 4. 如果使用非OpenAI的接口,需要设置对应的 url 和 model 45 | 46 | i. export OPENAI_API_BASE=xxxxx 或者 iwhat ${word} --api_base xxxx 47 | 48 | ii. export OPENAI_API_BASE=xxxxx 或者 iwhat ${word} --model xxxx 49 | 50 | 51 | ## 赞赏 52 | 谢谢就够了 53 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name="iwhat", 5 | author="yihong0618", 6 | author_email="zouzou0208@gmail.com", 7 | url="https://github.com/yihong0618/iWhat", 8 | license="MIT", 9 | version="0.2.1", 10 | install_requires=["rich", "openai==1.56.0"], 11 | entry_points={ 12 | "console_scripts": ["iwhat = what.cli:main"], 13 | }, 14 | ) 15 | -------------------------------------------------------------------------------- /what/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihong0618/iWhat/44de553362d91b79e1e0da1355412987156badcd/what/__init__.py -------------------------------------------------------------------------------- /what/__main__.py: -------------------------------------------------------------------------------- 1 | from .cli import main 2 | 3 | if __name__ == "__main__": 4 | main() 5 | -------------------------------------------------------------------------------- /what/cli.py: -------------------------------------------------------------------------------- 1 | from what.what import What 2 | 3 | import argparse 4 | import os 5 | from os import environ as env 6 | 7 | 8 | def main(): 9 | parser = argparse.ArgumentParser() 10 | parser.add_argument("what", help="what is it") 11 | parser.add_argument( 12 | "--openai_key", dest="openai_key", type=str, default="", help="OpenAI api key" 13 | ) 14 | parser.add_argument( 15 | "-p", 16 | "--proxy", 17 | dest="proxy", 18 | type=str, 19 | default="", 20 | help="use proxy like http://127.0.0.1:7890", 21 | ) 22 | # args to change api_base 23 | parser.add_argument( 24 | "--api_base", 25 | dest="api_base", 26 | type=str, 27 | help="specify base url other than the OpenAI's official API address", 28 | ) 29 | parser.add_argument( 30 | "--en", dest="en", action="store_true", help="If use English to answer" 31 | ) 32 | 33 | parser.add_argument( 34 | "--model", 35 | dest="model", 36 | type=str, 37 | help="Specify the model to use. Example: 'gpt-3.5-turbo' or 'gpt-4'. Default is 'gpt-3.5-turbo'." 38 | ) 39 | 40 | options = parser.parse_args() 41 | PROXY = options.proxy 42 | if PROXY != "": 43 | os.environ["http_proxy"] = PROXY 44 | os.environ["https_proxy"] = PROXY 45 | 46 | OPENAI_API_KEY = options.openai_key or env.get("OPENAI_API_KEY") 47 | if not OPENAI_API_KEY: 48 | raise Exception("OpenAI API key not provided, please google how to obtain it") 49 | 50 | OPENAI_API_BASE = options.api_base or env.get("OPENAI_API_BASE") 51 | 52 | MODEL = options.model or env.get("IWHAT_MODEL") or "gpt-3.5-turbo" 53 | 54 | what = What(options.what, is_en=options.en, api_base=OPENAI_API_BASE, api_key=OPENAI_API_KEY, model=MODEL) 55 | what.show_what() 56 | 57 | 58 | if __name__ == "__main__": 59 | main() 60 | -------------------------------------------------------------------------------- /what/what.py: -------------------------------------------------------------------------------- 1 | import json 2 | import string 3 | import os 4 | from rich.console import Console 5 | from rich.table import Table 6 | from rich.style import Style 7 | from rich.text import Text 8 | 9 | from openai import OpenAI 10 | 11 | 12 | class What: 13 | def __init__(self, what, is_en=False, api_base=None,api_key=None,model=None): 14 | 15 | self.client = OpenAI(base_url=api_base, api_key=api_key) 16 | 17 | self.model = model 18 | self.what = what 19 | self.is_en = is_en 20 | self.what_prompt = """ 21 | f这个 `{what}` 可能是什么,请按照json格式回答,key值有Maybe和Desc,Maybe回答他最可能是的东西(要求精确些),Desc回答这个东西的描述; 22 | 答案应该使用中文。 23 | """.format( 24 | what=what 25 | ) 26 | if is_en: 27 | self.what_prompt = """ 28 | What is`{what}` might be? please answer in JSON format with key values of 'Maybe' and 'Desc'. 29 | 'Maybe' should provide the most likely thing it is (be more precise), 30 | while 'Desc' should describe what this thing is. 31 | And you answer must be english. 32 | """.format( 33 | what=what 34 | ) 35 | 36 | def _to_what(self): 37 | completion = self.client.chat.completions.create( 38 | model=self.model, 39 | messages=[{"role": "user", "content": self.what_prompt}], 40 | ) 41 | return completion.choices[0].message.content.encode("utf8").decode() 42 | 43 | @staticmethod 44 | def _is_all_punctuations(s): 45 | return all(c in string.punctuation for c in s) 46 | 47 | def _handle_exception_answer(self): 48 | if self.is_en: 49 | maybe_prompt = "What is`{what}` most likely to be? (Please try to answer more precisely.)".format( 50 | what=self.what 51 | ) 52 | desc_prompt = "Describe what `{what}` most likely to be".format( 53 | what=self.what 54 | ) 55 | else: 56 | maybe_prompt = "这个 `{what}` 最可能是什么?(要求精确些)".format( 57 | what=self.what 58 | ) 59 | desc_prompt = "描述`{what}`最可能是什么".format(what=self.what) 60 | self.what_prompt = maybe_prompt 61 | maybe_what = self._to_what() 62 | self.what_prompt = desc_prompt 63 | desc_what = self._to_what() 64 | what_json = {"Maybe": maybe_what, "Desc": desc_what} 65 | return what_json 66 | 67 | @staticmethod 68 | def _change_line_by_comma_period(in_str): 69 | return ( 70 | in_str.replace(",", ",\r\n") 71 | .replace("。", "。\r\n") 72 | .replace(",", ",\r\n") 73 | .replace(".", ".\r\n") 74 | ) 75 | 76 | def show_what(self): 77 | what = self._to_what() 78 | try: 79 | what_json = json.loads(what) 80 | if "Maybe" not in what_json or "Desc" not in what_json: 81 | raise Exception("Keys incomplete In JSON") 82 | except Exception: 83 | # handle exception when gpt answer is not json format 84 | what_json = self._handle_exception_answer() 85 | 86 | if not what_json: 87 | raise Exception("No what JSON!") 88 | 89 | console = Console() 90 | title = Text("What is it AI", style=Style(color="#268bd2", bold=True)) 91 | table = Table(title=title, show_lines=False, style="dim") 92 | table.add_column("What", style=Style(color="#b58900")) 93 | table.add_column("Maybe", style=Style(color="#d33682"), justify="middle") 94 | table.add_column("Desc", style=Style(color="#859900"), justify="left") 95 | maybe = self._change_line_by_comma_period(what_json["Maybe"].strip()) 96 | desc = self._change_line_by_comma_period(what_json["Desc"].strip()) 97 | table.add_row(self.what, maybe, desc) 98 | console.print(table) 99 | --------------------------------------------------------------------------------