StartupBuddy 0.1.1__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.
- startupbuddy-0.1.1/PKG-INFO +21 -0
- startupbuddy-0.1.1/README.md +9 -0
- startupbuddy-0.1.1/StartupBuddy.egg-info/PKG-INFO +21 -0
- startupbuddy-0.1.1/StartupBuddy.egg-info/SOURCES.txt +27 -0
- startupbuddy-0.1.1/StartupBuddy.egg-info/dependency_links.txt +1 -0
- startupbuddy-0.1.1/StartupBuddy.egg-info/requires.txt +5 -0
- startupbuddy-0.1.1/StartupBuddy.egg-info/top_level.txt +3 -0
- startupbuddy-0.1.1/interfaces/__init__.py +19 -0
- startupbuddy-0.1.1/interfaces/databases.py +36 -0
- startupbuddy-0.1.1/interfaces/fs.py +21 -0
- startupbuddy-0.1.1/interfaces/model.py +6 -0
- startupbuddy-0.1.1/pyproject.toml +30 -0
- startupbuddy-0.1.1/screens/__init__.py +10 -0
- startupbuddy-0.1.1/screens/chat.py +145 -0
- startupbuddy-0.1.1/screens/components/__init__.py +0 -0
- startupbuddy-0.1.1/screens/components/message.py +19 -0
- startupbuddy-0.1.1/screens/main.py +79 -0
- startupbuddy-0.1.1/screens/menu.py +103 -0
- startupbuddy-0.1.1/screens/mockup.py +132 -0
- startupbuddy-0.1.1/screens/new.py +28 -0
- startupbuddy-0.1.1/screens/new_pitch.py +210 -0
- startupbuddy-0.1.1/screens/pitch.py +113 -0
- startupbuddy-0.1.1/screens/questions.py +204 -0
- startupbuddy-0.1.1/screens/settings.py +85 -0
- startupbuddy-0.1.1/screens/start.py +82 -0
- startupbuddy-0.1.1/setup.cfg +4 -0
- startupbuddy-0.1.1/startupbuddy/__init__.py +0 -0
- startupbuddy-0.1.1/startupbuddy/__main__.py +77 -0
- startupbuddy-0.1.1/startupbuddy/styles.tcss +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: StartupBuddy
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: StartupBuddy is a tool to help you launch your next big idea!
|
|
5
|
+
Requires-Python: >=3.13.2
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: coffy>=0.2.5
|
|
8
|
+
Requires-Dist: platformdirs>=4.10.0
|
|
9
|
+
Requires-Dist: startupbuddykernel>=0.1.4
|
|
10
|
+
Requires-Dist: textual>=8.2.7
|
|
11
|
+
Requires-Dist: textual-dev>=1.8.0
|
|
12
|
+
|
|
13
|
+
# StartupBuddy
|
|
14
|
+
|
|
15
|
+
This handy command line tool will guide you to generating a pitch and landing page for your next big idea.
|
|
16
|
+
|
|
17
|
+
To run it you must have `uv` installed and be running Python 3.13 or higher. You will also need a Wavespeed API key.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv run startupbuddy
|
|
21
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# StartupBuddy
|
|
2
|
+
|
|
3
|
+
This handy command line tool will guide you to generating a pitch and landing page for your next big idea.
|
|
4
|
+
|
|
5
|
+
To run it you must have `uv` installed and be running Python 3.13 or higher. You will also need a Wavespeed API key.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv run startupbuddy
|
|
9
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: StartupBuddy
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: StartupBuddy is a tool to help you launch your next big idea!
|
|
5
|
+
Requires-Python: >=3.13.2
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: coffy>=0.2.5
|
|
8
|
+
Requires-Dist: platformdirs>=4.10.0
|
|
9
|
+
Requires-Dist: startupbuddykernel>=0.1.4
|
|
10
|
+
Requires-Dist: textual>=8.2.7
|
|
11
|
+
Requires-Dist: textual-dev>=1.8.0
|
|
12
|
+
|
|
13
|
+
# StartupBuddy
|
|
14
|
+
|
|
15
|
+
This handy command line tool will guide you to generating a pitch and landing page for your next big idea.
|
|
16
|
+
|
|
17
|
+
To run it you must have `uv` installed and be running Python 3.13 or higher. You will also need a Wavespeed API key.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv run startupbuddy
|
|
21
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
StartupBuddy.egg-info/PKG-INFO
|
|
4
|
+
StartupBuddy.egg-info/SOURCES.txt
|
|
5
|
+
StartupBuddy.egg-info/dependency_links.txt
|
|
6
|
+
StartupBuddy.egg-info/requires.txt
|
|
7
|
+
StartupBuddy.egg-info/top_level.txt
|
|
8
|
+
interfaces/__init__.py
|
|
9
|
+
interfaces/databases.py
|
|
10
|
+
interfaces/fs.py
|
|
11
|
+
interfaces/model.py
|
|
12
|
+
screens/__init__.py
|
|
13
|
+
screens/chat.py
|
|
14
|
+
screens/main.py
|
|
15
|
+
screens/menu.py
|
|
16
|
+
screens/mockup.py
|
|
17
|
+
screens/new.py
|
|
18
|
+
screens/new_pitch.py
|
|
19
|
+
screens/pitch.py
|
|
20
|
+
screens/questions.py
|
|
21
|
+
screens/settings.py
|
|
22
|
+
screens/start.py
|
|
23
|
+
screens/components/__init__.py
|
|
24
|
+
screens/components/message.py
|
|
25
|
+
startupbuddy/__init__.py
|
|
26
|
+
startupbuddy/__main__.py
|
|
27
|
+
startupbuddy/styles.tcss
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .databases import DBInterface
|
|
2
|
+
from .fs import ProjectDirClass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def parse_sections(markdown):
|
|
6
|
+
sections = {}
|
|
7
|
+
current = None
|
|
8
|
+
lines = []
|
|
9
|
+
for line in markdown.splitlines():
|
|
10
|
+
if line.startswith('### '):
|
|
11
|
+
if current is not None:
|
|
12
|
+
sections[current] = '\n'.join(lines).strip()
|
|
13
|
+
current = line[3:].strip()
|
|
14
|
+
lines = []
|
|
15
|
+
elif current is not None:
|
|
16
|
+
lines.append(line)
|
|
17
|
+
if current is not None:
|
|
18
|
+
sections[current] = '\n'.join(lines).strip()
|
|
19
|
+
return sections
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from coffy.nosql import db
|
|
2
|
+
import uuid
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
def make_db_path(data_path, name: str):
|
|
6
|
+
return os.path.join(data_path, name)
|
|
7
|
+
class DBInterface:
|
|
8
|
+
def __init__(self, data_path):
|
|
9
|
+
|
|
10
|
+
self.project_db = db("projects", path=make_db_path(data_path, "projects.json"))
|
|
11
|
+
self.settings_db = db("settings", path=make_db_path(data_path, "settings.json"))
|
|
12
|
+
|
|
13
|
+
def create_new_project(self, name: str, folder: str = None):
|
|
14
|
+
new_id = uuid.uuid4().hex
|
|
15
|
+
self.project_db.add({ "id": new_id, "name": name, "folder": folder })
|
|
16
|
+
return new_id
|
|
17
|
+
|
|
18
|
+
def get_project(self, id: str):
|
|
19
|
+
return self.project_db.where("id").eq(id).first()
|
|
20
|
+
|
|
21
|
+
def update_project(self, id: str, changes: dict):
|
|
22
|
+
return self.project_db.where("id").eq(id).update(changes)
|
|
23
|
+
|
|
24
|
+
def get_projects(self):
|
|
25
|
+
return self.project_db.all()
|
|
26
|
+
|
|
27
|
+
def clear_projects(self):
|
|
28
|
+
return self.project_db.clear()
|
|
29
|
+
|
|
30
|
+
def get_settings(self):
|
|
31
|
+
return self.settings_db.first()
|
|
32
|
+
|
|
33
|
+
def save_settings(self, changes: dict):
|
|
34
|
+
if self.settings_db.first():
|
|
35
|
+
return self.settings_db.match_all().update(changes)
|
|
36
|
+
return self.settings_db.add(changes)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ProjectDirClass:
|
|
5
|
+
def __init__(self, project_path):
|
|
6
|
+
self.root = Path(project_path)
|
|
7
|
+
|
|
8
|
+
def create_directory(self, name):
|
|
9
|
+
target = self.root / name
|
|
10
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
11
|
+
return target
|
|
12
|
+
|
|
13
|
+
def write_file(self, name, content):
|
|
14
|
+
target = self.root / name
|
|
15
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
16
|
+
target.write_text(content)
|
|
17
|
+
return target
|
|
18
|
+
|
|
19
|
+
def read_file(self, name):
|
|
20
|
+
target = self.root / name
|
|
21
|
+
return target.read_text()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "StartupBuddy"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "StartupBuddy is a tool to help you launch your next big idea!"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13.2"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"coffy>=0.2.5",
|
|
9
|
+
"platformdirs>=4.10.0",
|
|
10
|
+
"startupbuddykernel>=0.1.4",
|
|
11
|
+
"textual>=8.2.7",
|
|
12
|
+
"textual-dev>=1.8.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["setuptools>=64"]
|
|
17
|
+
build-backend = "setuptools.build_meta"
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
# Only include specific packages and their subpackages
|
|
21
|
+
include = ["startupbuddy", "interfaces", "screens", "screens.components"]
|
|
22
|
+
|
|
23
|
+
# Exclude unwanted directories or build metadata
|
|
24
|
+
exclude = ["dist*"]
|
|
25
|
+
|
|
26
|
+
# Optional: Set the root scanning directory (defaults to ".")
|
|
27
|
+
where = ["."]
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.package-data]
|
|
30
|
+
startupbuddy = ["styles.tcss"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from .main import MainScreen
|
|
2
|
+
from .new import NewScreen
|
|
3
|
+
from .chat import ChatScreen
|
|
4
|
+
from .questions import QuestionScreen
|
|
5
|
+
from .pitch import PitchScreen
|
|
6
|
+
from .menu import MenuScreen
|
|
7
|
+
from .mockup import MockupScreen
|
|
8
|
+
from .settings import SettingsScreen
|
|
9
|
+
from .new_pitch import NewPitchScreen
|
|
10
|
+
from .start import StartScreen
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from textual.screen import Screen
|
|
4
|
+
from startupbuddykernel import bootstrap
|
|
5
|
+
from startupbuddykernel.kernel import Kernel, SysCommand
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from textual.app import App, ComposeResult
|
|
9
|
+
from textual.binding import Binding
|
|
10
|
+
from textual.containers import Horizontal, VerticalScroll
|
|
11
|
+
from textual.widgets import Footer, Header, Input, Static
|
|
12
|
+
from .components.message import Message
|
|
13
|
+
from textual import work
|
|
14
|
+
|
|
15
|
+
class ChatScreen(Screen):
|
|
16
|
+
"""A simple chat interface screen."""
|
|
17
|
+
|
|
18
|
+
CSS = """
|
|
19
|
+
Screen {
|
|
20
|
+
background: $surface;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#history {
|
|
24
|
+
padding: 1 2;
|
|
25
|
+
height: 1fr;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Message {
|
|
29
|
+
width: 100%;
|
|
30
|
+
margin-bottom: 1;
|
|
31
|
+
layout: vertical;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Message .meta {
|
|
35
|
+
margin-bottom: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Message .body {
|
|
39
|
+
padding: 1 2;
|
|
40
|
+
width: auto;
|
|
41
|
+
max-width: 70%;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* User messages: right-aligned, accent colored */
|
|
45
|
+
Message.user {
|
|
46
|
+
align-horizontal: right;
|
|
47
|
+
}
|
|
48
|
+
Message.user .meta {
|
|
49
|
+
text-align: right;
|
|
50
|
+
color: $accent;
|
|
51
|
+
}
|
|
52
|
+
Message.user .body {
|
|
53
|
+
background: $accent;
|
|
54
|
+
color: $text;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Bot messages: left-aligned, panel colored */
|
|
58
|
+
Message.bot {
|
|
59
|
+
align-horizontal: left;
|
|
60
|
+
}
|
|
61
|
+
Message.bot .meta {
|
|
62
|
+
color: $secondary;
|
|
63
|
+
}
|
|
64
|
+
Message.bot .body {
|
|
65
|
+
background: $panel;
|
|
66
|
+
color: $text;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#input-bar {
|
|
70
|
+
height: auto;
|
|
71
|
+
padding: 1 2;
|
|
72
|
+
background: $boost;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#message-input {
|
|
76
|
+
width: 1fr;
|
|
77
|
+
}
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
BINDINGS = [
|
|
81
|
+
Binding("ctrl+c", "quit", "Quit"),
|
|
82
|
+
Binding("ctrl+l", "clear", "Clear"),
|
|
83
|
+
]
|
|
84
|
+
kernel = None
|
|
85
|
+
|
|
86
|
+
def compose(self) -> ComposeResult:
|
|
87
|
+
yield Header(show_clock=True)
|
|
88
|
+
yield VerticalScroll(id="history")
|
|
89
|
+
with Horizontal(id="input-bar"):
|
|
90
|
+
yield Input(placeholder="Type a message and press Enter…", id="message-input")
|
|
91
|
+
yield Footer()
|
|
92
|
+
|
|
93
|
+
def on_mount(self) -> None:
|
|
94
|
+
self.kernel = bootstrap()
|
|
95
|
+
|
|
96
|
+
self.title = "Chat"
|
|
97
|
+
self.sub_title = "Textual chat interface"
|
|
98
|
+
self._add_message("Hi there! Type something below to get started.", "bot")
|
|
99
|
+
self.query_one("#message-input", Input).focus()
|
|
100
|
+
|
|
101
|
+
def _add_message(self, text: str, sender: str) -> None:
|
|
102
|
+
history = self.query_one("#history", VerticalScroll)
|
|
103
|
+
history.mount(Message(text, sender))
|
|
104
|
+
history.scroll_end(animate=False)
|
|
105
|
+
|
|
106
|
+
async def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
107
|
+
text = event.value.strip()
|
|
108
|
+
if not text:
|
|
109
|
+
return
|
|
110
|
+
event.input.value = ""
|
|
111
|
+
self._add_message(text, "user")
|
|
112
|
+
self.first_prompt(text)
|
|
113
|
+
self.prompt_loop()
|
|
114
|
+
|
|
115
|
+
@work(exclusive=True)
|
|
116
|
+
async def first_prompt(self, text):
|
|
117
|
+
result = await asyncio.to_thread(self.kernel.cmd, SysCommand.KERNEL_PROMPT, prompt=text, project_directory="/Users/shariqtorres/Documents/Gitea/kclikernel/tests/proj")
|
|
118
|
+
if result.code == Kernel.ExitCode.GOOD:
|
|
119
|
+
self._add_message(f"{result.data['response']}", "bot")
|
|
120
|
+
if len(result.data['tools']) > 0:
|
|
121
|
+
tool_results = await asyncio.to_thread(self.kernel.cmd, SysCommand.TOOL_CALL, tools=result.data['tools'])
|
|
122
|
+
if tool_results.code == Kernel.ExitCode.GOOD:
|
|
123
|
+
for r in tool_results.data['result']:
|
|
124
|
+
self._add_message(f"Calling {r['tool']} with these arguments: {r['arguments']}", "bot")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@work(exclusive=True)
|
|
128
|
+
async def prompt_loop(self):
|
|
129
|
+
while True:
|
|
130
|
+
result = await asyncio.to_thread(self.kernel.cmd, SysCommand.REPROMPT, project_directory="/Users/shariqtorres/Documents/Gitea/kclikernel/tests/proj")
|
|
131
|
+
if result.code == Kernel.ExitCode.GOOD:
|
|
132
|
+
self._add_message(f"{result.data['response']}", "bot")
|
|
133
|
+
if len(result.data['tools']) > 0:
|
|
134
|
+
tool_results = await asyncio.to_thread(self.kernel.cmd, SysCommand.TOOL_CALL, tools=result.data['tools'])
|
|
135
|
+
if tool_results.code == Kernel.ExitCode.GOOD:
|
|
136
|
+
for r in tool_results.data['result']:
|
|
137
|
+
self._add_message(f"Calling {r['tool']} with these arguments: {r['arguments']}", "bot")
|
|
138
|
+
continue
|
|
139
|
+
else:
|
|
140
|
+
break
|
|
141
|
+
return True
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def action_clear(self) -> None:
|
|
145
|
+
self.query_one("#history", VerticalScroll).remove_children()
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from textual.app import ComposeResult
|
|
2
|
+
from textual.widgets import Static
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
class Message(Static):
|
|
6
|
+
"""A single chat bubble."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, text: str, sender: str) -> None:
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.text = text
|
|
11
|
+
self.sender = sender # "user" or "bot"
|
|
12
|
+
|
|
13
|
+
def compose(self) -> ComposeResult:
|
|
14
|
+
stamp = datetime.now().strftime("%H:%M")
|
|
15
|
+
yield Static(f"[b]{self.sender.title()}[/b] [dim]{stamp}[/dim]", classes="meta")
|
|
16
|
+
yield Static(self.text, classes="body")
|
|
17
|
+
|
|
18
|
+
def on_mount(self) -> None:
|
|
19
|
+
self.add_class(self.sender)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from textual.screen import Screen
|
|
2
|
+
from textual.app import ComposeResult
|
|
3
|
+
from textual.widgets import Static, Header, Select, Button
|
|
4
|
+
from textual.containers import Horizontal
|
|
5
|
+
from textual import on, log
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
def get_current_projects(db):
|
|
9
|
+
projects = [('New Kernel', 1)]
|
|
10
|
+
projects.extend((p['name'], p['id']) for p in db.get_projects())
|
|
11
|
+
return projects
|
|
12
|
+
|
|
13
|
+
class MainScreen(Screen):
|
|
14
|
+
CSS = """
|
|
15
|
+
#api-key-banner {
|
|
16
|
+
width: 100%;
|
|
17
|
+
background: red;
|
|
18
|
+
color: white;
|
|
19
|
+
text-style: bold;
|
|
20
|
+
text-align: center;
|
|
21
|
+
padding-top: 2;
|
|
22
|
+
padding-bottom: 2;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#clear-projects, #settings-panel {
|
|
26
|
+
margin: 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Select {
|
|
30
|
+
margin-top: 2;
|
|
31
|
+
}
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def compose(self) -> ComposeResult:
|
|
35
|
+
yield Header()
|
|
36
|
+
yield Static(
|
|
37
|
+
"You must set your Wavespeed API key before using",
|
|
38
|
+
id="api-key-banner",
|
|
39
|
+
)
|
|
40
|
+
# yield Static("StartupBuddy -- the tool that helps you jumpstart your idea!")
|
|
41
|
+
yield Static("Select an existing project from the dropdown menu")
|
|
42
|
+
yield Select((name, (name, project_id)) for name, project_id in get_current_projects(self.app.db))
|
|
43
|
+
|
|
44
|
+
def on_mount(self) -> None:
|
|
45
|
+
self._refresh_api_key_banner()
|
|
46
|
+
|
|
47
|
+
def on_screen_resume(self) -> None:
|
|
48
|
+
# MainScreen is a cached singleton, so re-check the setting each time it shows.
|
|
49
|
+
self._refresh_api_key_banner()
|
|
50
|
+
|
|
51
|
+
def _refresh_api_key_banner(self) -> None:
|
|
52
|
+
settings = self.app.db.get_settings()
|
|
53
|
+
missing = not settings or "api_key" not in settings
|
|
54
|
+
self.query_one("#api-key-banner", Static).display = missing
|
|
55
|
+
log(f"The missing fvalue is {missing}")
|
|
56
|
+
if missing is False:
|
|
57
|
+
os.environ['WAVESPEED_API_KEY'] = settings['api_key']
|
|
58
|
+
|
|
59
|
+
@on(Select.Changed)
|
|
60
|
+
def select_changed(self, event: Select.Changed) -> None:
|
|
61
|
+
if event.value is Select.BLANK:
|
|
62
|
+
return
|
|
63
|
+
self.dismiss(event.value)
|
|
64
|
+
|
|
65
|
+
# @on(Button.Pressed, "#settings-panel")
|
|
66
|
+
# def open_settings(self, event: Button.Pressed) -> None:
|
|
67
|
+
# self.app.push_screen("settings")
|
|
68
|
+
|
|
69
|
+
# @on(Button.Pressed, "#clear-projects")
|
|
70
|
+
# def clear_projects(self, event: Button.Pressed) -> None:
|
|
71
|
+
# self.app.db.clear_projects()
|
|
72
|
+
# self.app.CURRENT_KERNEL_ID = None
|
|
73
|
+
# self.query_one(Select).set_options(
|
|
74
|
+
# (name, (name, project_id))
|
|
75
|
+
# for name, project_id in get_current_projects(self.app.db)
|
|
76
|
+
# )
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from textual.screen import Screen
|
|
2
|
+
from textual.app import ComposeResult
|
|
3
|
+
from textual.containers import Vertical, Horizontal
|
|
4
|
+
from textual.widgets import Header, Footer, Label, ListItem, ListView, Static, Markdown
|
|
5
|
+
from textual import on
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MenuScreen(Screen):
|
|
10
|
+
CSS = """
|
|
11
|
+
#project-header {
|
|
12
|
+
width: 100%;
|
|
13
|
+
height: auto;
|
|
14
|
+
align: center middle;
|
|
15
|
+
padding: 1 0;
|
|
16
|
+
}
|
|
17
|
+
#project-name {
|
|
18
|
+
width: 100%;
|
|
19
|
+
text-align: center;
|
|
20
|
+
text-style: bold;
|
|
21
|
+
color: black;
|
|
22
|
+
background: $accent;
|
|
23
|
+
padding: 1 0;
|
|
24
|
+
}
|
|
25
|
+
#project-directory {
|
|
26
|
+
width: 100%;
|
|
27
|
+
text-align: center;
|
|
28
|
+
text-style: dim;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#menu {
|
|
32
|
+
padding: 1;
|
|
33
|
+
}
|
|
34
|
+
#instructions {
|
|
35
|
+
width: 40%;
|
|
36
|
+
}
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
# (label shown in the list, screen name to open when selected)
|
|
40
|
+
MENU_ITEMS = [
|
|
41
|
+
("Business Idea", "questions"),
|
|
42
|
+
("Create Your Pitch", "pitch"),
|
|
43
|
+
("Website Mockup", "mockup"),
|
|
44
|
+
# ("Kernel Development", "chat"),
|
|
45
|
+
("Select Different Project", "main")
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
def compose(self) -> ComposeResult:
|
|
49
|
+
yield Header()
|
|
50
|
+
yield Vertical(
|
|
51
|
+
Static("", id="project-name"),
|
|
52
|
+
Static("", id="project-directory"),
|
|
53
|
+
id="project-header",
|
|
54
|
+
)
|
|
55
|
+
with Horizontal():
|
|
56
|
+
yield Markdown(id="instructions")
|
|
57
|
+
with Vertical():
|
|
58
|
+
yield ListView(id="menu",
|
|
59
|
+
*(ListItem(Label(label), id=screen) for label, screen in self.MENU_ITEMS)
|
|
60
|
+
)
|
|
61
|
+
yield Footer()
|
|
62
|
+
|
|
63
|
+
async def on_mount(self) -> None:
|
|
64
|
+
bl = os.path.dirname(__file__)
|
|
65
|
+
md = self.query_one("#instructions", Markdown)
|
|
66
|
+
await md.load(os.path.join(bl, "./texts/app_instructions.md"))
|
|
67
|
+
self._refresh_project()
|
|
68
|
+
|
|
69
|
+
def on_screen_resume(self) -> None:
|
|
70
|
+
# MenuScreen is a cached singleton in StartupBuddy.SCREENS, so re-read the
|
|
71
|
+
# current project each time the screen is shown (it may have changed).
|
|
72
|
+
self._refresh_project()
|
|
73
|
+
|
|
74
|
+
def _refresh_project(self) -> None:
|
|
75
|
+
project = self.app.db.get_project(self.app.CURRENT_KERNEL_ID)
|
|
76
|
+
if project:
|
|
77
|
+
name = " ".join(project["name"].upper())
|
|
78
|
+
directory = project.get("folder") or ""
|
|
79
|
+
else:
|
|
80
|
+
name = "NO PROJECT SELECTED"
|
|
81
|
+
directory = ""
|
|
82
|
+
if project and "business_answers" in project:
|
|
83
|
+
self.app.business_answers = project['business_answers']
|
|
84
|
+
self.query_one("#project-name", Static).update(name)
|
|
85
|
+
self.query_one("#project-directory", Static).update(directory)
|
|
86
|
+
|
|
87
|
+
@on(ListView.Selected)
|
|
88
|
+
def open_selected(self, event: ListView.Selected) -> None:
|
|
89
|
+
if event.item.id == "questions":
|
|
90
|
+
self.app.push_screen(event.item.id, self.app.business_questions_answered)
|
|
91
|
+
elif event.item.id == "pitch":
|
|
92
|
+
self.app.push_screen(event.item.id, self.app.pitch_created)
|
|
93
|
+
elif event.item.id == "mockup":
|
|
94
|
+
self.app.push_screen(event.item.id, self.app.pitch_created)
|
|
95
|
+
elif event.item.id == "chat":
|
|
96
|
+
self.app.push_screen(event.item.id, self.app.pitch_created)
|
|
97
|
+
elif event.item.id == "main":
|
|
98
|
+
# "Select Different Project": reuse the startup selection handler so
|
|
99
|
+
# CURRENT_KERNEL_ID is updated with the newly chosen project.
|
|
100
|
+
self.app.push_screen(event.item.id, self.app.check_main_selection)
|
|
101
|
+
else:
|
|
102
|
+
self.app.business_answers = None
|
|
103
|
+
self.app.push_screen(event.item.id)
|