2 Commits

Author SHA1 Message Date
ed 15b2bd622f slop intensifies 2026-02-21 14:12:48 -05:00
ed a4d064eddc progress 2026-02-21 14:11:41 -05:00
5 changed files with 321 additions and 512 deletions
+2
View File
@@ -1 +1,3 @@
credentials.toml credentials.toml
__pycache__
uv.lock
+12 -15
View File
@@ -1,38 +1,35 @@
# gemini.py # gemini.py
import tomllib import tomllib
from pathlib import Path from pathlib import Path
import google.generativeai as genai from google import genai
from google.genai import types
_cache = None _client = None
_model = None
_chat = None _chat = None
def _load_key() -> str: def _load_key() -> str:
with open("credentials.toml", "rb") as f: with open("credentials.toml", "rb") as f:
return tomllib.load(f)["gemini"]["api_key"] return tomllib.load(f)["gemini"]["api_key"]
def _ensure_model(): def _ensure_client():
global _model global _client
if _model is None: if _client is None:
genai.configure(api_key=_load_key()) _client = genai.Client(api_key=_load_key())
_model = genai.GenerativeModel("gemini-1.5-pro")
def _ensure_chat(): def _ensure_chat():
global _chat global _chat
if _chat is None: if _chat is None:
_ensure_model() _ensure_client()
_chat = _model.start_chat(history=[]) _chat = _client.chats.create(model="gemini-2.0-flash")
def send(md_content: str, user_message: str) -> str: def send(md_content: str, user_message: str) -> str:
global _cache, _chat global _chat
_ensure_chat() _ensure_chat()
full_message = f"<context>\n{md_content}\n</context>\n\n{user_message}" full_message = f"<context>\n{md_content}\n</context>\n\n{user_message}"
response = _chat.send_message(full_message) response = _chat.send_message(full_message)
return response.text return response.text
def reset_session(): def reset_session():
global _cache, _model, _chat global _client, _chat
_cache = None _client = None
_model = None
_chat = None _chat = None
+291 -472
View File
@@ -1,12 +1,10 @@
# gui.py # gui.py
import dearpygui.dearpygui as dpg
import tomllib import tomllib
import tomli_w import tomli_w
import imgui import threading
import pygame
from imgui.integrations.pygame import PygameRenderer
from pathlib import Path from pathlib import Path
from tkinter import filedialog, Tk from tkinter import filedialog, Tk
import threading
import aggregate import aggregate
import gemini import gemini
@@ -26,427 +24,132 @@ def hide_tk_root() -> Tk:
root.wm_attributes("-topmost", True) root.wm_attributes("-topmost", True)
return root return root
class State: class App:
def __init__(self): def __init__(self):
self.config = load_config() self.config = load_config()
self.files: list[str] = list(self.config["files"].get("paths", []))
self.screenshots: list[str] = list(self.config.get("screenshots", {}).get("paths", []))
self.history: list[str] = list(self.config.get("discussion", {}).get("history", []))
self.namespace_buf = self.config["output"]["namespace"]
self.output_dir_buf = self.config["output"]["output_dir"]
self.files_base_dir_buf = self.config["files"]["base_dir"]
self.screenshots_base_dir_buf = self.config.get("screenshots", {}).get("base_dir", ".")
self.files = list(self.config["files"].get("paths", []))
self.screenshots = list(self.config.get("screenshots", {}).get("paths", []))
self.history = list(self.config.get("discussion", {}).get("history", []))
self.history_edit_bufs = [h for h in self.history]
self.gemini_input_buf = ""
self.gemini_response = ""
self.gemini_status = "idle" self.gemini_status = "idle"
self.gemini_response = ""
self.last_md = "" self.last_md = ""
self.last_md_path = None self.last_md_path: Path | None = None
self.send_thread = None self.send_thread: threading.Thread | None = None
def flush_to_config(self): self.file_rows: list[str] = []
self.config["output"]["namespace"] = self.namespace_buf self.shot_rows: list[str] = []
self.config["output"]["output_dir"] = self.output_dir_buf
self.config["files"]["base_dir"] = self.files_base_dir_buf # ------------------------------------------------------------------ helpers
def _flush_to_config(self):
self.config["output"]["namespace"] = dpg.get_value("namespace")
self.config["output"]["output_dir"] = dpg.get_value("output_dir")
self.config["files"]["base_dir"] = dpg.get_value("files_base_dir")
if "screenshots" not in self.config: if "screenshots" not in self.config:
self.config["screenshots"] = {} self.config["screenshots"] = {}
self.config["screenshots"]["base_dir"] = self.screenshots_base_dir_buf self.config["screenshots"]["base_dir"] = dpg.get_value("shots_base_dir")
self.config["files"]["paths"] = self.files self.config["files"]["paths"] = self.files
self.config["screenshots"]["paths"] = self.screenshots self.config["screenshots"]["paths"] = self.screenshots
self.config["discussion"] = {"history": self.history_edit_bufs} raw = dpg.get_value("discussion_box")
self.history = [s.strip() for s in raw.split("---") if s.strip()]
self.config["discussion"] = {"history": self.history}
def sync_history_bufs(self): def _do_generate(self) -> tuple[str, Path]:
while len(self.history_edit_bufs) < len(self.history): self._flush_to_config()
self.history_edit_bufs.append("") save_config(self.config)
self.history_edit_bufs = self.history_edit_bufs[:len(self.history)] return aggregate.run(self.config)
def draw_config_panel(state: State): def _update_status(self, status: str):
imgui.begin("Config") self.gemini_status = status
dpg.set_value("gemini_status", f"Status: {status}")
changed, val = imgui.input_text("Namespace", state.namespace_buf, 256) def _update_response(self, text: str):
if changed: self.gemini_response = text
state.namespace_buf = val dpg.set_value("gemini_response", text)
changed, val = imgui.input_text("Output Dir", state.output_dir_buf, 512) def _rebuild_files_table(self):
if changed: dpg.delete_item("files_table", children_only=True)
state.output_dir_buf = val for i, f in enumerate(self.files):
with dpg.table_row(parent="files_table"):
if imgui.button("Save Config"): dpg.add_text(f)
state.flush_to_config() dpg.add_button(
save_config(state.config) label="x",
callback=self._make_remove_file_cb(i),
imgui.end() width=24
def draw_files_panel(state: State):
imgui.begin("Files")
changed, val = imgui.input_text("Base Dir##files", state.files_base_dir_buf, 512)
if changed:
state.files_base_dir_buf = val
if imgui.button("Browse Base Dir##files"):
root = hide_tk_root()
d = filedialog.askdirectory(title="Select Files Base Dir")
root.destroy()
if d:
state.files_base_dir_buf = d
imgui.separator()
to_remove = None
for i, f in enumerate(state.files):
imgui.text(f)
imgui.same_line()
if imgui.button(f"Remove##file{i}"):
to_remove = i
if to_remove is not None:
state.files.pop(to_remove)
if imgui.button("Add File"):
root = hide_tk_root()
paths = filedialog.askopenfilenames(title="Select Files")
root.destroy()
for p in paths:
if p not in state.files:
state.files.append(p)
if imgui.button("Add Wildcard##files"):
root = hide_tk_root()
d = filedialog.askdirectory(title="Select Dir for Wildcard")
root.destroy()
if d:
state.files.append(str(Path(d) / "**" / "*"))
imgui.end()
def draw_screenshots_panel(state: State):
imgui.begin("Screenshots")
changed, val = imgui.input_text("Base Dir##shots", state.screenshots_base_dir_buf, 512)
if changed:
state.screenshots_base_dir_buf = val
if imgui.button("Browse Base Dir##shots"):
root = hide_tk_root()
d = filedialog.askdirectory(title="Select Screenshots Base Dir")
root.destroy()
if d:
state.screenshots_base_dir_buf = d
imgui.separator()
to_remove = None
for i, s in enumerate(state.screenshots):
imgui.text(s)
imgui.same_line()
if imgui.button(f"Remove##shot{i}"):
to_remove = i
if to_remove is not None:
state.screenshots.pop(to_remove)
if imgui.button("Add Screenshot"):
root = hide_tk_root()
paths = filedialog.askopenfilenames(
title="Select Screenshots",
filetypes=[("Images", "*.png *.jpg *.jpeg *.gif *.bmp *.webp"), ("All", "*.*")]
)
root.destroy()
for p in paths:
if p not in state.screenshots:
state.screenshots.append(p)
imgui.end()
def draw_discussion_panel(state: State):
imgui.begin("Discussion History")
if imgui.button("Add Excerpt"):
state.history.append("")
state.history_edit_bufs.append("")
imgui.separator()
state.sync_history_bufs()
to_remove = None
for i in range(len(state.history)):
imgui.text(f"Excerpt {i + 1}")
imgui.same_line()
if imgui.button(f"Remove##hist{i}"):
to_remove = i
continue
changed, val = imgui.input_text_multiline(
f"##histbuf{i}",
state.history_edit_bufs[i],
2048,
width=-1,
height=120
)
if changed:
state.history_edit_bufs[i] = val
imgui.separator()
if to_remove is not None:
state.history.pop(to_remove)
state.history_edit_bufs.pop(to_remove)
imgui.end()
def draw_gemini_panel(state: State):
imgui.begin("Gemini")
imgui.text(f"Status: {state.gemini_status}")
if state.last_md_path:
imgui.text(f"Last MD: {state.last_md_path}")
imgui.separator()
changed, val = imgui.input_text_multiline(
"##gemini_input",
state.gemini_input_buf,
4096,
width=-1,
height=100
)
if changed:
state.gemini_input_buf = val
is_sending = state.send_thread is not None and state.send_thread.is_alive()
if is_sending:
imgui.begin_disabled()
if imgui.button("Generate MD + Send"):
state.flush_to_config()
save_config(state.config)
md, path = aggregate.run(state.config)
state.last_md = md
state.last_md_path = path
state.gemini_status = "sending..."
user_msg = state.gemini_input_buf
def do_send():
try:
response = gemini.send(state.last_md, user_msg)
state.gemini_response = response
state.gemini_status = "done"
except Exception as e:
state.gemini_response = f"ERROR: {e}"
state.gemini_status = "error"
state.send_thread = threading.Thread(target=do_send, daemon=True)
state.send_thread.start()
if is_sending:
imgui.end_disabled()
imgui.same_line()
if imgui.button("Generate MD Only"):
state.flush_to_config()
save_config(state.config)
md, path = aggregate.run(state.config)
state.last_md = md
state.last_md_path = path
state.gemini_status = "md generated"
imgui.same_line()
if imgui.button("Reset Session"):
gemini.reset_session()
state.gemini_status = "session reset"
state.gemini_response = ""
imgui.separator()
imgui.text("Response:")
imgui.input_text_multiline(
"##gemini_response",
state.gemini_response,
max_value_length=65536,
width=-1,
height=-1,
flags=imgui.INPUT_TEXT_READ_ONLY
) )
imgui.end() def _rebuild_shots_table(self):
dpg.delete_item("shots_table", children_only=True)
def main(): for i, s in enumerate(self.screenshots):
pygame.init() with dpg.table_row(parent="shots_table"):
screen = pygame.display.set_mode( dpg.add_text(s)
(1280, 720), dpg.add_button(
pygame.RESIZABLE label="x",
) callback=self._make_remove_shot_cb(i),
pygame.display.set_caption("manual slop") width=24
imgui.create_context()
io = imgui.get_io()
io.display_size = (1280, 720)
renderer = PygameRenderer()
state = State()
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
return
if event.type == pygame.VIDEORESIZE:
io.display_size = (event.w, event.h)
renderer.process_event(event)
imgui.new_frame()
vp = imgui.get_main_viewport()
imgui.set_next_window_position(vp.pos.x, vp.pos.y)
imgui.set_next_window_size(vp.size.x, vp.size.y)
imgui.begin(
"##root",
flags=(
imgui.WINDOW_NO_TITLE_BAR
| imgui.WINDOW_NO_RESIZE
| imgui.WINDOW_NO_MOVE
| imgui.WINDOW_NO_SCROLLBAR
| imgui.WINDOW_NO_SAVED_SETTINGS
)
) )
avail_w, avail_h = imgui.get_content_region_available() def _make_remove_file_cb(self, idx: int):
col_w = avail_w / 4 def cb():
if idx < len(self.files):
self.files.pop(idx)
self._rebuild_files_table()
return cb
imgui.begin_child("##left", width=col_w, height=avail_h, border=True) def _make_remove_shot_cb(self, idx: int):
draw_config_panel_inline(state) def cb():
imgui.separator() if idx < len(self.screenshots):
draw_files_panel_inline(state) self.screenshots.pop(idx)
imgui.end_child() self._rebuild_shots_table()
return cb
imgui.same_line() # ---------------------------------------------------------------- callbacks
imgui.begin_child("##mid", width=col_w, height=avail_h, border=True) def cb_browse_output(self):
draw_screenshots_panel_inline(state)
imgui.end_child()
imgui.same_line()
imgui.begin_child("##right_top", width=col_w, height=avail_h, border=True)
draw_discussion_panel_inline(state)
imgui.end_child()
imgui.same_line()
imgui.begin_child("##gemini", width=col_w, height=avail_h, border=True)
draw_gemini_panel_inline(state)
imgui.end_child()
imgui.end()
screen.fill((30, 30, 30))
imgui.render()
renderer.render(imgui.get_draw_data())
pygame.display.flip()
clock.tick(30)
def draw_config_panel_inline(state: State):
imgui.text("Config")
imgui.separator()
changed, val = imgui.input_text("Namespace", state.namespace_buf, 256)
if changed:
state.namespace_buf = val
changed, val = imgui.input_text("Output Dir", state.output_dir_buf, 512)
if changed:
state.output_dir_buf = val
if imgui.button("Browse Output Dir"):
root = hide_tk_root() root = hide_tk_root()
d = filedialog.askdirectory(title="Select Output Dir") d = filedialog.askdirectory(title="Select Output Dir")
root.destroy() root.destroy()
if d: if d:
state.output_dir_buf = d dpg.set_value("output_dir", d)
if imgui.button("Save Config"): def cb_save_config(self):
state.flush_to_config() self._flush_to_config()
save_config(state.config) save_config(self.config)
self._update_status("config saved")
def cb_browse_files_base(self):
def draw_files_panel_inline(state: State):
imgui.text("Files")
imgui.separator()
changed, val = imgui.input_text("Base Dir##files", state.files_base_dir_buf, 512)
if changed:
state.files_base_dir_buf = val
if imgui.button("Browse Base Dir##files"):
root = hide_tk_root() root = hide_tk_root()
d = filedialog.askdirectory(title="Select Files Base Dir") d = filedialog.askdirectory(title="Select Files Base Dir")
root.destroy() root.destroy()
if d: if d:
state.files_base_dir_buf = d dpg.set_value("files_base_dir", d)
imgui.separator() def cb_add_files(self):
to_remove = None
for i, f in enumerate(state.files):
imgui.text(f[:40] + "..." if len(f) > 40 else f)
imgui.same_line()
if imgui.button(f"x##file{i}"):
to_remove = i
if to_remove is not None:
state.files.pop(to_remove)
if imgui.button("Add File(s)##files"):
root = hide_tk_root() root = hide_tk_root()
paths = filedialog.askopenfilenames(title="Select Files") paths = filedialog.askopenfilenames(title="Select Files")
root.destroy() root.destroy()
for p in paths: for p in paths:
if p not in state.files: if p not in self.files:
state.files.append(p) self.files.append(p)
self._rebuild_files_table()
if imgui.button("Add Wildcard##files"): def cb_add_wildcard(self):
root = hide_tk_root() root = hide_tk_root()
d = filedialog.askdirectory(title="Select Dir for Wildcard") d = filedialog.askdirectory(title="Select Dir for Wildcard")
root.destroy() root.destroy()
if d: if d:
state.files.append(str(Path(d) / "**" / "*")) self.files.append(str(Path(d) / "**" / "*"))
self._rebuild_files_table()
def cb_browse_shots_base(self):
def draw_screenshots_panel_inline(state: State):
imgui.text("Screenshots")
imgui.separator()
changed, val = imgui.input_text("Base Dir##shots", state.screenshots_base_dir_buf, 512)
if changed:
state.screenshots_base_dir_buf = val
if imgui.button("Browse Base Dir##shots"):
root = hide_tk_root() root = hide_tk_root()
d = filedialog.askdirectory(title="Select Screenshots Base Dir") d = filedialog.askdirectory(title="Select Screenshots Base Dir")
root.destroy() root.destroy()
if d: if d:
state.screenshots_base_dir_buf = d dpg.set_value("shots_base_dir", d)
imgui.separator() def cb_add_shots(self):
to_remove = None
for i, s in enumerate(state.screenshots):
imgui.text(s[:40] + "..." if len(s) > 40 else s)
imgui.same_line()
if imgui.button(f"x##shot{i}"):
to_remove = i
if to_remove is not None:
state.screenshots.pop(to_remove)
if imgui.button("Add Screenshot(s)"):
root = hide_tk_root() root = hide_tk_root()
paths = filedialog.askopenfilenames( paths = filedialog.askopenfilenames(
title="Select Screenshots", title="Select Screenshots",
@@ -454,122 +157,238 @@ def draw_screenshots_panel_inline(state: State):
) )
root.destroy() root.destroy()
for p in paths: for p in paths:
if p not in state.screenshots: if p not in self.screenshots:
state.screenshots.append(p) self.screenshots.append(p)
self._rebuild_shots_table()
def cb_add_excerpt(self):
current = dpg.get_value("discussion_box")
dpg.set_value("discussion_box", current + "\n---\n")
def draw_discussion_panel_inline(state: State): def cb_clear_discussion(self):
imgui.text("Discussion History") dpg.set_value("discussion_box", "")
imgui.separator()
if imgui.button("Add Excerpt"): def cb_save_discussion(self):
state.history.append("") self._flush_to_config()
state.history_edit_bufs.append("") save_config(self.config)
self._update_status("discussion saved")
imgui.separator() def cb_md_only(self):
try:
md, path = self._do_generate()
self.last_md = md
self.last_md_path = path
self._update_status(f"md written: {path.name}")
except Exception as e:
self._update_status(f"error: {e}")
state.sync_history_bufs() def cb_reset_session(self):
to_remove = None gemini.reset_session()
for i in range(len(state.history)): self._update_status("session reset")
imgui.text(f"Excerpt {i + 1}") self._update_response("")
imgui.same_line()
if imgui.button(f"x##hist{i}"):
to_remove = i
continue
changed, val = imgui.input_text_multiline(
f"##histbuf{i}",
state.history_edit_bufs[i],
2048,
width=-1,
height=120
)
if changed:
state.history_edit_bufs[i] = val
imgui.separator()
if to_remove is not None: def cb_generate_send(self):
state.history.pop(to_remove) if self.send_thread and self.send_thread.is_alive():
state.history_edit_bufs.pop(to_remove) return
try:
md, path = self._do_generate()
self.last_md = md
self.last_md_path = path
except Exception as e:
self._update_status(f"generate error: {e}")
return
self._update_status("sending...")
def draw_gemini_panel_inline(state: State): user_msg = dpg.get_value("gemini_input")
imgui.text("Gemini")
imgui.separator()
imgui.text(f"Status: {state.gemini_status}")
if state.last_md_path:
imgui.text(f"Last MD: {str(state.last_md_path)[:40]}")
imgui.separator()
changed, val = imgui.input_text_multiline(
"##gemini_input",
state.gemini_input_buf,
4096,
width=-1,
height=120
)
if changed:
state.gemini_input_buf = val
is_sending = state.send_thread is not None and state.send_thread.is_alive()
if is_sending:
imgui.begin_disabled()
if imgui.button("Generate + Send"):
state.flush_to_config()
save_config(state.config)
md, path = aggregate.run(state.config)
state.last_md = md
state.last_md_path = path
state.gemini_status = "sending..."
user_msg = state.gemini_input_buf
def do_send(): def do_send():
try: try:
response = gemini.send(state.last_md, user_msg) response = gemini.send(self.last_md, user_msg)
state.gemini_response = response self._update_response(response)
state.gemini_status = "done" self._update_status("done")
except Exception as e: except Exception as e:
state.gemini_response = f"ERROR: {e}" self._update_response(f"ERROR: {e}")
state.gemini_status = "error" self._update_status("error")
state.send_thread = threading.Thread(target=do_send, daemon=True) self.send_thread = threading.Thread(target=do_send, daemon=True)
state.send_thread.start() self.send_thread.start()
if is_sending: # ---------------------------------------------------------------- build ui
imgui.end_disabled()
imgui.same_line() def _build_ui(self):
with dpg.window(
label="Config",
tag="win_config",
pos=(8, 8),
width=340,
height=220,
no_close=True
):
dpg.add_input_text(
label="Namespace",
tag="namespace",
default_value=self.config["output"]["namespace"],
width=-1
)
dpg.add_input_text(
label="Output Dir",
tag="output_dir",
default_value=self.config["output"]["output_dir"],
width=-1
)
dpg.add_button(label="Browse Output Dir", callback=self.cb_browse_output, width=-1)
dpg.add_separator()
dpg.add_button(label="Save Config", callback=self.cb_save_config, width=-1)
if imgui.button("MD Only"): with dpg.window(
state.flush_to_config() label="Files",
save_config(state.config) tag="win_files",
md, path = aggregate.run(state.config) pos=(8, 236),
state.last_md = md width=340,
state.last_md_path = path height=460,
state.gemini_status = "md generated" no_close=True
):
dpg.add_input_text(
label="Base Dir",
tag="files_base_dir",
default_value=self.config["files"]["base_dir"],
width=-1
)
dpg.add_button(label="Browse Base Dir##files", callback=self.cb_browse_files_base, width=-1)
dpg.add_separator()
imgui.same_line() with dpg.table(
tag="files_table",
header_row=False,
resizable=True,
borders_innerV=True,
scrollY=True,
height=280
):
dpg.add_table_column(label="Path", width_stretch=True)
dpg.add_table_column(label="", width_fixed=True, init_width_or_weight=28)
if imgui.button("Reset"): self._rebuild_files_table()
gemini.reset_session()
state.gemini_status = "session reset"
state.gemini_response = ""
imgui.separator() dpg.add_separator()
imgui.text("Response:") with dpg.group(horizontal=True):
dpg.add_button(label="Add File(s)", callback=self.cb_add_files, width=-1)
with dpg.group(horizontal=True):
dpg.add_button(label="Add Wildcard", callback=self.cb_add_wildcard, width=-1)
_, _ = imgui.input_text_multiline( with dpg.window(
"##gemini_response", label="Screenshots",
state.gemini_response, tag="win_screenshots",
max_value_length=65536, pos=(356, 8),
width=340,
height=460,
no_close=True
):
dpg.add_input_text(
label="Base Dir",
tag="shots_base_dir",
default_value=self.config.get("screenshots", {}).get("base_dir", "."),
width=-1
)
dpg.add_button(label="Browse Base Dir##shots", callback=self.cb_browse_shots_base, width=-1)
dpg.add_separator()
with dpg.table(
tag="shots_table",
header_row=False,
resizable=True,
borders_innerV=True,
scrollY=True,
height=280
):
dpg.add_table_column(label="Path", width_stretch=True)
dpg.add_table_column(label="", width_fixed=True, init_width_or_weight=28)
self._rebuild_shots_table()
dpg.add_separator()
dpg.add_button(label="Add Screenshot(s)", callback=self.cb_add_shots, width=-1)
with dpg.window(
label="Discussion History",
tag="win_discussion",
pos=(704, 8),
width=340,
height=460,
no_close=True
):
dpg.add_input_text(
label="##discussion_box",
tag="discussion_box",
default_value="\n---\n".join(self.history),
multiline=True,
width=-1, width=-1,
height=-1, height=340
flags=imgui.INPUT_TEXT_READ_ONLY )
dpg.add_separator()
with dpg.group(horizontal=True):
dpg.add_button(label="Add Separator", callback=self.cb_add_excerpt)
dpg.add_button(label="Clear", callback=self.cb_clear_discussion)
dpg.add_button(label="Save", callback=self.cb_save_discussion)
with dpg.window(
label="Gemini",
tag="win_gemini",
pos=(1052, 8),
width=340,
height=700,
no_close=True
):
dpg.add_text("Status: idle", tag="gemini_status")
dpg.add_separator()
dpg.add_input_text(
label="##gemini_input",
tag="gemini_input",
multiline=True,
width=-1,
height=120
)
dpg.add_separator()
with dpg.group(horizontal=True):
dpg.add_button(label="Gen + Send", callback=self.cb_generate_send)
dpg.add_button(label="MD Only", callback=self.cb_md_only)
dpg.add_button(label="Reset", callback=self.cb_reset_session)
dpg.add_separator()
dpg.add_text("Response:")
dpg.add_input_text(
label="##gemini_response",
tag="gemini_response",
multiline=True,
readonly=True,
width=-1,
height=-1
) )
def run(self):
dpg.create_context()
dpg.configure_app(docking=True, docking_space=True)
dpg.create_viewport(
title="manual slop",
width=1600,
height=900
)
dpg.setup_dearpygui()
dpg.show_viewport()
dpg.maximize_viewport()
self._build_ui()
while dpg.is_dearpygui_running():
dpg.render_dearpygui_frame()
dpg.destroy_context()
def main():
app = App()
app.run()
if __name__ == "__main__": if __name__ == "__main__":
main() main()
+3 -4
View File
@@ -1,11 +1,10 @@
# pyproject.toml
[project] [project]
name = "manual_slop" name = "manual_slop"
version = "0.1.0" version = "0.1.0"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [
"imgui[pygame]", "dearpygui",
"pygame", "google-genai",
"google-generativeai",
"tomli-w" "tomli-w"
] ]
Generated
-8
View File
@@ -1,8 +0,0 @@
version = 1
revision = 3
requires-python = ">=3.11"
[[package]]
name = "aggregator"
version = "0.1.0"
source = { virtual = "." }