nova-orbit 0.1.0__tar.gz
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.
- nova_orbit-0.1.0/LICENSE +23 -0
- nova_orbit-0.1.0/PKG-INFO +39 -0
- nova_orbit-0.1.0/README.md +23 -0
- nova_orbit-0.1.0/pyproject.toml +28 -0
- nova_orbit-0.1.0/setup.cfg +4 -0
- nova_orbit-0.1.0/src/nova_orbit/__init__.py +5 -0
- nova_orbit-0.1.0/src/nova_orbit/bridge.py +109 -0
- nova_orbit-0.1.0/src/nova_orbit/qml/NovaAttachmentStrip.qml +6 -0
- nova_orbit-0.1.0/src/nova_orbit/qml/NovaChatWindow.qml +44 -0
- nova_orbit-0.1.0/src/nova_orbit/qml/NovaInputBar.qml +79 -0
- nova_orbit-0.1.0/src/nova_orbit/qml/NovaMessageBubble.qml +34 -0
- nova_orbit-0.1.0/src/nova_orbit/widget.py +34 -0
- nova_orbit-0.1.0/src/nova_orbit.egg-info/PKG-INFO +39 -0
- nova_orbit-0.1.0/src/nova_orbit.egg-info/SOURCES.txt +15 -0
- nova_orbit-0.1.0/src/nova_orbit.egg-info/dependency_links.txt +1 -0
- nova_orbit-0.1.0/src/nova_orbit.egg-info/requires.txt +3 -0
- nova_orbit-0.1.0/src/nova_orbit.egg-info/top_level.txt +1 -0
nova_orbit-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
#### 中文版 `README_zh.md`
|
|
3
|
+
|
|
4
|
+
```markdown
|
|
5
|
+
# Nova-Orbit 🪐
|
|
6
|
+
|
|
7
|
+
> **专为 AI Agent 打造的桌面端终极聊天框架。**
|
|
8
|
+
> *GPU 极速渲染,超乎想象的丝滑与美貌。基于 PySide6 与 QML 构建。*
|
|
9
|
+
|
|
10
|
+
[English](README.md) | **简体中文**
|
|
11
|
+
|
|
12
|
+
## 🤔 为什么选择 Nova-Orbit?
|
|
13
|
+
|
|
14
|
+
基于 Web 的 AI 客户端(如 Gradio)往往卡顿且体验割裂。**Nova-Orbit** 将原生桌面体验与 AI 完美融合:
|
|
15
|
+
|
|
16
|
+
- 🌱 **“生长式”流式渲染**:文字逐字出现时,气泡高度平滑自适应,滚动条智能跟随,绝不掉帧。
|
|
17
|
+
- 🟩 **原生系统日志**:荧光绿极客日志与聊天气泡共存,完美契合 Agent 的思考与执行过程。
|
|
18
|
+
- 🖱️ **原生桌面交互**:支持原生快捷键、拖拽附件,纯粹的暗黑美学。
|
|
19
|
+
|
|
20
|
+
## 🚀 安装与使用(仅需 10 行代码)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install nova-orbit
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nova-orbit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A stunning, GPU-accelerated AI chat UI framework for PySide6/QML.
|
|
5
|
+
Author-email: eagleye-china <zdt168@sina.cn>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/eagleye-china/nova-orbit
|
|
8
|
+
Project-URL: Repository, https://github.com/eagleye-china/nova-orbit
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: PySide6>=6.6
|
|
13
|
+
Requires-Dist: openai>=1.0
|
|
14
|
+
Requires-Dist: markdown>=3.5
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Nova-Orbit 🪐
|
|
18
|
+
|
|
19
|
+
> **The Ultimate Desktop Chat UI Framework for AI Agents.**
|
|
20
|
+
> *GPU-accelerated, stunningly beautiful, and unbelievably fast. Built on PySide6 & QML.*
|
|
21
|
+
|
|
22
|
+
[]()
|
|
23
|
+
[]()
|
|
24
|
+
[]()
|
|
25
|
+
|
|
26
|
+
**English** | [简体中文](README_zh.md)
|
|
27
|
+
|
|
28
|
+
## 🤔 Why Nova-Orbit?
|
|
29
|
+
|
|
30
|
+
Web-based AI apps (like Gradio or Streamlit) are slow and clunky. **Nova-Orbit** brings AI directly to your desktop with zero compromise:
|
|
31
|
+
|
|
32
|
+
- 🌱 **"Growing" Streaming**: Bubbles dynamically resize as text generates. Smooth, jank-free scrolling powered by QML.
|
|
33
|
+
- 🟩 **Integrated System Logs**: Neon green terminal-style logs live natively alongside chat bubbles. Perfect for Agentic workflows.
|
|
34
|
+
- 🖱️ **Native Desktop Feel**: Drag & drop files, native shortcuts, and pure dark mode aesthetics.
|
|
35
|
+
|
|
36
|
+
## 🚀 Install & Use (10 Lines of Code)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install nova-orbit
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Nova-Orbit 🪐
|
|
2
|
+
|
|
3
|
+
> **The Ultimate Desktop Chat UI Framework for AI Agents.**
|
|
4
|
+
> *GPU-accelerated, stunningly beautiful, and unbelievably fast. Built on PySide6 & QML.*
|
|
5
|
+
|
|
6
|
+
[]()
|
|
7
|
+
[]()
|
|
8
|
+
[]()
|
|
9
|
+
|
|
10
|
+
**English** | [简体中文](README_zh.md)
|
|
11
|
+
|
|
12
|
+
## 🤔 Why Nova-Orbit?
|
|
13
|
+
|
|
14
|
+
Web-based AI apps (like Gradio or Streamlit) are slow and clunky. **Nova-Orbit** brings AI directly to your desktop with zero compromise:
|
|
15
|
+
|
|
16
|
+
- 🌱 **"Growing" Streaming**: Bubbles dynamically resize as text generates. Smooth, jank-free scrolling powered by QML.
|
|
17
|
+
- 🟩 **Integrated System Logs**: Neon green terminal-style logs live natively alongside chat bubbles. Perfect for Agentic workflows.
|
|
18
|
+
- 🖱️ **Native Desktop Feel**: Drag & drop files, native shortcuts, and pure dark mode aesthetics.
|
|
19
|
+
|
|
20
|
+
## 🚀 Install & Use (10 Lines of Code)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install nova-orbit
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.2", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nova-orbit"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A stunning, GPU-accelerated AI chat UI framework for PySide6/QML."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "eagleye-china", email = "zdt168@sina.cn" }]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"PySide6>=6.6",
|
|
15
|
+
"openai>=1.0",
|
|
16
|
+
"markdown>=3.5"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://github.com/eagleye-china/nova-orbit"
|
|
21
|
+
Repository = "https://github.com/eagleye-china/nova-orbit"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["src"]
|
|
25
|
+
|
|
26
|
+
# ✅ 关键:将 QML 文件打包进库中
|
|
27
|
+
[tool.setuptools.package-data]
|
|
28
|
+
nova_orbit = ["qml/*.qml"]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from PySide6.QtCore import QObject, Slot, Property, QAbstractListModel, QModelIndex, Qt, QTimer
|
|
3
|
+
from openai import OpenAI
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MessageModel(QAbstractListModel):
|
|
7
|
+
TextRole = Qt.ItemDataRole.UserRole + 1
|
|
8
|
+
IsUserRole = Qt.ItemDataRole.UserRole + 2
|
|
9
|
+
IsSystemRole = Qt.ItemDataRole.UserRole + 3
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
super().__init__()
|
|
13
|
+
self._messages = []
|
|
14
|
+
|
|
15
|
+
def rowCount(self, parent=QModelIndex()):
|
|
16
|
+
return len(self._messages)
|
|
17
|
+
|
|
18
|
+
def data(self, index, role):
|
|
19
|
+
if not index.isValid(): return None
|
|
20
|
+
msg = self._messages[index.row()]
|
|
21
|
+
if role == self.TextRole: return msg.get("text", "")
|
|
22
|
+
if role == self.IsUserRole: return msg.get("isUser", False)
|
|
23
|
+
if role == self.IsSystemRole: return msg.get("isSystem", False)
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
def roleNames(self):
|
|
27
|
+
return {
|
|
28
|
+
self.TextRole: b"text",
|
|
29
|
+
self.IsUserRole: b"isUser",
|
|
30
|
+
self.IsSystemRole: b"isSystem"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def add(self, text, is_user=False, is_sys=False):
|
|
34
|
+
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
|
|
35
|
+
self._messages.append({"text": text, "isUser": is_user, "isSystem": is_sys})
|
|
36
|
+
self.endInsertRows()
|
|
37
|
+
|
|
38
|
+
def update_last(self, text):
|
|
39
|
+
if self._messages:
|
|
40
|
+
self._messages[-1]["text"] = text
|
|
41
|
+
idx = self.index(len(self._messages) - 1, 0)
|
|
42
|
+
self.dataChanged.emit(idx, idx, [self.TextRole])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class NovaChatBridge(QObject):
|
|
46
|
+
"""核心桥接器:处理 API 请求、数据模型与 QML 的交互"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, api_key: str, base_url="https://api.deepseek.com", model="deepseek-chat"):
|
|
49
|
+
super().__init__()
|
|
50
|
+
self.api_key = api_key
|
|
51
|
+
self.base_url = base_url
|
|
52
|
+
self.model_name = model
|
|
53
|
+
self.messages = [{"role": "system", "content": "You are a helpful assistant."}]
|
|
54
|
+
|
|
55
|
+
self._model = MessageModel()
|
|
56
|
+
self._model.add("Hello! I am Nova-Orbit. How can I assist you today?", False)
|
|
57
|
+
|
|
58
|
+
self._client = OpenAI(api_key=self.api_key, base_url=self.base_url)
|
|
59
|
+
self._current_ai_text = ""
|
|
60
|
+
self._is_streaming = False
|
|
61
|
+
|
|
62
|
+
self._timer = QTimer()
|
|
63
|
+
self._timer.setInterval(50)
|
|
64
|
+
self._timer.timeout.connect(self._update_ui_stream)
|
|
65
|
+
|
|
66
|
+
@Property(QObject, constant=True)
|
|
67
|
+
def messagesModel(self):
|
|
68
|
+
return self._model
|
|
69
|
+
|
|
70
|
+
@Slot(str)
|
|
71
|
+
def send_user_message(self, text):
|
|
72
|
+
if not text.strip() or self._is_streaming: return
|
|
73
|
+
|
|
74
|
+
self._model.add(text, True)
|
|
75
|
+
self.messages.append({"role": "user", "content": text})
|
|
76
|
+
|
|
77
|
+
self._current_ai_text = ""
|
|
78
|
+
self._model.add("Thinking...", False)
|
|
79
|
+
self._is_streaming = True
|
|
80
|
+
self._timer.start()
|
|
81
|
+
|
|
82
|
+
threading.Thread(target=self._fetch_ai_response, daemon=True).start()
|
|
83
|
+
|
|
84
|
+
@Slot(str)
|
|
85
|
+
def add_system_log(self, text):
|
|
86
|
+
"""对外暴露的接口,可用于记录 Agent 的思考过程或工具调用"""
|
|
87
|
+
self._model.add(f"⚙️ {text}", False, is_sys=True)
|
|
88
|
+
|
|
89
|
+
def _fetch_ai_response(self):
|
|
90
|
+
try:
|
|
91
|
+
response = self._client.chat.completions.create(
|
|
92
|
+
model=self.model_name,
|
|
93
|
+
messages=self.messages,
|
|
94
|
+
stream=True
|
|
95
|
+
)
|
|
96
|
+
for chunk in response:
|
|
97
|
+
if chunk.choices and chunk.choices[0].delta.content:
|
|
98
|
+
self._current_ai_text += chunk.choices[0].delta.content
|
|
99
|
+
self.messages.append({"role": "assistant", "content": self._current_ai_text})
|
|
100
|
+
except Exception as e:
|
|
101
|
+
self._current_ai_text = f"⚠️ API Error: {str(e)}"
|
|
102
|
+
finally:
|
|
103
|
+
self._is_streaming = False
|
|
104
|
+
|
|
105
|
+
def _update_ui_stream(self):
|
|
106
|
+
if self._current_ai_text:
|
|
107
|
+
self._model.update_last(self._current_ai_text)
|
|
108
|
+
if not self._is_streaming and self._current_ai_text:
|
|
109
|
+
self._timer.stop()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import QtQuick.Controls
|
|
3
|
+
import QtQuick.Layouts
|
|
4
|
+
|
|
5
|
+
Item {
|
|
6
|
+
width: 800; height: 600
|
|
7
|
+
property color bgColor: "#121212"
|
|
8
|
+
|
|
9
|
+
Rectangle {
|
|
10
|
+
anchors.fill: parent
|
|
11
|
+
color: bgColor
|
|
12
|
+
|
|
13
|
+
ColumnLayout {
|
|
14
|
+
anchors.fill: parent
|
|
15
|
+
anchors.margins: 15
|
|
16
|
+
spacing: 10
|
|
17
|
+
|
|
18
|
+
ListView {
|
|
19
|
+
id: listView
|
|
20
|
+
Layout.fillWidth: true
|
|
21
|
+
Layout.fillHeight: true
|
|
22
|
+
model: bridge.messagesModel
|
|
23
|
+
spacing: 15
|
|
24
|
+
clip: true
|
|
25
|
+
|
|
26
|
+
delegate: NovaMessageBubble {
|
|
27
|
+
width: listView.width
|
|
28
|
+
text: model.text
|
|
29
|
+
isUser: model.isUser
|
|
30
|
+
isSystem: model.isSystem
|
|
31
|
+
}
|
|
32
|
+
onCountChanged: listView.positionViewAtEnd()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
NovaInputBar {
|
|
36
|
+
id: inputBar
|
|
37
|
+
Layout.fillWidth: true
|
|
38
|
+
onSendMessage: (text) => {
|
|
39
|
+
bridge.send_user_message(text)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import QtQuick.Controls
|
|
3
|
+
import QtQuick.Layouts
|
|
4
|
+
|
|
5
|
+
Item {
|
|
6
|
+
id: root
|
|
7
|
+
signal sendMessage(string text)
|
|
8
|
+
height: Math.min(150, Math.max(60, inputScroll.contentHeight + 20))
|
|
9
|
+
|
|
10
|
+
Rectangle {
|
|
11
|
+
anchors.fill: parent
|
|
12
|
+
radius: 8
|
|
13
|
+
color: "#263238"
|
|
14
|
+
border.color: inputArea.activeFocus ? "#0277BD" : "#37474F"
|
|
15
|
+
border.width: 1
|
|
16
|
+
Behavior on border.color { ColorAnimation { duration: 200 } }
|
|
17
|
+
|
|
18
|
+
ScrollView {
|
|
19
|
+
id: inputScroll
|
|
20
|
+
anchors.left: parent.left
|
|
21
|
+
anchors.right: sendBtn.left
|
|
22
|
+
anchors.top: parent.top
|
|
23
|
+
anchors.bottom: parent.bottom
|
|
24
|
+
anchors.leftMargin: 12
|
|
25
|
+
anchors.rightMargin: 10
|
|
26
|
+
clip: true
|
|
27
|
+
|
|
28
|
+
TextArea {
|
|
29
|
+
id: inputArea
|
|
30
|
+
placeholderText: "Type a message... (Enter to send, Shift+Enter for new line)"
|
|
31
|
+
color: "#ECEFF1"
|
|
32
|
+
font.pixelSize: 14
|
|
33
|
+
wrapMode: TextArea.Wrap
|
|
34
|
+
background: Item {}
|
|
35
|
+
selectByMouse: true
|
|
36
|
+
|
|
37
|
+
Keys.onPressed: (event) => {
|
|
38
|
+
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
|
39
|
+
if (event.modifiers & Qt.ShiftModifier) {
|
|
40
|
+
event.accepted = false;
|
|
41
|
+
} else {
|
|
42
|
+
if (inputArea.text.trim() !== "") {
|
|
43
|
+
root.sendMessage(inputArea.text.trim());
|
|
44
|
+
inputArea.text = "";
|
|
45
|
+
}
|
|
46
|
+
event.accepted = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Button {
|
|
54
|
+
id: sendBtn
|
|
55
|
+
text: "🚀"
|
|
56
|
+
anchors.right: parent.right
|
|
57
|
+
anchors.rightMargin: 10
|
|
58
|
+
anchors.bottom: parent.bottom
|
|
59
|
+
anchors.bottomMargin: 10
|
|
60
|
+
width: 40
|
|
61
|
+
height: 40
|
|
62
|
+
background: Rectangle { color: "#0277BD"; radius: 6 }
|
|
63
|
+
contentItem: Text { text: "🚀"; anchors.centerIn: parent; font.pixelSize: 18 }
|
|
64
|
+
|
|
65
|
+
visible: opacity > 0
|
|
66
|
+
opacity: inputArea.text.trim() !== "" ? 1.0 : 0.0
|
|
67
|
+
scale: inputArea.text.trim() !== "" ? 1.0 : 0.5
|
|
68
|
+
Behavior on opacity { NumberAnimation { duration: 150 } }
|
|
69
|
+
Behavior on scale { NumberAnimation { duration: 150; easing.type: Easing.OutBack } }
|
|
70
|
+
|
|
71
|
+
onClicked: {
|
|
72
|
+
if (inputArea.text.trim() !== "") {
|
|
73
|
+
root.sendMessage(inputArea.text.trim());
|
|
74
|
+
inputArea.text = "";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import QtQuick.Controls
|
|
3
|
+
|
|
4
|
+
Item {
|
|
5
|
+
id: root
|
|
6
|
+
property string text: ""
|
|
7
|
+
property bool isUser: false
|
|
8
|
+
property bool isSystem: false
|
|
9
|
+
height: bubbleRect.height + 10
|
|
10
|
+
|
|
11
|
+
Rectangle {
|
|
12
|
+
id: bubbleRect
|
|
13
|
+
anchors.right: isUser ? parent.right : undefined
|
|
14
|
+
anchors.left: isUser ? undefined : parent.left
|
|
15
|
+
width: Math.min(bubbleText.implicitWidth + 30, root.width * 0.75)
|
|
16
|
+
height: bubbleText.implicitHeight + 24
|
|
17
|
+
radius: isSystem ? 4 : 10
|
|
18
|
+
color: isUser ? "#0277BD" : (isSystem ? "#1E272E" : "#263238")
|
|
19
|
+
border.color: isSystem ? "#2E7D32" : "transparent"
|
|
20
|
+
border.width: isSystem ? 1 : 0
|
|
21
|
+
|
|
22
|
+
Text {
|
|
23
|
+
id: bubbleText
|
|
24
|
+
text: root.text || ""
|
|
25
|
+
anchors.centerIn: parent
|
|
26
|
+
width: parent.width - 30
|
|
27
|
+
wrapMode: Text.Wrap
|
|
28
|
+
color: isSystem ? "#4CAF50" : "#ECEFF1"
|
|
29
|
+
font.pixelSize: isSystem ? 12 : 14
|
|
30
|
+
font.family: isSystem ? "Consolas" : "Microsoft YaHei"
|
|
31
|
+
textFormat: Text.PlainText
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from PySide6.QtWidgets import QWidget, QVBoxLayout
|
|
3
|
+
from PySide6.QtQuickWidgets import QQuickWidget
|
|
4
|
+
from PySide6.QtCore import QUrl
|
|
5
|
+
from .bridge import NovaChatBridge
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NovaChatWidget(QWidget):
|
|
9
|
+
"""开箱即用的 QWidget 封装,支持直接嵌入任何 PySide 应用"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, api_key: str, parent=None):
|
|
12
|
+
super().__init__(parent)
|
|
13
|
+
self.setStyleSheet("background-color: #121212;")
|
|
14
|
+
|
|
15
|
+
layout = QVBoxLayout(self)
|
|
16
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
17
|
+
|
|
18
|
+
self.quick_widget = QQuickWidget()
|
|
19
|
+
self.quick_widget.setResizeMode(QQuickWidget.ResizeMode.SizeRootObjectToView)
|
|
20
|
+
|
|
21
|
+
self.bridge = NovaChatBridge(api_key=api_key)
|
|
22
|
+
|
|
23
|
+
# 将 Python 桥接对象注入到 QML 环境
|
|
24
|
+
engine = self.quick_widget.engine()
|
|
25
|
+
engine.rootContext().setContextProperty("bridge", self.bridge)
|
|
26
|
+
|
|
27
|
+
# 加载打包在库里的 QML 文件
|
|
28
|
+
qml_path = os.path.join(os.path.dirname(__file__), "qml", "NovaChatWindow.qml")
|
|
29
|
+
self.quick_widget.setSource(QUrl.fromLocalFile(qml_path))
|
|
30
|
+
|
|
31
|
+
layout.addWidget(self.quick_widget)
|
|
32
|
+
|
|
33
|
+
# 暴露日志接口给外部
|
|
34
|
+
self.add_log = self.bridge.add_system_log
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nova-orbit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A stunning, GPU-accelerated AI chat UI framework for PySide6/QML.
|
|
5
|
+
Author-email: eagleye-china <zdt168@sina.cn>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/eagleye-china/nova-orbit
|
|
8
|
+
Project-URL: Repository, https://github.com/eagleye-china/nova-orbit
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: PySide6>=6.6
|
|
13
|
+
Requires-Dist: openai>=1.0
|
|
14
|
+
Requires-Dist: markdown>=3.5
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Nova-Orbit 🪐
|
|
18
|
+
|
|
19
|
+
> **The Ultimate Desktop Chat UI Framework for AI Agents.**
|
|
20
|
+
> *GPU-accelerated, stunningly beautiful, and unbelievably fast. Built on PySide6 & QML.*
|
|
21
|
+
|
|
22
|
+
[]()
|
|
23
|
+
[]()
|
|
24
|
+
[]()
|
|
25
|
+
|
|
26
|
+
**English** | [简体中文](README_zh.md)
|
|
27
|
+
|
|
28
|
+
## 🤔 Why Nova-Orbit?
|
|
29
|
+
|
|
30
|
+
Web-based AI apps (like Gradio or Streamlit) are slow and clunky. **Nova-Orbit** brings AI directly to your desktop with zero compromise:
|
|
31
|
+
|
|
32
|
+
- 🌱 **"Growing" Streaming**: Bubbles dynamically resize as text generates. Smooth, jank-free scrolling powered by QML.
|
|
33
|
+
- 🟩 **Integrated System Logs**: Neon green terminal-style logs live natively alongside chat bubbles. Perfect for Agentic workflows.
|
|
34
|
+
- 🖱️ **Native Desktop Feel**: Drag & drop files, native shortcuts, and pure dark mode aesthetics.
|
|
35
|
+
|
|
36
|
+
## 🚀 Install & Use (10 Lines of Code)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install nova-orbit
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/nova_orbit/__init__.py
|
|
5
|
+
src/nova_orbit/bridge.py
|
|
6
|
+
src/nova_orbit/widget.py
|
|
7
|
+
src/nova_orbit.egg-info/PKG-INFO
|
|
8
|
+
src/nova_orbit.egg-info/SOURCES.txt
|
|
9
|
+
src/nova_orbit.egg-info/dependency_links.txt
|
|
10
|
+
src/nova_orbit.egg-info/requires.txt
|
|
11
|
+
src/nova_orbit.egg-info/top_level.txt
|
|
12
|
+
src/nova_orbit/qml/NovaAttachmentStrip.qml
|
|
13
|
+
src/nova_orbit/qml/NovaChatWindow.qml
|
|
14
|
+
src/nova_orbit/qml/NovaInputBar.qml
|
|
15
|
+
src/nova_orbit/qml/NovaMessageBubble.qml
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nova_orbit
|