feat: add desktop ui foundation

This commit is contained in:
2026-07-30 20:15:20 +02:00
parent d3e85f2077
commit 1658e0485e
13 changed files with 1069 additions and 84 deletions
+15 -72
View File
@@ -12,19 +12,15 @@ from pathlib import Path
from javis.config.local_config import LocalConfigError, write_gemini_activation
from javis.config.settings import ConfigurationError, Settings
from javis.core.chat_service import ChatService, SessionProviderMismatchError
from javis.core.provider_router import HybridProvider
from javis.core.runtime import build_javis_runtime
from javis.memory.sqlite_store import (
AmbiguousSessionReferenceError,
ChatSession,
SessionNotFoundError,
SessionStoreError,
SQLiteSessionStore,
)
from javis.memory.usage_store import SQLiteUsageStore, UsageStoreError
from javis.providers.base import MissingApiKeyError, ProviderError
from javis.providers.gemini import GeminiProvider
from javis.providers.ollama import OllamaProvider
from javis.security.privacy import PrivacyDecision, PrivacyRouter
from javis.memory.usage_store import UsageStoreError
from javis.providers.base import ProviderError
from javis.security.secrets import SecretProvider, SecretStoreError
InputFunction = Callable[[str], str]
@@ -52,6 +48,7 @@ def build_parser() -> argparse.ArgumentParser:
subparsers = parser.add_subparsers(dest="command", required=True)
chat_parser = subparsers.add_parser("chat", help="lokalen Textchat starten")
chat_parser.add_argument("--session", help="vorhandene Sitzungs-ID laden")
subparsers.add_parser("gui", help="Javis-Desktopoberfläche starten")
configure_parser = subparsers.add_parser(
"configure",
help="nicht geheime lokale Einstellungen einrichten",
@@ -385,79 +382,21 @@ def build_runtime(
output: OutputFunction,
secret_provider: SecretProvider | None = None,
) -> tuple[ChatService, StatusFunction]:
store = SQLiteSessionStore(settings.database_path)
usage_store = SQLiteUsageStore(settings.usage_database_path)
secrets = secret_provider or SecretProvider()
local = OllamaProvider(
settings.model,
settings.ollama_base_url,
settings.timeout_seconds,
)
def approve(decision: PrivacyDecision) -> bool:
answer = input_fn(
f"Cloudfreigabe erforderlich ({decision.reason}). Einmalig erlauben? [j/N] "
)
def approve(_decision: object) -> bool:
answer = input_fn("Cloudfreigabe erforderlich. Einmalig erlauben? [j/N] ")
return _confirmed(answer)
def create_cloud_provider() -> GeminiProvider:
try:
key = secrets.get_gemini_key()
except SecretStoreError as exc:
raise MissingApiKeyError(
"Der Gemini-Schlüssel konnte nicht sicher geladen werden."
) from exc
return GeminiProvider(
model=settings.gemini_model,
api_key=key,
timeout_seconds=settings.gemini_timeout_seconds,
max_output_tokens=settings.max_cloud_output_tokens,
max_retries=settings.gemini_max_retries,
)
hybrid = HybridProvider(
local_provider=local,
cloud_provider_factory=create_cloud_provider,
cloud_model=settings.gemini_model,
privacy_router=PrivacyRouter(),
usage_store=usage_store,
runtime = build_javis_runtime(
settings,
approval_callback=approve,
notice_callback=output,
mode=settings.provider_mode,
cloud_enabled=settings.gemini_enabled,
billing_confirmed_disabled=settings.billing_confirmed_disabled,
free_only=settings.free_only,
max_cloud_requests_per_day=settings.max_cloud_requests_per_day,
max_cloud_input_chars=settings.max_cloud_input_chars,
max_cloud_context_messages=settings.max_cloud_context_messages,
secret_provider=secret_provider,
)
def status() -> list[str]:
ollama = local.probe()
try:
key_available = secrets.gemini_key_available()
except SecretStoreError:
key_available = False
gemini_configured = (
settings.gemini_enabled and settings.free_only and settings.billing_confirmed_disabled
)
try:
cloud_requests = usage_store.cloud_requests_on()
local_fallbacks = usage_store.local_fallbacks_on()
except UsageStoreError:
cloud_requests = local_fallbacks = -1
return [
f"Ollama erreichbar: {'ja' if ollama.reachable else 'nein'}",
f"Lokales Modell vorhanden: {'ja' if ollama.model_available else 'nein'}",
f"Gemini-Schlüssel vorhanden: {'ja' if key_available else 'nein'}",
f"Gemini konfiguriert: {'ja' if gemini_configured else 'nein'}",
f"Nullkostenmodus: {'aktiv' if settings.free_only else 'inaktiv'}",
f"Aktiver Providermodus: {hybrid.mode}",
f"Heutige Cloudanfragen: {cloud_requests if cloud_requests >= 0 else 'unbekannt'}",
f"Heutige lokale Fallbacks: {local_fallbacks if local_fallbacks >= 0 else 'unbekannt'}",
]
return runtime.status().cli_lines()
return ChatService(store, hybrid), status
return runtime.service, status
def main(
@@ -484,6 +423,10 @@ def main(
input_fn=input_fn,
output=output,
)
if args.command == "gui":
from javis.ui.app import run_gui
return run_gui([])
if args.command != "chat":
return 2