orrin-cli 0.1.8.2__tar.gz → 0.1.9__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.
- {orrin_cli-0.1.8.2/orrin_cli.egg-info → orrin_cli-0.1.9}/PKG-INFO +1 -1
- {orrin_cli-0.1.8.2 → orrin_cli-0.1.9}/orrin/cli.py +33 -22
- orrin_cli-0.1.9/orrin/minimal_display.py +548 -0
- {orrin_cli-0.1.8.2 → orrin_cli-0.1.9/orrin_cli.egg-info}/PKG-INFO +1 -1
- {orrin_cli-0.1.8.2 → orrin_cli-0.1.9}/pyproject.toml +1 -1
- orrin_cli-0.1.8.2/orrin/minimal_display.py +0 -305
- {orrin_cli-0.1.8.2 → orrin_cli-0.1.9}/LICENSE +0 -0
- {orrin_cli-0.1.8.2 → orrin_cli-0.1.9}/README.md +0 -0
- {orrin_cli-0.1.8.2 → orrin_cli-0.1.9}/orrin/__init__.py +0 -0
- {orrin_cli-0.1.8.2 → orrin_cli-0.1.9}/orrin/chat_tui.py +0 -0
- {orrin_cli-0.1.8.2 → orrin_cli-0.1.9}/orrin_cli.egg-info/SOURCES.txt +0 -0
- {orrin_cli-0.1.8.2 → orrin_cli-0.1.9}/orrin_cli.egg-info/dependency_links.txt +0 -0
- {orrin_cli-0.1.8.2 → orrin_cli-0.1.9}/orrin_cli.egg-info/entry_points.txt +0 -0
- {orrin_cli-0.1.8.2 → orrin_cli-0.1.9}/orrin_cli.egg-info/requires.txt +0 -0
- {orrin_cli-0.1.8.2 → orrin_cli-0.1.9}/orrin_cli.egg-info/top_level.txt +0 -0
- {orrin_cli-0.1.8.2 → orrin_cli-0.1.9}/setup.cfg +0 -0
|
@@ -26,7 +26,7 @@ app.add_typer(ui, name="ui")
|
|
|
26
26
|
projects = typer.Typer(help='OrrinSDK project commands')
|
|
27
27
|
app.add_typer(projects, name='projects')
|
|
28
28
|
|
|
29
|
-
API_BASE = "https://stellr-company.com"
|
|
29
|
+
API_BASE = "https://stellr-company.com"
|
|
30
30
|
|
|
31
31
|
APP_NAME = "orrin"
|
|
32
32
|
APP_AUTHOR = "orrin" # optional but recommended on Windows
|
|
@@ -198,6 +198,17 @@ def init_app_backend_project():
|
|
|
198
198
|
'name': project_name,
|
|
199
199
|
'desc': project_desc
|
|
200
200
|
},
|
|
201
|
+
'public': {
|
|
202
|
+
'display_name': project_name,
|
|
203
|
+
'display_desc': project_desc,
|
|
204
|
+
'whats_new': '<describe_version>',
|
|
205
|
+
'authors': [],
|
|
206
|
+
'open_source': False,
|
|
207
|
+
'copyright': '<your_copyright>',
|
|
208
|
+
'dependencies': [
|
|
209
|
+
{}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
201
212
|
}
|
|
202
213
|
|
|
203
214
|
path = os.path.join(os.getcwd(), project_name)
|
|
@@ -253,32 +264,32 @@ def init_tool_project():
|
|
|
253
264
|
)
|
|
254
265
|
|
|
255
266
|
project_toml = {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
},
|
|
267
|
+
'general': {
|
|
268
|
+
'project_type': 'tool',
|
|
269
|
+
'name': project_name,
|
|
270
|
+
'desc': project_desc,
|
|
271
|
+
'nuances': {},
|
|
262
272
|
'icons': {
|
|
263
273
|
'24x24': '<path>',
|
|
264
274
|
'32x32': '<path>',
|
|
265
275
|
'48x48': '<path>',
|
|
266
276
|
'64x64': '<path>'
|
|
267
277
|
},
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
278
|
+
},
|
|
279
|
+
'plugins': {
|
|
280
|
+
'supported': False
|
|
281
|
+
},
|
|
282
|
+
'metadata': {},
|
|
283
|
+
'public': {
|
|
284
|
+
'display_name': project_name,
|
|
285
|
+
'display_desc': project_desc,
|
|
286
|
+
'usage_rundown': '<rundown_of_tool_usage>',
|
|
287
|
+
'whats_new': '<explain_version>',
|
|
288
|
+
'authors': [],
|
|
289
|
+
'open_source': False,
|
|
290
|
+
'copyright': '<your_copyright>'
|
|
281
291
|
}
|
|
292
|
+
}
|
|
282
293
|
|
|
283
294
|
# Create project
|
|
284
295
|
path = os.path.join(os.getcwd(), project_name)
|
|
@@ -446,8 +457,8 @@ def connect_to_project():
|
|
|
446
457
|
def itp():
|
|
447
458
|
init_tool_project()
|
|
448
459
|
|
|
449
|
-
"""@
|
|
450
|
-
def
|
|
460
|
+
"""@app.command(help='Launch GUI interface for OrrinCLI and get access to tools and agents')
|
|
461
|
+
def launch():
|
|
451
462
|
from minimal_display import OrrinCLI
|
|
452
463
|
OrrinCLI().start()"""
|
|
453
464
|
|
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import flet as ft
|
|
3
|
+
from orrinsdk import OrrinAISDK, Models
|
|
4
|
+
|
|
5
|
+
class OrrinCLI:
|
|
6
|
+
# --- DESIGN SYSTEM ---
|
|
7
|
+
BG_BASE = "#09090B" # Deep black/grey (Editor/Main)
|
|
8
|
+
BG_PANEL = "#18181B" # Slightly lighter (Sidebars/Chat)
|
|
9
|
+
BG_SURFACE = "#27272A" # Interactive/Hover elements
|
|
10
|
+
BORDER = "#27272A" # Subtle dividers
|
|
11
|
+
TEXT_PRIMARY = "#FAFAFA" # High contrast text
|
|
12
|
+
TEXT_SECONDARY = "#A1A1AA" # Muted text
|
|
13
|
+
ACCENT = "#3B82F6" # Cursor/Modern Blue
|
|
14
|
+
ACCENT_HOVER = "#2563EB"
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.root = Path.cwd()
|
|
18
|
+
self.python_files = list(self.root.rglob("*.py"))
|
|
19
|
+
self.connected_file = None
|
|
20
|
+
self.file_content = ""
|
|
21
|
+
|
|
22
|
+
# ---------------- AI INIT ----------------
|
|
23
|
+
# Kept your exact initialization parameters
|
|
24
|
+
self.ai = OrrinAISDK(
|
|
25
|
+
api_key='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',
|
|
26
|
+
model=Models.GPT_5_MINI
|
|
27
|
+
)
|
|
28
|
+
self.messages = []
|
|
29
|
+
|
|
30
|
+
# ---------------- UI STATE & CONTROLS ----------------
|
|
31
|
+
self.page = None
|
|
32
|
+
self.main_container = ft.Container(expand=True, bgcolor=self.BG_BASE)
|
|
33
|
+
|
|
34
|
+
# Modern Floating Chat Input
|
|
35
|
+
self.chat_input_field = ft.TextField(
|
|
36
|
+
expand=True,
|
|
37
|
+
hint_text="Ask Orrin to edit or explain...",
|
|
38
|
+
hint_style=ft.TextStyle(color=self.TEXT_SECONDARY, size=13, font_family="Inter"),
|
|
39
|
+
text_style=ft.TextStyle(color=self.TEXT_PRIMARY, size=13, font_family="Inter"),
|
|
40
|
+
border=ft.InputBorder.NONE,
|
|
41
|
+
cursor_color=self.ACCENT,
|
|
42
|
+
content_padding=ft.padding.symmetric(vertical=14, horizontal=16),
|
|
43
|
+
on_submit=self.send_message,
|
|
44
|
+
on_change=self.update_send_button_state,
|
|
45
|
+
shift_enter=True,
|
|
46
|
+
multiline=True,
|
|
47
|
+
min_lines=1,
|
|
48
|
+
max_lines=6,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
self.send_button = ft.IconButton(
|
|
52
|
+
icon=ft.icons.ARROW_UPWARD_ROUNDED,
|
|
53
|
+
icon_color=ft.colors.BLACK26, # Default (will be updated dynamically)
|
|
54
|
+
bgcolor=ft.colors.WHITE,
|
|
55
|
+
icon_size=16,
|
|
56
|
+
width=28,
|
|
57
|
+
height=28,
|
|
58
|
+
style=ft.ButtonStyle(
|
|
59
|
+
padding=0,
|
|
60
|
+
shape=ft.RoundedRectangleBorder(radius=6)
|
|
61
|
+
),
|
|
62
|
+
tooltip="Send (Enter)",
|
|
63
|
+
on_click=self.send_message,
|
|
64
|
+
# Store reference
|
|
65
|
+
ref=ft.Ref(), # Optional but helpful
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
self.chat_listview = ft.ListView(
|
|
69
|
+
expand=True,
|
|
70
|
+
spacing=24,
|
|
71
|
+
auto_scroll=True,
|
|
72
|
+
padding=ft.padding.only(right=8, top=16, bottom=16)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# ---------------- MAIN ENTRY ----------------
|
|
76
|
+
def main(self, page: ft.Page):
|
|
77
|
+
self.page = page
|
|
78
|
+
page.title = "OrrinCLI - AI Code Editor"
|
|
79
|
+
page.bgcolor = self.BG_BASE
|
|
80
|
+
page.window.width = 1200
|
|
81
|
+
page.window.height = 850
|
|
82
|
+
page.padding = 0
|
|
83
|
+
|
|
84
|
+
# Beautiful custom scrollbars and theme settings
|
|
85
|
+
page.theme_mode = ft.ThemeMode.DARK
|
|
86
|
+
page.theme = ft.Theme(
|
|
87
|
+
scrollbar_theme=ft.ScrollbarTheme(
|
|
88
|
+
track_color=ft.colors.TRANSPARENT,
|
|
89
|
+
thumb_color=self.BG_SURFACE,
|
|
90
|
+
thickness=6,
|
|
91
|
+
radius=10,
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
page.fonts = {
|
|
96
|
+
"Inter": "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap",
|
|
97
|
+
"JetBrains": "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
page.add(
|
|
101
|
+
ft.Column([
|
|
102
|
+
self.build_top_nav(),
|
|
103
|
+
self.main_container,
|
|
104
|
+
self.build_status_bar()
|
|
105
|
+
], expand=True, spacing=0)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
self.render_unselected_view()
|
|
109
|
+
|
|
110
|
+
# ---------------- VIEW MANAGERS ----------------
|
|
111
|
+
def update_send_button_state(self, e):
|
|
112
|
+
"""Dynamically update send button color and enabled state"""
|
|
113
|
+
has_text = bool(self.chat_input_field.value and self.chat_input_field.value.strip())
|
|
114
|
+
|
|
115
|
+
if self.send_button:
|
|
116
|
+
self.send_button.icon_color = ft.colors.BLACK if has_text else ft.colors.BLACK26
|
|
117
|
+
self.send_button.disabled = not has_text
|
|
118
|
+
self.send_button.update()
|
|
119
|
+
|
|
120
|
+
def render_unselected_view(self):
|
|
121
|
+
"""Initial state: Minimal file explorer & clean empty state."""
|
|
122
|
+
self.main_container.content = ft.Row(
|
|
123
|
+
expand=True,
|
|
124
|
+
spacing=0,
|
|
125
|
+
controls=[
|
|
126
|
+
self.build_vertical_file_panel(),
|
|
127
|
+
self.build_empty_editor_state()
|
|
128
|
+
]
|
|
129
|
+
)
|
|
130
|
+
if self.page:
|
|
131
|
+
self.page.update()
|
|
132
|
+
|
|
133
|
+
def render_selected_view(self):
|
|
134
|
+
"""Active state: Editor (Dominant) + AI Panel (Right)."""
|
|
135
|
+
self.update_chat_ui()
|
|
136
|
+
|
|
137
|
+
self.main_container.content = ft.Row(
|
|
138
|
+
expand=True,
|
|
139
|
+
spacing=0,
|
|
140
|
+
controls=[
|
|
141
|
+
# Left Side: Editor & Tabs (Expands to take 71% of screen)
|
|
142
|
+
ft.Container(
|
|
143
|
+
expand=5,
|
|
144
|
+
bgcolor=self.BG_BASE,
|
|
145
|
+
content=ft.Column(
|
|
146
|
+
expand=True,
|
|
147
|
+
spacing=0,
|
|
148
|
+
controls=[
|
|
149
|
+
self.build_horizontal_file_list(),
|
|
150
|
+
self.build_code_editor()
|
|
151
|
+
]
|
|
152
|
+
)
|
|
153
|
+
),
|
|
154
|
+
# Vertical Divider
|
|
155
|
+
ft.Container(width=1, bgcolor=self.BORDER),
|
|
156
|
+
# Right Side: AI Assistant (Expands to take 29% of screen)
|
|
157
|
+
self.build_chat_panel()
|
|
158
|
+
]
|
|
159
|
+
)
|
|
160
|
+
if self.page:
|
|
161
|
+
self.page.update()
|
|
162
|
+
|
|
163
|
+
def connect_file(self, file_path):
|
|
164
|
+
"""Reads content safely and triggers layout refresh."""
|
|
165
|
+
self.connected_file = file_path
|
|
166
|
+
try:
|
|
167
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
168
|
+
self.file_content = f.read()
|
|
169
|
+
except Exception as e:
|
|
170
|
+
self.file_content = f"# Error reading file:\n{str(e)}"
|
|
171
|
+
|
|
172
|
+
self.render_selected_view()
|
|
173
|
+
|
|
174
|
+
# ---------------- UI: BASE COMPONENTS ----------------
|
|
175
|
+
def build_top_nav(self):
|
|
176
|
+
"""Ultra-minimal, modern application header."""
|
|
177
|
+
return ft.Container(
|
|
178
|
+
bgcolor=self.BG_PANEL,
|
|
179
|
+
padding=ft.padding.symmetric(horizontal=16, vertical=10),
|
|
180
|
+
border=ft.border.only(bottom=ft.border.BorderSide(1, self.BORDER)),
|
|
181
|
+
content=ft.Row(
|
|
182
|
+
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
|
183
|
+
controls=[
|
|
184
|
+
ft.Row([
|
|
185
|
+
# Premium Logo mark
|
|
186
|
+
ft.Container(
|
|
187
|
+
bgcolor=self.TEXT_PRIMARY, border_radius=5, width=30, height=30,
|
|
188
|
+
alignment=ft.alignment.center,
|
|
189
|
+
content=ft.Image('OrrinLogoBlack_T.png', width=28, height=28)
|
|
190
|
+
),
|
|
191
|
+
ft.Text("Orrin", color=self.TEXT_PRIMARY, size=14, weight=ft.FontWeight.W_600, font_family="Inter"),
|
|
192
|
+
ft.Text("Workspace", color=self.TEXT_SECONDARY, size=14, weight=ft.FontWeight.W_400, font_family="Inter"),
|
|
193
|
+
], spacing=12),
|
|
194
|
+
|
|
195
|
+
# Window Controls / Actions
|
|
196
|
+
ft.Row([
|
|
197
|
+
ft.IconButton(icon=ft.icons.SEARCH, icon_color=self.TEXT_SECONDARY, icon_size=16, tooltip="Search (Cmd+P)"),
|
|
198
|
+
ft.IconButton(icon=ft.icons.SETTINGS_OUTLINED, icon_color=self.TEXT_SECONDARY, icon_size=16, tooltip="Settings"),
|
|
199
|
+
], spacing=4)
|
|
200
|
+
]
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def build_status_bar(self):
|
|
205
|
+
"""VS Code / Cursor style bottom status bar."""
|
|
206
|
+
return ft.Container(
|
|
207
|
+
bgcolor=self.ACCENT,
|
|
208
|
+
padding=ft.padding.symmetric(horizontal=12, vertical=4),
|
|
209
|
+
content=ft.Row(
|
|
210
|
+
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
|
211
|
+
controls=[
|
|
212
|
+
ft.Row([
|
|
213
|
+
ft.Icon(ft.icons.CHECK_CIRCLE_OUTLINE, color=self.TEXT_PRIMARY, size=12),
|
|
214
|
+
ft.Text("OrrinSDK Ready", color=self.TEXT_PRIMARY, size=11, font_family="Inter", weight=ft.FontWeight.W_500)
|
|
215
|
+
], spacing=6),
|
|
216
|
+
ft.Row([
|
|
217
|
+
ft.Text("UTF-8", color=self.TEXT_PRIMARY, size=11, font_family="Inter"),
|
|
218
|
+
ft.Container(width=12),
|
|
219
|
+
ft.Text("Python 3", color=self.TEXT_PRIMARY, size=11, font_family="Inter"),
|
|
220
|
+
], spacing=0)
|
|
221
|
+
]
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# ---------------- UI: UNSELECTED COMPONENTS ----------------
|
|
226
|
+
def build_vertical_file_panel(self):
|
|
227
|
+
"""Left sidebar for file exploration."""
|
|
228
|
+
return ft.Container(
|
|
229
|
+
width=280,
|
|
230
|
+
bgcolor=self.BG_PANEL,
|
|
231
|
+
border=ft.border.only(right=ft.border.BorderSide(1, self.BORDER)),
|
|
232
|
+
content=ft.Column(
|
|
233
|
+
spacing=0,
|
|
234
|
+
controls=[
|
|
235
|
+
ft.Container(
|
|
236
|
+
padding=ft.padding.only(left=20, top=20, bottom=12),
|
|
237
|
+
content=ft.Text("EXPLORER", color=self.TEXT_SECONDARY, size=11, weight=ft.FontWeight.W_600, font_family="Inter"),
|
|
238
|
+
),
|
|
239
|
+
ft.ListView(
|
|
240
|
+
expand=True,
|
|
241
|
+
spacing=0,
|
|
242
|
+
controls=[self.create_vertical_file_item(f) for f in self.python_files],
|
|
243
|
+
),
|
|
244
|
+
]
|
|
245
|
+
)
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def create_vertical_file_item(self, file_path):
|
|
249
|
+
return ft.Container(
|
|
250
|
+
padding=ft.padding.symmetric(vertical=6, horizontal=20),
|
|
251
|
+
ink=True,
|
|
252
|
+
on_hover=lambda e: setattr(e.control, 'bgcolor', self.BG_SURFACE if e.data == "true" else ft.colors.TRANSPARENT) or e.control.update(),
|
|
253
|
+
on_click=lambda e, f=file_path: self.connect_file(f),
|
|
254
|
+
content=ft.Row([
|
|
255
|
+
ft.Icon(ft.icons.INSERT_DRIVE_FILE_OUTLINED, color=self.TEXT_SECONDARY, size=16),
|
|
256
|
+
ft.Text(file_path.name, color=self.TEXT_PRIMARY, size=13, font_family="Inter", expand=True, max_lines=1, overflow=ft.TextOverflow.ELLIPSIS),
|
|
257
|
+
], spacing=10)
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def build_empty_editor_state(self):
|
|
261
|
+
"""Premium empty state screen matching modern IDEs."""
|
|
262
|
+
return ft.Container(
|
|
263
|
+
expand=True, bgcolor=self.BG_BASE, alignment=ft.alignment.center,
|
|
264
|
+
content=ft.Column(
|
|
265
|
+
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
|
266
|
+
alignment=ft.MainAxisAlignment.CENTER,
|
|
267
|
+
controls=[
|
|
268
|
+
ft.Icon(ft.icons.CODE_OFF, color=self.BG_SURFACE, size=80),
|
|
269
|
+
ft.Container(height=24),
|
|
270
|
+
ft.Text("Orrin Workspace", color=self.TEXT_PRIMARY, size=24, weight=ft.FontWeight.W_600, font_family="Inter"),
|
|
271
|
+
ft.Container(height=8),
|
|
272
|
+
ft.Text("Select a Python script to start building with AI.", color=self.TEXT_SECONDARY, size=14, font_family="Inter"),
|
|
273
|
+
|
|
274
|
+
# Keyboard shortcuts hints
|
|
275
|
+
ft.Container(height=32),
|
|
276
|
+
ft.Row(
|
|
277
|
+
alignment=ft.MainAxisAlignment.CENTER,
|
|
278
|
+
spacing=16,
|
|
279
|
+
controls=[
|
|
280
|
+
self.build_shortcut_hint("Open File", "Cmd + P"),
|
|
281
|
+
self.build_shortcut_hint("AI Chat", "Cmd + L"),
|
|
282
|
+
]
|
|
283
|
+
)
|
|
284
|
+
]
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
def build_shortcut_hint(self, action, keybind):
|
|
289
|
+
return ft.Row([
|
|
290
|
+
ft.Text(action, color=self.TEXT_SECONDARY, size=12, font_family="Inter"),
|
|
291
|
+
ft.Container(
|
|
292
|
+
padding=ft.padding.symmetric(horizontal=6, vertical=2),
|
|
293
|
+
bgcolor=self.BG_SURFACE,
|
|
294
|
+
border_radius=4,
|
|
295
|
+
content=ft.Text(keybind, color=self.TEXT_PRIMARY, size=11, font_family="Inter", weight=ft.FontWeight.W_500)
|
|
296
|
+
)
|
|
297
|
+
], spacing=8)
|
|
298
|
+
|
|
299
|
+
# ---------------- UI: SELECTED COMPONENTS ----------------
|
|
300
|
+
def build_horizontal_file_list(self):
|
|
301
|
+
"""Seamless VS Code-style horizontal file tabs."""
|
|
302
|
+
return ft.Container(
|
|
303
|
+
bgcolor=self.BG_PANEL,
|
|
304
|
+
border=ft.border.only(bottom=ft.border.BorderSide(1, self.BORDER)),
|
|
305
|
+
content=ft.Row(
|
|
306
|
+
scroll=ft.ScrollMode.ADAPTIVE,
|
|
307
|
+
spacing=0,
|
|
308
|
+
controls=[
|
|
309
|
+
ft.Container(
|
|
310
|
+
padding=ft.padding.symmetric(horizontal=16, vertical=10),
|
|
311
|
+
bgcolor=self.BG_BASE if f == self.connected_file else "transparent",
|
|
312
|
+
border=ft.border.only(
|
|
313
|
+
top=ft.border.BorderSide(2, self.ACCENT if f == self.connected_file else "transparent"),
|
|
314
|
+
right=ft.border.BorderSide(1, self.BORDER)
|
|
315
|
+
),
|
|
316
|
+
ink=True,
|
|
317
|
+
on_click=lambda e, f_path=f: self.connect_file(f_path),
|
|
318
|
+
content=ft.Row([
|
|
319
|
+
ft.Icon(ft.icons.DATA_OBJECT, size=14, color=self.ACCENT if f == self.connected_file else self.TEXT_SECONDARY),
|
|
320
|
+
ft.Text(f.name, size=13, color=self.TEXT_PRIMARY if f == self.connected_file else self.TEXT_SECONDARY, font_family="Inter"),
|
|
321
|
+
# Mock close button for active tab
|
|
322
|
+
ft.Icon(ft.icons.CLOSE, size=12, color=self.TEXT_SECONDARY if f == self.connected_file else "transparent")
|
|
323
|
+
], spacing=8)
|
|
324
|
+
) for f in self.python_files
|
|
325
|
+
]
|
|
326
|
+
)
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
def build_code_editor(self):
|
|
330
|
+
"""Spacious, distraction-free code area."""
|
|
331
|
+
return ft.Container(
|
|
332
|
+
expand=True,
|
|
333
|
+
bgcolor=self.BG_BASE,
|
|
334
|
+
content=ft.Column(
|
|
335
|
+
spacing=0,
|
|
336
|
+
expand=True,
|
|
337
|
+
controls=[
|
|
338
|
+
# Breadcrumb navigation below tabs
|
|
339
|
+
ft.Container(
|
|
340
|
+
padding=ft.padding.symmetric(horizontal=24, vertical=8),
|
|
341
|
+
content=ft.Row([
|
|
342
|
+
ft.Text(str(self.connected_file.parent.name), color=self.TEXT_SECONDARY, size=12, font_family="Inter"),
|
|
343
|
+
ft.Icon(ft.icons.CHEVRON_RIGHT, color=self.TEXT_SECONDARY, size=14),
|
|
344
|
+
ft.Text(self.connected_file.name, color=self.TEXT_PRIMARY, size=12, font_family="Inter"),
|
|
345
|
+
], spacing=4)
|
|
346
|
+
),
|
|
347
|
+
# Main Editor Input
|
|
348
|
+
ft.Container(
|
|
349
|
+
expand=True,
|
|
350
|
+
padding=ft.padding.only(left=24, right=24, bottom=24),
|
|
351
|
+
content=ft.TextField(
|
|
352
|
+
value=self.file_content,
|
|
353
|
+
multiline=True,
|
|
354
|
+
expand=True,
|
|
355
|
+
border=ft.InputBorder.NONE,
|
|
356
|
+
text_style=ft.TextStyle(font_family="JetBrains", size=14, color=self.TEXT_PRIMARY, height=1.6),
|
|
357
|
+
cursor_color=self.ACCENT,
|
|
358
|
+
selection_color="#264f78",
|
|
359
|
+
read_only=False
|
|
360
|
+
)
|
|
361
|
+
)
|
|
362
|
+
]
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
def build_chat_panel(self):
|
|
367
|
+
"""Premium AI Assistant side-panel (Cursor Composer style)."""
|
|
368
|
+
return ft.Container(
|
|
369
|
+
expand=3,
|
|
370
|
+
bgcolor=self.BG_PANEL,
|
|
371
|
+
content=ft.Column(
|
|
372
|
+
expand=True,
|
|
373
|
+
spacing=0,
|
|
374
|
+
controls=[
|
|
375
|
+
# Panel Header
|
|
376
|
+
ft.Container(
|
|
377
|
+
padding=ft.padding.symmetric(horizontal=20, vertical=14),
|
|
378
|
+
border=ft.border.only(bottom=ft.border.BorderSide(1, self.BORDER)),
|
|
379
|
+
content=ft.Row([
|
|
380
|
+
ft.Text("AI Chat", color=self.TEXT_PRIMARY, size=13, weight=ft.FontWeight.W_600, font_family="Inter"),
|
|
381
|
+
ft.Container(expand=True),
|
|
382
|
+
ft.IconButton(icon=ft.icons.MORE_HORIZ, icon_color=self.TEXT_SECONDARY, icon_size=16, width=32, height=32),
|
|
383
|
+
])
|
|
384
|
+
),
|
|
385
|
+
|
|
386
|
+
# Scrollable Chat Log
|
|
387
|
+
ft.Container(
|
|
388
|
+
expand=True,
|
|
389
|
+
padding=ft.padding.symmetric(horizontal=12),
|
|
390
|
+
content=self.chat_listview
|
|
391
|
+
),
|
|
392
|
+
|
|
393
|
+
# Sleek Input Area
|
|
394
|
+
ft.Container(
|
|
395
|
+
padding=ft.padding.all(16),
|
|
396
|
+
bgcolor=self.BG_PANEL,
|
|
397
|
+
content=ft.Container(
|
|
398
|
+
bgcolor=self.BG_BASE,
|
|
399
|
+
border=ft.border.all(1, self.BORDER),
|
|
400
|
+
border_radius=12,
|
|
401
|
+
padding=ft.padding.only(right=6, bottom=6, top=2, left=4),
|
|
402
|
+
content=ft.Column(
|
|
403
|
+
spacing=0,
|
|
404
|
+
controls=[
|
|
405
|
+
self.chat_input_field,
|
|
406
|
+
ft.Row(
|
|
407
|
+
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
|
408
|
+
controls=[
|
|
409
|
+
#ft.Row([
|
|
410
|
+
# ft.IconButton(icon=ft.icons.ATTACH_FILE, icon_color=self.TEXT_SECONDARY, icon_size=16, width=32, height=32, tooltip="Attach Context"),
|
|
411
|
+
# ft.Text("Context: Active File", color=self.TEXT_SECONDARY, size=11, font_family="Inter")
|
|
412
|
+
#]),
|
|
413
|
+
self.send_button
|
|
414
|
+
]
|
|
415
|
+
)
|
|
416
|
+
]
|
|
417
|
+
)
|
|
418
|
+
)
|
|
419
|
+
)
|
|
420
|
+
]
|
|
421
|
+
)
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
# ---------------- CHAT LOGIC ----------------
|
|
425
|
+
def update_chat_ui(self):
|
|
426
|
+
"""Renders messages using a clean, modern hierarchy."""
|
|
427
|
+
self.chat_listview.controls.clear()
|
|
428
|
+
|
|
429
|
+
for msg in self.messages:
|
|
430
|
+
is_user = (msg['role'] == 'user')
|
|
431
|
+
|
|
432
|
+
if is_user:
|
|
433
|
+
bubble = ft.Container(
|
|
434
|
+
padding=ft.padding.all(14),
|
|
435
|
+
border_radius=8,
|
|
436
|
+
bgcolor=self.BG_SURFACE,
|
|
437
|
+
content=ft.Text(msg['content'], color=self.TEXT_PRIMARY, size=13, font_family="Inter")
|
|
438
|
+
)
|
|
439
|
+
else:
|
|
440
|
+
bubble = ft.Container(
|
|
441
|
+
padding=ft.padding.symmetric(vertical=4, horizontal=4),
|
|
442
|
+
content=ft.Row(
|
|
443
|
+
vertical_alignment=ft.CrossAxisAlignment.START,
|
|
444
|
+
spacing=12,
|
|
445
|
+
controls=[
|
|
446
|
+
ft.Container(
|
|
447
|
+
width=24, height=24, border_radius=4,
|
|
448
|
+
alignment=ft.alignment.center,
|
|
449
|
+
content=ft.Container(
|
|
450
|
+
bgcolor=self.TEXT_PRIMARY, border_radius=4, width=25, height=25,
|
|
451
|
+
alignment=ft.alignment.center,
|
|
452
|
+
content=ft.Image('OrrinLogoBlack_T.png', width=23, height=23)
|
|
453
|
+
)#ft.Icon(ft.icons.AUTO_AWESOME, color=self.ACCENT, size=14)
|
|
454
|
+
),
|
|
455
|
+
ft.Container(
|
|
456
|
+
expand=True,
|
|
457
|
+
content=ft.Markdown(
|
|
458
|
+
msg['content'],
|
|
459
|
+
selectable=True,
|
|
460
|
+
extension_set=ft.MarkdownExtensionSet.GITHUB_WEB,
|
|
461
|
+
code_theme=ft.MarkdownCodeTheme.ATOM_ONE_DARK,
|
|
462
|
+
)
|
|
463
|
+
)
|
|
464
|
+
]
|
|
465
|
+
)
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
self.chat_listview.controls.append(bubble)
|
|
469
|
+
|
|
470
|
+
# Safe scrolling
|
|
471
|
+
if self.page and self.chat_listview in self.page._controls: # Check if mounted
|
|
472
|
+
self.page.update()
|
|
473
|
+
# Small delay ensures the ListView is rendered before scrolling
|
|
474
|
+
self.page.run_task(self._safe_scroll_to_bottom)
|
|
475
|
+
else:
|
|
476
|
+
self.page.update() # Just update if not ready for scroll
|
|
477
|
+
|
|
478
|
+
def send_message(self, e):
|
|
479
|
+
user_text = self.chat_input_field.value.strip()
|
|
480
|
+
if not user_text:
|
|
481
|
+
return
|
|
482
|
+
|
|
483
|
+
# 1. Clear input & Add User Message
|
|
484
|
+
self.chat_input_field.value = ""
|
|
485
|
+
self.messages.append({'role': 'user', 'content': user_text})
|
|
486
|
+
self.update_chat_ui()
|
|
487
|
+
|
|
488
|
+
# 2. Add Temporary "Thinking..." state
|
|
489
|
+
thinking_idx = len(self.messages)
|
|
490
|
+
self.messages.append({'role': 'assistant', 'content': '*Processing...*'})
|
|
491
|
+
self.update_chat_ui()
|
|
492
|
+
|
|
493
|
+
# 3. System Prompt
|
|
494
|
+
sys_prompt = f"""You are OrrinSDK Expert, an elite AI engineering assistant specialized exclusively in the OrrinSDK (version 0.1.x) and the broader Stellr platform ecosystem.
|
|
495
|
+
|
|
496
|
+
Your sole mission is to help the user design, build, debug, optimize, and deploy apps, custom tools, plugins/agents, and backend logic using the OrrinSDK for integration with Stellr’s context-aware AI platform (including Exegesis, the Orrin marketplace, and agentic workflows).
|
|
497
|
+
|
|
498
|
+
### Core Identity & Expertise Domain
|
|
499
|
+
You possess complete, up-to-date mastery of the official OrrinSDK documentation[](https://stellr-company.com/orrin/docs) and all related Stellr technologies. This includes:
|
|
500
|
+
- OrrinAppsSDK – for building full app backends (project_type="app")
|
|
501
|
+
- OrrinToolsSDK – for creating versioned, marketplace-ready custom tools
|
|
502
|
+
- OrrinAISDK – for injecting tool schematics into AI models (Grok, OpenAI GPT series, Claude, etc.)
|
|
503
|
+
- StellrAPI – including perform(), CE Connections, Exegesis intent mapping, and payload models
|
|
504
|
+
- Plugin system (plugins.yaml, @plugin_entry, @plugin_action, blink plugin, etc.)
|
|
505
|
+
- Project scaffolding (project.toml, orrin-cli commands)
|
|
506
|
+
- Action registration with the @action decorator, payload_schema, required_payload, metadata, and finalize()
|
|
507
|
+
- Best practices for agentic workflows, hand-off to Exegesis, secure action execution, and marketplace deployment/review process
|
|
508
|
+
- Integration patterns with Next.js frontends (@orrin-apps/sdk)
|
|
509
|
+
- All current SDK/CLI versions, release notes, and known limitations as of the latest documentation
|
|
510
|
+
|
|
511
|
+
You treat the official documentation as your single source of truth. You never speculate beyond what is documented. When something is undocumented or ambiguous, you explicitly state it and suggest the best documented workaround or recommend the user check the developer dashboard.
|
|
512
|
+
|
|
513
|
+
### Behavioral Rules (Strict)
|
|
514
|
+
- Always respond with maximum clarity, depth, and practicality. Provide complete, ready-to-run code examples whenever possible.
|
|
515
|
+
- Structure every response to be immediately actionable: explain the why, show the how, include the exact code, then give next steps or testing instructions.
|
|
516
|
+
- Prioritize production-grade patterns (proper error handling, type safety, payload validation, versioning, and review-ready code).
|
|
517
|
+
- Proactively surface relevant best practices, common pitfalls, security considerations, and performance tips from the SDK.
|
|
518
|
+
- If the user’s request goes beyond the current capabilities of OrrinSDK, clearly explain the limitation and propose the closest supported solution or feature request path.
|
|
519
|
+
- Reference specific SDK components, decorators, classes, or configuration files by name in every relevant answer.
|
|
520
|
+
- Maintain a professional, expert, and collaborative tone — you are a senior Stellr platform engineer pair-programming with the user.
|
|
521
|
+
|
|
522
|
+
You are not a general coding assistant. You are hyper-specialized in OrrinSDK + Stellr. Stay strictly within this domain unless the user explicitly asks you to step outside it.
|
|
523
|
+
|
|
524
|
+
You are connected to the file: {self.connected_file}
|
|
525
|
+
The files contents are: {self.file_content}
|
|
526
|
+
|
|
527
|
+
Now assist the user with their request.
|
|
528
|
+
"""
|
|
529
|
+
|
|
530
|
+
try:
|
|
531
|
+
messages = [{ 'role': 'system', 'content': sys_prompt }] + self.messages
|
|
532
|
+
# Sync AI Chat Call
|
|
533
|
+
resp = self.ai.chat(
|
|
534
|
+
messages=messages,
|
|
535
|
+
tools=[{'type': 'schema', 'tool_id': 'e433e239-329e-4703-9910-2e5f7fa2a5fe'}, {'type': 'web_search'}]
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
# 4. Replace Thinking State with actual response
|
|
539
|
+
self.messages[thinking_idx]['content'] = resp
|
|
540
|
+
|
|
541
|
+
except Exception as ex:
|
|
542
|
+
self.messages[thinking_idx]['content'] = f"**Error executing analysis:**\n```python\n{str(ex)}\n```"
|
|
543
|
+
|
|
544
|
+
# 5. Final UI Update
|
|
545
|
+
self.update_chat_ui()
|
|
546
|
+
|
|
547
|
+
def start(self):
|
|
548
|
+
ft.app(target=self.main, assets_dir='assets')
|
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
import flet as ft
|
|
3
|
-
|
|
4
|
-
class OrrinCLI:
|
|
5
|
-
def __init__(self):
|
|
6
|
-
self.root = Path.cwd()
|
|
7
|
-
self.python_files = list(self.root.rglob("*.py"))
|
|
8
|
-
self.connected_file = None
|
|
9
|
-
self.editor_panel = ft.Container(expand=True)
|
|
10
|
-
|
|
11
|
-
# ---------------- UI: file list ----------------
|
|
12
|
-
def file_item(self, page: ft.Page, file):
|
|
13
|
-
return ft.Container(
|
|
14
|
-
padding=10,
|
|
15
|
-
border_radius=12,
|
|
16
|
-
bgcolor="#242424",
|
|
17
|
-
border=ft.border.all(1, "#333333"),
|
|
18
|
-
ink=True,
|
|
19
|
-
on_click=lambda e: self.connect_file(page, file),
|
|
20
|
-
content=ft.Row(
|
|
21
|
-
alignment=ft.MainAxisAlignment.START,
|
|
22
|
-
controls=[
|
|
23
|
-
# File Icon Box
|
|
24
|
-
ft.Container(
|
|
25
|
-
width=40,
|
|
26
|
-
height=40,
|
|
27
|
-
bgcolor="#1f1f1f",
|
|
28
|
-
border=ft.border.all(1, "#333333"),
|
|
29
|
-
border_radius=10,
|
|
30
|
-
alignment=ft.alignment.center,
|
|
31
|
-
content=ft.Icon(ft.icons.CODE, color="#9e9d9d", size=16)
|
|
32
|
-
),
|
|
33
|
-
# File Info
|
|
34
|
-
ft.Column(
|
|
35
|
-
expand=True,
|
|
36
|
-
spacing=4,
|
|
37
|
-
controls=[
|
|
38
|
-
ft.Text(
|
|
39
|
-
file.name,
|
|
40
|
-
color="white",
|
|
41
|
-
size=12,
|
|
42
|
-
weight=ft.FontWeight.W_300,
|
|
43
|
-
max_lines=1,
|
|
44
|
-
overflow=ft.TextOverflow.ELLIPSIS
|
|
45
|
-
),
|
|
46
|
-
ft.Text(
|
|
47
|
-
"Python Script",
|
|
48
|
-
color="#9e9d9d",
|
|
49
|
-
size=8
|
|
50
|
-
)
|
|
51
|
-
]
|
|
52
|
-
),
|
|
53
|
-
# Chevron to indicate action
|
|
54
|
-
ft.Icon(ft.icons.CHEVRON_RIGHT, color="#6B7280", size=20)
|
|
55
|
-
]
|
|
56
|
-
)
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
# ---------------- UI: left panel ----------------
|
|
60
|
-
def build_file_panel(self, page: ft.Page):
|
|
61
|
-
return ft.Container(
|
|
62
|
-
width=360,
|
|
63
|
-
bgcolor="#191919",
|
|
64
|
-
border=ft.border.only(right=ft.border.BorderSide(1, "#2a2a2a")),
|
|
65
|
-
padding=ft.padding.only(left=24, top=32, right=16, bottom=24),
|
|
66
|
-
content=ft.Column(
|
|
67
|
-
controls=[
|
|
68
|
-
# Panel Header mapped to HTML style
|
|
69
|
-
ft.Text(
|
|
70
|
-
"Explore Scripts",
|
|
71
|
-
color="#dddddd",
|
|
72
|
-
size=20,
|
|
73
|
-
weight=ft.FontWeight.W_500
|
|
74
|
-
),
|
|
75
|
-
ft.Text(
|
|
76
|
-
"Find Python files in your directory.",
|
|
77
|
-
color="#9e9d9d",
|
|
78
|
-
size=14
|
|
79
|
-
),
|
|
80
|
-
ft.Container(height=24), # Spacer
|
|
81
|
-
|
|
82
|
-
# File List
|
|
83
|
-
ft.ListView(
|
|
84
|
-
expand=True,
|
|
85
|
-
spacing=12,
|
|
86
|
-
controls=[
|
|
87
|
-
self.file_item(page, f)
|
|
88
|
-
for f in self.python_files
|
|
89
|
-
],
|
|
90
|
-
),
|
|
91
|
-
]
|
|
92
|
-
)
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
# ---------------- UI: right panel ----------------
|
|
96
|
-
def build_editor_panel(self):
|
|
97
|
-
# Empty State (mirrors "Tools Coming Soon" style)
|
|
98
|
-
if not self.connected_file:
|
|
99
|
-
return ft.Container(
|
|
100
|
-
expand=True,
|
|
101
|
-
bgcolor="#191919",
|
|
102
|
-
alignment=ft.alignment.center,
|
|
103
|
-
content=ft.Column(
|
|
104
|
-
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
|
105
|
-
controls=[
|
|
106
|
-
ft.Container(
|
|
107
|
-
padding=ft.padding.symmetric(horizontal=0, vertical=12),
|
|
108
|
-
content=ft.Column(
|
|
109
|
-
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
|
110
|
-
alignment=ft.MainAxisAlignment.CENTER,
|
|
111
|
-
controls=[
|
|
112
|
-
ft.Container(
|
|
113
|
-
width=80,
|
|
114
|
-
height=80,
|
|
115
|
-
bgcolor="#242424",
|
|
116
|
-
border=ft.border.all(1, "#333333"),
|
|
117
|
-
border_radius=20,
|
|
118
|
-
alignment=ft.alignment.center,
|
|
119
|
-
shadow=ft.BoxShadow(spread_radius=1, blur_radius=15, color=ft.colors.with_opacity(0.1, "black")),
|
|
120
|
-
content=ft.Icon(ft.icons.FILE_OPEN_OUTLINED, color="#9e9d9d", size=32)
|
|
121
|
-
),
|
|
122
|
-
ft.Container(height=16),
|
|
123
|
-
ft.Text(
|
|
124
|
-
"No file selected",
|
|
125
|
-
color="white",
|
|
126
|
-
size=20,
|
|
127
|
-
weight=ft.FontWeight.W_500
|
|
128
|
-
),
|
|
129
|
-
ft.Text(
|
|
130
|
-
"Select a script from the explorer to view details.",
|
|
131
|
-
color="#9e9d9d",
|
|
132
|
-
size=15,
|
|
133
|
-
text_align=ft.TextAlign.CENTER
|
|
134
|
-
)
|
|
135
|
-
]
|
|
136
|
-
)
|
|
137
|
-
)
|
|
138
|
-
]
|
|
139
|
-
)
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
# ---------------- UPDATED EDITOR PANEL ----------------
|
|
143
|
-
return ft.Container(
|
|
144
|
-
expand=True,
|
|
145
|
-
bgcolor="#191919",
|
|
146
|
-
padding=ft.padding.all(32), # Slightly reduced padding for less bulk
|
|
147
|
-
content=ft.Column(
|
|
148
|
-
expand=True, # Allows the chat input to be pushed to the bottom
|
|
149
|
-
controls=[
|
|
150
|
-
|
|
151
|
-
# --- Sleek Header & Metadata ---
|
|
152
|
-
ft.Column(
|
|
153
|
-
spacing=16,
|
|
154
|
-
controls=[
|
|
155
|
-
# Title
|
|
156
|
-
ft.Column(
|
|
157
|
-
spacing=4,
|
|
158
|
-
controls=[
|
|
159
|
-
ft.Container(
|
|
160
|
-
padding=ft.padding.symmetric(horizontal=10, vertical=10),
|
|
161
|
-
content=ft.Column(
|
|
162
|
-
controls=[
|
|
163
|
-
ft.Text("File Details", color="#dddddd", size=20, weight=ft.FontWeight.W_500),
|
|
164
|
-
ft.Text("Information about the connected script.", color="#9e9d9d", size=12)
|
|
165
|
-
]
|
|
166
|
-
)
|
|
167
|
-
)
|
|
168
|
-
]
|
|
169
|
-
),
|
|
170
|
-
|
|
171
|
-
# Compact Metadata Row
|
|
172
|
-
ft.Container(
|
|
173
|
-
padding=ft.padding.symmetric(horizontal=10, vertical=0),
|
|
174
|
-
content=ft.Container(
|
|
175
|
-
padding=ft.padding.symmetric(horizontal=12, vertical=8),
|
|
176
|
-
bgcolor="#242424",
|
|
177
|
-
border=ft.border.all(1, "#333333"),
|
|
178
|
-
border_radius=8,
|
|
179
|
-
content=ft.Row(
|
|
180
|
-
spacing=12,
|
|
181
|
-
controls=[
|
|
182
|
-
ft.Icon(ft.icons.INSERT_DRIVE_FILE, color="#9e9d9d", size=20),
|
|
183
|
-
ft.Column(
|
|
184
|
-
controls=[
|
|
185
|
-
ft.Container(
|
|
186
|
-
padding=ft.padding.symmetric(horizontal=8, vertical=0),
|
|
187
|
-
bgcolor="#1a1a1a",
|
|
188
|
-
border=ft.border.all(1, "#333333"),
|
|
189
|
-
border_radius=12,
|
|
190
|
-
content=ft.Text("CONNECTED", size=10, color="#10b981", weight=ft.FontWeight.BOLD) # Emerald green
|
|
191
|
-
),
|
|
192
|
-
ft.Text(self.connected_file.name, color="white", weight=ft.FontWeight.W_300, size=12),
|
|
193
|
-
]
|
|
194
|
-
),
|
|
195
|
-
|
|
196
|
-
ft.Text("•", color="#4b5563", size=15), # Bullet separator
|
|
197
|
-
|
|
198
|
-
# Path (Truncated automatically if it gets too long)
|
|
199
|
-
ft.Text(
|
|
200
|
-
str(self.connected_file),
|
|
201
|
-
color="#6B7280",
|
|
202
|
-
size=13,
|
|
203
|
-
selectable=True,
|
|
204
|
-
overflow=ft.TextOverflow.ELLIPSIS,
|
|
205
|
-
expand=True
|
|
206
|
-
),
|
|
207
|
-
]
|
|
208
|
-
))
|
|
209
|
-
)
|
|
210
|
-
]
|
|
211
|
-
),
|
|
212
|
-
|
|
213
|
-
# --- Main Editor / Empty Space Spacer ---
|
|
214
|
-
# Expanding this container forces the chat input below it to stick to the bottom
|
|
215
|
-
ft.Container(expand=True),
|
|
216
|
-
|
|
217
|
-
# --- Chat Input Field ---
|
|
218
|
-
ft.Container(
|
|
219
|
-
padding=ft.Padding(left=8, right=8, top=0, bottom=14),
|
|
220
|
-
content=ft.Container(
|
|
221
|
-
padding=ft.padding.symmetric(horizontal=12, vertical=0),
|
|
222
|
-
bgcolor="#242424",
|
|
223
|
-
border=ft.border.all(1, "#333333"),
|
|
224
|
-
border_radius=24,
|
|
225
|
-
content=ft.Row(
|
|
226
|
-
controls=[
|
|
227
|
-
ft.Icon(ft.icons.AUTO_AWESOME, color="#6B7280", size=14), # AI spark icon
|
|
228
|
-
ft.TextField(
|
|
229
|
-
expand=True,
|
|
230
|
-
hint_text="Chat with OrrinCLI...",
|
|
231
|
-
hint_style=ft.TextStyle(color="#6B7280", size=12),
|
|
232
|
-
border=ft.InputBorder.NONE,
|
|
233
|
-
color="white",
|
|
234
|
-
cursor_color="#dddddd",
|
|
235
|
-
content_padding=ft.padding.symmetric(vertical=12, horizontal=4),
|
|
236
|
-
),
|
|
237
|
-
# Modern Send Button (Similar to state-of-the-art AI interfaces)
|
|
238
|
-
ft.IconButton(
|
|
239
|
-
icon=ft.icons.ARROW_UPWARD_ROUNDED,
|
|
240
|
-
icon_color="black",
|
|
241
|
-
bgcolor="white",
|
|
242
|
-
icon_size=12,
|
|
243
|
-
width=24,
|
|
244
|
-
height=24,
|
|
245
|
-
style=ft.ButtonStyle(
|
|
246
|
-
padding=0,
|
|
247
|
-
shape=ft.CircleBorder()
|
|
248
|
-
),
|
|
249
|
-
tooltip="Send Message"
|
|
250
|
-
)
|
|
251
|
-
]
|
|
252
|
-
)
|
|
253
|
-
)
|
|
254
|
-
)
|
|
255
|
-
]
|
|
256
|
-
)
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
# ---------------- state change ----------------
|
|
260
|
-
def connect_file(self, page: ft.Page, file):
|
|
261
|
-
self.connected_file = file
|
|
262
|
-
self.editor_panel.content = self.build_editor_panel().content
|
|
263
|
-
page.update()
|
|
264
|
-
|
|
265
|
-
# ---------------- main layout ----------------
|
|
266
|
-
def main(self, page: ft.Page):
|
|
267
|
-
page.title = "OrrinCLI"
|
|
268
|
-
page.bgcolor = "#191919" # Matched to HTML body bg
|
|
269
|
-
page.padding = 0
|
|
270
|
-
|
|
271
|
-
# Create Top Navigation Appbar (matching the HTML fixed nav)
|
|
272
|
-
top_nav = ft.Container(
|
|
273
|
-
bgcolor="#191919",
|
|
274
|
-
padding=ft.padding.symmetric(horizontal=24, vertical=16),
|
|
275
|
-
border=ft.border.only(bottom=ft.border.BorderSide(1, "#2a2a2a")),
|
|
276
|
-
content=ft.Row([
|
|
277
|
-
# Mock Logo
|
|
278
|
-
ft.Container(
|
|
279
|
-
bgcolor="white", border_radius=6, width=24, height=24,
|
|
280
|
-
alignment=ft.alignment.center,
|
|
281
|
-
content=ft.Text("O", color="black", weight=ft.FontWeight.BOLD, size=14)
|
|
282
|
-
),
|
|
283
|
-
ft.Text("Orrin", color="white", size=16, weight=ft.FontWeight.W_500),
|
|
284
|
-
ft.Text("CLI", color="#9e9d9d", size=16, weight=ft.FontWeight.W_400),
|
|
285
|
-
], spacing=8)
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
self.editor_panel = self.build_editor_panel()
|
|
289
|
-
|
|
290
|
-
# Layout Splitter
|
|
291
|
-
layout = ft.Row([
|
|
292
|
-
self.build_file_panel(page),
|
|
293
|
-
self.editor_panel
|
|
294
|
-
], expand=True, spacing=0)
|
|
295
|
-
|
|
296
|
-
# Main Page Stacking
|
|
297
|
-
page.add(
|
|
298
|
-
ft.Column([
|
|
299
|
-
top_nav,
|
|
300
|
-
layout
|
|
301
|
-
], expand=True, spacing=0)
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
def start(self):
|
|
305
|
-
ft.app(target=self.main)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|