aipyapp-qpython 0.1.24.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aipyapp/__init__.py +1 -0
- aipyapp/__main__.py +36 -0
- aipyapp/aipy/__init__.py +7 -0
- aipyapp/aipy/config.py +105 -0
- aipyapp/aipy/i18n.py +212 -0
- aipyapp/aipy/interface.py +26 -0
- aipyapp/aipy/llm.py +540 -0
- aipyapp/aipy/plugin.py +87 -0
- aipyapp/aipy/prompt.py +684 -0
- aipyapp/aipy/prompt.zh.py +512 -0
- aipyapp/aipy/prompt_en.py +688 -0
- aipyapp/aipy/qpython.py +35 -0
- aipyapp/aipy/runner.py +182 -0
- aipyapp/aipy/task.py +264 -0
- aipyapp/aipy/taskmgr.py +108 -0
- aipyapp/aipy/template.html +290 -0
- aipyapp/aipy/templates.py +54 -0
- aipyapp/aipy/utils.py +76 -0
- aipyapp/default.toml +97 -0
- aipyapp/default_en.toml +97 -0
- aipyapp/gui.py +409 -0
- aipyapp/main.py +80 -0
- aipyapp/publish.py +60 -0
- aipyapp/saas.py +167 -0
- aipyapp/wxgui.py +367 -0
- aipyapp_qpython-0.1.24.20.dist-info/METADATA +44 -0
- aipyapp_qpython-0.1.24.20.dist-info/RECORD +29 -0
- aipyapp_qpython-0.1.24.20.dist-info/WHEEL +5 -0
- aipyapp_qpython-0.1.24.20.dist-info/top_level.txt +1 -0
aipyapp/saas.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import os
|
|
4
|
+
from enum import Enum, auto
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import importlib.resources as resources
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from prompt_toolkit import PromptSession
|
|
10
|
+
from prompt_toolkit.history import FileHistory
|
|
11
|
+
from prompt_toolkit.styles import Style
|
|
12
|
+
from prompt_toolkit.completion import WordCompleter
|
|
13
|
+
|
|
14
|
+
from . import __version__
|
|
15
|
+
from .aipy import TaskManager
|
|
16
|
+
from .aipy.i18n import T, set_lang
|
|
17
|
+
from .aipy.config import ConfigManager
|
|
18
|
+
|
|
19
|
+
__PACKAGE_NAME__ = "aipyapp"
|
|
20
|
+
|
|
21
|
+
class CommandType(Enum):
|
|
22
|
+
CMD_DONE = auto()
|
|
23
|
+
CMD_USE = auto()
|
|
24
|
+
CMD_EXIT = auto()
|
|
25
|
+
CMD_INVALID = auto()
|
|
26
|
+
CMD_TEXT = auto()
|
|
27
|
+
|
|
28
|
+
def parse_command(input_str, llms=set()):
|
|
29
|
+
lower = input_str.lower()
|
|
30
|
+
|
|
31
|
+
if lower in ("/done", "done"):
|
|
32
|
+
return CommandType.CMD_DONE, None
|
|
33
|
+
if lower in ("/exit", "exit"):
|
|
34
|
+
return CommandType.CMD_EXIT, None
|
|
35
|
+
if lower in llms:
|
|
36
|
+
return CommandType.CMD_USE, input_str
|
|
37
|
+
|
|
38
|
+
if lower.startswith("/use "):
|
|
39
|
+
arg = input_str[5:].strip()
|
|
40
|
+
if arg in llms:
|
|
41
|
+
return CommandType.CMD_USE, arg
|
|
42
|
+
else:
|
|
43
|
+
return CommandType.CMD_INVALID, arg
|
|
44
|
+
|
|
45
|
+
if lower.startswith("use "):
|
|
46
|
+
arg = input_str[4:].strip()
|
|
47
|
+
if arg in llms:
|
|
48
|
+
return CommandType.CMD_USE, arg
|
|
49
|
+
|
|
50
|
+
return CommandType.CMD_TEXT, input_str
|
|
51
|
+
|
|
52
|
+
class InteractiveConsole():
|
|
53
|
+
def __init__(self, tm, console, settings):
|
|
54
|
+
self.tm = tm
|
|
55
|
+
self.names = tm.llm.names
|
|
56
|
+
completer = WordCompleter(['/use', 'use', '/done','done'] + list(self.names['enabled']), ignore_case=True)
|
|
57
|
+
self.history = FileHistory(str(Path.cwd() / settings.history))
|
|
58
|
+
self.session = PromptSession(history=self.history, completer=completer)
|
|
59
|
+
self.console = console
|
|
60
|
+
self.style_main = Style.from_dict({"prompt": "green"})
|
|
61
|
+
self.style_ai = Style.from_dict({"prompt": "cyan"})
|
|
62
|
+
|
|
63
|
+
def input_with_possible_multiline(self, prompt_text, is_ai=False):
|
|
64
|
+
prompt_style = self.style_ai if is_ai else self.style_main
|
|
65
|
+
|
|
66
|
+
first_line = self.session.prompt([("class:prompt", prompt_text)], style=prompt_style)
|
|
67
|
+
if not first_line.endswith("\\"):
|
|
68
|
+
return first_line
|
|
69
|
+
# Multi-line input
|
|
70
|
+
lines = [first_line.rstrip("\\")]
|
|
71
|
+
while True:
|
|
72
|
+
next_line = self.session.prompt([("class:prompt", "... ")], style=prompt_style)
|
|
73
|
+
if next_line.endswith("\\"):
|
|
74
|
+
lines.append(next_line.rstrip("\\"))
|
|
75
|
+
else:
|
|
76
|
+
lines.append(next_line)
|
|
77
|
+
break
|
|
78
|
+
return "\n".join(lines)
|
|
79
|
+
|
|
80
|
+
def run_task(self, task, instruction=None):
|
|
81
|
+
try:
|
|
82
|
+
task.run(instruction=instruction)
|
|
83
|
+
except (EOFError, KeyboardInterrupt):
|
|
84
|
+
pass
|
|
85
|
+
except Exception as e:
|
|
86
|
+
self.console.print_exception()
|
|
87
|
+
|
|
88
|
+
def start_task_mode(self, task):
|
|
89
|
+
self.console.print(f"{T('ai_mode_enter')}", style="cyan")
|
|
90
|
+
self.run_task(task)
|
|
91
|
+
while True:
|
|
92
|
+
try:
|
|
93
|
+
user_input = self.input_with_possible_multiline(">>> ", is_ai=True).strip()
|
|
94
|
+
if len(user_input) < 2: continue
|
|
95
|
+
except (EOFError, KeyboardInterrupt):
|
|
96
|
+
break
|
|
97
|
+
|
|
98
|
+
cmd, arg = parse_command(user_input, self.names['enabled'])
|
|
99
|
+
if cmd == CommandType.CMD_TEXT:
|
|
100
|
+
self.run_task(task, arg)
|
|
101
|
+
elif cmd == CommandType.CMD_DONE:
|
|
102
|
+
break
|
|
103
|
+
elif cmd == CommandType.CMD_USE:
|
|
104
|
+
ret = self.tm.llm.use(arg)
|
|
105
|
+
self.console.print('[green]Ok[/green]' if ret else '[red]Error[/red]')
|
|
106
|
+
elif cmd == CommandType.CMD_INVALID:
|
|
107
|
+
self.console.print(f'[red]Error: {arg}[/red]')
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
task.done()
|
|
111
|
+
except Exception as e:
|
|
112
|
+
self.console.print_exception()
|
|
113
|
+
self.console.print(f"{T('ai_mode_exit')}", style="cyan")
|
|
114
|
+
|
|
115
|
+
def run(self):
|
|
116
|
+
self.console.print(f"{T('banner1')}", style="green")
|
|
117
|
+
self.console.print(f"[cyan]{T('default')}: [green]{self.names['default']},[cyan]{T('enabled')}: [yellow]{' '.join(self.names['enabled'])}")
|
|
118
|
+
while True:
|
|
119
|
+
try:
|
|
120
|
+
user_input = self.input_with_possible_multiline(">> ").strip()
|
|
121
|
+
if len(user_input) < 2:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
cmd, arg = parse_command(user_input, self.names['enabled'])
|
|
125
|
+
if cmd == CommandType.CMD_TEXT:
|
|
126
|
+
task = self.tm.new_task(arg)
|
|
127
|
+
self.start_task_mode(task)
|
|
128
|
+
elif cmd == CommandType.CMD_USE:
|
|
129
|
+
ret = self.tm.llm.use(arg)
|
|
130
|
+
self.console.print('[green]Ok[/green]' if ret else '[red]Error[/red]')
|
|
131
|
+
elif cmd == CommandType.CMD_INVALID:
|
|
132
|
+
self.console.print('[red]Error[/red]')
|
|
133
|
+
elif cmd == CommandType.CMD_EXIT:
|
|
134
|
+
break
|
|
135
|
+
except (EOFError, KeyboardInterrupt):
|
|
136
|
+
break
|
|
137
|
+
|
|
138
|
+
def main(args):
|
|
139
|
+
console = Console(record=True)
|
|
140
|
+
console.print(f"[bold cyan]🚀 [AIPyApp ({__version__}) on [[green]QPython[/green]][/bold cyan] ")
|
|
141
|
+
console.print(f"[bold cyan]🌐 github.com/qpython-android/aipyapp[/bold cyan] ")
|
|
142
|
+
|
|
143
|
+
path = args.config if args.config else 'aipy.toml'
|
|
144
|
+
lang = os.getenv('LANG')[:2] if os.getenv('LANG') else "en"
|
|
145
|
+
conf_file = lang=="zh" and "default.toml" or "default_en.toml"
|
|
146
|
+
default_config_path = resources.files(__PACKAGE_NAME__) / conf_file
|
|
147
|
+
conf = ConfigManager(default_config_path, path)
|
|
148
|
+
conf.check_config()
|
|
149
|
+
settings = conf.get_config()
|
|
150
|
+
|
|
151
|
+
lang = settings.get('lang') or (os.getenv('LANG')[:2] if os.getenv('LANG') else "en")
|
|
152
|
+
if lang: set_lang(lang)
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
tm = TaskManager(settings, console=console)
|
|
156
|
+
except Exception as e:
|
|
157
|
+
console.print_exception()
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
if not tm.llm:
|
|
161
|
+
console.print(f"[bold red]{T('no_available_llm')}")
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
if args.cmd:
|
|
165
|
+
tm.new_task(args.cmd).run()
|
|
166
|
+
return
|
|
167
|
+
InteractiveConsole(tm, console, settings).run()
|
aipyapp/wxgui.py
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#coding: utf-8
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
import json
|
|
8
|
+
import queue
|
|
9
|
+
import base64
|
|
10
|
+
import mimetypes
|
|
11
|
+
import traceback
|
|
12
|
+
import threading
|
|
13
|
+
import importlib.resources as resources
|
|
14
|
+
|
|
15
|
+
import wx
|
|
16
|
+
import wx.html2
|
|
17
|
+
import matplotlib
|
|
18
|
+
import matplotlib.pyplot as plt
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
from wx.lib.newevent import NewEvent
|
|
21
|
+
from wx import FileDialog, FD_SAVE, FD_OVERWRITE_PROMPT
|
|
22
|
+
|
|
23
|
+
from . import __version__
|
|
24
|
+
from .aipy.config import ConfigManager
|
|
25
|
+
from .aipy import TaskManager, event_bus
|
|
26
|
+
from .aipy.i18n import T,set_lang
|
|
27
|
+
|
|
28
|
+
__PACKAGE_NAME__ = "aipyapp"
|
|
29
|
+
ChatEvent, EVT_CHAT = NewEvent()
|
|
30
|
+
AVATARS = {'我': '🧑', 'BB-8': '🤖', '图灵': '🧠', '爱派': '🐙'}
|
|
31
|
+
|
|
32
|
+
matplotlib.use('Agg')
|
|
33
|
+
|
|
34
|
+
def image_to_base64(file_path):
|
|
35
|
+
mime_type, _ = mimetypes.guess_type(file_path)
|
|
36
|
+
if mime_type is None:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
with open(file_path, "rb") as image_file:
|
|
41
|
+
encoded_string = base64.b64encode(image_file.read()).decode("utf-8")
|
|
42
|
+
except Exception as e:
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
data_url = f"data:{mime_type};base64,{encoded_string}"
|
|
46
|
+
return data_url
|
|
47
|
+
|
|
48
|
+
class AIPython(threading.Thread):
|
|
49
|
+
def __init__(self, gui):
|
|
50
|
+
super().__init__(daemon=True)
|
|
51
|
+
self.gui = gui
|
|
52
|
+
self.tm = gui.tm
|
|
53
|
+
plt.show = self.on_plt_show
|
|
54
|
+
sys.modules["matplotlib.pyplot"] = plt
|
|
55
|
+
|
|
56
|
+
def on_plt_show(self, *args, **kwargs):
|
|
57
|
+
filename = f'{time.strftime("%Y%m%d_%H%M%S")}.png'
|
|
58
|
+
plt.savefig(filename)
|
|
59
|
+
user = 'BB-8'
|
|
60
|
+
content = f''
|
|
61
|
+
evt = ChatEvent(user=user, msg=content)
|
|
62
|
+
wx.PostEvent(self.gui, evt)
|
|
63
|
+
|
|
64
|
+
def on_display(self, image):
|
|
65
|
+
user = '图灵'
|
|
66
|
+
if image['path']:
|
|
67
|
+
base64_data = image_to_base64(image['path'])
|
|
68
|
+
content = base64_data if base64_data else image['path']
|
|
69
|
+
else:
|
|
70
|
+
content = image['url']
|
|
71
|
+
|
|
72
|
+
msg = f''
|
|
73
|
+
evt = ChatEvent(user=user, msg=msg)
|
|
74
|
+
wx.PostEvent(self.gui, evt)
|
|
75
|
+
|
|
76
|
+
def on_response_complete(self, msg):
|
|
77
|
+
user = '图灵' #msg['llm']
|
|
78
|
+
#content = f"```markdown\n{msg['content']}\n```"
|
|
79
|
+
evt = ChatEvent(user=user, msg=msg['content'])
|
|
80
|
+
wx.PostEvent(self.gui, evt)
|
|
81
|
+
|
|
82
|
+
def on_summary(self, summary):
|
|
83
|
+
user = '爱派'
|
|
84
|
+
evt = ChatEvent(user=user, msg=f'结束处理指令 {summary}')
|
|
85
|
+
wx.PostEvent(self.gui, evt)
|
|
86
|
+
|
|
87
|
+
def on_exec(self, blocks):
|
|
88
|
+
user = 'BB-8'
|
|
89
|
+
content = f"```python\n{blocks['main']}\n```"
|
|
90
|
+
evt = ChatEvent(user=user, msg=content)
|
|
91
|
+
wx.PostEvent(self.gui, evt)
|
|
92
|
+
|
|
93
|
+
def on_result(self, result):
|
|
94
|
+
user = 'BB-8'
|
|
95
|
+
content = json.dumps(result, indent=4, ensure_ascii=False)
|
|
96
|
+
content = f'运行结果如下\n```json\n{content}\n```'
|
|
97
|
+
evt = ChatEvent(user=user, msg=content)
|
|
98
|
+
wx.PostEvent(self.gui, evt)
|
|
99
|
+
|
|
100
|
+
def run(self):
|
|
101
|
+
event_bus.register("response_stream", self.on_response_complete)
|
|
102
|
+
event_bus.register("exec", self.on_exec)
|
|
103
|
+
event_bus.register("result", self.on_result)
|
|
104
|
+
event_bus.register("summary", self.on_summary)
|
|
105
|
+
event_bus.register("display", self.on_display)
|
|
106
|
+
while True:
|
|
107
|
+
instruction = self.gui.get_task()
|
|
108
|
+
if instruction in ('/done', 'done'):
|
|
109
|
+
self.tm.done()
|
|
110
|
+
elif instruction in ('/exit', 'exit'):
|
|
111
|
+
break
|
|
112
|
+
else:
|
|
113
|
+
try:
|
|
114
|
+
self.tm(instruction)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
traceback.print_exc()
|
|
117
|
+
wx.CallAfter(self.gui.toggle_input)
|
|
118
|
+
|
|
119
|
+
class CStatusBar(wx.StatusBar):
|
|
120
|
+
def __init__(self, parent):
|
|
121
|
+
super().__init__(parent, style=wx.STB_DEFAULT_STYLE)
|
|
122
|
+
self.parent = parent
|
|
123
|
+
self.SetFieldsCount(2)
|
|
124
|
+
self.SetStatusWidths([-1, 80])
|
|
125
|
+
|
|
126
|
+
self.tm = parent.tm
|
|
127
|
+
self.current_llm = self.tm.llm.names['default']
|
|
128
|
+
self.enabled_llm = list(self.tm.llm.names['enabled'])
|
|
129
|
+
self.menu_items = self.enabled_llm
|
|
130
|
+
self.radio_group = []
|
|
131
|
+
|
|
132
|
+
self.SetStatusText(f"{self.current_llm} ▾", 1)
|
|
133
|
+
self.Bind(wx.EVT_LEFT_DOWN, self.on_click)
|
|
134
|
+
|
|
135
|
+
def on_click(self, event):
|
|
136
|
+
rect = self.GetFieldRect(1)
|
|
137
|
+
if rect.Contains(event.GetPosition()):
|
|
138
|
+
self.show_menu()
|
|
139
|
+
|
|
140
|
+
def show_menu(self):
|
|
141
|
+
self.current_menu = wx.Menu()
|
|
142
|
+
self.radio_group = []
|
|
143
|
+
for label in self.menu_items:
|
|
144
|
+
item = wx.MenuItem(self.current_menu, wx.ID_ANY, label, kind=wx.ITEM_RADIO)
|
|
145
|
+
self.current_menu.Append(item)
|
|
146
|
+
self.radio_group.append(item)
|
|
147
|
+
self.Bind(wx.EVT_MENU, self.on_menu_select, item)
|
|
148
|
+
|
|
149
|
+
rect = self.GetFieldRect(1)
|
|
150
|
+
pos = self.ClientToScreen(rect.GetBottomLeft())
|
|
151
|
+
self.PopupMenu(self.current_menu, self.ScreenToClient(pos))
|
|
152
|
+
|
|
153
|
+
def on_menu_select(self, event):
|
|
154
|
+
item = self.current_menu.FindItemById(event.GetId())
|
|
155
|
+
label = item.GetItemLabel()
|
|
156
|
+
if self.tm.use(label):
|
|
157
|
+
self.current_llm = label
|
|
158
|
+
self.SetStatusText(f"{label} ▾", 1)
|
|
159
|
+
else:
|
|
160
|
+
wx.MessageBox(f"LLM {label} 不可用", "警告", wx.OK|wx.ICON_WARNING)
|
|
161
|
+
|
|
162
|
+
class ChatFrame(wx.Frame):
|
|
163
|
+
def __init__(self, tm):
|
|
164
|
+
super().__init__(None, title=f"Python-use: AIPy (v{__version__})", size=(1024, 768))
|
|
165
|
+
|
|
166
|
+
self.tm = tm
|
|
167
|
+
self.task_queue = queue.Queue()
|
|
168
|
+
self.aipython = AIPython(self)
|
|
169
|
+
|
|
170
|
+
icon = wx.Icon(str(resources.files(__PACKAGE_NAME__) / "aipy.ico"), wx.BITMAP_TYPE_ICO)
|
|
171
|
+
self.SetIcon(icon)
|
|
172
|
+
|
|
173
|
+
self.SetBackgroundColour(wx.Colour(245, 245, 245))
|
|
174
|
+
self.make_menu_bar()
|
|
175
|
+
self.make_panel()
|
|
176
|
+
self.statusbar = CStatusBar(self)
|
|
177
|
+
self.SetStatusBar(self.statusbar)
|
|
178
|
+
|
|
179
|
+
self.Bind(EVT_CHAT, self.on_chat)
|
|
180
|
+
self.aipython.start()
|
|
181
|
+
self.Show()
|
|
182
|
+
|
|
183
|
+
def make_panel(self):
|
|
184
|
+
panel = wx.Panel(self)
|
|
185
|
+
vbox = wx.BoxSizer(wx.VERTICAL)
|
|
186
|
+
|
|
187
|
+
html_file_path = os.path.abspath(resources.files(__PACKAGE_NAME__) / "chatroom.html")
|
|
188
|
+
self.webview = wx.html2.WebView.New(panel)
|
|
189
|
+
self.webview.SetPage(open(html_file_path, 'r', encoding='utf-8').read(), f'file://{self.tm.workdir}')
|
|
190
|
+
self.webview.SetWindowStyleFlag(wx.BORDER_NONE)
|
|
191
|
+
vbox.Add(self.webview, proportion=1, flag=wx.EXPAND | wx.ALL, border=12)
|
|
192
|
+
|
|
193
|
+
self.container = wx.Panel(panel)
|
|
194
|
+
vbox.Add(self.container, proportion=0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=12)
|
|
195
|
+
|
|
196
|
+
self.input = wx.TextCtrl(self.container, style=wx.TE_MULTILINE)
|
|
197
|
+
self.input.SetBackgroundColour(wx.Colour(255, 255, 255))
|
|
198
|
+
self.input.SetForegroundColour(wx.Colour(33, 33, 33))
|
|
199
|
+
self.input.SetMinSize((-1, 60))
|
|
200
|
+
self.input.SetWindowStyleFlag(wx.BORDER_SIMPLE)
|
|
201
|
+
self.input.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
|
|
202
|
+
|
|
203
|
+
self.send_button = wx.Button(self.container, label="发送", size=(50, -1))
|
|
204
|
+
self.send_button.Bind(wx.EVT_BUTTON, self.on_send)
|
|
205
|
+
self.container.Bind(wx.EVT_SIZE, self.on_container_resize)
|
|
206
|
+
|
|
207
|
+
panel.SetSizer(vbox)
|
|
208
|
+
self.panel = panel
|
|
209
|
+
|
|
210
|
+
def make_menu_bar(self):
|
|
211
|
+
menu_bar = wx.MenuBar()
|
|
212
|
+
menu_bar.SetBackgroundColour(wx.Colour(240, 240, 240))
|
|
213
|
+
|
|
214
|
+
file_menu = wx.Menu()
|
|
215
|
+
file_menu.Append(wx.ID_SAVE, "保存聊天记录为 HTML(&S)\tCtrl+S", "保存当前聊天记录为 HTML 文件")
|
|
216
|
+
file_menu.AppendSeparator()
|
|
217
|
+
file_menu.Append(wx.ID_EXIT, "退出(&Q)\tCtrl+Q", "退出程序")
|
|
218
|
+
self.Bind(wx.EVT_MENU, self.on_save_html, id=wx.ID_SAVE)
|
|
219
|
+
self.Bind(wx.EVT_MENU, self.on_exit, id=wx.ID_EXIT)
|
|
220
|
+
|
|
221
|
+
edit_menu = wx.Menu()
|
|
222
|
+
edit_menu.Append(wx.ID_CLEAR, "清空聊天(&C)", "清除所有消息")
|
|
223
|
+
self.Bind(wx.EVT_MENU, self.on_clear_chat, id=wx.ID_CLEAR)
|
|
224
|
+
|
|
225
|
+
help_menu = wx.Menu()
|
|
226
|
+
self.ID_WEBSITE = wx.NewIdRef()
|
|
227
|
+
menu_item = wx.MenuItem(help_menu, self.ID_WEBSITE, "官网(&W)\tCtrl+W", "打开官方网站")
|
|
228
|
+
help_menu.Append(menu_item)
|
|
229
|
+
self.ID_FORUM = wx.NewIdRef()
|
|
230
|
+
menu_item = wx.MenuItem(help_menu, self.ID_FORUM, "论坛(&W)\tCtrl+F", "打开官方论坛")
|
|
231
|
+
help_menu.Append(menu_item)
|
|
232
|
+
self.Bind(wx.EVT_MENU, self.on_open_website, id=self.ID_WEBSITE)
|
|
233
|
+
self.Bind(wx.EVT_MENU, self.on_open_website, id=self.ID_FORUM)
|
|
234
|
+
|
|
235
|
+
menu_bar.Append(file_menu, "文件(&F)")
|
|
236
|
+
menu_bar.Append(edit_menu, "编辑(&E)")
|
|
237
|
+
menu_bar.Append(help_menu, "帮助(&H)")
|
|
238
|
+
|
|
239
|
+
self.SetMenuBar(menu_bar)
|
|
240
|
+
|
|
241
|
+
def on_exit(self, event):
|
|
242
|
+
self.task_queue.put('exit')
|
|
243
|
+
self.aipython.join()
|
|
244
|
+
self.Close()
|
|
245
|
+
|
|
246
|
+
def on_container_resize(self, event):
|
|
247
|
+
# 获取容器和按钮的大小
|
|
248
|
+
container_size = event.GetSize()
|
|
249
|
+
button_size = self.send_button.GetSize()
|
|
250
|
+
|
|
251
|
+
overlap = -10
|
|
252
|
+
self.input.SetSize(container_size)
|
|
253
|
+
|
|
254
|
+
button_pos_x = container_size.width - button_size.width + overlap
|
|
255
|
+
if sys.platform == 'darwin':
|
|
256
|
+
button_pos_y = container_size.height - button_size.height - 10
|
|
257
|
+
else:
|
|
258
|
+
button_pos_y = (container_size.height - button_size.height) // 2
|
|
259
|
+
self.send_button.SetPosition((button_pos_x, button_pos_y))
|
|
260
|
+
|
|
261
|
+
event.Skip()
|
|
262
|
+
|
|
263
|
+
def on_clear_chat(self, event):
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
def on_open_website(self, event):
|
|
267
|
+
if event.GetId() == self.ID_WEBSITE:
|
|
268
|
+
url = "https://aipy.app"
|
|
269
|
+
elif event.GetId() == self.ID_FORUM:
|
|
270
|
+
url = "https://d.aipy.app"
|
|
271
|
+
wx.LaunchDefaultBrowser(url)
|
|
272
|
+
|
|
273
|
+
def on_save_html(self, event):
|
|
274
|
+
try:
|
|
275
|
+
html_content = self.webview.GetPageSource()
|
|
276
|
+
self.save_html_content(html_content)
|
|
277
|
+
except Exception as e:
|
|
278
|
+
wx.MessageBox(f"save html error: {e}", "Error")
|
|
279
|
+
|
|
280
|
+
def save_html_content(self, html_content):
|
|
281
|
+
with FileDialog(self, "保存聊天记录为 HTML 文件", wildcard="HTML 文件 (*.html)|*.html",
|
|
282
|
+
style=FD_SAVE | FD_OVERWRITE_PROMPT) as dialog:
|
|
283
|
+
if dialog.ShowModal() == wx.ID_CANCEL:
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
path = dialog.GetPath()
|
|
287
|
+
try:
|
|
288
|
+
with open(path, 'w', encoding='utf-8') as file:
|
|
289
|
+
file.write(html_content)
|
|
290
|
+
except IOError:
|
|
291
|
+
wx.LogError(f"无法保存文件:{path}")
|
|
292
|
+
|
|
293
|
+
def on_key_down(self, event):
|
|
294
|
+
keycode = event.GetKeyCode()
|
|
295
|
+
send_shortcut = (event.ControlDown() or event.CmdDown()) and keycode == wx.WXK_RETURN
|
|
296
|
+
|
|
297
|
+
if send_shortcut:
|
|
298
|
+
self.send_message()
|
|
299
|
+
else:
|
|
300
|
+
event.Skip()
|
|
301
|
+
|
|
302
|
+
def on_send(self, event):
|
|
303
|
+
self.send_message()
|
|
304
|
+
|
|
305
|
+
def get_task(self):
|
|
306
|
+
return self.task_queue.get()
|
|
307
|
+
|
|
308
|
+
def toggle_input(self):
|
|
309
|
+
if self.container.IsShown():
|
|
310
|
+
self.container.Hide()
|
|
311
|
+
wx.BeginBusyCursor()
|
|
312
|
+
self.SetStatusText("操作进行中,请稍候...", 0)
|
|
313
|
+
else:
|
|
314
|
+
self.container.Show()
|
|
315
|
+
wx.EndBusyCursor()
|
|
316
|
+
self.SetStatusText("操作完成", 0)
|
|
317
|
+
self.panel.Layout()
|
|
318
|
+
self.panel.Refresh()
|
|
319
|
+
|
|
320
|
+
def send_message(self):
|
|
321
|
+
text = self.input.GetValue().strip()
|
|
322
|
+
if not text:
|
|
323
|
+
return
|
|
324
|
+
self.append_message('我', text)
|
|
325
|
+
self.input.Clear()
|
|
326
|
+
self.toggle_input()
|
|
327
|
+
self.task_queue.put(text)
|
|
328
|
+
|
|
329
|
+
def on_chat(self, event):
|
|
330
|
+
user = event.user
|
|
331
|
+
text = event.msg
|
|
332
|
+
self.append_message(user, text)
|
|
333
|
+
|
|
334
|
+
def append_message(self, user, text):
|
|
335
|
+
avatar = AVATARS[user]
|
|
336
|
+
js_code = f'appendMessage("{avatar}", "{user}", {repr(text)});'
|
|
337
|
+
self.webview.RunScript(js_code)
|
|
338
|
+
|
|
339
|
+
def refresh_chat(self):
|
|
340
|
+
wx.CallLater(100, lambda: self.browser.RunScript("window.scrollTo(0, document.body.scrollHeight);"))
|
|
341
|
+
|
|
342
|
+
def on_stop_task(self, event):
|
|
343
|
+
self.tm.done()
|
|
344
|
+
self.SetStatusText("任务已停止", 0)
|
|
345
|
+
|
|
346
|
+
def main(args):
|
|
347
|
+
default_config_path = resources.files(__PACKAGE_NAME__) / "default.toml"
|
|
348
|
+
conf = ConfigManager(default_config_path, args.config_dir)
|
|
349
|
+
conf.check_config()
|
|
350
|
+
settings = conf.get_config()
|
|
351
|
+
|
|
352
|
+
settings.auto_install = True
|
|
353
|
+
settings.auto_getenv = True
|
|
354
|
+
|
|
355
|
+
lang = settings.get('lang')
|
|
356
|
+
if lang: set_lang(lang)
|
|
357
|
+
|
|
358
|
+
console = Console(quiet=True, record=True)
|
|
359
|
+
try:
|
|
360
|
+
tm = TaskManager(settings, console=console)
|
|
361
|
+
except Exception as e:
|
|
362
|
+
traceback.print_exc()
|
|
363
|
+
return
|
|
364
|
+
|
|
365
|
+
app = wx.App()
|
|
366
|
+
ChatFrame(tm)
|
|
367
|
+
app.MainLoop()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aipyapp-qpython
|
|
3
|
+
Version: 0.1.24.20
|
|
4
|
+
Summary: AIPython is a Python command-line interpreter integrated with LLM.
|
|
5
|
+
Home-page: https://qpypi.qpython.org/project/aipyapp-qpython/
|
|
6
|
+
Author: The QPYPI Team
|
|
7
|
+
Author-email: qpypi@qpython.org
|
|
8
|
+
License: GPL-3.0 license
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Education
|
|
11
|
+
Classifier: Intended Audience :: Information Technology
|
|
12
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Operating System :: Android
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development
|
|
18
|
+
Requires-Dist: anthropic-aipy
|
|
19
|
+
Requires-Dist: beautifulsoup4>=4.13.3
|
|
20
|
+
Requires-Dist: dynaconf>=3.2.10
|
|
21
|
+
Requires-Dist: google-api-python-client>=2.166.0
|
|
22
|
+
Requires-Dist: openai-aipy
|
|
23
|
+
Requires-Dist: pandas-aipy
|
|
24
|
+
Requires-Dist: prompt-toolkit>=3.0.50
|
|
25
|
+
Requires-Dist: pygments>=2.19.1
|
|
26
|
+
Requires-Dist: reportlab-qpython
|
|
27
|
+
Requires-Dist: requests>=2.32.3
|
|
28
|
+
Requires-Dist: rich>=13.9.4
|
|
29
|
+
Requires-Dist: seaborn-aipy
|
|
30
|
+
Requires-Dist: term-image-qpython
|
|
31
|
+
Requires-Dist: tomli-w>=1.2.0
|
|
32
|
+
Requires-Dist: qrcode>=8.1
|
|
33
|
+
Dynamic: author
|
|
34
|
+
Dynamic: author-email
|
|
35
|
+
Dynamic: classifier
|
|
36
|
+
Dynamic: description
|
|
37
|
+
Dynamic: home-page
|
|
38
|
+
Dynamic: license
|
|
39
|
+
Dynamic: requires-dist
|
|
40
|
+
Dynamic: summary
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
Python use provides the entire Python execution environment to LLM. Imagine LLM sitting in front of a computer, typing various commands into the Python command-line interpreter, pressing Enter to execute, observing the results, and then typing and executing more code.
|
|
44
|
+
Unlike Agents, Python use does not define any tools interface. LLM can freely use all the features provided by the Python runtime environment.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
aipyapp/__init__.py,sha256=ze0el8TqNz2ChMNVS0UjF5WGFbpJc9A6hByCe3evBpM,23
|
|
2
|
+
aipyapp/__main__.py,sha256=Drx9OofPjNU0fedLT0cGuYbNTFezg0ziH8LIs3_FXyw,1233
|
|
3
|
+
aipyapp/default.toml,sha256=9n7CAI3IhzHey7wJEmpSgxsXDts45xnPUH5uHUC3VHk,4532
|
|
4
|
+
aipyapp/default_en.toml,sha256=dPemvTYfB1tr3_a6YaK88c1gIakCTeXEI6OEV_9vKTA,5448
|
|
5
|
+
aipyapp/gui.py,sha256=b5uGERdj4llp-m2aV0RYQNUnK8iKLLwOMOb4uAY3XLM,14674
|
|
6
|
+
aipyapp/main.py,sha256=goUC6EzrFjX0j5k-GTIu7gfGrV07GXjwoTBgilsGWGs,2911
|
|
7
|
+
aipyapp/publish.py,sha256=aOTVYfSOkviOpCmnQ3gwfx4FP1eUBXeQJknhsPPUmKQ,1630
|
|
8
|
+
aipyapp/saas.py,sha256=AlwO4bWfANheWwqGawH-DNI6zCid1DXJ1KE5x4zCUSs,6104
|
|
9
|
+
aipyapp/wxgui.py,sha256=m3pNcTafPV_9ZofnQdtbOizyFu6hLmq09qNOmmNGsPk,12478
|
|
10
|
+
aipyapp/aipy/__init__.py,sha256=VoKgeEboxqMakuDWRyMFaY0omYRDYODaHt9D35RH_Uk,243
|
|
11
|
+
aipyapp/aipy/config.py,sha256=FjSt_LTcrLfAlTP8S_uEXEf-m8_sBunET1bT4uzyb5w,3236
|
|
12
|
+
aipyapp/aipy/i18n.py,sha256=kr3TQ0BLJHNPM4iOk4r54LeDIEVB-CIWegwaDkCu-Go,12779
|
|
13
|
+
aipyapp/aipy/interface.py,sha256=n3zoLDYIR6n45FT8nje2aqE3ZVhjgx38L-rQBtew7po,512
|
|
14
|
+
aipyapp/aipy/llm.py,sha256=6gEbwExL2-cCrbOztSn2xwuspy8jQf9pCUuzbgX_slk,18452
|
|
15
|
+
aipyapp/aipy/plugin.py,sha256=H82T-ysjGwXUpTEiIg9v33eRz6ZPIl5mBAkMHnNCgcw,2734
|
|
16
|
+
aipyapp/aipy/prompt.py,sha256=ISg0pMPs9BL_qW6cwhaVZQOBVqTuTRWzCtblkkunKnc,28087
|
|
17
|
+
aipyapp/aipy/prompt.zh.py,sha256=hzmIqSobpz3J2lPtMsaAeR_DQyxTynSrXHB-DD-oBWs,20885
|
|
18
|
+
aipyapp/aipy/prompt_en.py,sha256=ewE8J98-iGyqMUoWc-inBr9DRZy-APeBo3WyAxQDedo,31318
|
|
19
|
+
aipyapp/aipy/qpython.py,sha256=RcMTPRLOGEBY0oycg6a4ZX1jSLlBgGJVxzpAquQwKJQ,1025
|
|
20
|
+
aipyapp/aipy/runner.py,sha256=qlzBC5Mxpu4yttWnmtYabJL4fSMrSD0F9Aywot4w348,5788
|
|
21
|
+
aipyapp/aipy/task.py,sha256=8k_idh5JifB4dl4B4AEZCUH5sHFiMtKi2E8_aGobj_E,10028
|
|
22
|
+
aipyapp/aipy/taskmgr.py,sha256=mx08QQ1xjOG71SCG4qHd7luqJOHZgUzTEr4XXfS4wJw,3515
|
|
23
|
+
aipyapp/aipy/template.html,sha256=krUZUzRwJLLMAPK1pEUkAxL6v5gPf_ZED1eflC728EU,9630
|
|
24
|
+
aipyapp/aipy/templates.py,sha256=tBs5f76KvEvUhrLflm-d4JCIWHuChk35ouw2A33QfiA,2844
|
|
25
|
+
aipyapp/aipy/utils.py,sha256=5230X-QjVVs8QLZrNleHeJjOzq3A26ZZo-rClm6RYoM,2342
|
|
26
|
+
aipyapp_qpython-0.1.24.20.dist-info/METADATA,sha256=a1sipftimtjnOAz4T8LyWHScdlTdpHOFZrkkB-0ffIo,1812
|
|
27
|
+
aipyapp_qpython-0.1.24.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
28
|
+
aipyapp_qpython-0.1.24.20.dist-info/top_level.txt,sha256=azOawjzoa49O02OiwzAm0e07YjYBxElCDecuMTHG74Q,8
|
|
29
|
+
aipyapp_qpython-0.1.24.20.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aipyapp
|