Implement prior-art search functionality in concept-maker_gui.py

This commit is contained in:
2025-09-13 03:51:24 +02:00
parent b8e35762e2
commit eca3cf3928

View File

@@ -2220,6 +2220,146 @@ class App(TkinterDnD.Tk): # type: ignore
self._ui(lambda m=f"Failed to save session:\n{e}": messagebox.showerror("Error", m))
return False
# --- Prior-art search (SearXNG + embeddings)
def on_prior_art(self):
model = (self.ollama_model.get() or "").strip()
if not model or model == "Select model...":
self._ui(lambda: messagebox.showinfo("Select model", "Please select a model first."))
return
if not self.files and not self.notes.get("1.0", tk.END).strip():
self._ui(lambda: messagebox.showinfo("Nothing to search", "Add files or write some notes first."))
return
threading.Thread(target=self._prior_art_thread, daemon=True).start()
def _prior_art_thread(self):
try:
self._set_status("Preparing knowledge base…")
records = self.ensure_and_load_kb_for_current()
notes = self.notes.get("1.0", tk.END).strip()
kb_str = build_kb_string(records)
assets = [p for p in self.files if self.include_map.get(str(p), True)]
searx_url = (self.searx_url.get() or "").strip() or None
def _status_cb(s: str):
self._set_status(s)
res = websearch.prior_art_search(
ollama_host=self.ollama_host.get(),
model=self.ollama_model.get(),
notes=notes,
kb=kb_str,
assets=[str(p) for p in assets],
searx_url=searx_url,
status_cb=_status_cb,
)
self._set_status("Prior-art search complete")
self._ui(lambda r=res: self._show_prior_art_results(r))
except Exception as e:
self._set_status("Prior-art search failed")
msg = f"Prior-art search failed:\n{e}"
self._ui(lambda m=msg: messagebox.showerror("Error", m))
def _show_prior_art_results(self, result: Dict[str, Any]):
try:
win = tk.Toplevel(self)
win.title("Prior Art Results")
try:
win.geometry("980x640")
except Exception:
pass
top = ttk.Frame(win)
top.pack(side=tk.TOP, fill=tk.X, padx=8, pady=8)
ttk.Label(top, text="Queries:").pack(side=tk.LEFT)
q_str = "; ".join(result.get("queries", [])[:3])
q_entry = ttk.Entry(top)
q_entry.insert(0, q_str)
q_entry.configure(state="readonly")
q_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(6,0))
body = ttk.Panedwindow(win, orient=tk.VERTICAL)
body.pack(fill=tk.BOTH, expand=True, padx=8, pady=(0,8))
upper = ttk.Frame(body)
lower = ttk.Frame(body)
body.add(upper, weight=3)
body.add(lower, weight=2)
cols = ("score", "url")
tv = ttk.Treeview(upper, columns=cols, show="headings")
tv.heading("score", text="Score")
tv.heading("url", text="URL")
tv.column("score", width=60, anchor=tk.W)
tv.column("url", width=860, anchor=tk.W)
vs = ttk.Scrollbar(upper, orient=tk.VERTICAL, command=tv.yview)
tv.configure(yscrollcommand=vs.set)
tv.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
vs.pack(side=tk.RIGHT, fill=tk.Y)
# Snippet area
snip = tk.Text(lower, wrap=tk.WORD)
snip.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
snip_sb = ttk.Scrollbar(lower, orient=tk.VERTICAL, command=snip.yview)
snip.configure(yscrollcommand=snip_sb.set)
snip_sb.pack(side=tk.RIGHT, fill=tk.Y)
rows = result.get("results", []) or []
for i, item in enumerate(rows):
score = item.get("score", 0)
url = item.get("url", "")
tv.insert('', tk.END, iid=str(i), values=(f"{score:.1f}", url))
def _on_sel(_e=None):
sel = tv.selection()
if not sel:
return
try:
i = int(sel[0])
except Exception:
return
try:
data = rows[i]
except Exception:
data = {}
snippet = (data.get("snippet", "") or "").strip()
snip.configure(state="normal")
snip.delete("1.0", tk.END)
if snippet:
snip.insert("1.0", snippet)
snip.configure(state="disabled")
tv.bind('<<TreeviewSelect>>', _on_sel)
def _open_selected(_e=None):
sel = tv.selection()
if not sel:
return
try:
i = int(sel[0])
url = rows[i].get("url")
if url:
webbrowser.open(url)
except Exception:
pass
tv.bind('<Double-1>', _open_selected)
# Initial selection
try:
first = tv.get_children('')[:1]
if first:
tv.selection_set(first)
_on_sel()
except Exception:
pass
except Exception:
# fallback: messagebox
try:
urls = [x.get("url", "") for x in (result.get("results", []) or [])][:10]
messagebox.showinfo("Results", "\n".join(urls) or "No results")
except Exception:
pass
def _maybe_save_if_dirty(self) -> bool:
try:
# Only prompt if content truly differs from last saved snapshot