nova-chat 0.1.0__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.
nova_agent/__init__.py ADDED
File without changes
File without changes
@@ -0,0 +1,230 @@
1
+ # src/ai_chat/core/chat_worker.py
2
+ import json
3
+ import os
4
+ import shutil
5
+ import subprocess # ✅ 新增:用于执行 Ruff 命令行
6
+
7
+ from PySide6.QtCore import QThread, Signal
8
+ from openai import OpenAI
9
+
10
+ # =====================================================================
11
+ # 📜 智能体内置《架构宪法》(永久记忆)
12
+ # =====================================================================
13
+ SYSTEM_PROMPT = """
14
+ 你是 PySide-Quasar 和 PySide-Nova 框架的首席 AI 架构师。
15
+ 在协助开发时,必须遵守以下规范:
16
+ 1. QPainter 规范(Quasar):纯自绘,禁止 QSS。继承 QBaseWidget,使用 setWidthF。
17
+ 2. QML 规范(Nova):纯 QML 声明式 UI,Python 负责逻辑,利用 Property 进行双向绑定。
18
+ 3. ⚠️ QML 特殊禁忌:严禁对分组属性(如 border.color)使用 Behavior,必须改用 states + transitions 或独立图层!严禁直接用 parent.width 计算 delegate 宽度,防止死循环!
19
+ 4. 动画要求:PySide6 动画必须用 Property(float),严禁使用 @property。
20
+ 5. 代码必须包含完整的 Type Hints。
21
+
22
+ 工作流程:
23
+ 1. 使用 `write_multiple_files` 生成或修改代码。
24
+ 2. 生成后,务必调用 `run_ruff` 检查 Python 代码语法(如果是 QML 则跳过)。
25
+ 3. 如果 Ruff 报错(除 N802/E501 之外),请修复并重写。
26
+ """
27
+
28
+
29
+ class ChatWorker(QThread):
30
+ chunk_received = Signal(str)
31
+ action_log = Signal(str)
32
+ finished_signal = Signal()
33
+ error_signal = Signal(str)
34
+
35
+ def __init__(self, api_key, base_url, model, messages, project_root):
36
+ super().__init__()
37
+ self.api_key = api_key
38
+ self.base_url = base_url
39
+ self.model = model
40
+ self.messages = messages
41
+ self.project_root = project_root
42
+ self._is_running = True
43
+
44
+ # ✅ 定义 AI 可以使用的工具(新增了 run_ruff)
45
+ self.tools = [
46
+ {
47
+ "type": "function",
48
+ "function": {
49
+ "name": "read_file",
50
+ "description": "读取项目中的文件内容。在修改代码或参考现有组件风格时非常有用。",
51
+ "parameters": {
52
+ "type": "object",
53
+ "properties": {
54
+ "file_path": {"type": "string",
55
+ "description": "相对路径,如 src/pyside_quasar/.../button.py"}
56
+ },
57
+ "required": ["file_path"]
58
+ }
59
+ }
60
+ },
61
+ {
62
+ "type": "function",
63
+ "function": {
64
+ "name": "write_multiple_files",
65
+ "description": "将生成的代码同时写入到多个指定的项目文件中。当需要创建组件及其演示文件,或者 QML+Python 组合时,使用此工具。",
66
+ "parameters": {
67
+ "type": "object",
68
+ "properties": {
69
+ "files": {
70
+ "type": "array",
71
+ "description": "要写入的文件列表",
72
+ "items": {
73
+ "type": "object",
74
+ "properties": {
75
+ "file_path": {"type": "string", "description": "相对路径,如 src/xxx.py"},
76
+ "code": {"type": "string", "description": "完整的代码内容"}
77
+ },
78
+ "required": ["file_path", "code"]
79
+ }
80
+ }
81
+ },
82
+ "required": ["files"]
83
+ }
84
+ }
85
+ },
86
+ {
87
+ "type": "function",
88
+ "function": {
89
+ "name": "run_ruff",
90
+ "description": "运行 ruff check --fix 检查代码语法和格式。如果报错,你需要根据报错修改代码。",
91
+ "parameters": {
92
+ "type": "object",
93
+ "properties": {
94
+ "file_path": {"type": "string", "description": "需要检查的文件相对路径"}
95
+ },
96
+ "required": ["file_path"]
97
+ }
98
+ }
99
+ }
100
+ ]
101
+
102
+ def stop(self):
103
+ self._is_running = False
104
+
105
+ def _read_file(self, file_path):
106
+ full_path = os.path.join(self.project_root, file_path)
107
+ if not os.path.exists(full_path):
108
+ return f"错误: 文件 {file_path} 不存在。"
109
+ try:
110
+ with open(full_path, "r", encoding="utf-8") as f:
111
+ return f.read()
112
+ except Exception as e:
113
+ return f"读取失败: {str(e)}"
114
+
115
+ def _write_files(self, files_list):
116
+ results = []
117
+ for item in files_list:
118
+ file_path = item.get("file_path", "")
119
+ code = item.get("code", "")
120
+ if not file_path or not code:
121
+ continue
122
+
123
+ full_path = os.path.join(self.project_root, file_path)
124
+ os.makedirs(os.path.dirname(full_path), exist_ok=True)
125
+
126
+ # 防覆盖机制
127
+ if os.path.exists(full_path):
128
+ bak_path = full_path + ".bak"
129
+ shutil.copy2(full_path, bak_path)
130
+ self.action_log.emit(f"⚠️ 检测到旧文件,已备份至 {file_path}.bak")
131
+
132
+ with open(full_path, "w", encoding="utf-8") as f:
133
+ f.write(code)
134
+ results.append(file_path)
135
+
136
+ return f"成功批量写入 {len(results)} 个文件: {', '.join(results)}"
137
+
138
+ # ✅ 新增:运行 Ruff 检查的核心函数
139
+ def _run_ruff(self, file_path):
140
+ if file_path.endswith(".qml") or file_path.endswith(".qmldir"):
141
+ return "跳过 Ruff 检查:QML 文件无需 Python 的 Ruff 检查,请使用 QML 引擎直接验证。"
142
+
143
+ full_path = os.path.join(self.project_root, file_path)
144
+ if not os.path.exists(full_path):
145
+ return f"错误: 文件 {file_path} 不存在,无法检查。"
146
+
147
+ try:
148
+ # 调用系统中的 ruff,加上 --fix 自动修复格式问题
149
+ result = subprocess.run(
150
+ ["ruff", "check", "--fix", full_path],
151
+ capture_output=True, text=True, encoding="utf-8"
152
+ )
153
+ output = result.stdout + result.stderr
154
+ if not output.strip():
155
+ return "Ruff 检查通过,没有发现任何语法问题。"
156
+ return f"Ruff 发现以下问题(能自动修复的已修复),请修复剩余的:\n{output}"
157
+ except FileNotFoundError:
158
+ return "错误: 系统中找不到 ruff 命令,请先在终端 pip install ruff。"
159
+ except Exception as e:
160
+ return f"执行 Ruff 时发生异常: {str(e)}"
161
+
162
+ def run(self):
163
+ try:
164
+ client = OpenAI(api_key=self.api_key, base_url=self.base_url)
165
+
166
+ # ✅ 注入系统提示词(宪法)
167
+ final_messages = [{"role": "system", "content": SYSTEM_PROMPT}] + self.messages
168
+
169
+ while self._is_running:
170
+ response = client.chat.completions.create(
171
+ model=self.model,
172
+ messages=final_messages,
173
+ tools=self.tools,
174
+ tool_choice="auto"
175
+ )
176
+
177
+ message = response.choices[0].message
178
+
179
+ assistant_msg = {"role": "assistant", "content": message.content or ""}
180
+ if message.tool_calls:
181
+ assistant_msg["tool_calls"] = [
182
+ {"id": tc.id, "type": tc.type,
183
+ "function": {"name": tc.function.name, "arguments": tc.function.arguments}}
184
+ for tc in message.tool_calls
185
+ ]
186
+ self.messages.append(assistant_msg)
187
+ final_messages.append(assistant_msg)
188
+
189
+ if message.tool_calls:
190
+ for tool_call in message.tool_calls:
191
+ tool_content = "未执行"
192
+ try:
193
+ args = json.loads(tool_call.function.arguments)
194
+
195
+ if tool_call.function.name == "read_file":
196
+ self.action_log.emit(f"📖 正在读取: {args.get('file_path')}")
197
+ tool_content = self._read_file(args.get("file_path", ""))
198
+
199
+ elif tool_call.function.name == "write_multiple_files":
200
+ files = args.get("files", [])
201
+ file_names = [f.get("file_path") for f in files]
202
+ self.action_log.emit(f"🛠️ 准备批量写入文件: {', '.join(file_names)}")
203
+ tool_content = self._write_files(files)
204
+
205
+ # ✅ 新增:处理 AI 调用 Ruff 的请求
206
+ elif tool_call.function.name == "run_ruff":
207
+ path = args.get("file_path", "")
208
+ self.action_log.emit(f"🔍 正在用 Ruff 检查代码: {path}")
209
+ tool_content = self._run_ruff(path)
210
+
211
+ except json.JSONDecodeError:
212
+ tool_content = "错误: JSON 解析失败,请确保输出格式正确。"
213
+ except Exception as e:
214
+ tool_content = f"工具执行异常: {str(e)}"
215
+
216
+ # 闭环回复
217
+ tool_msg = {"role": "tool", "tool_call_id": tool_call.id, "content": tool_content}
218
+ self.messages.append(tool_msg)
219
+ final_messages.append(tool_msg)
220
+ continue
221
+
222
+ if message.content:
223
+ self.chunk_received.emit(message.content)
224
+
225
+ self.finished_signal.emit()
226
+ break
227
+
228
+ except Exception as e:
229
+ if self._is_running:
230
+ self.error_signal.emit(str(e))
@@ -0,0 +1,2 @@
1
+ # -*- coding: utf-8 -*-
2
+ """tools"""
nova_agent/main.py ADDED
@@ -0,0 +1,53 @@
1
+ # src/dev_toolbox_pro/main.py
2
+ import ctypes
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from PySide6.QtCore import QTranslator, QLibraryInfo
7
+ from PySide6.QtGui import QIcon
8
+ from PySide6.QtWidgets import QApplication
9
+ from nova_agent.ui.main_window import MainWindow
10
+ from nova_agent.utils.logger import setup_logger
11
+ from nova_agent.version import APP_USER_MODEL_ID, APP_NAME
12
+
13
+ try:
14
+ from resources import resources_rc # noqa
15
+ except ImportError:
16
+ print("[WARN] 未找到 resources_rc,图标可能无法显示。")
17
+
18
+
19
+ def main():
20
+ if sys.platform == "win32":
21
+ try:
22
+ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(APP_USER_MODEL_ID)
23
+ except Exception as e:
24
+ print(f"设置 AppUserModelID 失败: {e}")
25
+
26
+ logger = setup_logger()
27
+ logger.info(f"{APP_NAME} starting...")
28
+
29
+ app = QApplication(sys.argv)
30
+
31
+ # 汉化逻辑
32
+ translator = QTranslator()
33
+ translations_path = QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath)
34
+ if translator.load("qtbase_zh_CN", translations_path):
35
+ app.installTranslator(translator)
36
+
37
+ app.setApplicationName(APP_NAME)
38
+ app.setWindowIcon(QIcon(":/icons/set-up.svg"))
39
+
40
+ # 加载样式表 (因为 main.py 和 ui 同级,直接用相对路径)
41
+ qss_path = Path(__file__).parent / "ui" / "styles.qss"
42
+ if qss_path.exists():
43
+ app.setStyleSheet(qss_path.read_text(encoding="utf-8"))
44
+ else:
45
+ logger.warning(f"未找到样式表: {qss_path}")
46
+
47
+ window = MainWindow()
48
+ window.show()
49
+ sys.exit(app.exec())
50
+
51
+
52
+ if __name__ == "__main__":
53
+ main()
@@ -0,0 +1,72 @@
1
+ // CardDemo.qml
2
+ // NovaCard 演示程序:展示圆角、阴影与 hover 放大动画
3
+ //
4
+ // 注意:卡片宽度由 Column 的 width 统一约束,卡片内部不使用 parent.width
5
+ // 反推自身尺寸,避免与 hover scale 形成布局死循环。
6
+
7
+ import QtQuick 2.15
8
+ import QtQuick.Window 2.15
9
+ import QtQuick.Controls 2.15
10
+
11
+ Window {
12
+ id: window
13
+ width: 900
14
+ height: 620
15
+ visible: true
16
+ title: "NovaCard Demo"
17
+ color: "#F7F8FA"
18
+
19
+ readonly property var cardModel: [
20
+ { title: "基础卡片", subtitle: "圆角 + 阴影 + hover 放大" },
21
+ { title: "Elevation", subtitle: "通过 elevation 调整阴影强度" },
22
+ { title: "可交互", subtitle: "点击卡片会打印日志" },
23
+ { title: "内容插槽", subtitle: "default property 支持自定义内容" },
24
+ { title: "长标题会被省略号截断展示效果", subtitle: "ElideRight 模式" },
25
+ { title: "浅色主题", subtitle: "所有颜色均可通过属性覆盖" }
26
+ ]
27
+
28
+ Text {
29
+ id: header
30
+ anchors.left: parent.left
31
+ anchors.top: parent.top
32
+ anchors.margins: 28
33
+ text: "NovaCard"
34
+ font.pixelSize: 26
35
+ font.bold: true
36
+ color: "#1D2129"
37
+ }
38
+
39
+ GridView {
40
+ id: grid
41
+ anchors.left: parent.left
42
+ anchors.right: parent.right
43
+ anchors.top: header.bottom
44
+ anchors.bottom: parent.bottom
45
+ anchors.margins: 28
46
+ anchors.topMargin: 16
47
+ cellWidth: (width) / 3
48
+ cellHeight: 200
49
+ clip: true
50
+ model: window.cardModel
51
+
52
+ delegate: Item {
53
+ // 使用 GridView 提供的宽度基准,而非 parent.width,
54
+ // 避免 delegate 与父级宽度互相依赖造成循环绑定。
55
+ width: grid.cellWidth
56
+ height: grid.cellHeight
57
+
58
+ NovaCard {
59
+ anchors.centerIn: parent
60
+ width: Math.min(parent.width - 24, 280)
61
+ height: 168
62
+ title: modelData.title
63
+ subtitle: modelData.subtitle
64
+ elevation: index % 2 === 0 ? 18 : 26
65
+
66
+ onClicked: console.log("Clicked:", modelData.title)
67
+ }
68
+ }
69
+
70
+ ScrollBar.vertical: ScrollBar { }
71
+ }
72
+ }
@@ -0,0 +1,93 @@
1
+ import QtQuick
2
+ import QtQuick.Window
3
+ import QtQuick.Layouts
4
+
5
+ /*!
6
+ DemoButton.qml
7
+ NovaButton 的演示界面:全部 UI 声明式完成,逻辑通过 bridge 交给 Python。
8
+ */
9
+ Window {
10
+ id: win
11
+
12
+ width: 520
13
+ height: 380
14
+ visible: true
15
+ title: "NovaButton Demo"
16
+ color: "#121212"
17
+
18
+ ColumnLayout {
19
+ anchors.fill: parent
20
+ anchors.margins: 28
21
+ spacing: 22
22
+
23
+ Text {
24
+ text: "NovaButton · hover 动画演示"
25
+ color: "#ECEFF1"
26
+ font.pixelSize: 20
27
+ font.weight: Font.DemiBold
28
+ Layout.fillWidth: true
29
+ }
30
+
31
+ Text {
32
+ text: "把鼠标移到按钮上,观察背景 / 边框的平滑过渡。"
33
+ color: "#90A4AE"
34
+ font.pixelSize: 13
35
+ Layout.fillWidth: true
36
+ }
37
+
38
+ RowLayout {
39
+ spacing: 16
40
+ Layout.topMargin: 8
41
+
42
+ NovaButton {
43
+ text: "主要按钮"
44
+ variant: "primary"
45
+ onTapped: bridge.onPrimaryClicked()
46
+ }
47
+
48
+ NovaButton {
49
+ text: "次要按钮"
50
+ variant: "secondary"
51
+ onTapped: bridge.onSecondaryClicked()
52
+ }
53
+
54
+ NovaButton {
55
+ text: "危险操作"
56
+ variant: "danger"
57
+ onTapped: bridge.onDangerClicked()
58
+ }
59
+ }
60
+
61
+ NovaButton {
62
+ text: "禁用态(无 hover)"
63
+ variant: "secondary"
64
+ enabled: false
65
+ }
66
+
67
+ Rectangle {
68
+ Layout.fillWidth: true
69
+ Layout.preferredHeight: 1
70
+ color: "#263238"
71
+ }
72
+
73
+ Text {
74
+ id: counter
75
+ text: "主要按钮点击次数: " + bridge.clickedCount
76
+ color: "#4CAF50"
77
+ font.pixelSize: 14
78
+ Layout.fillWidth: true
79
+
80
+ // Python 修改后自动刷新(bridge 属性变化不会自动通知,靠 Connections 手动同步)
81
+ property int clickedCount: 0
82
+ }
83
+
84
+ Item { Layout.fillHeight: true }
85
+ }
86
+
87
+ Connections {
88
+ target: bridge
89
+ function onClickedCountChanged(count: int): void {
90
+ counter.clickedCount = count
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,104 @@
1
+ // NovaCard.qml —— Nova 框架卡片组件
2
+ // 纯 QML 声明式 UI:圆角 + 阴影 + hover 放大动画
3
+ import QtQuick
4
+ import QtQuick.Effects
5
+
6
+ Item {
7
+ id: root
8
+
9
+ // ---- 对外属性 ----
10
+ property alias radius: surface.radius
11
+ property alias color: surface.color
12
+ property alias borderColor: surface.border.color
13
+ property alias borderWidth: surface.border.width
14
+ property alias contentItem: contentHost
15
+
16
+ // 阴影配置
17
+ property color shadowColor: "#40000000"
18
+ property real shadowBlur: 24.0
19
+ property real shadowOffsetY: 6.0
20
+ property real shadowOpacity: 1.0
21
+
22
+ // hover 动画配置
23
+ property real hoverScale: 1.03
24
+ property int animationDuration: 180
25
+ property bool hoverEnabled: true
26
+
27
+ // 只读状态
28
+ readonly property bool hovered: hoverArea.containsMouse
29
+
30
+ implicitWidth: 240
31
+ implicitHeight: 140
32
+
33
+ // 放大动画根节点(避免缩放影响外部布局)
34
+ Item {
35
+ id: scaleRoot
36
+ anchors.fill: parent
37
+ scale: root.hoverEnabled && root.hovered ? root.hoverScale : 1.0
38
+ transformOrigin: Item.Center
39
+
40
+ Behavior on scale {
41
+ NumberAnimation {
42
+ duration: root.animationDuration
43
+ easing.type: Easing.OutCubic
44
+ }
45
+ }
46
+
47
+ // 阴影层
48
+ MultiEffect {
49
+ source: surface
50
+ anchors.fill: surface
51
+ shadowEnabled: true
52
+ shadowColor: root.shadowColor
53
+ shadowBlur: root.shadowBlur / 32.0
54
+ shadowVerticalOffset: root.shadowOffsetY
55
+ shadowOpacity: root.shadowOpacity
56
+ opacity: root.hovered ? 1.0 : 0.85
57
+
58
+ Behavior on opacity {
59
+ NumberAnimation {
60
+ duration: root.animationDuration
61
+ easing.type: Easing.OutCubic
62
+ }
63
+ }
64
+ }
65
+
66
+ // 卡片本体
67
+ Rectangle {
68
+ id: surface
69
+ anchors.fill: parent
70
+ radius: 12
71
+ color: "#1E2228"
72
+ antialiasing: true
73
+
74
+ border.color: root.hovered ? "#4C8DFF" : "#2E3440"
75
+ border.width: 1
76
+
77
+ Behavior on border.color {
78
+ ColorAnimation {
79
+ duration: root.animationDuration
80
+ easing.type: Easing.OutCubic
81
+ }
82
+ }
83
+
84
+ // 背景仅负责绘制,内容放在上层容器
85
+ clip: true
86
+ }
87
+
88
+ // 内容容器(位于 surface 之上,避免被裁剪到阴影层)
89
+ Item {
90
+ id: contentHost
91
+ anchors.fill: surface
92
+ anchors.margins: 16
93
+ }
94
+ }
95
+
96
+ // hover 检测区域
97
+ MouseArea {
98
+ id: hoverArea
99
+ anchors.fill: parent
100
+ hoverEnabled: true
101
+ acceptedButtons: Qt.NoButton
102
+ cursorShape: Qt.PointingHandCursor
103
+ }
104
+ }
@@ -0,0 +1,57 @@
1
+ import QtQuick
2
+ import QtQuick.Controls
3
+ import QtQuick.Layouts
4
+
5
+ ApplicationWindow {
6
+ id: window
7
+ width: 560
8
+ height: 360
9
+ visible: true
10
+ color: "#f0f2f5"
11
+ title: "NovaCard Demo"
12
+
13
+ GridLayout {
14
+ anchors.centerIn: parent
15
+ columns: 2
16
+ rowSpacing: 32
17
+ columnSpacing: 32
18
+
19
+ Repeater {
20
+ model: [
21
+ { title: "基础卡片", desc: "默认圆角与时影" },
22
+ { title: "悬停试试", desc: "鼠标移上去会放大哦" },
23
+ { title: "自定义半径", desc: "radius: 8" },
24
+ { title: "无阴影", desc: "shadowBlur: 0" }
25
+ ]
26
+
27
+ NovaCard {
28
+ width: 220
29
+ height: 120
30
+ radius: index === 2 ? 8 : 16
31
+ shadowBlur: index === 3 ? 0.0 : 1.0
32
+
33
+ ColumnLayout {
34
+ anchors.fill: parent
35
+ anchors.margins: 16
36
+ spacing: 6
37
+
38
+ Label {
39
+ text: modelData.title
40
+ font.pixelSize: 16
41
+ font.bold: true
42
+ color: "#1f2328"
43
+ }
44
+ Label {
45
+ text: modelData.desc
46
+ font.pixelSize: 12
47
+ color: "#6b7280"
48
+ Layout.fillWidth: true
49
+ wrapMode: Text.WordWrap
50
+ }
51
+ }
52
+
53
+ onClicked: console.log("clicked:", modelData.title)
54
+ }
55
+ }
56
+ }
57
+ }
File without changes
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from PySide6.QtCore import QObject, QUrl, Signal, Slot
7
+ from PySide6.QtGui import QGuiApplication
8
+ from PySide6.QtQml import QQmlApplicationEngine
9
+ from PySide6.QtQuick import QQuickWindow, QSGRendererInterface
10
+
11
+
12
+ class ButtonDemoBridge(QObject):
13
+ """Python 侧逻辑桥:只负责逻辑,UI 全部在 QML 中声明。"""
14
+
15
+ clickedCountChanged = Signal(int)
16
+
17
+ def __init__(self, parent: QObject | None = None) -> None:
18
+ super().__init__(parent)
19
+ self._clicked_count: int = 0
20
+
21
+ @Slot()
22
+ def onPrimaryClicked(self) -> None:
23
+ self._clicked_count += 1
24
+ print(f"[primary] clicked, total={self._clicked_count}")
25
+ self.clickedCountChanged.emit(self._clicked_count)
26
+
27
+ @Slot()
28
+ def onSecondaryClicked(self) -> None:
29
+ print("[secondary] clicked")
30
+
31
+ @Slot()
32
+ def onDangerClicked(self) -> None:
33
+ print("[danger] clicked")
34
+
35
+
36
+ def main() -> None:
37
+ QQuickWindow.setGraphicsApi(QSGRendererInterface.GraphicsApi.Direct3D11)
38
+
39
+ app: QGuiApplication = QGuiApplication(sys.argv)
40
+
41
+ engine: QQmlApplicationEngine = QQmlApplicationEngine()
42
+
43
+ qml_dir: Path = Path(__file__).resolve().parent
44
+ engine.addImportPath(str(qml_dir))
45
+
46
+ bridge: ButtonDemoBridge = ButtonDemoBridge()
47
+ engine.rootContext().setContextProperty("bridge", bridge)
48
+
49
+ demo_qml: Path = qml_dir / "DemoButton.qml"
50
+ engine.load(QUrl.fromLocalFile(str(demo_qml)))
51
+
52
+ if not engine.rootObjects():
53
+ sys.exit(-1)
54
+
55
+ sys.exit(app.exec())
56
+
57
+
58
+ if __name__ == "__main__":
59
+ main()