This commit is contained in:
vasilije 2025-11-18 06:12:35 -08:00
parent 6a087cbdfc
commit 30fd84e20d
9 changed files with 126 additions and 74 deletions

View file

@ -43,12 +43,10 @@ Perfect for managing Cognee from the terminal or SSH sessions!
# Import and run TUI # Import and run TUI
from cognee.cli.tui import run_tui from cognee.cli.tui import run_tui
run_tui(mouse=not args.no_mouse) run_tui(mouse=not args.no_mouse)
except KeyboardInterrupt: except KeyboardInterrupt:
fmt.note("\nTUI closed by user") fmt.note("\nTUI closed by user")
except Exception as e: except Exception as e:
raise CliCommandException( raise CliCommandException(f"Failed to start TUI: {str(e)}", error_code=1) from e
f"Failed to start TUI: {str(e)}",
error_code=1
) from e

View file

@ -1,4 +1,5 @@
"""Cognee TUI - Terminal User Interface""" """Cognee TUI - Terminal User Interface"""
from cognee.cli.tui.app import run_tui, CogneeTUI from cognee.cli.tui.app import run_tui, CogneeTUI
__all__ = ["run_tui", "CogneeTUI"] __all__ = ["run_tui", "CogneeTUI"]

View file

@ -2,6 +2,7 @@
Cognee TUI - Main Application Cognee TUI - Main Application
Text-based User Interface for managing Cognee knowledge graphs Text-based User Interface for managing Cognee knowledge graphs
""" """
from typing import ClassVar from typing import ClassVar
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
from textual.binding import Binding from textual.binding import Binding
@ -75,6 +76,7 @@ class CogneeTUI(App):
Binding("?", "help", "Help"), Binding("?", "help", "Help"),
Binding("d", "toggle_dark", "Toggle Dark Mode"), Binding("d", "toggle_dark", "Toggle Dark Mode"),
] ]
def on_mount(self) -> None: def on_mount(self) -> None:
"""Initialize the app with the home screen""" """Initialize the app with the home screen"""
self.push_screen(HomeScreen()) self.push_screen(HomeScreen())
@ -104,7 +106,6 @@ class CogneeTUI(App):
self.push_screen(HelpScreen(help_text)) self.push_screen(HelpScreen(help_text))
class HelpScreen(Screen): class HelpScreen(Screen):
"""Help screen""" """Help screen"""

View file

@ -1,4 +1,5 @@
"""Screens for Cognee TUI""" """Screens for Cognee TUI"""
from cognee.cli.tui.screens.home import HomeScreen from cognee.cli.tui.screens.home import HomeScreen
from cognee.cli.tui.screens.context import ContextScreen from cognee.cli.tui.screens.context import ContextScreen
from cognee.cli.tui.screens.query import QueryScreen from cognee.cli.tui.screens.query import QueryScreen

View file

@ -1,4 +1,5 @@
"""Context Management Screen""" """Context Management Screen"""
import asyncio import asyncio
import io import io
import os import os
@ -49,15 +50,23 @@ class ContextScreen(Screen):
yield DataTable(id="files_table") yield DataTable(id="files_table")
# Tables are the primary UI (no dropdowns) # Tables are the primary UI (no dropdowns)
yield Input(placeholder="comma-separated node sets (optional)", id="nodeset_input") yield Input(placeholder="comma-separated node sets (optional)", id="nodeset_input")
yield Static("\nEnter text or a file path to add to the selected dataset:", classes="center") yield Static(
"\nEnter text or a file path to add to the selected dataset:", classes="center"
)
yield Input(placeholder="Text or /absolute/path/to/file.pdf", id="data_input") yield Input(placeholder="Text or /absolute/path/to/file.pdf", id="data_input")
yield Button("Add to Context", id="add_btn", variant="primary") yield Button("Add to Context", id="add_btn", variant="primary")
yield Button("Cognify (process data)", id="cognify_btn", variant="success") yield Button("Cognify (process data)", id="cognify_btn", variant="success")
yield Static("\nSearch (runs against selected dataset context):", classes="center") yield Static("\nSearch (runs against selected dataset context):", classes="center")
yield Input(placeholder="e.g., What are the main topics?", id="search_input") yield Input(placeholder="e.g., What are the main topics?", id="search_input")
yield Button("Search", id="search_btn", variant="default") yield Button("Search", id="search_btn", variant="default")
yield Checkbox("Save search output to searched_context.md", id="save_search_checkbox", value=False) yield Checkbox(
yield Static("\nExport context to Markdown (runs one or more queries):", classes="center") "Save search output to searched_context.md",
id="save_search_checkbox",
value=False,
)
yield Static(
"\nExport context to Markdown (runs one or more queries):", classes="center"
)
yield Input(placeholder="Queries to export (comma-separated)", id="export_queries") yield Input(placeholder="Queries to export (comma-separated)", id="export_queries")
yield Button("Export Context to MD", id="export_btn", variant="default") yield Button("Export Context to MD", id="export_btn", variant="default")
yield Static("", id="status") yield Static("", id="status")
@ -93,7 +102,9 @@ class ContextScreen(Screen):
self._dataset_row_to_id.clear() self._dataset_row_to_id.clear()
self._dataset_row_keys = [] self._dataset_row_keys = []
for d in normalized: for d in normalized:
row_key = ds_table.add_row(d.get("name", ""), d.get("id", ""), d.get("created_at", "") or "") row_key = ds_table.add_row(
d.get("name", ""), d.get("id", ""), d.get("created_at", "") or ""
)
self._dataset_row_to_id[row_key] = d["id"] self._dataset_row_to_id[row_key] = d["id"]
self._dataset_row_keys.append(row_key) self._dataset_row_keys.append(row_key)
# Focus datasets table and preselect first row if available # Focus datasets table and preselect first row if available
@ -149,7 +160,17 @@ class ContextScreen(Screen):
self._file_row_to_id.clear() self._file_row_to_id.clear()
self._file_row_keys = [] self._file_row_keys = []
for i in normalized: for i in normalized:
row_key = files_table.add_row(i.get("name", ""), i.get("id", ""), (i.get("original_data_location") or i.get("raw_data_location") or i.get("orig") or i.get("raw") or "") ) row_key = files_table.add_row(
i.get("name", ""),
i.get("id", ""),
(
i.get("original_data_location")
or i.get("raw_data_location")
or i.get("orig")
or i.get("raw")
or ""
),
)
self._file_row_to_id[row_key] = i["id"] self._file_row_to_id[row_key] = i["id"]
self._file_row_keys.append(row_key) self._file_row_keys.append(row_key)
self._selected_data_id = None self._selected_data_id = None
@ -169,8 +190,10 @@ class ContextScreen(Screen):
await self._set_status("[red]Please enter text or a file path[/red]") await self._set_status("[red]Please enter text or a file path[/red]")
return return
try: try:
await self._set_status(f"Adding data to dataset [b]{dataset_name}[/b] " await self._set_status(
f"{'(with node sets: ' + ', '.join(node_set) + ')' if node_set else ''}...") f"Adding data to dataset [b]{dataset_name}[/b] "
f"{'(with node sets: ' + ', '.join(node_set) + ')' if node_set else ''}..."
)
with redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()): with redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()):
await cognee.add(content, dataset_name=dataset_name, node_set=node_set) await cognee.add(content, dataset_name=dataset_name, node_set=node_set)
await self._set_status("[green]✓ Added successfully.[/green] You can now run Cognify.") await self._set_status("[green]✓ Added successfully.[/green] You can now run Cognify.")
@ -202,7 +225,11 @@ class ContextScreen(Screen):
if ds_name: if ds_name:
kwargs["datasets"] = [ds_name] kwargs["datasets"] = [ds_name]
results = await cognee.search(query_text=query_text, **kwargs) results = await cognee.search(query_text=query_text, **kwargs)
rendered = "\n".join(f"- {str(item)}" for item in results) if isinstance(results, list) else str(results) rendered = (
"\n".join(f"- {str(item)}" for item in results)
if isinstance(results, list)
else str(results)
)
await self._set_status(f"[b]Search results[/b]:\n{rendered}") await self._set_status(f"[b]Search results[/b]:\n{rendered}")
# Optionally save to searched_context.md # Optionally save to searched_context.md
if save_cb.value: if save_cb.value:
@ -211,10 +238,17 @@ class ContextScreen(Screen):
if self._selected_data_id: if self._selected_data_id:
data_item = self._data_items_by_id.get(self._selected_data_id) data_item = self._data_items_by_id.get(self._selected_data_id)
if data_item: if data_item:
loc = data_item.get("original_data_location") or data_item.get("raw_data_location") or data_item.get("orig") or data_item.get("raw") loc = (
data_item.get("original_data_location")
or data_item.get("raw_data_location")
or data_item.get("orig")
or data_item.get("raw")
)
if isinstance(loc, str) and loc.startswith("file://"): if isinstance(loc, str) and loc.startswith("file://"):
loc = loc[len("file://") :] loc = loc[len("file://") :]
if isinstance(loc, str) and (loc.startswith("/") or (len(loc) > 2 and loc[1:3] in (":\\", ":/"))): if isinstance(loc, str) and (
loc.startswith("/") or (len(loc) > 2 and loc[1:3] in (":\\", ":/"))
):
try: try:
p = Path(loc) p = Path(loc)
target_dir = p.parent if p.exists() or p.parent.exists() else None target_dir = p.parent if p.exists() or p.parent.exists() else None
@ -241,7 +275,9 @@ class ContextScreen(Screen):
# Accept absolute POSIX paths or file:// URIs # Accept absolute POSIX paths or file:// URIs
if isinstance(loc, str) and loc.startswith("file://"): if isinstance(loc, str) and loc.startswith("file://"):
loc = loc[len("file://") :] loc = loc[len("file://") :]
if not isinstance(loc, str) or not (loc.startswith("/") or loc[1:3] == ":\\" or loc[1:3] == ":/"): if not isinstance(loc, str) or not (
loc.startswith("/") or loc[1:3] == ":\\" or loc[1:3] == ":/"
):
return None return None
try: try:
p = Path(loc) p = Path(loc)
@ -266,17 +302,21 @@ class ContextScreen(Screen):
return return
export_path = self._choose_export_path(data_item) export_path = self._choose_export_path(data_item)
if not export_path: if not export_path:
await self._set_status("[red]Can't determine a local file path to save Markdown next to the original file.[/red]") await self._set_status(
"[red]Can't determine a local file path to save Markdown next to the original file.[/red]"
)
return return
raw_queries = (export_queries.value or "").strip() raw_queries = (export_queries.value or "").strip()
queries = [q.strip() for q in raw_queries.split(",") if q.strip()] queries = [q.strip() for q in raw_queries.split(",") if q.strip()]
await self._set_status("Running export...") await self._set_status("Running export...")
md_parts: list[str] = [] md_parts: list[str] = []
md_parts.append(f"# Context Export for {data_item.get('name','selected item')}") md_parts.append(f"# Context Export for {data_item.get('name', 'selected item')}")
# Include simple metadata block # Include simple metadata block
md_parts.append("") md_parts.append("")
md_parts.append("## Source") md_parts.append("## Source")
md_parts.append(f"- Dataset: {self._dataset_id_to_name.get(self._selected_dataset_id, self.DEFAULT_DATASET)}") md_parts.append(
f"- Dataset: {self._dataset_id_to_name.get(self._selected_dataset_id, self.DEFAULT_DATASET)}"
)
if data_item.get("orig"): if data_item.get("orig"):
md_parts.append(f"- Original: {data_item.get('orig')}") md_parts.append(f"- Original: {data_item.get('orig')}")
if data_item.get("raw"): if data_item.get("raw"):
@ -409,7 +449,11 @@ class ContextScreen(Screen):
except Exception: except Exception:
pass pass
if event.key in ("up", "down"): if event.key in ("up", "down"):
max_rows = len(self._file_row_keys) if table.id == "files_table" else len(self._dataset_row_keys) max_rows = (
len(self._file_row_keys)
if table.id == "files_table"
else len(self._dataset_row_keys)
)
if max_rows <= 0: if max_rows <= 0:
return return
if event.key == "up": if event.key == "up":

View file

@ -1,4 +1,5 @@
"""Home Screen for Cognee TUI""" """Home Screen for Cognee TUI"""
from textual.screen import Screen from textual.screen import Screen
from textual.app import ComposeResult from textual.app import ComposeResult
from textual.widgets import Header, Footer, Button, Static from textual.widgets import Header, Footer, Button, Static
@ -31,14 +32,17 @@ class HomeScreen(Screen):
if button_id == "context": if button_id == "context":
from cognee.cli.tui.screens.context import ContextScreen from cognee.cli.tui.screens.context import ContextScreen
self.app.push_screen(ContextScreen()) self.app.push_screen(ContextScreen())
elif button_id == "query": elif button_id == "query":
from cognee.cli.tui.screens.query import QueryScreen from cognee.cli.tui.screens.query import QueryScreen
self.app.push_screen(QueryScreen()) self.app.push_screen(QueryScreen())
elif button_id == "settings": elif button_id == "settings":
from cognee.cli.tui.screens.settings import SettingsScreen from cognee.cli.tui.screens.settings import SettingsScreen
self.app.push_screen(SettingsScreen()) self.app.push_screen(SettingsScreen())
elif button_id == "help": elif button_id == "help":

View file

@ -1,4 +1,5 @@
"""Query Screen""" """Query Screen"""
import asyncio import asyncio
from textual.screen import Screen from textual.screen import Screen
from textual.app import ComposeResult from textual.app import ComposeResult

View file

@ -1,4 +1,5 @@
"""Settings Screen""" """Settings Screen"""
from textual.screen import Screen from textual.screen import Screen
from textual.app import ComposeResult from textual.app import ComposeResult
from textual.widgets import Header, Footer, Button, Static from textual.widgets import Header, Footer, Button, Static

View file

@ -1,3 +1,4 @@
"""Custom widgets for Cognee TUI""" """Custom widgets for Cognee TUI"""
# Custom widgets can be added here as needed # Custom widgets can be added here as needed
__all__ = [] __all__ = []