From c5683c62de4c092a6416b23643fd6a292a1ea894 Mon Sep 17 00:00:00 2001 From: hude Date: Tue, 5 May 2026 15:14:03 +0900 Subject: [PATCH 01/10] Add wiki support to the TUI and bridge --- Cargo.lock | 17 +- Cargo.toml | 1 + rust/clients/launcher.rs | 75 +++ rust/tui/adapter.rs | 2 + rust/tui/bridge.rs | 417 +++++++++++++++- rust/tui/provider/mod.rs | 461 +++++++++++++++++- rust/tui/provider/tests.rs | 3 + rust/tui/provider/tests/live_browser.rs | 33 +- rust/tui/provider/tests/settings.rs | 5 +- rust/tui/provider/tests/wiki.rs | 82 ++++ rust/tui/ui_config.rs | 7 +- tui/crates/tui-kit-host/src/form_tab_flow.rs | 1 + tui/crates/tui-kit-host/src/lib.rs | 3 +- tui/crates/tui-kit-host/src/runtime_loop.rs | 2 + .../src/ui/app/screens/create/mod.rs | 23 +- .../tui-kit-render/src/ui/app/ui_builder.rs | 13 +- .../tui-kit-render/src/ui/app/ui_state.rs | 7 +- .../tui-kit-runtime/src/form_descriptor.rs | 14 +- tui/crates/tui-kit-runtime/src/kinic_tabs.rs | 12 +- tui/crates/tui-kit-runtime/src/lib.rs | 73 ++- 20 files changed, 1187 insertions(+), 64 deletions(-) create mode 100644 rust/tui/provider/tests/wiki.rs diff --git a/Cargo.lock b/Cargo.lock index 6d576c2..7116625 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,9 +362,9 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.20" +version = "0.10.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8037a01ec09d6c06883a38bad4f47b8d06158ad360b841e0ae5707c9884dfaf6" +checksum = "1ba5b4833b63bf7b785fecdd2cc918ed90d988fd974825d5c48e7b407c39ef38" dependencies = [ "anyhow", "binread", @@ -385,9 +385,9 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.20" +version = "0.10.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb45f4d5eff3805598ee633dd80f8afb306c023249d34b5b7dfdc2080ea1df2e" +checksum = "7b60664b6324832dfb4863f3b19eb4d58819cd38fba6d3941b101213cea0d9ec" dependencies = [ "lazy_static", "proc-macro2", @@ -2224,6 +2224,7 @@ dependencies = [ "tui-kit-model", "tui-kit-render", "tui-kit-runtime", + "vfs-types", ] [[package]] @@ -4654,6 +4655,14 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vfs-types" +version = "0.1.0" +dependencies = [ + "candid", + "serde", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 09290d0..bc13aa5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ tui-kit-host = { path = "tui/crates/tui-kit-host" } tui-kit-runtime = { path = "tui/crates/tui-kit-runtime" } tui-kit-model = { path = "tui/crates/tui-kit-model" } tui-kit-render = { path = "tui/crates/tui-kit-render" } +vfs-types = { path = "../llm-wiki/crates/vfs_types" } [dev-dependencies] serde_yaml = "0.9" diff --git a/rust/clients/launcher.rs b/rust/clients/launcher.rs index 4491033..5989df9 100644 --- a/rust/clients/launcher.rs +++ b/rust/clients/launcher.rs @@ -97,6 +97,21 @@ impl LauncherClient { Ok(result?) } + pub async fn deploy_wiki(&self, name: &str) -> Result { + let payload = encode_deploy_canister_args(name, CanisterType::Wiki, None)?; + let response = self + .agent + .update(&self.launcher_id, "deploy_canister") + .with_arg(payload) + .call_and_wait() + .await + .context("Failed to call deploy_canister")?; + + let result = Decode!(&response, std::result::Result) + .context("Failed to decode deploy_canister response")?; + Ok(result?) + } + pub async fn list_memories(&self) -> Result> { let response = self .agent @@ -110,6 +125,19 @@ impl LauncherClient { Ok(result) } + pub async fn list_typed_instances(&self) -> Result> { + let response = self + .agent + .query(&self.launcher_id, "list_typed_instance") + .call() + .await + .context("Failed to call list_typed_instance")?; + + let result = + Decode!(&response, Vec).context("Failed to decode typed instance list")?; + Ok(result) + } + pub async fn update_instance(&self, instance_pid_str: &str) -> Result<()> { let payload = encode_update_instance_args(instance_pid_str)?; let response = self @@ -138,6 +166,14 @@ fn encode_deploy_args(name: &str, description: &str) -> Result> { ))?) } +fn encode_deploy_canister_args( + name: &str, + canister_type: CanisterType, + dim: Option, +) -> Result> { + Ok(candid::encode_args((name.to_string(), canister_type, dim))?) +} + fn encode_update_instance_args(instance_pid_str: &str) -> Result> { Ok(candid::encode_one(instance_pid_str)?) } @@ -188,3 +224,42 @@ pub enum State { SettingUp(Principal), Running(Principal), } + +#[derive(CandidType, candid::Deserialize, Clone, Debug, PartialEq, Eq)] +pub enum CanisterType { + Memory, + Wiki, +} + +#[derive(CandidType, candid::Deserialize, Clone, Debug)] +pub struct TypedState { + pub state: State, + pub canister_type: CanisterType, +} + +#[cfg(test)] +mod tests { + use super::*; + use candid::Encode; + + #[test] + fn typed_state_decodes_wiki_running_shape() { + let principal = Principal::from_text("aaaaa-aa").unwrap(); + let bytes = Encode!(&vec![TypedState { + state: State::Running(principal), + canister_type: CanisterType::Wiki, + }]) + .unwrap(); + let decoded = Decode!(&bytes, Vec).unwrap(); + assert_eq!(decoded[0].canister_type, CanisterType::Wiki); + } + + #[test] + fn deploy_wiki_args_encode_as_text_type_and_no_dim() { + let bytes = encode_deploy_canister_args("Project Wiki", CanisterType::Wiki, None).unwrap(); + let decoded = Decode!(&bytes, String, CanisterType, Option).unwrap(); + assert_eq!(decoded.0, "Project Wiki"); + assert_eq!(decoded.1, CanisterType::Wiki); + assert_eq!(decoded.2, None); + } +} diff --git a/rust/tui/adapter.rs b/rust/tui/adapter.rs index 57dc965..8c0796d 100644 --- a/rust/tui/adapter.rs +++ b/rust/tui/adapter.rs @@ -19,6 +19,7 @@ pub fn to_content(record: &KinicRecord, memory_summary: Option<&str>) -> UiItemC match record.group.as_str() { "search-result" => search_result_content(record), "memories" => memory_content(record, memory_summary), + "wiki" => generic_content(record), _ => generic_content(record), } } @@ -27,6 +28,7 @@ fn summary_kind(record: &KinicRecord) -> UiItemKind { match record.group.as_str() { "search-result" => UiItemKind::Custom(String::new()), "memories" => UiItemKind::Custom("memory".to_string()), + "wiki" => UiItemKind::Custom("wiki".to_string()), other => UiItemKind::Custom(other.to_string()), } } diff --git a/rust/tui/bridge.rs b/rust/tui/bridge.rs index 93492a4..81b1fed 100644 --- a/rust/tui/bridge.rs +++ b/rust/tui/bridge.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use super::chat_prompt::ActiveMemoryContext; use crate::{ clients::{ - launcher::{LauncherClient, State}, + launcher::{CanisterType, LauncherClient, State, TypedState}, memory::MemoryClient, }, create_domain::{BalanceDelta, balance_delta, required_balance}, @@ -23,9 +23,14 @@ use crate::{ }; use anyhow::{Context, Result}; +use candid::{CandidType, Decode, Encode}; use ic_agent::{Agent, export::Principal}; use kinic_core::amount::format_e8s_to_kinic_string_nat; use tui_kit_runtime::{AccessControlAction, AccessControlRole, ChatScope, SessionAccountOverview}; +use vfs_types::{ + ChildNode, ListChildrenRequest, Node, NodeEntryKind, SearchNodeHit, SearchNodesRequest, + SearchPreviewMode, +}; pub(crate) use crate::shared::memory_metadata::DescriptionUpdate; @@ -44,6 +49,45 @@ pub struct MemorySummary { pub users: Option>, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WikiSummary { + pub id: String, + pub status: String, + pub detail: String, + pub searchable_wiki_id: Option, + pub name: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InstanceSummaries { + pub memories: Vec, + pub wikis: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WikiChildNode { + pub path: String, + pub name: String, + pub kind: String, + pub has_children: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WikiNode { + pub path: String, + pub content: String, + pub etag: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct WikiSearchHit { + pub path: String, + pub score: f32, + pub snippet: Option, +} + +const DEFAULT_WIKI_DATABASE_ID: &str = "default"; + pub type SearchResultItem = SearchHit; #[derive(Debug, Clone, PartialEq)] @@ -89,12 +133,27 @@ pub struct MemoryDetails { } #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(dead_code)] pub struct CreateMemorySuccess { pub id: String, pub memories: Option>, pub refresh_warning: Option, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CreateTargetKind { + Memory, + Wiki, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CreateInstanceSuccess { + pub id: String, + pub kind: CreateTargetKind, + pub instances: Option, + pub refresh_warning: Option, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct InsertMemorySuccess { pub memory_id: String, @@ -178,6 +237,7 @@ pub async fn build_search_agent(use_mainnet: bool, auth: TuiAuth) -> Result Result> { let factory = resolve_agent_factory(use_mainnet, &auth)?; let agent = factory.build().await?; @@ -186,6 +246,15 @@ pub async fn list_memories(use_mainnet: bool, auth: TuiAuth) -> Result Result { + let factory = resolve_agent_factory(use_mainnet, &auth)?; + let agent = factory.build().await?; + let client = LauncherClient::new(agent); + let states = client.list_typed_instances().await?; + Ok(instance_summaries_from_typed_states(states)) +} + +#[allow(dead_code)] pub async fn create_memory( use_mainnet: bool, auth: TuiAuth, @@ -247,6 +316,98 @@ pub async fn create_memory( }) } +pub async fn create_instance( + use_mainnet: bool, + auth: TuiAuth, + kind: CreateTargetKind, + name: String, + description: String, +) -> Result { + let factory = resolve_agent_factory(use_mainnet, &auth) + .map_err(|error| CreateMemoryError::Principal(short_error(&error.to_string())))?; + let agent = factory + .build() + .await + .map_err(|error| CreateMemoryError::Principal(short_error(&error.to_string())))?; + let client = LauncherClient::new(agent.clone()); + let (balance, price) = tokio::join!(fetch_balance(&agent), client.fetch_deployment_price()); + let balance = + balance.map_err(|error| CreateMemoryError::Balance(short_error(&error.to_string())))?; + let price = price.map_err(|error| CreateMemoryError::Price(short_error(&error.to_string())))?; + let fee = fetch_fee(&agent) + .await + .map_err(|error| CreateMemoryError::Fee(short_error(&error.to_string())))?; + match balance_delta(&price, balance, fee) { + BalanceDelta::Surplus(_) => {} + BalanceDelta::Shortfall(shortfall) => { + let required_total = required_balance(&price, fee); + return Err(CreateMemoryError::InsufficientBalance { + required_total_kinic: format_e8s_to_kinic_string_nat(&required_total), + required_total_base_units: required_total.to_string(), + shortfall_kinic: format_e8s_to_kinic_string_nat(&shortfall), + shortfall_base_units: shortfall.to_string(), + }); + } + } + client + .approve_launcher(&price, fee) + .await + .map_err(|error| CreateMemoryError::Approve(short_error(&error.to_string())))?; + let id = match kind { + CreateTargetKind::Memory => client.deploy_memory(&name, &description).await, + CreateTargetKind::Wiki => client.deploy_wiki(&name).await, + } + .map_err(|error| CreateMemoryError::Deploy(short_error(&error.to_string())))?; + let (instances, refresh_warning) = match client.list_typed_instances().await { + Ok(states) => (Some(instance_summaries_from_typed_states(states)), None), + Err(error) => ( + None, + Some(format!( + "Automatic reload failed after create. Press Ctrl-R to refresh. Cause: {}", + short_error(&error.to_string()) + )), + ), + }; + + Ok(CreateInstanceSuccess { + id, + kind, + instances, + refresh_warning, + }) +} + +pub async fn list_wiki_root_children( + use_mainnet: bool, + auth: TuiAuth, + wiki_id: String, +) -> Result> { + let agent = build_search_agent(use_mainnet, auth).await?; + VfsClient::new(agent, wiki_id)?.list_children("/Wiki").await +} + +pub async fn read_wiki_node( + use_mainnet: bool, + auth: TuiAuth, + wiki_id: String, + path: String, +) -> Result> { + let agent = build_search_agent(use_mainnet, auth).await?; + VfsClient::new(agent, wiki_id)?.read_node(&path).await +} + +pub async fn search_wiki_nodes( + use_mainnet: bool, + auth: TuiAuth, + wiki_id: String, + query: String, +) -> Result> { + let agent = build_search_agent(use_mainnet, auth).await?; + VfsClient::new(agent, wiki_id)? + .search_nodes(&query, "/Wiki", 20) + .await +} + pub async fn load_session_account_overview( use_mainnet: bool, auth: TuiAuth, @@ -558,6 +719,65 @@ pub async fn run_insert( }) } +fn instance_summaries_from_typed_states(states: Vec) -> InstanceSummaries { + let mut memories = Vec::new(); + let mut wikis = Vec::new(); + for typed in states { + match typed.canister_type { + CanisterType::Memory => memories.push(memory_summary_from_state(typed.state)), + CanisterType::Wiki => wikis.push(wiki_summary_from_state(typed.state)), + } + } + InstanceSummaries { memories, wikis } +} + +fn wiki_summary_from_state(state: State) -> WikiSummary { + match state { + State::Empty(message) => WikiSummary { + id: format!("wiki-empty:{message}"), + status: "empty".to_string(), + detail: message.clone(), + searchable_wiki_id: None, + name: message, + }, + State::Pending(message) => WikiSummary { + id: format!("wiki-pending:{message}"), + status: "pending".to_string(), + detail: message.clone(), + searchable_wiki_id: None, + name: message, + }, + State::Creation(message) => WikiSummary { + id: format!("wiki-creation:{message}"), + status: "creation".to_string(), + detail: message.clone(), + searchable_wiki_id: None, + name: message, + }, + State::Installation(principal, message) => WikiSummary { + id: principal.to_text(), + status: "installation".to_string(), + detail: message.clone(), + searchable_wiki_id: Some(principal.to_text()), + name: message, + }, + State::SettingUp(principal) => WikiSummary { + id: principal.to_text(), + status: "setting_up".to_string(), + detail: "Launcher is setting up this wiki.".to_string(), + searchable_wiki_id: Some(principal.to_text()), + name: "unknown".to_string(), + }, + State::Running(principal) => WikiSummary { + id: principal.to_text(), + status: "running".to_string(), + detail: "Wiki is ready for browsing and search.".to_string(), + searchable_wiki_id: Some(principal.to_text()), + name: "Wiki".to_string(), + }, + } +} + fn memory_summary_from_state(state: State) -> MemorySummary { match state { State::Empty(message) => MemorySummary { @@ -700,6 +920,145 @@ fn role_code(role: AccessControlRole, principal_id: &str) -> Result { Ok(memory_role.code()) } +#[derive(Clone)] +struct VfsClient { + agent: Agent, + canister_id: Principal, +} + +impl VfsClient { + fn new(agent: Agent, canister_id: String) -> Result { + Ok(Self { + agent, + canister_id: Principal::from_text(canister_id) + .context("failed to parse wiki canister principal")?, + }) + } + + async fn query(&self, method: &str, arg: &Arg) -> Result + where + Arg: CandidType, + Out: for<'de> candid::Deserialize<'de> + CandidType, + { + let bytes = self + .agent + .query(&self.canister_id, method) + .with_arg(Encode!(arg).context("failed to encode wiki query args")?) + .call() + .await + .with_context(|| format!("wiki query failed for {method}"))?; + Decode!(&bytes, Out).with_context(|| format!("failed to decode wiki response for {method}")) + } + + async fn query2(&self, method: &str, a: &A, b: &B) -> Result + where + A: CandidType, + B: CandidType, + Out: for<'de> candid::Deserialize<'de> + CandidType, + { + let bytes = self + .agent + .query(&self.canister_id, method) + .with_arg(Encode!(a, b).context("failed to encode wiki query args")?) + .call() + .await + .with_context(|| format!("wiki query failed for {method}"))?; + Decode!(&bytes, Out).with_context(|| format!("failed to decode wiki response for {method}")) + } + + async fn list_children(&self, path: &str) -> Result> { + let result: Result, String> = self + .query( + "list_children", + &ListChildrenRequest { + database_id: DEFAULT_WIKI_DATABASE_ID.to_string(), + path: path.to_string(), + }, + ) + .await?; + Ok(result + .map_err(anyhow::Error::msg)? + .into_iter() + .map(WikiChildNode::from) + .collect()) + } + + async fn read_node(&self, path: &str) -> Result> { + let result: Result, String> = self + .query2( + "read_node", + &DEFAULT_WIKI_DATABASE_ID.to_string(), + &path.to_string(), + ) + .await?; + Ok(result.map_err(anyhow::Error::msg)?.map(WikiNode::from)) + } + + async fn search_nodes( + &self, + query_text: &str, + prefix: &str, + top_k: u32, + ) -> Result> { + let result: Result, String> = self + .query( + "search_nodes", + &SearchNodesRequest { + database_id: DEFAULT_WIKI_DATABASE_ID.to_string(), + query_text: query_text.to_string(), + prefix: Some(prefix.to_string()), + top_k, + preview_mode: Some(SearchPreviewMode::ContentStart), + }, + ) + .await?; + Ok(result + .map_err(anyhow::Error::msg)? + .into_iter() + .map(WikiSearchHit::from) + .collect()) + } +} + +impl From for WikiChildNode { + fn from(node: ChildNode) -> Self { + Self { + path: node.path, + name: node.name, + kind: match node.kind { + NodeEntryKind::File => "file", + NodeEntryKind::Source => "source", + NodeEntryKind::Directory => "directory", + } + .to_string(), + has_children: node.is_virtual, + } + } +} + +impl From for WikiNode { + fn from(node: Node) -> Self { + Self { + path: node.path, + content: node.content, + etag: node.etag, + } + } +} + +impl From for WikiSearchHit { + fn from(hit: SearchNodeHit) -> Self { + Self { + path: hit.path, + score: hit.score, + snippet: hit + .preview + .and_then(|preview| preview.excerpt) + .or(hit.snippet), + } + } +} + fn short_error(message: &str) -> String { message.lines().next().unwrap_or(message).trim().to_string() } @@ -769,6 +1128,62 @@ mod tests { ); } + #[test] + fn wiki_child_node_from_vfs_types_projects_display_fields() { + let child = WikiChildNode::from(ChildNode { + path: "/Wiki/index.md".to_string(), + name: "index.md".to_string(), + kind: NodeEntryKind::File, + updated_at: Some(1), + etag: Some("abc".to_string()), + size_bytes: Some(10), + is_virtual: false, + }); + + assert_eq!(child.path, "/Wiki/index.md"); + assert_eq!(child.name, "index.md"); + assert_eq!(child.kind, "file"); + assert!(!child.has_children); + } + + #[test] + fn wiki_node_from_vfs_types_keeps_render_payload() { + let node = WikiNode::from(Node { + path: "/Wiki/index.md".to_string(), + kind: vfs_types::NodeKind::File, + content: "# Index".to_string(), + created_at: 1, + updated_at: 2, + etag: "etag".to_string(), + metadata_json: "{}".to_string(), + }); + + assert_eq!(node.path, "/Wiki/index.md"); + assert_eq!(node.content, "# Index"); + assert_eq!(node.etag, "etag"); + } + + #[test] + fn wiki_search_hit_prefers_preview_excerpt() { + let hit = WikiSearchHit::from(SearchNodeHit { + path: "/Wiki/index.md".to_string(), + kind: vfs_types::NodeKind::File, + snippet: Some("fallback".to_string()), + preview: Some(vfs_types::SearchPreview { + field: vfs_types::SearchPreviewField::Content, + match_reason: "content".to_string(), + char_offset: 0, + excerpt: Some("preview".to_string()), + }), + score: 0.75, + match_reasons: vec!["content".to_string()], + }); + + assert_eq!(hit.path, "/Wiki/index.md"); + assert_eq!(hit.score, 0.75); + assert_eq!(hit.snippet.as_deref(), Some("preview")); + } + #[test] fn insert_error_variants_keep_failure_stage() { let resolve = InsertMemoryError::ResolveAgentFactory("auth missing".to_string()); diff --git a/rust/tui/provider/mod.rs b/rust/tui/provider/mod.rs index b844754..2a212b5 100644 --- a/rust/tui/provider/mod.rs +++ b/rust/tui/provider/mod.rs @@ -47,7 +47,7 @@ use tui_kit_runtime::{ SessionAccountOverview, SessionSettingsSnapshot, TransferModalMode, kinic_tabs::{ KINIC_CREATE_TAB_ID, KINIC_INSERT_TAB_ID, KINIC_MARKET_TAB_ID, KINIC_MEMORIES_TAB_ID, - KINIC_SETTINGS_TAB_ID, + KINIC_SETTINGS_TAB_ID, KINIC_WIKI_TAB_ID, }, }; @@ -66,6 +66,7 @@ pub struct KinicRecord { pub content_md: String, pub searchable_memory_id: Option, pub source_memory_id: Option, + pub source_wiki_id: Option, } impl KinicRecord { @@ -84,6 +85,7 @@ impl KinicRecord { content_md: content_md.into(), searchable_memory_id: None, source_memory_id: None, + source_wiki_id: None, } } @@ -96,6 +98,11 @@ impl KinicRecord { self.source_memory_id = Some(memory_id.into()); self } + + pub fn with_source_wiki_id(mut self, wiki_id: impl Into) -> Self { + self.source_wiki_id = Some(wiki_id.into()); + self + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -148,6 +155,8 @@ pub struct KinicProvider { active_memory: Option, memory_summaries: Vec, memory_records: Vec, + wiki_summaries: Vec, + wiki_records: Vec, result_records: Vec, memories_mode: MemoriesMode, pending_initial_memories: Option>, @@ -187,6 +196,12 @@ pub struct KinicProvider { next_memory_detail_request_id: u64, pending_memory_detail_memory_id: Option, memory_detail_prefetch: MemoryDetailPrefetchState, + wiki_children_cache: HashMap, + wiki_children_task: RequestTaskState, + next_wiki_children_request_id: u64, + pending_wiki_children_wiki_id: Option, + wiki_search_task: RequestTaskState, + next_wiki_search_request_id: u64, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -205,6 +220,24 @@ struct SearchTaskOutput { result: Result, } +#[derive(Debug, Clone, PartialEq, Eq)] +struct WikiChildrenContent { + body_lines: Vec, + index_preview: Option>, +} + +struct WikiChildrenTaskOutput { + request_id: u64, + wiki_id: String, + result: Result, +} + +struct WikiSearchTaskOutput { + request_id: u64, + wiki_id: String, + result: Result, String>, +} + /// In-flight memory search with explicit cancellation. Kept separate from /// `RequestTaskState` because workers use `CancellationToken` and batching /// differs from other request/response tasks. @@ -253,7 +286,7 @@ fn fold_live_search_results( } struct InitialMemoriesTaskOutput { - result: Result, String>, + result: Result, } struct CreateCostTaskOutput { @@ -263,7 +296,7 @@ struct CreateCostTaskOutput { struct CreateSubmitTaskOutput { request_id: u64, - result: Result, + result: Result, } struct ChatTaskOutput { @@ -938,6 +971,46 @@ fn load_memory_details_task_result( .map_err(|error| error.to_string()) } +fn load_wiki_children_content( + use_mainnet: bool, + auth: TuiAuth, + wiki_id: String, +) -> Result { + let runtime = Runtime::new().expect("failed to create tokio runtime for wiki browser load"); + let children = runtime + .block_on(bridge::list_wiki_root_children( + use_mainnet, + auth.clone(), + wiki_id.clone(), + )) + .map_err(|error| error.to_string())?; + let index = runtime + .block_on(bridge::read_wiki_node( + use_mainnet, + auth, + wiki_id, + "/Wiki/index.md".to_string(), + )) + .map_err(|error| error.to_string())?; + let body_lines = if children.is_empty() { + vec!["No /Wiki children found.".to_string()] + } else { + children + .into_iter() + .map(|child| { + let marker = if child.has_children { "+" } else { "-" }; + format!("{marker} {} ({})", child.path, child.kind) + }) + .collect() + }; + let index_preview = + index.map(|node| node.content.lines().take(8).map(str::to_string).collect()); + Ok(WikiChildrenContent { + body_lines, + index_preview, + }) +} + #[derive(Debug, Clone, PartialEq, Eq)] enum MemoryContentSelection<'a> { RenameMemory, @@ -981,6 +1054,8 @@ impl KinicProvider { active_memory: None, memory_summaries: Vec::new(), memory_records: Vec::new(), + wiki_summaries: Vec::new(), + wiki_records: Vec::new(), result_records: Vec::new(), memories_mode: MemoriesMode::Browser, pending_initial_memories: None, @@ -1020,6 +1095,12 @@ impl KinicProvider { next_memory_detail_request_id: 0, pending_memory_detail_memory_id: None, memory_detail_prefetch: MemoryDetailPrefetchState::default(), + wiki_children_cache: HashMap::new(), + wiki_children_task: RequestTaskState::default(), + next_wiki_children_request_id: 0, + pending_wiki_children_wiki_id: None, + wiki_search_task: RequestTaskState::default(), + next_wiki_search_request_id: 0, } } @@ -1046,6 +1127,8 @@ impl KinicProvider { self.all = vec![loading_memories_record()]; self.memory_summaries.clear(); self.memory_records.clear(); + self.wiki_summaries.clear(); + self.wiki_records.clear(); self.memory_content_summaries.clear(); self.failed_memory_content_summaries.clear(); self.memory_summary_tasks.clear(); @@ -1065,7 +1148,7 @@ impl KinicProvider { let runtime = Runtime::new().expect("failed to create tokio runtime for initial memories load"); let result = runtime - .block_on(bridge::list_memories(use_mainnet, auth)) + .block_on(bridge::list_instances(use_mainnet, auth)) .map_err(|error| error.to_string()); let _ = tx.send(InitialMemoriesTaskOutput { result }); }); @@ -1083,6 +1166,10 @@ impl KinicProvider { match self.tab_id.as_str() { KINIC_CREATE_TAB_ID => self.start_create_cost_refresh().into_iter().collect(), KINIC_INSERT_TAB_ID => Vec::new(), + KINIC_WIKI_TAB_ID => self + .start_live_memories_load(Some("Refreshing wiki list..."), true) + .into_iter() + .collect(), KINIC_MEMORIES_TAB_ID => self .start_live_memories_load(None, true) .into_iter() @@ -1093,6 +1180,13 @@ impl KinicProvider { } fn current_records(&self) -> Vec<&KinicRecord> { + if self.tab_id == KINIC_WIKI_TAB_ID { + if self.result_records.is_empty() { + return self.wiki_records.iter().collect(); + } + return self.result_records.iter().collect(); + } + if self.memories_mode == MemoriesMode::Browser && self.memory_records.is_empty() { return self.all.iter().collect(); } @@ -1714,10 +1808,75 @@ impl KinicProvider { let mut content = adapter::to_content(record, summary_text.as_deref()); if record.group == "memories" { self.apply_access_content(&mut content, state); + } else if record.group == "wiki" && self.tab_id == KINIC_WIKI_TAB_ID { + self.apply_wiki_children_content(&mut content, record); } content } + fn apply_wiki_children_content( + &self, + content: &mut tui_kit_model::UiItemContent, + record: &KinicRecord, + ) { + let Some(wiki_id) = record.source_wiki_id.as_ref() else { + return; + }; + let (body_lines, index_preview) = match self.wiki_children_cache.get(wiki_id.as_str()) { + Some(content) => (content.body_lines.clone(), content.index_preview.clone()), + None if self.pending_wiki_children_wiki_id.as_deref() == Some(wiki_id.as_str()) => { + (vec!["Loading /Wiki children...".to_string()], None) + } + None => ( + vec!["Select or refresh this wiki to load /Wiki children.".to_string()], + None, + ), + }; + content.sections.push(tui_kit_model::UiSection { + heading: "/Wiki".to_string(), + rows: vec![], + body_lines, + }); + if let Some(body_lines) = index_preview { + content.sections.push(tui_kit_model::UiSection { + heading: "/Wiki/index.md".to_string(), + rows: vec![], + body_lines, + }); + } + } + + fn start_selected_wiki_children_load(&mut self, state: &CoreState) { + if self.tab_id != KINIC_WIKI_TAB_ID { + return; + } + let Some(wiki_id) = self.selected_wiki_id(state) else { + return; + }; + if self.wiki_children_cache.contains_key(wiki_id.as_str()) + || self.pending_wiki_children_wiki_id.as_deref() == Some(wiki_id.as_str()) + { + return; + } + + let auth = self.config.auth.clone(); + let use_mainnet = self.config.use_mainnet; + self.pending_wiki_children_wiki_id = Some(wiki_id.clone()); + spawn_request_task( + &mut self.next_wiki_children_request_id, + &mut self.wiki_children_task, + move |request_id, tx| { + let requested_wiki_id = wiki_id.clone(); + let result = load_wiki_children_content(use_mainnet, auth, wiki_id); + let _ = tx.send(WikiChildrenTaskOutput { + request_id, + wiki_id: requested_wiki_id, + result, + }); + }, + ); + } + fn active_rename_target( &self, state: &CoreState, @@ -1909,6 +2068,29 @@ impl KinicProvider { self.all = self.memory_records.clone(); } + fn refresh_wiki_records_from_summaries(&mut self) { + self.wiki_records = self + .wiki_summaries + .iter() + .cloned() + .map(record_from_wiki_summary) + .collect(); + let wiki_ids = self + .wiki_records + .iter() + .filter_map(|record| record.source_wiki_id.clone()) + .collect::>(); + self.wiki_children_cache + .retain(|wiki_id, _| wiki_ids.contains(wiki_id)); + } + + fn apply_instance_summaries(&mut self, instances: bridge::InstanceSummaries) { + self.memory_summaries = instances.memories; + self.wiki_summaries = instances.wikis; + self.refresh_memory_records_from_summaries(); + self.refresh_wiki_records_from_summaries(); + } + fn normalize_memory_summaries(&mut self) { let mut seen_ids = HashSet::new(); self.memory_summaries @@ -2290,6 +2472,11 @@ impl KinicProvider { } let selected_content = if state.current_tab_id == KINIC_SETTINGS_TAB_ID { None + } else if state.current_tab_id == KINIC_WIKI_TAB_ID { + let sel = state.selected_index.unwrap_or(0); + filtered + .get(sel) + .map(|record| self.selected_content_for_record(record, state)) } else if self.memories_mode == MemoriesMode::Browser { if self.active_memory.is_none() && self.memory_records.is_empty() { filtered @@ -2308,6 +2495,9 @@ impl KinicProvider { }; let selected_index = if state.current_tab_id == KINIC_SETTINGS_TAB_ID { None + } else if state.current_tab_id == KINIC_WIKI_TAB_ID { + let current = state.selected_index.unwrap_or(0); + (!filtered.is_empty()).then_some(current.min(filtered.len().saturating_sub(1))) } else if self.is_add_memory_action_selected(state) { Some(filtered.len()) } else if self.memories_mode == MemoriesMode::Browser { @@ -2638,7 +2828,12 @@ impl KinicProvider { None } - fn start_create_submit(&mut self, name: String, description: String) -> CoreEffect { + fn start_create_submit( + &mut self, + kind: tui_kit_runtime::CreateTargetKind, + name: String, + description: String, + ) -> CoreEffect { let auth = self.config.auth.clone(); let use_mainnet = self.config.use_mainnet; spawn_request_task( @@ -2647,13 +2842,71 @@ impl KinicProvider { move |request_id, tx| { let runtime = Runtime::new().expect("failed to create tokio runtime for create submit"); - let result = - runtime.block_on(bridge::create_memory(use_mainnet, auth, name, description)); + let bridge_kind = match kind { + tui_kit_runtime::CreateTargetKind::Memory => bridge::CreateTargetKind::Memory, + tui_kit_runtime::CreateTargetKind::Wiki => bridge::CreateTargetKind::Wiki, + }; + let result = runtime.block_on(bridge::create_instance( + use_mainnet, + auth, + bridge_kind, + name, + description, + )); let _ = tx.send(CreateSubmitTaskOutput { request_id, result }); }, ); - CoreEffect::Notify("Creating memory...".to_string()) + CoreEffect::Notify(format!("Creating {}...", kind.label().to_lowercase())) + } + + fn run_wiki_search(&mut self, state: &CoreState) -> CoreEffect { + let query = state.query.trim(); + if query.is_empty() { + self.result_records.clear(); + return CoreEffect::Notify("Enter a wiki search query.".to_string()); + } + let Some(wiki_id) = self.selected_wiki_id(state) else { + return CoreEffect::Notify("Select a running wiki before searching.".to_string()); + }; + if self.wiki_search_task.in_flight { + return CoreEffect::Notify("Wiki search request already running.".to_string()); + } + let auth = self.config.auth.clone(); + let use_mainnet = self.config.use_mainnet; + let query = query.to_string(); + spawn_request_task( + &mut self.next_wiki_search_request_id, + &mut self.wiki_search_task, + move |request_id, tx| { + let result = + Runtime::new() + .map_err(|error| error.to_string()) + .and_then(|runtime| { + runtime + .block_on(bridge::search_wiki_nodes( + use_mainnet, + auth, + wiki_id.clone(), + query, + )) + .map_err(|error| error.to_string()) + }); + let _ = tx.send(WikiSearchTaskOutput { + request_id, + wiki_id, + result, + }); + }, + ); + CoreEffect::Notify("Searching wiki nodes...".to_string()) + } + + fn selected_wiki_id(&self, state: &CoreState) -> Option { + let index = state.selected_index.unwrap_or(0); + self.current_records() + .get(index) + .and_then(|record| record.source_wiki_id.clone()) } fn start_insert_submit(&mut self, request: InsertRequest) -> CoreEffect { @@ -3009,6 +3262,9 @@ impl KinicProvider { if self.tab_id == KINIC_SETTINGS_TAB_ID { return "Review session details and default memory settings here.".to_string(); } + if self.tab_id == KINIC_WIKI_TAB_ID { + return "Browse wiki canisters and search wiki nodes.".to_string(); + } if self.tab_id == KINIC_MARKET_TAB_ID { return "Market is not implemented yet.".to_string(); } @@ -3841,10 +4097,9 @@ impl KinicProvider { self.pending_initial_memories = None; self.initial_memories_in_flight = false; match output.result { - Ok(memories) => { + Ok(instances) => { let previous_active_memory = self.active_memory.clone(); - self.memory_summaries = memories; - self.refresh_memory_records_from_summaries(); + self.apply_instance_summaries(instances); let initial_memory_id = self .preferred_memory_after_refresh .take() @@ -3892,6 +4147,8 @@ impl KinicProvider { Err(error) => { let previous_active_memory = self.active_memory.clone(); self.memory_records.clear(); + self.wiki_records.clear(); + self.wiki_summaries.clear(); self.result_records.clear(); self.memories_mode = MemoriesMode::Browser; let notify_message = format_live_load_failure_message(&error); @@ -4115,11 +4372,10 @@ impl KinicProvider { let mut effects = Vec::new(); match output.result { Ok(success) => { - if let Some(memories) = success.memories { - self.memory_summaries = memories; + if let Some(instances) = success.instances { + self.apply_instance_summaries(instances); self.loaded_memory_details.clear(); self.memory_detail_prefetch.reset(); - self.refresh_memory_records_from_summaries(); if let Some(index) = self.memory_records.iter().position(|r| r.id == success.id) { let record = self.memory_records.remove(index); @@ -4135,25 +4391,37 @@ impl KinicProvider { } } } - self.set_active_memory_by_id(success.id.clone()); - self.memories_mode = MemoriesMode::Browser; + let target_tab = match success.kind { + bridge::CreateTargetKind::Memory => KINIC_MEMORIES_TAB_ID, + bridge::CreateTargetKind::Wiki => KINIC_WIKI_TAB_ID, + }; + if success.kind == bridge::CreateTargetKind::Memory { + self.set_active_memory_by_id(success.id.clone()); + self.memories_mode = MemoriesMode::Browser; + } self.result_records.clear(); self.invalidate_pending_search(); self.last_search_state = None; - self.start_memory_detail_prefetch_for_records(); - self.start_active_memory_detail_load(); - self.start_selected_memory_summary_load(false); + if success.kind == bridge::CreateTargetKind::Memory { + self.start_memory_detail_prefetch_for_records(); + self.start_active_memory_detail_load(); + self.start_selected_memory_summary_load(false); + } let _ = self.start_create_cost_refresh(); - effects.extend(self.set_tab(KINIC_MEMORIES_TAB_ID)); + effects.extend(self.set_tab(target_tab)); effects.push(CoreEffect::SelectFirstListItem); effects.push(CoreEffect::ResetCreateFormAndSetTab { - tab_id: KINIC_MEMORIES_TAB_ID.to_string(), + tab_id: target_tab.to_string(), }); effects.push(CoreEffect::FocusPane(PaneFocus::Items)); + let noun = match success.kind { + bridge::CreateTargetKind::Memory => "memory", + bridge::CreateTargetKind::Wiki => "wiki", + }; let status = if let Some(warning) = success.refresh_warning { - format!("Created memory {}. {}", success.id, warning) + format!("Created {noun} {}. {}", success.id, warning) } else { - format!("Created memory {}", success.id) + format!("Created {noun} {}", success.id) }; effects.push(CoreEffect::Notify(status)); } @@ -4202,6 +4470,81 @@ impl KinicProvider { Some(self.snapshot_output(state, effects)) } + fn poll_wiki_children_background(&mut self, state: &CoreState) -> Option { + let receiver = self.wiki_children_task.receiver.as_ref()?; + let output = match poll_pending_task(receiver) { + PendingTaskPoll::Pending => return None, + PendingTaskPoll::Ready(output) => output, + PendingTaskPoll::Disconnected => { + reset_request_task(&mut self.wiki_children_task); + self.pending_wiki_children_wiki_id = None; + return Some(self.disconnected_request_output( + state, + CoreEffect::Notify("Wiki browser load failed unexpectedly.".to_string()), + )); + } + }; + + let is_current = finish_request_task(&mut self.wiki_children_task, output.request_id); + self.pending_wiki_children_wiki_id = None; + if !is_current { + return Some(self.stale_request_output(state)); + } + + let effects = match output.result { + Ok(content) => { + self.wiki_children_cache.insert(output.wiki_id, content); + Vec::new() + } + Err(error) => vec![CoreEffect::Notify(format!( + "Wiki browser load failed: {}", + short_error(error.as_str()) + ))], + }; + + Some(self.snapshot_output(state, effects)) + } + + fn poll_wiki_search_background(&mut self, state: &CoreState) -> Option { + let receiver = self.wiki_search_task.receiver.as_ref()?; + let output = match poll_pending_task(receiver) { + PendingTaskPoll::Pending => return None, + PendingTaskPoll::Ready(output) => output, + PendingTaskPoll::Disconnected => { + reset_request_task(&mut self.wiki_search_task); + return Some(self.disconnected_request_output( + state, + CoreEffect::Notify("Wiki search failed unexpectedly.".to_string()), + )); + } + }; + + let is_current = finish_request_task(&mut self.wiki_search_task, output.request_id); + if !is_current { + return Some(self.stale_request_output(state)); + } + + let effects = match output.result { + Ok(hits) => { + self.result_records = hits + .into_iter() + .enumerate() + .map(|(index, hit)| record_from_wiki_search_hit(&output.wiki_id, index, hit)) + .collect(); + vec![CoreEffect::Notify(format!( + "Loaded {} wiki search results.", + self.result_records.len() + ))] + } + Err(error) => vec![CoreEffect::Notify(format!( + "Wiki search failed: {}", + short_error(error.as_str()) + ))], + }; + + Some(self.snapshot_output(state, effects)) + } + fn poll_transfer_prerequisites_background( &mut self, state: &CoreState, @@ -4339,6 +4682,10 @@ impl KinicProvider { } effects } + KINIC_WIKI_TAB_ID => { + self.result_records.clear(); + Vec::new() + } KINIC_MARKET_TAB_ID => { vec![CoreEffect::Notify( "Market is not implemented yet.".to_string(), @@ -4468,7 +4815,10 @@ impl DataProvider for KinicProvider { } } CoreAction::SearchSubmit => { - if let Some(effect) = self.run_live_search(state.search_scope) { + if self.tab_id == KINIC_WIKI_TAB_ID { + effects.push(self.run_wiki_search(state)); + self.start_selected_wiki_children_load(state); + } else if let Some(effect) = self.run_live_search(state.search_scope) { effects.push(effect); } } @@ -4500,6 +4850,8 @@ impl DataProvider for KinicProvider { effects.extend(self.set_tab(id.0.as_str())); if id.0.as_str() == KINIC_INSERT_TAB_ID { self.start_insert_dim_load(); + } else if id.0.as_str() == KINIC_WIKI_TAB_ID { + self.start_selected_wiki_children_load(state); } } CoreAction::ChatSubmit => { @@ -4566,16 +4918,23 @@ impl DataProvider for KinicProvider { CoreAction::CreateSubmit => { let name = state.create_name.trim().to_string(); let description = state.create_description.trim().to_string(); - if name.is_empty() || description.is_empty() { + if name.is_empty() + || (state.create_target_kind == tui_kit_runtime::CreateTargetKind::Memory + && description.is_empty()) + { effects.push(CoreEffect::CreateFormError(Some( - "Name and description are required.".to_string(), + "Name is required. Description is required for memory.".to_string(), ))); } else if self.create_submit_task.in_flight { effects.push(CoreEffect::Notify( "Create request already running.".to_string(), )); } else { - effects.push(self.start_create_submit(name, description)); + effects.push(self.start_create_submit( + state.create_target_kind, + name, + description, + )); } } CoreAction::InsertSubmit => { @@ -5085,6 +5444,7 @@ impl DataProvider for KinicProvider { } _ => self.build_snapshot(state), }; + self.start_selected_wiki_children_load(state); let chat_scope_follows_active_memory = state.chat_scope == ChatScope::Selected && !matches!( action, @@ -5123,6 +5483,8 @@ impl DataProvider for KinicProvider { .or_else(|| self.poll_insert_submit_background(state)) .or_else(|| self.poll_create_cost_background(state)) .or_else(|| self.poll_session_settings_background(state)) + .or_else(|| self.poll_wiki_children_background(state)) + .or_else(|| self.poll_wiki_search_background(state)) .or_else(|| self.poll_search_background(state)) } } @@ -5352,6 +5714,51 @@ fn record_from_memory_summary(memory: MemorySummary) -> KinicRecord { .with_searchable_memory_id_option(memory.searchable_memory_id) } +fn record_from_wiki_summary(wiki: bridge::WikiSummary) -> KinicRecord { + let display_name = match wiki.name.trim() { + "" | "unknown" | "Wiki" => format!("Wiki {}", adapter::short_id(wiki.id.as_str())), + other => other.to_string(), + }; + let summary = format!("Id: {}\nStatus: {}", wiki.id, wiki.status); + let record = KinicRecord::new( + wiki.id.clone(), + display_name, + "wiki", + summary, + format!( + "## Wiki\n\n- Id: `{}`\n- Status: `{}`\n- Name: `{}`\n\n### Browser\nSelect this wiki to browse `/Wiki` children and search wiki nodes.\n\n### Detail\n{}\n", + wiki.id, wiki.status, wiki.name, wiki.detail + ), + ); + match wiki.searchable_wiki_id { + Some(wiki_id) => record.with_source_wiki_id(wiki_id), + None => record, + } +} + +fn record_from_wiki_search_hit( + wiki_id: &str, + index: usize, + hit: bridge::WikiSearchHit, +) -> KinicRecord { + let snippet = hit + .snippet + .as_deref() + .filter(|value| !value.trim().is_empty()) + .unwrap_or("No preview available."); + KinicRecord::new( + format!("wiki-search:{wiki_id}:{index}"), + hit.path.clone(), + "wiki", + format!("Score: {:.3}", hit.score), + format!( + "## Wiki Search Hit\n\n- Wiki: `{wiki_id}`\n- Path: `{}`\n- Score: `{:.3}`\n\n### Preview\n{}\n", + hit.path, hit.score, snippet + ), + ) + .with_source_wiki_id(wiki_id.to_string()) +} + fn display_memory_name(name: &str, detail_name: Option<&str>) -> String { if let Some(detail_name) = detail_name { return detail_name.to_string(); diff --git a/rust/tui/provider/tests.rs b/rust/tui/provider/tests.rs index 881834c..9bf1473 100644 --- a/rust/tui/provider/tests.rs +++ b/rust/tui/provider/tests.rs @@ -229,3 +229,6 @@ mod rename; #[path = "tests/chat.rs"] mod chat; + +#[path = "tests/wiki.rs"] +mod wiki; diff --git a/rust/tui/provider/tests/live_browser.rs b/rust/tui/provider/tests/live_browser.rs index bccea2b..a025fb1 100644 --- a/rust/tui/provider/tests/live_browser.rs +++ b/rust/tui/provider/tests/live_browser.rs @@ -22,10 +22,13 @@ fn poll_initial_memories_background_applies_loaded_memories_and_prefers_saved_de provider.pending_initial_memories = Some(rx); provider.initial_memories_in_flight = true; tx.send(InitialMemoriesTaskOutput { - result: Ok(vec![ - running_memory_summary("aaaaa-aa", "first"), - running_memory_summary("bbbbb-bb", "second"), - ]), + result: Ok(bridge::InstanceSummaries { + memories: vec![ + running_memory_summary("aaaaa-aa", "first"), + running_memory_summary("bbbbb-bb", "second"), + ], + wikis: vec![], + }), }) .unwrap(); @@ -49,10 +52,13 @@ fn poll_initial_memories_background_prefetches_active_memory_too() { push_test_memory_detail_result("aaaaa-aa", Ok(memory_details("Alpha loaded"))); push_test_memory_detail_result("bbbbb-bb", Ok(memory_details("Beta loaded"))); tx.send(InitialMemoriesTaskOutput { - result: Ok(vec![ - running_memory_summary("aaaaa-aa", "first"), - running_memory_summary("bbbbb-bb", "second"), - ]), + result: Ok(bridge::InstanceSummaries { + memories: vec![ + running_memory_summary("aaaaa-aa", "first"), + running_memory_summary("bbbbb-bb", "second"), + ], + wikis: vec![], + }), }) .unwrap(); @@ -123,10 +129,13 @@ fn poll_initial_memories_background_falls_back_to_first_when_default_missing() { provider.pending_initial_memories = Some(rx); provider.initial_memories_in_flight = true; tx.send(InitialMemoriesTaskOutput { - result: Ok(vec![ - running_memory_summary("aaaaa-aa", "first"), - running_memory_summary("bbbbb-bb", "second"), - ]), + result: Ok(bridge::InstanceSummaries { + memories: vec![ + running_memory_summary("aaaaa-aa", "first"), + running_memory_summary("bbbbb-bb", "second"), + ], + wikis: vec![], + }), }) .unwrap(); diff --git a/rust/tui/provider/tests/settings.rs b/rust/tui/provider/tests/settings.rs index 88ada4e..e7fb7fd 100644 --- a/rust/tui/provider/tests/settings.rs +++ b/rust/tui/provider/tests/settings.rs @@ -270,9 +270,10 @@ fn poll_background_keeps_create_success_and_default_memory_when_reload_fails() { provider.create_submit_task.in_flight = true; tx.send(CreateSubmitTaskOutput { request_id: 5, - result: Ok(bridge::CreateMemorySuccess { + result: Ok(bridge::CreateInstanceSuccess { id: "aaaaa-aa".to_string(), - memories: None, + kind: bridge::CreateTargetKind::Memory, + instances: None, refresh_warning: Some( "Automatic reload failed after create. Press F5 to refresh. Cause: boom" .to_string(), diff --git a/rust/tui/provider/tests/wiki.rs b/rust/tui/provider/tests/wiki.rs new file mode 100644 index 0000000..176d260 --- /dev/null +++ b/rust/tui/provider/tests/wiki.rs @@ -0,0 +1,82 @@ +use super::*; + +fn wiki_summary(id: &str, searchable_wiki_id: Option<&str>, status: &str) -> bridge::WikiSummary { + bridge::WikiSummary { + id: id.to_string(), + status: status.to_string(), + detail: format!("{status} detail"), + searchable_wiki_id: searchable_wiki_id.map(str::to_string), + name: "Wiki".to_string(), + } +} + +#[test] +fn wiki_summary_record_marks_only_searchable_wikis_queryable() { + let pending = record_from_wiki_summary(wiki_summary("wiki-pending:abc", None, "pending")); + assert_eq!(pending.source_wiki_id, None); + + let running = + record_from_wiki_summary(wiki_summary("launcher-row", Some("aaaaa-aa"), "running")); + assert_eq!(running.source_wiki_id.as_deref(), Some("aaaaa-aa")); +} + +#[test] +fn selected_wiki_id_follows_displayed_search_results() { + let mut provider = KinicProvider::new(live_config()); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_records = vec![ + record_from_wiki_summary(wiki_summary("launcher-a", Some("wiki-a"), "running")), + record_from_wiki_summary(wiki_summary("launcher-b", Some("wiki-b"), "running")), + ]; + provider.result_records = vec![ + record_from_wiki_search_hit( + "wiki-a", + 0, + bridge::WikiSearchHit { + path: "/Wiki/a.md".to_string(), + score: 1.0, + snippet: None, + }, + ), + record_from_wiki_search_hit( + "wiki-a", + 1, + bridge::WikiSearchHit { + path: "/Wiki/b.md".to_string(), + score: 0.8, + snippet: None, + }, + ), + ]; + + let state = CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + selected_index: Some(1), + ..CoreState::default() + }; + assert_eq!(provider.selected_wiki_id(&state).as_deref(), Some("wiki-a")); +} + +#[test] +fn wiki_content_render_uses_cache_without_starting_query() { + let mut provider = KinicProvider::new(live_config()); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_children_cache.insert( + "wiki-a".to_string(), + WikiChildrenContent { + body_lines: vec!["- /Wiki/index.md (file)".to_string()], + index_preview: Some(vec!["cached preview".to_string()]), + }, + ); + let record = record_from_wiki_summary(wiki_summary("launcher-a", Some("wiki-a"), "running")); + + let content = provider.selected_content_for_record(&record, &CoreState::default()); + + assert!(content.sections.iter().any(|section| { + section.heading == "/Wiki" && section.body_lines == vec!["- /Wiki/index.md (file)"] + })); + assert!(content.sections.iter().any(|section| { + section.heading == "/Wiki/index.md" && section.body_lines == vec!["cached preview"] + })); + assert!(!provider.wiki_children_task.in_flight); +} diff --git a/rust/tui/ui_config.rs b/rust/tui/ui_config.rs index 1e0c5dc..e8665ad 100644 --- a/rust/tui/ui_config.rs +++ b/rust/tui/ui_config.rs @@ -1,7 +1,7 @@ use tui_kit_render::ui::{BrandingText, HeaderText, TabId, TabSpec, UiConfig}; pub use tui_kit_runtime::kinic_tabs::{ KINIC_CREATE_TAB_ID, KINIC_INSERT_TAB_ID, KINIC_MARKET_TAB_ID, KINIC_MEMORIES_TAB_ID, - KINIC_SETTINGS_TAB_ID, + KINIC_SETTINGS_TAB_ID, KINIC_WIKI_TAB_ID, }; pub fn kinic_ui_config() -> UiConfig { @@ -41,6 +41,11 @@ pub fn kinic_ui_config() -> UiConfig { title: "Create".to_string(), search_placeholder: "Create a memory...".to_string(), }, + TabSpec { + id: TabId::new(KINIC_WIKI_TAB_ID), + title: "Wiki".to_string(), + search_placeholder: "Search wiki nodes...".to_string(), + }, TabSpec { id: TabId::new(KINIC_MARKET_TAB_ID), title: "Market".to_string(), diff --git a/tui/crates/tui-kit-host/src/form_tab_flow.rs b/tui/crates/tui-kit-host/src/form_tab_flow.rs index 69b667e..aae97a4 100644 --- a/tui/crates/tui-kit-host/src/form_tab_flow.rs +++ b/tui/crates/tui-kit-host/src/form_tab_flow.rs @@ -62,6 +62,7 @@ pub fn reset_form_state_for_tab(state: &mut CoreState, tab_id: &str) { fn reset_create_fields(state: &mut CoreState) { state.create_name.clear(); state.create_description.clear(); + state.create_target_kind = tui_kit_runtime::CreateTargetKind::Memory; } fn reset_insert_fields(state: &mut CoreState) { diff --git a/tui/crates/tui-kit-host/src/lib.rs b/tui/crates/tui-kit-host/src/lib.rs index b18ed01..20f4fb1 100644 --- a/tui/crates/tui-kit-host/src/lib.rs +++ b/tui/crates/tui-kit-host/src/lib.rs @@ -362,7 +362,8 @@ pub fn execute_effects_to_status(state: &mut CoreState, effects: Vec state.create_submit_state = CreateSubmitState::Idle; state.create_spinner_frame = 0; state.create_error = None; - state.create_focus = CreateModalFocus::Name; + state.create_target_kind = tui_kit_runtime::CreateTargetKind::Memory; + state.create_focus = CreateModalFocus::Type; } CoreEffect::ResetInsertFormForRepeat => { if state.insert_tag_is_auto { diff --git a/tui/crates/tui-kit-host/src/runtime_loop.rs b/tui/crates/tui-kit-host/src/runtime_loop.rs index d18e485..f2e253b 100644 --- a/tui/crates/tui-kit-host/src/runtime_loop.rs +++ b/tui/crates/tui-kit-host/src/runtime_loop.rs @@ -212,6 +212,7 @@ pub fn run_provider_app_with_hooks>( .show_create_modal(false) .create_name(&state.create_name) .create_description(&state.create_description) + .create_target_kind(state.create_target_kind) .create_description_cursor(textarea_cursor( active_textarea(&state), ActiveTextarea::CreateDescription, @@ -697,6 +698,7 @@ fn build_ui<'a>( .show_create_modal(false) .create_name(&state.create_name) .create_description(&state.create_description) + .create_target_kind(state.create_target_kind) .create_description_cursor(textarea_cursor( active_textarea(state), ActiveTextarea::CreateDescription, diff --git a/tui/crates/tui-kit-render/src/ui/app/screens/create/mod.rs b/tui/crates/tui-kit-render/src/ui/app/screens/create/mod.rs index acedddf..fd9f8b1 100644 --- a/tui/crates/tui-kit-render/src/ui/app/screens/create/mod.rs +++ b/tui/crates/tui-kit-render/src/ui/app/screens/create/mod.rs @@ -146,11 +146,13 @@ impl CreateFormLines<'_> { } fn create_form_lines<'a>(ui: &'a TuiKitUi<'a>, layout: CreateScreenLayout) -> CreateFormLines<'a> { + let type_style = create_field_style(ui, CreateModalFocus::Type); let name_style = create_field_style(ui, CreateModalFocus::Name); let description_style = create_field_style(ui, CreateModalFocus::Description); let submit_style = create_field_style(ui, CreateModalFocus::Submit); let close_hint = create_close_hint(ui); let submit_text = create_submit_text(ui); + let type_hint = next_entry_hint(ui, CreateModalFocus::Type); let name_hint = next_entry_hint(ui, CreateModalFocus::Name); let description_hint = next_entry_hint(ui, CreateModalFocus::Description); let submit_hint = next_entry_hint(ui, CreateModalFocus::Submit); @@ -176,6 +178,19 @@ fn create_form_lines<'a>(ui: &'a TuiKitUi<'a>, layout: CreateScreenLayout) -> Cr let mut lines = Vec::with_capacity(if ui.create_error.is_some() { 20 } else { 18 }); let mut rows = FormRows::default(); + let type_value = format!("< {} >", ui.create_target_kind.label()); + rows.push_labeled_row( + &mut lines, + Line::from(Span::styled("Type", ui.theme.style_dim())), + CreateModalFocus::Type, + Line::from(vec![ + create_input_indent(), + Span::styled(type_value.clone(), type_style), + type_hint, + ]), + type_value.as_str(), + ); + lines.push(Line::from("")); rows.push_labeled_row( &mut lines, Line::from(Span::styled( @@ -445,10 +460,14 @@ fn display_create_value<'a>(value: &'a str, placeholder: &'a str) -> &'a str { } fn create_submit_text(ui: &TuiKitUi<'_>) -> String { + let idle_label = match ui.create_target_kind { + tui_kit_runtime::CreateTargetKind::Memory => ui.ui_config.create.submit_label.as_str(), + tui_kit_runtime::CreateTargetKind::Wiki => "Create Wiki", + }; submit_button_text( &ui.create_submit_state, ui.create_spinner_frame, - ui.ui_config.create.submit_label.as_str(), + idle_label, ui.ui_config.create.submit_pending_label.as_str(), ) } @@ -473,7 +492,7 @@ fn fit_single_line(value: &str, max_width: u16, keep_end: bool) -> String { } } fn first_create_focus() -> CreateModalFocus { - CreateModalFocus::Name + CreateModalFocus::Type } fn take_prefix_by_width(value: &str, max_width: u16) -> String { let mut width = 0; diff --git a/tui/crates/tui-kit-render/src/ui/app/ui_builder.rs b/tui/crates/tui-kit-render/src/ui/app/ui_builder.rs index 00c6731..d74cbad 100644 --- a/tui/crates/tui-kit-render/src/ui/app/ui_builder.rs +++ b/tui/crates/tui-kit-render/src/ui/app/ui_builder.rs @@ -4,9 +4,10 @@ use crate::ui::animation::AnimationState; use crate::ui::model::{UiContextNode, UiItemContent, UiItemSummary}; use crate::ui::search::CompletionCandidate; use tui_kit_runtime::{ - AccessControlModalState, CreateCostState, CreateModalFocus, CreateSubmitState, InsertFormFocus, - InsertMode, MemorySelection, PickerState, RemoveMemoryModalState, RenameMemoryModalState, - SearchScope, SettingsSnapshot, TextInputModalState, TransferModalState, + AccessControlModalState, CreateCostState, CreateModalFocus, CreateSubmitState, + CreateTargetKind, InsertFormFocus, InsertMode, MemorySelection, PickerState, + RemoveMemoryModalState, RenameMemoryModalState, SearchScope, SettingsSnapshot, + TextInputModalState, TransferModalState, }; use super::{Focus, TabId, TabSpec, TuiKitUi, UiConfig}; @@ -175,6 +176,12 @@ impl<'a> TuiKitUi<'a> { self } + #[must_use] + pub fn create_target_kind(mut self, value: CreateTargetKind) -> Self { + self.create_target_kind = value; + self + } + #[must_use] pub fn create_description_cursor(mut self, value: Option<(usize, usize)>) -> Self { self.create_description_cursor = value; diff --git a/tui/crates/tui-kit-render/src/ui/app/ui_state.rs b/tui/crates/tui-kit-render/src/ui/app/ui_state.rs index f2b72c9..8246e7f 100644 --- a/tui/crates/tui-kit-render/src/ui/app/ui_state.rs +++ b/tui/crates/tui-kit-render/src/ui/app/ui_state.rs @@ -6,8 +6,9 @@ use crate::ui::search::CompletionCandidate; use crate::ui::theme::Theme; use tui_kit_runtime::{ AccessControlModalState, ChatScope, CreateCostState, CreateModalFocus, CreateSubmitState, - InsertFormFocus, InsertMode, MemorySelection, PickerState, RemoveMemoryModalState, - RenameMemoryModalState, SearchScope, SettingsSnapshot, TextInputModalState, TransferModalState, + CreateTargetKind, InsertFormFocus, InsertMode, MemorySelection, PickerState, + RemoveMemoryModalState, RenameMemoryModalState, SearchScope, SettingsSnapshot, + TextInputModalState, TransferModalState, }; use super::{Focus, TabId, TabSpec, UiConfig, default_tab_specs}; @@ -41,6 +42,7 @@ pub struct TuiKitUi<'a> { pub(super) show_create_modal: bool, pub(super) create_name: &'a str, pub(super) create_description: &'a str, + pub(super) create_target_kind: CreateTargetKind, pub(super) create_description_cursor: Option<(usize, usize)>, pub(super) create_submit_state: CreateSubmitState, pub(super) create_spinner_frame: usize, @@ -117,6 +119,7 @@ impl<'a> TuiKitUi<'a> { show_create_modal: false, create_name: "", create_description: "", + create_target_kind: CreateTargetKind::Memory, create_description_cursor: None, create_submit_state: CreateSubmitState::Idle, create_spinner_frame: 0, diff --git a/tui/crates/tui-kit-runtime/src/form_descriptor.rs b/tui/crates/tui-kit-runtime/src/form_descriptor.rs index ef16679..7d4bf4d 100644 --- a/tui/crates/tui-kit-runtime/src/form_descriptor.rs +++ b/tui/crates/tui-kit-runtime/src/form_descriptor.rs @@ -88,7 +88,7 @@ struct InsertFieldSpec { } const CREATE_DESCRIPTOR: FormDescriptor = FormDescriptor { kind: FormKind::Create, - first_focus: FormFocus::Create(CreateModalFocus::Name), + first_focus: FormFocus::Create(CreateModalFocus::Type), reset_kind: FormResetKind::Create, }; const INSERT_DESCRIPTOR: FormDescriptor = FormDescriptor { @@ -96,7 +96,13 @@ const INSERT_DESCRIPTOR: FormDescriptor = FormDescriptor { first_focus: FormFocus::Insert(InsertFormFocus::Mode), reset_kind: FormResetKind::Insert, }; -const CREATE_FIELDS: [FormFieldSpec; 3] = [ +const CREATE_FIELDS: [FormFieldSpec; 4] = [ + FormFieldSpec { + focus: FormFocus::Create(CreateModalFocus::Type), + enter_command: FormCommand::NextField, + accepts_input: false, + supports_horizontal_change: true, + }, FormFieldSpec { focus: FormFocus::Create(CreateModalFocus::Name), enter_command: FormCommand::NextField, @@ -237,6 +243,8 @@ pub fn form_command_to_action(kind: FormKind, command: FormCommand) -> Option Some(CoreAction::CreateNextField), (FormKind::Create, FormCommand::PrevField) => Some(CoreAction::CreatePrevField), (FormKind::Create, FormCommand::Submit) => Some(CoreAction::CreateSubmit), + (FormKind::Create, FormCommand::HorizontalChangePrev) => Some(CoreAction::CreatePrevType), + (FormKind::Create, FormCommand::HorizontalChangeNext) => Some(CoreAction::CreateNextType), (FormKind::Insert, FormCommand::Input(c)) => Some(CoreAction::InsertInput(c)), (FormKind::Insert, FormCommand::Backspace) => Some(CoreAction::InsertBackspace), (FormKind::Insert, FormCommand::NextField) => Some(CoreAction::InsertNextField), @@ -269,6 +277,8 @@ pub fn core_action_to_form_command(action: &CoreAction) -> Option<(FormKind, For CoreAction::CreateNextField => Some((FormKind::Create, FormCommand::NextField)), CoreAction::CreatePrevField => Some((FormKind::Create, FormCommand::PrevField)), CoreAction::CreateSubmit => Some((FormKind::Create, FormCommand::Submit)), + CoreAction::CreatePrevType => Some((FormKind::Create, FormCommand::HorizontalChangePrev)), + CoreAction::CreateNextType => Some((FormKind::Create, FormCommand::HorizontalChangeNext)), _ => None, } } diff --git a/tui/crates/tui-kit-runtime/src/kinic_tabs.rs b/tui/crates/tui-kit-runtime/src/kinic_tabs.rs index fd3ec99..42a500b 100644 --- a/tui/crates/tui-kit-runtime/src/kinic_tabs.rs +++ b/tui/crates/tui-kit-runtime/src/kinic_tabs.rs @@ -3,13 +3,15 @@ pub const KINIC_MEMORIES_TAB_ID: &str = "kinic-memories"; pub const KINIC_INSERT_TAB_ID: &str = "kinic-insert"; pub const KINIC_CREATE_TAB_ID: &str = "kinic-create"; +pub const KINIC_WIKI_TAB_ID: &str = "kinic-wiki"; pub const KINIC_MARKET_TAB_ID: &str = "kinic-market"; pub const KINIC_SETTINGS_TAB_ID: &str = "kinic-settings"; -pub const KINIC_TAB_IDS: [&str; 5] = [ +pub const KINIC_TAB_IDS: [&str; 6] = [ KINIC_MEMORIES_TAB_ID, KINIC_INSERT_TAB_ID, KINIC_CREATE_TAB_ID, + KINIC_WIKI_TAB_ID, KINIC_MARKET_TAB_ID, KINIC_SETTINGS_TAB_ID, ]; @@ -19,6 +21,7 @@ pub enum TabKind { Memories, InsertForm, CreateForm, + Wiki, PlaceholderMarket, PlaceholderSettings, Unknown, @@ -29,6 +32,7 @@ pub fn tab_kind(tab_id: &str) -> TabKind { KINIC_MEMORIES_TAB_ID => TabKind::Memories, KINIC_INSERT_TAB_ID => TabKind::InsertForm, KINIC_CREATE_TAB_ID => TabKind::CreateForm, + KINIC_WIKI_TAB_ID => TabKind::Wiki, KINIC_MARKET_TAB_ID => TabKind::PlaceholderMarket, KINIC_SETTINGS_TAB_ID => TabKind::PlaceholderSettings, _ => TabKind::Unknown, @@ -51,6 +55,10 @@ pub fn is_kinic_create_tab(tab_id: &str) -> bool { matches!(tab_kind(tab_id), TabKind::CreateForm) } +pub fn is_kinic_wiki_tab(tab_id: &str) -> bool { + matches!(tab_kind(tab_id), TabKind::Wiki) +} + pub fn is_kinic_market_tab(tab_id: &str) -> bool { matches!(tab_kind(tab_id), TabKind::PlaceholderMarket) } @@ -68,6 +76,7 @@ mod tests { assert_eq!(tab_kind(KINIC_MEMORIES_TAB_ID), TabKind::Memories); assert_eq!(tab_kind(KINIC_INSERT_TAB_ID), TabKind::InsertForm); assert_eq!(tab_kind(KINIC_CREATE_TAB_ID), TabKind::CreateForm); + assert_eq!(tab_kind(KINIC_WIKI_TAB_ID), TabKind::Wiki); assert_eq!(tab_kind(KINIC_MARKET_TAB_ID), TabKind::PlaceholderMarket); assert_eq!( tab_kind(KINIC_SETTINGS_TAB_ID), @@ -76,6 +85,7 @@ mod tests { assert_eq!(tab_kind("unknown"), TabKind::Unknown); assert!(is_kinic_insert_tab(KINIC_INSERT_TAB_ID)); assert!(is_kinic_create_tab(KINIC_CREATE_TAB_ID)); + assert!(is_kinic_wiki_tab(KINIC_WIKI_TAB_ID)); assert!(is_form_tab(KINIC_CREATE_TAB_ID)); assert!(is_form_tab(KINIC_INSERT_TAB_ID)); assert!(is_kinic_memories_tab(KINIC_MEMORIES_TAB_ID)); diff --git a/tui/crates/tui-kit-runtime/src/lib.rs b/tui/crates/tui-kit-runtime/src/lib.rs index bbff9f0..1e73486 100644 --- a/tui/crates/tui-kit-runtime/src/lib.rs +++ b/tui/crates/tui-kit-runtime/src/lib.rs @@ -94,6 +94,15 @@ pub fn tab_focus_policy(tab_id: &str) -> TabFocusPolicy { allows_form: false, allows_chat: true, }, + kinic_tabs::TabKind::Wiki => TabFocusPolicy { + default_focus: PaneFocus::Search, + allows_search: true, + allows_items: true, + allows_tabs: true, + allows_content: true, + allows_form: false, + allows_chat: false, + }, kinic_tabs::TabKind::InsertForm | kinic_tabs::TabKind::CreateForm => TabFocusPolicy { default_focus: PaneFocus::Tabs, allows_search: false, @@ -123,7 +132,7 @@ fn chat_supported_for_tab(tab_id: &str) -> bool { pub fn tab_entry_focus(tab_id: &str) -> Option { match kinic_tabs::tab_kind(tab_id) { - kinic_tabs::TabKind::Memories => Some(PaneFocus::Search), + kinic_tabs::TabKind::Memories | kinic_tabs::TabKind::Wiki => Some(PaneFocus::Search), kinic_tabs::TabKind::InsertForm | kinic_tabs::TabKind::CreateForm => Some(PaneFocus::Form), kinic_tabs::TabKind::PlaceholderMarket | kinic_tabs::TabKind::PlaceholderSettings @@ -134,11 +143,39 @@ pub fn tab_entry_focus(tab_id: &str) -> Option { #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum CreateModalFocus { #[default] + Type, Name, Description, Submit, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum CreateTargetKind { + #[default] + Memory, + Wiki, +} + +impl CreateTargetKind { + pub fn label(self) -> &'static str { + match self { + Self::Memory => "Memory", + Self::Wiki => "Wiki", + } + } + + pub fn next(self) -> Self { + match self { + Self::Memory => Self::Wiki, + Self::Wiki => Self::Memory, + } + } + + pub fn prev(self) -> Self { + self.next() + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum InsertMode { #[default] @@ -650,6 +687,7 @@ pub struct CoreState { pub chat_scope_label: Option, pub create_name: String, pub create_description: String, + pub create_target_kind: CreateTargetKind, pub create_submit_state: CreateSubmitState, pub create_spinner_frame: usize, pub create_error: Option, @@ -706,6 +744,7 @@ impl Default for CoreState { chat_scope_label: None, create_name: String::new(), create_description: String::new(), + create_target_kind: CreateTargetKind::default(), create_submit_state: CreateSubmitState::default(), create_spinner_frame: 0, create_error: None, @@ -829,6 +868,8 @@ pub enum CoreAction { CreateBackspace, CreateNextField, CreatePrevField, + CreatePrevType, + CreateNextType, CreateRefresh, RefreshCurrentView, CreateSubmit, @@ -1402,6 +1443,7 @@ pub fn apply_core_action(state: &mut CoreState, action: &CoreAction) { }, CoreAction::CreateInput(c) => { match state.create_focus { + CreateModalFocus::Type => {} CreateModalFocus::Name => state.create_name.push(*c), CreateModalFocus::Description => state.create_description.push(*c), CreateModalFocus::Submit => {} @@ -1412,6 +1454,7 @@ pub fn apply_core_action(state: &mut CoreState, action: &CoreAction) { } } CoreAction::CreateBackspace => match state.create_focus { + CreateModalFocus::Type => {} CreateModalFocus::Name => { state.create_name.pop(); } @@ -1422,18 +1465,32 @@ pub fn apply_core_action(state: &mut CoreState, action: &CoreAction) { }, CoreAction::CreateNextField => { state.create_focus = match state.create_focus { + CreateModalFocus::Type => CreateModalFocus::Name, CreateModalFocus::Name => CreateModalFocus::Description, CreateModalFocus::Description => CreateModalFocus::Submit, - CreateModalFocus::Submit => CreateModalFocus::Name, + CreateModalFocus::Submit => CreateModalFocus::Type, }; } CoreAction::CreatePrevField => { state.create_focus = match state.create_focus { - CreateModalFocus::Name => CreateModalFocus::Submit, + CreateModalFocus::Type => CreateModalFocus::Submit, + CreateModalFocus::Name => CreateModalFocus::Type, CreateModalFocus::Description => CreateModalFocus::Name, CreateModalFocus::Submit => CreateModalFocus::Description, }; } + CoreAction::CreatePrevType => { + if state.create_focus == CreateModalFocus::Type { + state.create_target_kind = state.create_target_kind.prev(); + state.create_error = None; + } + } + CoreAction::CreateNextType => { + if state.create_focus == CreateModalFocus::Type { + state.create_target_kind = state.create_target_kind.next(); + state.create_error = None; + } + } CoreAction::CreateRefresh => { state.create_cost_state = CreateCostState::Loading; state.create_spinner_frame = 0; @@ -1554,7 +1611,7 @@ pub fn apply_core_action(state: &mut CoreState, action: &CoreAction) { state.focus = PaneFocus::Form; match kinic_tabs::tab_kind(state.current_tab_id.as_str()) { kinic_tabs::TabKind::CreateForm => { - state.create_focus = CreateModalFocus::Name; + state.create_focus = CreateModalFocus::Type; } kinic_tabs::TabKind::InsertForm => { state.insert_focus = InsertFormFocus::Mode; @@ -1878,6 +1935,7 @@ fn start_insert_submit(state: &mut CoreState) { fn apply_create_text_input(state: &mut CoreState, c: char) { match state.create_focus { + CreateModalFocus::Type => {} CreateModalFocus::Name => state.create_name.push(c), CreateModalFocus::Description => state.create_description.push(c), CreateModalFocus::Submit => {} @@ -1887,6 +1945,7 @@ fn apply_create_text_input(state: &mut CoreState, c: char) { fn apply_create_backspace(state: &mut CoreState) { match state.create_focus { + CreateModalFocus::Type => {} CreateModalFocus::Name => { state.create_name.pop(); } @@ -1912,15 +1971,17 @@ fn start_create_submit(state: &mut CoreState) { fn next_create_focus(focus: CreateModalFocus) -> CreateModalFocus { match focus { + CreateModalFocus::Type => CreateModalFocus::Name, CreateModalFocus::Name => CreateModalFocus::Description, CreateModalFocus::Description => CreateModalFocus::Submit, - CreateModalFocus::Submit => CreateModalFocus::Name, + CreateModalFocus::Submit => CreateModalFocus::Type, } } fn prev_create_focus(focus: CreateModalFocus) -> CreateModalFocus { match focus { - CreateModalFocus::Name => CreateModalFocus::Submit, + CreateModalFocus::Type => CreateModalFocus::Submit, + CreateModalFocus::Name => CreateModalFocus::Type, CreateModalFocus::Description => CreateModalFocus::Name, CreateModalFocus::Submit => CreateModalFocus::Description, } From f2aedc4b07f37ebcb3caba86172cfc14ff8e563c Mon Sep 17 00:00:00 2001 From: hude Date: Mon, 11 May 2026 10:52:16 +0900 Subject: [PATCH 02/10] Add Wiki database browsing to TUI --- Cargo.lock | 9 - Cargo.toml | 1 - README.md | 3 +- docs/tui.md | 4 +- rust/clients/launcher.rs | 32 -- rust/tui/bridge.rs | 270 ++++++++------ rust/tui/mod.rs | 21 ++ rust/tui/provider/mod.rs | 332 ++++++++++++------ rust/tui/provider/tests.rs | 1 + rust/tui/provider/tests/live_browser.rs | 3 - rust/tui/provider/tests/settings.rs | 1 - rust/tui/provider/tests/wiki.rs | 131 +++++-- tui/crates/tui-kit-host/src/form_tab_flow.rs | 1 - tui/crates/tui-kit-host/src/lib.rs | 3 +- tui/crates/tui-kit-host/src/runtime_loop.rs | 2 - .../src/ui/app/screens/create/mod.rs | 23 +- .../tui-kit-render/src/ui/app/ui_builder.rs | 13 +- .../tui-kit-render/src/ui/app/ui_state.rs | 7 +- .../tui-kit-runtime/src/form_descriptor.rs | 14 +- tui/crates/tui-kit-runtime/src/lib.rs | 62 +--- 20 files changed, 543 insertions(+), 390 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7116625..5fb7880 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2224,7 +2224,6 @@ dependencies = [ "tui-kit-model", "tui-kit-render", "tui-kit-runtime", - "vfs-types", ] [[package]] @@ -4655,14 +4654,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "vfs-types" -version = "0.1.0" -dependencies = [ - "candid", - "serde", -] - [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index bc13aa5..09290d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,6 @@ tui-kit-host = { path = "tui/crates/tui-kit-host" } tui-kit-runtime = { path = "tui/crates/tui-kit-runtime" } tui-kit-model = { path = "tui/crates/tui-kit-model" } tui-kit-render = { path = "tui/crates/tui-kit-render" } -vfs-types = { path = "../llm-wiki/crates/vfs_types" } [dev-dependencies] serde_yaml = "0.9" diff --git a/README.md b/README.md index 139d0d9..cf8decc 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ For the fastest first success, start with `Inline Text`. It avoids file chooser ### Step 1: Start the TUI and Learn the Layout -When the TUI starts, it opens on the `Memories` tab. Use `1` to `5` to switch tabs, `Tab` / `Shift+Tab` to move focus, and `?` to open help. In normal list navigation, `q` quits and `Ctrl+R` refreshes the current view. +When the TUI starts, it opens on the `Memories` tab. Use `1` to `6` to switch tabs, `Tab` / `Shift+Tab` to move focus, and `?` to open help. In normal list navigation, `q` quits and `Ctrl+R` refreshes the current view. ![Memories tab screenshot](./docs/images/tui-memories.png) @@ -128,6 +128,7 @@ What to understand first: - `Memories`: list, search, detail view, and chat - `Insert`: add files, text, or manual embeddings - `Create`: create a new memory +- `Wiki`: browse databases from the configured wiki canister; `KINIC_WIKI_CANISTER_ID` can override the default - `Market`: reserved and not implemented yet - `Settings`: principal, balance, default memory, saved tags, and retrieval settings diff --git a/docs/tui.md b/docs/tui.md index ad316ee..2e0e3bd 100644 --- a/docs/tui.md +++ b/docs/tui.md @@ -75,15 +75,17 @@ If you want to try File mode, either install `yazi` first or type the file path ## Layout -The TUI has five tabs. +The TUI has six tabs. - `Memories`: list, search, and details - `Insert`: add data - `Create`: create a new memory +- `Wiki`: browse databases from the configured wiki canister; `KINIC_WIKI_CANISTER_ID` can override the default - `Market`: reserved for future use and currently not implemented - `Settings`: view and change current connection info and saved settings The `Memories` tab opens first when the TUI starts. +By default, the Wiki tab uses `xis3j-paaaa-aaaai-axumq-cai`. The local preferences shown in `Settings`, including the default memory, saved tags, and manually tracked memories, can also be managed from the CLI with `kinic-cli prefs ...`. ## Basic Controls diff --git a/rust/clients/launcher.rs b/rust/clients/launcher.rs index 5989df9..19ffb62 100644 --- a/rust/clients/launcher.rs +++ b/rust/clients/launcher.rs @@ -97,21 +97,6 @@ impl LauncherClient { Ok(result?) } - pub async fn deploy_wiki(&self, name: &str) -> Result { - let payload = encode_deploy_canister_args(name, CanisterType::Wiki, None)?; - let response = self - .agent - .update(&self.launcher_id, "deploy_canister") - .with_arg(payload) - .call_and_wait() - .await - .context("Failed to call deploy_canister")?; - - let result = Decode!(&response, std::result::Result) - .context("Failed to decode deploy_canister response")?; - Ok(result?) - } - pub async fn list_memories(&self) -> Result> { let response = self .agent @@ -166,14 +151,6 @@ fn encode_deploy_args(name: &str, description: &str) -> Result> { ))?) } -fn encode_deploy_canister_args( - name: &str, - canister_type: CanisterType, - dim: Option, -) -> Result> { - Ok(candid::encode_args((name.to_string(), canister_type, dim))?) -} - fn encode_update_instance_args(instance_pid_str: &str) -> Result> { Ok(candid::encode_one(instance_pid_str)?) } @@ -253,13 +230,4 @@ mod tests { let decoded = Decode!(&bytes, Vec).unwrap(); assert_eq!(decoded[0].canister_type, CanisterType::Wiki); } - - #[test] - fn deploy_wiki_args_encode_as_text_type_and_no_dim() { - let bytes = encode_deploy_canister_args("Project Wiki", CanisterType::Wiki, None).unwrap(); - let decoded = Decode!(&bytes, String, CanisterType, Option).unwrap(); - assert_eq!(decoded.0, "Project Wiki"); - assert_eq!(decoded.1, CanisterType::Wiki); - assert_eq!(decoded.2, None); - } } diff --git a/rust/tui/bridge.rs b/rust/tui/bridge.rs index 81b1fed..9d785e9 100644 --- a/rust/tui/bridge.rs +++ b/rust/tui/bridge.rs @@ -26,11 +26,8 @@ use anyhow::{Context, Result}; use candid::{CandidType, Decode, Encode}; use ic_agent::{Agent, export::Principal}; use kinic_core::amount::format_e8s_to_kinic_string_nat; +use serde::{Deserialize, Serialize}; use tui_kit_runtime::{AccessControlAction, AccessControlRole, ChatScope, SessionAccountOverview}; -use vfs_types::{ - ChildNode, ListChildrenRequest, Node, NodeEntryKind, SearchNodeHit, SearchNodesRequest, - SearchPreviewMode, -}; pub(crate) use crate::shared::memory_metadata::DescriptionUpdate; @@ -49,19 +46,9 @@ pub struct MemorySummary { pub users: Option>, } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct WikiSummary { - pub id: String, - pub status: String, - pub detail: String, - pub searchable_wiki_id: Option, - pub name: String, -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct InstanceSummaries { pub memories: Vec, - pub wikis: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -86,7 +73,114 @@ pub struct WikiSearchHit { pub snippet: Option, } -const DEFAULT_WIKI_DATABASE_ID: &str = "default"; +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub enum DatabaseRole { + Owner, + Writer, + Reader, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub enum DatabaseStatus { + Hot, + Restoring, + Archiving, + Archived, + Deleted, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct DatabaseSummary { + pub database_id: String, + pub status: DatabaseStatus, + pub role: DatabaseRole, + pub logical_size_bytes: u64, + pub archived_at_ms: Option, + pub deleted_at_ms: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +struct ListChildrenRequest { + database_id: String, + path: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +enum NodeEntryKind { + File, + Source, + Directory, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +struct ChildNode { + path: String, + name: String, + kind: NodeEntryKind, + updated_at: Option, + etag: Option, + size_bytes: Option, + is_virtual: bool, + has_children: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +enum NodeKind { + File, + Source, + Directory, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +struct Node { + path: String, + kind: NodeKind, + content: String, + created_at: i64, + updated_at: i64, + etag: String, + metadata_json: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +enum SearchPreviewMode { + Light, + ContentStart, + None, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +enum SearchPreviewField { + Path, + Content, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +struct SearchPreview { + field: SearchPreviewField, + char_offset: u32, + match_reason: String, + excerpt: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, CandidType)] +struct SearchNodeHit { + path: String, + kind: NodeKind, + snippet: Option, + preview: Option, + score: f32, + match_reasons: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +struct SearchNodesRequest { + database_id: String, + query_text: String, + prefix: Option, + top_k: u32, + preview_mode: Option, +} pub type SearchResultItem = SearchHit; @@ -140,16 +234,9 @@ pub struct CreateMemorySuccess { pub refresh_warning: Option, } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum CreateTargetKind { - Memory, - Wiki, -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct CreateInstanceSuccess { pub id: String, - pub kind: CreateTargetKind, pub instances: Option, pub refresh_warning: Option, } @@ -319,7 +406,6 @@ pub async fn create_memory( pub async fn create_instance( use_mainnet: bool, auth: TuiAuth, - kind: CreateTargetKind, name: String, description: String, ) -> Result { @@ -353,11 +439,10 @@ pub async fn create_instance( .approve_launcher(&price, fee) .await .map_err(|error| CreateMemoryError::Approve(short_error(&error.to_string())))?; - let id = match kind { - CreateTargetKind::Memory => client.deploy_memory(&name, &description).await, - CreateTargetKind::Wiki => client.deploy_wiki(&name).await, - } - .map_err(|error| CreateMemoryError::Deploy(short_error(&error.to_string())))?; + let id = client + .deploy_memory(&name, &description) + .await + .map_err(|error| CreateMemoryError::Deploy(short_error(&error.to_string())))?; let (instances, refresh_warning) = match client.list_typed_instances().await { Ok(states) => (Some(instance_summaries_from_typed_states(states)), None), Err(error) => ( @@ -371,7 +456,6 @@ pub async fn create_instance( Ok(CreateInstanceSuccess { id, - kind, instances, refresh_warning, }) @@ -381,31 +465,44 @@ pub async fn list_wiki_root_children( use_mainnet: bool, auth: TuiAuth, wiki_id: String, + database_id: String, ) -> Result> { let agent = build_search_agent(use_mainnet, auth).await?; - VfsClient::new(agent, wiki_id)?.list_children("/Wiki").await + let client = VfsClient::new(agent, wiki_id)?; + client.list_children(&database_id, "/Wiki").await } pub async fn read_wiki_node( use_mainnet: bool, auth: TuiAuth, wiki_id: String, + database_id: String, path: String, ) -> Result> { let agent = build_search_agent(use_mainnet, auth).await?; - VfsClient::new(agent, wiki_id)?.read_node(&path).await + let client = VfsClient::new(agent, wiki_id)?; + client.read_node(&database_id, &path).await } pub async fn search_wiki_nodes( use_mainnet: bool, auth: TuiAuth, wiki_id: String, + database_id: String, query: String, ) -> Result> { let agent = build_search_agent(use_mainnet, auth).await?; - VfsClient::new(agent, wiki_id)? - .search_nodes(&query, "/Wiki", 20) - .await + let client = VfsClient::new(agent, wiki_id)?; + client.search_nodes(&database_id, &query, "/Wiki", 20).await +} + +pub async fn list_wiki_databases( + use_mainnet: bool, + auth: TuiAuth, + wiki_id: String, +) -> Result> { + let agent = build_search_agent(use_mainnet, auth).await?; + VfsClient::new(agent, wiki_id)?.list_databases().await } pub async fn load_session_account_overview( @@ -721,61 +818,13 @@ pub async fn run_insert( fn instance_summaries_from_typed_states(states: Vec) -> InstanceSummaries { let mut memories = Vec::new(); - let mut wikis = Vec::new(); for typed in states { match typed.canister_type { CanisterType::Memory => memories.push(memory_summary_from_state(typed.state)), - CanisterType::Wiki => wikis.push(wiki_summary_from_state(typed.state)), + CanisterType::Wiki => {} } } - InstanceSummaries { memories, wikis } -} - -fn wiki_summary_from_state(state: State) -> WikiSummary { - match state { - State::Empty(message) => WikiSummary { - id: format!("wiki-empty:{message}"), - status: "empty".to_string(), - detail: message.clone(), - searchable_wiki_id: None, - name: message, - }, - State::Pending(message) => WikiSummary { - id: format!("wiki-pending:{message}"), - status: "pending".to_string(), - detail: message.clone(), - searchable_wiki_id: None, - name: message, - }, - State::Creation(message) => WikiSummary { - id: format!("wiki-creation:{message}"), - status: "creation".to_string(), - detail: message.clone(), - searchable_wiki_id: None, - name: message, - }, - State::Installation(principal, message) => WikiSummary { - id: principal.to_text(), - status: "installation".to_string(), - detail: message.clone(), - searchable_wiki_id: Some(principal.to_text()), - name: message, - }, - State::SettingUp(principal) => WikiSummary { - id: principal.to_text(), - status: "setting_up".to_string(), - detail: "Launcher is setting up this wiki.".to_string(), - searchable_wiki_id: Some(principal.to_text()), - name: "unknown".to_string(), - }, - State::Running(principal) => WikiSummary { - id: principal.to_text(), - status: "running".to_string(), - detail: "Wiki is ready for browsing and search.".to_string(), - searchable_wiki_id: Some(principal.to_text()), - name: "Wiki".to_string(), - }, - } + InstanceSummaries { memories } } fn memory_summary_from_state(state: State) -> MemorySummary { @@ -966,12 +1015,18 @@ impl VfsClient { Decode!(&bytes, Out).with_context(|| format!("failed to decode wiki response for {method}")) } - async fn list_children(&self, path: &str) -> Result> { + async fn list_databases(&self) -> Result> { + let result: Result, String> = + self.query("list_databases", &()).await?; + result.map_err(anyhow::Error::msg) + } + + async fn list_children(&self, database_id: &str, path: &str) -> Result> { let result: Result, String> = self .query( "list_children", &ListChildrenRequest { - database_id: DEFAULT_WIKI_DATABASE_ID.to_string(), + database_id: database_id.to_string(), path: path.to_string(), }, ) @@ -983,19 +1038,16 @@ impl VfsClient { .collect()) } - async fn read_node(&self, path: &str) -> Result> { + async fn read_node(&self, database_id: &str, path: &str) -> Result> { let result: Result, String> = self - .query2( - "read_node", - &DEFAULT_WIKI_DATABASE_ID.to_string(), - &path.to_string(), - ) + .query2("read_node", &database_id.to_string(), &path.to_string()) .await?; Ok(result.map_err(anyhow::Error::msg)?.map(WikiNode::from)) } async fn search_nodes( &self, + database_id: &str, query_text: &str, prefix: &str, top_k: u32, @@ -1004,7 +1056,7 @@ impl VfsClient { .query( "search_nodes", &SearchNodesRequest { - database_id: DEFAULT_WIKI_DATABASE_ID.to_string(), + database_id: database_id.to_string(), query_text: query_text.to_string(), prefix: Some(prefix.to_string()), top_k, @@ -1031,7 +1083,7 @@ impl From for WikiChildNode { NodeEntryKind::Directory => "directory", } .to_string(), - has_children: node.is_virtual, + has_children: node.has_children, } } } @@ -1129,7 +1181,24 @@ mod tests { } #[test] - fn wiki_child_node_from_vfs_types_projects_display_fields() { + fn wiki_database_summary_decodes_list_databases_shape() { + let bytes = Encode!(&Ok::<_, String>(vec![DatabaseSummary { + database_id: "default".to_string(), + status: DatabaseStatus::Hot, + role: DatabaseRole::Owner, + logical_size_bytes: 42, + archived_at_ms: None, + deleted_at_ms: None, + }])) + .expect("database list should encode"); + let decoded = Decode!(&bytes, Result, String>) + .expect("database list should decode"); + + assert_eq!(decoded.unwrap()[0].database_id, "default"); + } + + #[test] + fn wiki_child_node_from_canister_types_projects_display_fields() { let child = WikiChildNode::from(ChildNode { path: "/Wiki/index.md".to_string(), name: "index.md".to_string(), @@ -1138,6 +1207,7 @@ mod tests { etag: Some("abc".to_string()), size_bytes: Some(10), is_virtual: false, + has_children: false, }); assert_eq!(child.path, "/Wiki/index.md"); @@ -1147,10 +1217,10 @@ mod tests { } #[test] - fn wiki_node_from_vfs_types_keeps_render_payload() { + fn wiki_node_from_canister_types_keeps_render_payload() { let node = WikiNode::from(Node { path: "/Wiki/index.md".to_string(), - kind: vfs_types::NodeKind::File, + kind: NodeKind::File, content: "# Index".to_string(), created_at: 1, updated_at: 2, @@ -1167,10 +1237,10 @@ mod tests { fn wiki_search_hit_prefers_preview_excerpt() { let hit = WikiSearchHit::from(SearchNodeHit { path: "/Wiki/index.md".to_string(), - kind: vfs_types::NodeKind::File, + kind: NodeKind::File, snippet: Some("fallback".to_string()), - preview: Some(vfs_types::SearchPreview { - field: vfs_types::SearchPreviewField::Content, + preview: Some(SearchPreview { + field: SearchPreviewField::Content, match_reason: "content".to_string(), char_offset: 0, excerpt: Some("preview".to_string()), diff --git a/rust/tui/mod.rs b/rust/tui/mod.rs index 2db2e70..8714962 100644 --- a/rust/tui/mod.rs +++ b/rust/tui/mod.rs @@ -116,8 +116,12 @@ impl TuiAuth { pub struct TuiLaunchConfig { pub auth: TuiAuth, pub use_mainnet: bool, + pub wiki_canister_id: Option, } +const WIKI_CANISTER_ID_ENV_VAR: &str = "KINIC_WIKI_CANISTER_ID"; +const DEFAULT_WIKI_CANISTER_ID: &str = "xis3j-paaaa-aaaai-axumq-cai"; + pub fn run(global: &GlobalOpts) -> Result<()> { run_with_config(build_launch_config_from_global(global)?) } @@ -126,6 +130,7 @@ pub fn build_launch_config(identity: String, use_mainnet: bool) -> Result Result Option { + Some( + std::env::var(WIKI_CANISTER_ID_ENV_VAR) + .ok() + .map(|value| value.trim().to_string()) + .filter(|value| !value.is_empty()) + .unwrap_or_else(|| DEFAULT_WIKI_CANISTER_ID.to_string()), + ) +} + pub fn run_with_config(config: TuiLaunchConfig) -> Result<()> { let mut provider = provider::KinicProvider::new(TuiConfig { auth: config.auth, use_mainnet: config.use_mainnet, + wiki_canister_id: config.wiki_canister_id, }); let mut hooks = KinicRuntimeHooks; @@ -208,6 +225,10 @@ mod tests { assert!(matches!(config.auth, TuiAuth::DeferredIdentity { .. })); assert!(config.use_mainnet); + assert_eq!( + config.wiki_canister_id.as_deref(), + Some(DEFAULT_WIKI_CANISTER_ID) + ); } #[test] diff --git a/rust/tui/provider/mod.rs b/rust/tui/provider/mod.rs index 2a212b5..c13db09 100644 --- a/rust/tui/provider/mod.rs +++ b/rust/tui/provider/mod.rs @@ -55,6 +55,7 @@ use tui_kit_runtime::{ pub struct TuiConfig { pub auth: TuiAuth, pub use_mainnet: bool, + pub wiki_canister_id: Option, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -67,6 +68,7 @@ pub struct KinicRecord { pub searchable_memory_id: Option, pub source_memory_id: Option, pub source_wiki_id: Option, + pub source_wiki_database_id: Option, } impl KinicRecord { @@ -86,6 +88,7 @@ impl KinicRecord { searchable_memory_id: None, source_memory_id: None, source_wiki_id: None, + source_wiki_database_id: None, } } @@ -103,6 +106,11 @@ impl KinicRecord { self.source_wiki_id = Some(wiki_id.into()); self } + + pub fn with_source_wiki_database_id(mut self, database_id: impl Into) -> Self { + self.source_wiki_database_id = Some(database_id.into()); + self + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -155,7 +163,7 @@ pub struct KinicProvider { active_memory: Option, memory_summaries: Vec, memory_records: Vec, - wiki_summaries: Vec, + wiki_databases: Vec, wiki_records: Vec, result_records: Vec, memories_mode: MemoriesMode, @@ -199,9 +207,11 @@ pub struct KinicProvider { wiki_children_cache: HashMap, wiki_children_task: RequestTaskState, next_wiki_children_request_id: u64, - pending_wiki_children_wiki_id: Option, + pending_wiki_children_database_id: Option, wiki_search_task: RequestTaskState, next_wiki_search_request_id: u64, + wiki_databases_task: RequestTaskState, + next_wiki_databases_request_id: u64, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -228,16 +238,22 @@ struct WikiChildrenContent { struct WikiChildrenTaskOutput { request_id: u64, - wiki_id: String, + database_id: String, result: Result, } struct WikiSearchTaskOutput { request_id: u64, wiki_id: String, + database_id: String, result: Result, String>, } +struct WikiDatabasesTaskOutput { + request_id: u64, + result: Result, String>, +} + /// In-flight memory search with explicit cancellation. Kept separate from /// `RequestTaskState` because workers use `CancellationToken` and batching /// differs from other request/response tasks. @@ -975,6 +991,7 @@ fn load_wiki_children_content( use_mainnet: bool, auth: TuiAuth, wiki_id: String, + database_id: String, ) -> Result { let runtime = Runtime::new().expect("failed to create tokio runtime for wiki browser load"); let children = runtime @@ -982,6 +999,7 @@ fn load_wiki_children_content( use_mainnet, auth.clone(), wiki_id.clone(), + database_id.clone(), )) .map_err(|error| error.to_string())?; let index = runtime @@ -989,6 +1007,7 @@ fn load_wiki_children_content( use_mainnet, auth, wiki_id, + database_id, "/Wiki/index.md".to_string(), )) .map_err(|error| error.to_string())?; @@ -1054,7 +1073,7 @@ impl KinicProvider { active_memory: None, memory_summaries: Vec::new(), memory_records: Vec::new(), - wiki_summaries: Vec::new(), + wiki_databases: Vec::new(), wiki_records: Vec::new(), result_records: Vec::new(), memories_mode: MemoriesMode::Browser, @@ -1098,9 +1117,11 @@ impl KinicProvider { wiki_children_cache: HashMap::new(), wiki_children_task: RequestTaskState::default(), next_wiki_children_request_id: 0, - pending_wiki_children_wiki_id: None, + pending_wiki_children_database_id: None, wiki_search_task: RequestTaskState::default(), next_wiki_search_request_id: 0, + wiki_databases_task: RequestTaskState::default(), + next_wiki_databases_request_id: 0, } } @@ -1127,7 +1148,7 @@ impl KinicProvider { self.all = vec![loading_memories_record()]; self.memory_summaries.clear(); self.memory_records.clear(); - self.wiki_summaries.clear(); + self.wiki_databases.clear(); self.wiki_records.clear(); self.memory_content_summaries.clear(); self.failed_memory_content_summaries.clear(); @@ -1166,10 +1187,7 @@ impl KinicProvider { match self.tab_id.as_str() { KINIC_CREATE_TAB_ID => self.start_create_cost_refresh().into_iter().collect(), KINIC_INSERT_TAB_ID => Vec::new(), - KINIC_WIKI_TAB_ID => self - .start_live_memories_load(Some("Refreshing wiki list..."), true) - .into_iter() - .collect(), + KINIC_WIKI_TAB_ID => vec![self.start_wiki_databases_load()], KINIC_MEMORIES_TAB_ID => self .start_live_memories_load(None, true) .into_iter() @@ -1179,6 +1197,41 @@ impl KinicProvider { } } + fn start_wiki_databases_load(&mut self) -> CoreEffect { + self.result_records.clear(); + self.invalidate_pending_search(); + reset_request_task(&mut self.wiki_children_task); + reset_request_task(&mut self.wiki_search_task); + self.pending_wiki_children_database_id = None; + + let Some(wiki_canister_id) = self.config.wiki_canister_id.clone() else { + self.wiki_databases.clear(); + self.wiki_records = vec![wiki_not_configured_record()]; + self.wiki_children_cache.clear(); + return CoreEffect::Notify("Wiki canister is not configured.".to_string()); + }; + + let auth = self.config.auth.clone(); + let use_mainnet = self.config.use_mainnet; + spawn_request_task( + &mut self.next_wiki_databases_request_id, + &mut self.wiki_databases_task, + move |request_id, tx| { + let runtime = + Runtime::new().expect("failed to create tokio runtime for wiki databases load"); + let result = runtime + .block_on(bridge::list_wiki_databases( + use_mainnet, + auth, + wiki_canister_id, + )) + .map_err(|error| error.to_string()); + let _ = tx.send(WikiDatabasesTaskOutput { request_id, result }); + }, + ); + CoreEffect::Notify("Refreshing wiki databases...".to_string()) + } + fn current_records(&self) -> Vec<&KinicRecord> { if self.tab_id == KINIC_WIKI_TAB_ID { if self.result_records.is_empty() { @@ -1819,16 +1872,18 @@ impl KinicProvider { content: &mut tui_kit_model::UiItemContent, record: &KinicRecord, ) { - let Some(wiki_id) = record.source_wiki_id.as_ref() else { + let Some(database_id) = record.source_wiki_database_id.as_ref() else { return; }; - let (body_lines, index_preview) = match self.wiki_children_cache.get(wiki_id.as_str()) { + let (body_lines, index_preview) = match self.wiki_children_cache.get(database_id.as_str()) { Some(content) => (content.body_lines.clone(), content.index_preview.clone()), - None if self.pending_wiki_children_wiki_id.as_deref() == Some(wiki_id.as_str()) => { + None if self.pending_wiki_children_database_id.as_deref() + == Some(database_id.as_str()) => + { (vec!["Loading /Wiki children...".to_string()], None) } None => ( - vec!["Select or refresh this wiki to load /Wiki children.".to_string()], + vec!["Select or refresh this database to load /Wiki children.".to_string()], None, ), }; @@ -1850,27 +1905,27 @@ impl KinicProvider { if self.tab_id != KINIC_WIKI_TAB_ID { return; } - let Some(wiki_id) = self.selected_wiki_id(state) else { + let Some((wiki_id, database_id)) = self.selected_wiki_target(state) else { return; }; - if self.wiki_children_cache.contains_key(wiki_id.as_str()) - || self.pending_wiki_children_wiki_id.as_deref() == Some(wiki_id.as_str()) + if self.wiki_children_cache.contains_key(database_id.as_str()) + || self.pending_wiki_children_database_id.as_deref() == Some(database_id.as_str()) { return; } let auth = self.config.auth.clone(); let use_mainnet = self.config.use_mainnet; - self.pending_wiki_children_wiki_id = Some(wiki_id.clone()); + self.pending_wiki_children_database_id = Some(database_id.clone()); spawn_request_task( &mut self.next_wiki_children_request_id, &mut self.wiki_children_task, move |request_id, tx| { - let requested_wiki_id = wiki_id.clone(); - let result = load_wiki_children_content(use_mainnet, auth, wiki_id); + let requested_database_id = database_id.clone(); + let result = load_wiki_children_content(use_mainnet, auth, wiki_id, database_id); let _ = tx.send(WikiChildrenTaskOutput { request_id, - wiki_id: requested_wiki_id, + database_id: requested_database_id, result, }); }, @@ -2068,27 +2123,35 @@ impl KinicProvider { self.all = self.memory_records.clone(); } - fn refresh_wiki_records_from_summaries(&mut self) { - self.wiki_records = self - .wiki_summaries - .iter() - .cloned() - .map(record_from_wiki_summary) + fn refresh_wiki_records_from_databases(&mut self) { + let Some(wiki_canister_id) = self.config.wiki_canister_id.as_deref() else { + self.wiki_records = vec![wiki_not_configured_record()]; + self.wiki_children_cache.clear(); + return; + }; + let mut databases = self.wiki_databases.clone(); + databases.sort_by_key(|database| { + ( + database.status != bridge::DatabaseStatus::Hot, + database.database_id.clone(), + ) + }); + self.wiki_records = databases + .into_iter() + .map(|database| record_from_wiki_database(wiki_canister_id, database)) .collect(); - let wiki_ids = self + let database_ids = self .wiki_records .iter() - .filter_map(|record| record.source_wiki_id.clone()) + .filter_map(|record| record.source_wiki_database_id.clone()) .collect::>(); self.wiki_children_cache - .retain(|wiki_id, _| wiki_ids.contains(wiki_id)); + .retain(|database_id, _| database_ids.contains(database_id)); } fn apply_instance_summaries(&mut self, instances: bridge::InstanceSummaries) { self.memory_summaries = instances.memories; - self.wiki_summaries = instances.wikis; self.refresh_memory_records_from_summaries(); - self.refresh_wiki_records_from_summaries(); } fn normalize_memory_summaries(&mut self) { @@ -2828,12 +2891,7 @@ impl KinicProvider { None } - fn start_create_submit( - &mut self, - kind: tui_kit_runtime::CreateTargetKind, - name: String, - description: String, - ) -> CoreEffect { + fn start_create_submit(&mut self, name: String, description: String) -> CoreEffect { let auth = self.config.auth.clone(); let use_mainnet = self.config.use_mainnet; spawn_request_task( @@ -2842,14 +2900,9 @@ impl KinicProvider { move |request_id, tx| { let runtime = Runtime::new().expect("failed to create tokio runtime for create submit"); - let bridge_kind = match kind { - tui_kit_runtime::CreateTargetKind::Memory => bridge::CreateTargetKind::Memory, - tui_kit_runtime::CreateTargetKind::Wiki => bridge::CreateTargetKind::Wiki, - }; let result = runtime.block_on(bridge::create_instance( use_mainnet, auth, - bridge_kind, name, description, )); @@ -2857,7 +2910,7 @@ impl KinicProvider { }, ); - CoreEffect::Notify(format!("Creating {}...", kind.label().to_lowercase())) + CoreEffect::Notify("Creating memory...".to_string()) } fn run_wiki_search(&mut self, state: &CoreState) -> CoreEffect { @@ -2866,8 +2919,8 @@ impl KinicProvider { self.result_records.clear(); return CoreEffect::Notify("Enter a wiki search query.".to_string()); } - let Some(wiki_id) = self.selected_wiki_id(state) else { - return CoreEffect::Notify("Select a running wiki before searching.".to_string()); + let Some((wiki_id, database_id)) = self.selected_wiki_target(state) else { + return CoreEffect::Notify("Select a wiki database before searching.".to_string()); }; if self.wiki_search_task.in_flight { return CoreEffect::Notify("Wiki search request already running.".to_string()); @@ -2888,6 +2941,7 @@ impl KinicProvider { use_mainnet, auth, wiki_id.clone(), + database_id.clone(), query, )) .map_err(|error| error.to_string()) @@ -2895,6 +2949,7 @@ impl KinicProvider { let _ = tx.send(WikiSearchTaskOutput { request_id, wiki_id, + database_id, result, }); }, @@ -2902,11 +2957,14 @@ impl KinicProvider { CoreEffect::Notify("Searching wiki nodes...".to_string()) } - fn selected_wiki_id(&self, state: &CoreState) -> Option { + fn selected_wiki_target(&self, state: &CoreState) -> Option<(String, String)> { let index = state.selected_index.unwrap_or(0); - self.current_records() - .get(index) - .and_then(|record| record.source_wiki_id.clone()) + let records = self.current_records(); + let record = records.get(index)?; + Some(( + record.source_wiki_id.clone()?, + record.source_wiki_database_id.clone()?, + )) } fn start_insert_submit(&mut self, request: InsertRequest) -> CoreEffect { @@ -3263,7 +3321,7 @@ impl KinicProvider { return "Review session details and default memory settings here.".to_string(); } if self.tab_id == KINIC_WIKI_TAB_ID { - return "Browse wiki canisters and search wiki nodes.".to_string(); + return "Browse wiki databases and search wiki nodes.".to_string(); } if self.tab_id == KINIC_MARKET_TAB_ID { return "Market is not implemented yet.".to_string(); @@ -4148,7 +4206,7 @@ impl KinicProvider { let previous_active_memory = self.active_memory.clone(); self.memory_records.clear(); self.wiki_records.clear(); - self.wiki_summaries.clear(); + self.wiki_databases.clear(); self.result_records.clear(); self.memories_mode = MemoriesMode::Browser; let notify_message = format_live_load_failure_message(&error); @@ -4391,22 +4449,15 @@ impl KinicProvider { } } } - let target_tab = match success.kind { - bridge::CreateTargetKind::Memory => KINIC_MEMORIES_TAB_ID, - bridge::CreateTargetKind::Wiki => KINIC_WIKI_TAB_ID, - }; - if success.kind == bridge::CreateTargetKind::Memory { - self.set_active_memory_by_id(success.id.clone()); - self.memories_mode = MemoriesMode::Browser; - } + let target_tab = KINIC_MEMORIES_TAB_ID; + self.set_active_memory_by_id(success.id.clone()); + self.memories_mode = MemoriesMode::Browser; self.result_records.clear(); self.invalidate_pending_search(); self.last_search_state = None; - if success.kind == bridge::CreateTargetKind::Memory { - self.start_memory_detail_prefetch_for_records(); - self.start_active_memory_detail_load(); - self.start_selected_memory_summary_load(false); - } + self.start_memory_detail_prefetch_for_records(); + self.start_active_memory_detail_load(); + self.start_selected_memory_summary_load(false); let _ = self.start_create_cost_refresh(); effects.extend(self.set_tab(target_tab)); effects.push(CoreEffect::SelectFirstListItem); @@ -4414,14 +4465,10 @@ impl KinicProvider { tab_id: target_tab.to_string(), }); effects.push(CoreEffect::FocusPane(PaneFocus::Items)); - let noun = match success.kind { - bridge::CreateTargetKind::Memory => "memory", - bridge::CreateTargetKind::Wiki => "wiki", - }; let status = if let Some(warning) = success.refresh_warning { - format!("Created {noun} {}. {}", success.id, warning) + format!("Created memory {}. {}", success.id, warning) } else { - format!("Created {noun} {}", success.id) + format!("Created memory {}", success.id) }; effects.push(CoreEffect::Notify(status)); } @@ -4477,7 +4524,7 @@ impl KinicProvider { PendingTaskPoll::Ready(output) => output, PendingTaskPoll::Disconnected => { reset_request_task(&mut self.wiki_children_task); - self.pending_wiki_children_wiki_id = None; + self.pending_wiki_children_database_id = None; return Some(self.disconnected_request_output( state, CoreEffect::Notify("Wiki browser load failed unexpectedly.".to_string()), @@ -4486,14 +4533,14 @@ impl KinicProvider { }; let is_current = finish_request_task(&mut self.wiki_children_task, output.request_id); - self.pending_wiki_children_wiki_id = None; + self.pending_wiki_children_database_id = None; if !is_current { return Some(self.stale_request_output(state)); } let effects = match output.result { Ok(content) => { - self.wiki_children_cache.insert(output.wiki_id, content); + self.wiki_children_cache.insert(output.database_id, content); Vec::new() } Err(error) => vec![CoreEffect::Notify(format!( @@ -4505,6 +4552,57 @@ impl KinicProvider { Some(self.snapshot_output(state, effects)) } + fn poll_wiki_databases_background(&mut self, state: &CoreState) -> Option { + let receiver = self.wiki_databases_task.receiver.as_ref()?; + let output = match poll_pending_task(receiver) { + PendingTaskPoll::Pending => return None, + PendingTaskPoll::Ready(output) => output, + PendingTaskPoll::Disconnected => { + reset_request_task(&mut self.wiki_databases_task); + self.wiki_records = vec![load_error_record( + "Wiki databases load failed unexpectedly.".to_string(), + )]; + return Some(self.disconnected_request_output( + state, + CoreEffect::Notify("Wiki databases load failed unexpectedly.".to_string()), + )); + } + }; + + let is_current = finish_request_task(&mut self.wiki_databases_task, output.request_id); + if !is_current { + return Some(self.stale_request_output(state)); + } + + let effects = match output.result { + Ok(databases) => { + self.wiki_databases = databases; + self.refresh_wiki_records_from_databases(); + if self.wiki_records.is_empty() { + vec![CoreEffect::Notify("No wiki databases found.".to_string())] + } else { + vec![ + CoreEffect::SelectFirstListItem, + CoreEffect::Notify(format!( + "Loaded {} wiki databases.", + self.wiki_records.len() + )), + ] + } + } + Err(error) => { + self.wiki_databases.clear(); + self.wiki_records = vec![load_error_record(error.clone())]; + vec![CoreEffect::Notify(format!( + "Wiki databases load failed: {}", + short_error(error.as_str()) + ))] + } + }; + + Some(self.snapshot_output(state, effects)) + } + fn poll_wiki_search_background(&mut self, state: &CoreState) -> Option { let receiver = self.wiki_search_task.receiver.as_ref()?; let output = match poll_pending_task(receiver) { @@ -4529,7 +4627,14 @@ impl KinicProvider { self.result_records = hits .into_iter() .enumerate() - .map(|(index, hit)| record_from_wiki_search_hit(&output.wiki_id, index, hit)) + .map(|(index, hit)| { + record_from_wiki_search_hit( + &output.wiki_id, + &output.database_id, + index, + hit, + ) + }) .collect(); vec![CoreEffect::Notify(format!( "Loaded {} wiki search results.", @@ -4684,7 +4789,7 @@ impl KinicProvider { } KINIC_WIKI_TAB_ID => { self.result_records.clear(); - Vec::new() + vec![self.start_wiki_databases_load()] } KINIC_MARKET_TAB_ID => { vec![CoreEffect::Notify( @@ -4850,8 +4955,6 @@ impl DataProvider for KinicProvider { effects.extend(self.set_tab(id.0.as_str())); if id.0.as_str() == KINIC_INSERT_TAB_ID { self.start_insert_dim_load(); - } else if id.0.as_str() == KINIC_WIKI_TAB_ID { - self.start_selected_wiki_children_load(state); } } CoreAction::ChatSubmit => { @@ -4918,23 +5021,16 @@ impl DataProvider for KinicProvider { CoreAction::CreateSubmit => { let name = state.create_name.trim().to_string(); let description = state.create_description.trim().to_string(); - if name.is_empty() - || (state.create_target_kind == tui_kit_runtime::CreateTargetKind::Memory - && description.is_empty()) - { + if name.is_empty() || description.is_empty() { effects.push(CoreEffect::CreateFormError(Some( - "Name is required. Description is required for memory.".to_string(), + "Name and description are required.".to_string(), ))); } else if self.create_submit_task.in_flight { effects.push(CoreEffect::Notify( "Create request already running.".to_string(), )); } else { - effects.push(self.start_create_submit( - state.create_target_kind, - name, - description, - )); + effects.push(self.start_create_submit(name, description)); } } CoreAction::InsertSubmit => { @@ -5483,6 +5579,7 @@ impl DataProvider for KinicProvider { .or_else(|| self.poll_insert_submit_background(state)) .or_else(|| self.poll_create_cost_background(state)) .or_else(|| self.poll_session_settings_background(state)) + .or_else(|| self.poll_wiki_databases_background(state)) .or_else(|| self.poll_wiki_children_background(state)) .or_else(|| self.poll_wiki_search_background(state)) .or_else(|| self.poll_search_background(state)) @@ -5714,30 +5811,52 @@ fn record_from_memory_summary(memory: MemorySummary) -> KinicRecord { .with_searchable_memory_id_option(memory.searchable_memory_id) } -fn record_from_wiki_summary(wiki: bridge::WikiSummary) -> KinicRecord { - let display_name = match wiki.name.trim() { - "" | "unknown" | "Wiki" => format!("Wiki {}", adapter::short_id(wiki.id.as_str())), - other => other.to_string(), - }; - let summary = format!("Id: {}\nStatus: {}", wiki.id, wiki.status); - let record = KinicRecord::new( - wiki.id.clone(), - display_name, +fn wiki_not_configured_record() -> KinicRecord { + KinicRecord::new( + "wiki-not-configured", + "Wiki canister is not configured", + "wiki", + "Set KINIC_WIKI_CANISTER_ID to browse wiki databases.", + "## Wiki\n\nWiki canister is not configured.\n\nSet `KINIC_WIKI_CANISTER_ID` and refresh this tab.", + ) +} + +fn record_from_wiki_database( + wiki_canister_id: &str, + database: bridge::DatabaseSummary, +) -> KinicRecord { + let status = format!("{:?}", database.status); + let role = format!("{:?}", database.role); + let summary = format!( + "Status: {status}\nRole: {role}\nSize: {} bytes", + database.logical_size_bytes + ); + KinicRecord::new( + format!("wiki-db:{}", database.database_id), + database.database_id.clone(), "wiki", summary, format!( - "## Wiki\n\n- Id: `{}`\n- Status: `{}`\n- Name: `{}`\n\n### Browser\nSelect this wiki to browse `/Wiki` children and search wiki nodes.\n\n### Detail\n{}\n", - wiki.id, wiki.status, wiki.name, wiki.detail + "## Wiki Database\n\n- Canister: `{wiki_canister_id}`\n- Database: `{}`\n- Status: `{status}`\n- Role: `{role}`\n- Logical size: `{}` bytes\n- Archived at ms: `{}`\n- Deleted at ms: `{}`\n\n### Browser\nSelect this database to browse `/Wiki` children and search wiki nodes.\n", + database.database_id, + database.logical_size_bytes, + database + .archived_at_ms + .map(|value| value.to_string()) + .unwrap_or_else(|| "none".to_string()), + database + .deleted_at_ms + .map(|value| value.to_string()) + .unwrap_or_else(|| "none".to_string()) ), - ); - match wiki.searchable_wiki_id { - Some(wiki_id) => record.with_source_wiki_id(wiki_id), - None => record, - } + ) + .with_source_wiki_id(wiki_canister_id.to_string()) + .with_source_wiki_database_id(database.database_id) } fn record_from_wiki_search_hit( wiki_id: &str, + database_id: &str, index: usize, hit: bridge::WikiSearchHit, ) -> KinicRecord { @@ -5747,16 +5866,17 @@ fn record_from_wiki_search_hit( .filter(|value| !value.trim().is_empty()) .unwrap_or("No preview available."); KinicRecord::new( - format!("wiki-search:{wiki_id}:{index}"), + format!("wiki-search:{wiki_id}:{database_id}:{index}"), hit.path.clone(), "wiki", format!("Score: {:.3}", hit.score), format!( - "## Wiki Search Hit\n\n- Wiki: `{wiki_id}`\n- Path: `{}`\n- Score: `{:.3}`\n\n### Preview\n{}\n", + "## Wiki Search Hit\n\n- Wiki: `{wiki_id}`\n- Database: `{database_id}`\n- Path: `{}`\n- Score: `{:.3}`\n\n### Preview\n{}\n", hit.path, hit.score, snippet ), ) .with_source_wiki_id(wiki_id.to_string()) + .with_source_wiki_database_id(database_id.to_string()) } fn display_memory_name(name: &str, detail_name: Option<&str>) -> String { diff --git a/rust/tui/provider/tests.rs b/rust/tui/provider/tests.rs index 9bf1473..ead759a 100644 --- a/rust/tui/provider/tests.rs +++ b/rust/tui/provider/tests.rs @@ -24,6 +24,7 @@ fn live_config() -> TuiConfig { TuiConfig { auth: TuiAuth::resolved_for_tests(), use_mainnet: false, + wiki_canister_id: None, } } diff --git a/rust/tui/provider/tests/live_browser.rs b/rust/tui/provider/tests/live_browser.rs index a025fb1..0dfc0cc 100644 --- a/rust/tui/provider/tests/live_browser.rs +++ b/rust/tui/provider/tests/live_browser.rs @@ -27,7 +27,6 @@ fn poll_initial_memories_background_applies_loaded_memories_and_prefers_saved_de running_memory_summary("aaaaa-aa", "first"), running_memory_summary("bbbbb-bb", "second"), ], - wikis: vec![], }), }) .unwrap(); @@ -57,7 +56,6 @@ fn poll_initial_memories_background_prefetches_active_memory_too() { running_memory_summary("aaaaa-aa", "first"), running_memory_summary("bbbbb-bb", "second"), ], - wikis: vec![], }), }) .unwrap(); @@ -134,7 +132,6 @@ fn poll_initial_memories_background_falls_back_to_first_when_default_missing() { running_memory_summary("aaaaa-aa", "first"), running_memory_summary("bbbbb-bb", "second"), ], - wikis: vec![], }), }) .unwrap(); diff --git a/rust/tui/provider/tests/settings.rs b/rust/tui/provider/tests/settings.rs index e7fb7fd..d47dc28 100644 --- a/rust/tui/provider/tests/settings.rs +++ b/rust/tui/provider/tests/settings.rs @@ -272,7 +272,6 @@ fn poll_background_keeps_create_success_and_default_memory_when_reload_fails() { request_id: 5, result: Ok(bridge::CreateInstanceSuccess { id: "aaaaa-aa".to_string(), - kind: bridge::CreateTargetKind::Memory, instances: None, refresh_warning: Some( "Automatic reload failed after create. Press F5 to refresh. Cause: boom" diff --git a/rust/tui/provider/tests/wiki.rs b/rust/tui/provider/tests/wiki.rs index 176d260..04d6bdd 100644 --- a/rust/tui/provider/tests/wiki.rs +++ b/rust/tui/provider/tests/wiki.rs @@ -1,36 +1,80 @@ use super::*; -fn wiki_summary(id: &str, searchable_wiki_id: Option<&str>, status: &str) -> bridge::WikiSummary { - bridge::WikiSummary { - id: id.to_string(), - status: status.to_string(), - detail: format!("{status} detail"), - searchable_wiki_id: searchable_wiki_id.map(str::to_string), - name: "Wiki".to_string(), +fn wiki_database(database_id: &str, status: bridge::DatabaseStatus) -> bridge::DatabaseSummary { + bridge::DatabaseSummary { + database_id: database_id.to_string(), + status, + role: bridge::DatabaseRole::Owner, + logical_size_bytes: 42, + archived_at_ms: None, + deleted_at_ms: None, } } #[test] -fn wiki_summary_record_marks_only_searchable_wikis_queryable() { - let pending = record_from_wiki_summary(wiki_summary("wiki-pending:abc", None, "pending")); - assert_eq!(pending.source_wiki_id, None); +fn wiki_not_configured_record_is_not_queryable() { + let record = wiki_not_configured_record(); - let running = - record_from_wiki_summary(wiki_summary("launcher-row", Some("aaaaa-aa"), "running")); - assert_eq!(running.source_wiki_id.as_deref(), Some("aaaaa-aa")); + assert_eq!(record.source_wiki_id, None); + assert_eq!(record.source_wiki_database_id, None); + assert!( + record + .content_md + .contains("Wiki canister is not configured") + ); } #[test] -fn selected_wiki_id_follows_displayed_search_results() { - let mut provider = KinicProvider::new(live_config()); - provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); - provider.wiki_records = vec![ - record_from_wiki_summary(wiki_summary("launcher-a", Some("wiki-a"), "running")), - record_from_wiki_summary(wiki_summary("launcher-b", Some("wiki-b"), "running")), +fn wiki_database_record_keeps_canister_and_database_ids() { + let record = record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-owner-123", bridge::DatabaseStatus::Hot), + ); + + assert_eq!(record.source_wiki_id.as_deref(), Some("aaaaa-aa")); + assert_eq!( + record.source_wiki_database_id.as_deref(), + Some("db-owner-123") + ); + assert!(record.summary.contains("Status: Hot")); + assert!(record.summary.contains("Role: Owner")); + assert!(!record.summary.contains("Schema:")); +} + +#[test] +fn wiki_database_records_sort_hot_database_first() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.wiki_databases = vec![ + wiki_database("archived-db", bridge::DatabaseStatus::Archived), + wiki_database("hot-db", bridge::DatabaseStatus::Hot), ]; + + provider.refresh_wiki_records_from_databases(); + + assert_eq!( + provider.wiki_records[0].source_wiki_database_id.as_deref(), + Some("hot-db") + ); +} + +#[test] +fn selected_wiki_target_follows_displayed_search_results() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_records = vec![record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-a", bridge::DatabaseStatus::Hot), + )]; provider.result_records = vec![ record_from_wiki_search_hit( - "wiki-a", + "aaaaa-aa", + "db-a", 0, bridge::WikiSearchHit { path: "/Wiki/a.md".to_string(), @@ -39,7 +83,8 @@ fn selected_wiki_id_follows_displayed_search_results() { }, ), record_from_wiki_search_hit( - "wiki-a", + "aaaaa-aa", + "db-a", 1, bridge::WikiSearchHit { path: "/Wiki/b.md".to_string(), @@ -54,21 +99,30 @@ fn selected_wiki_id_follows_displayed_search_results() { selected_index: Some(1), ..CoreState::default() }; - assert_eq!(provider.selected_wiki_id(&state).as_deref(), Some("wiki-a")); + assert_eq!( + provider.selected_wiki_target(&state), + Some(("aaaaa-aa".to_string(), "db-a".to_string())) + ); } #[test] -fn wiki_content_render_uses_cache_without_starting_query() { - let mut provider = KinicProvider::new(live_config()); +fn wiki_content_render_uses_database_cache_without_starting_query() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); provider.wiki_children_cache.insert( - "wiki-a".to_string(), + "db-a".to_string(), WikiChildrenContent { body_lines: vec!["- /Wiki/index.md (file)".to_string()], index_preview: Some(vec!["cached preview".to_string()]), }, ); - let record = record_from_wiki_summary(wiki_summary("launcher-a", Some("wiki-a"), "running")); + let record = record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-a", bridge::DatabaseStatus::Hot), + ); let content = provider.selected_content_for_record(&record, &CoreState::default()); @@ -80,3 +134,28 @@ fn wiki_content_render_uses_cache_without_starting_query() { })); assert!(!provider.wiki_children_task.in_flight); } + +#[test] +fn set_wiki_tab_starts_database_load_once() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + let state = CoreState::default(); + + let output = provider + .handle_action(&CoreAction::SetTab(KINIC_WIKI_TAB_ID.into()), &state) + .expect("set tab should build output"); + + let refresh_count = output + .effects + .iter() + .filter(|effect| { + matches!( + effect, + CoreEffect::Notify(message) if message == "Refreshing wiki databases..." + ) + }) + .count(); + assert_eq!(refresh_count, 1); +} diff --git a/tui/crates/tui-kit-host/src/form_tab_flow.rs b/tui/crates/tui-kit-host/src/form_tab_flow.rs index aae97a4..69b667e 100644 --- a/tui/crates/tui-kit-host/src/form_tab_flow.rs +++ b/tui/crates/tui-kit-host/src/form_tab_flow.rs @@ -62,7 +62,6 @@ pub fn reset_form_state_for_tab(state: &mut CoreState, tab_id: &str) { fn reset_create_fields(state: &mut CoreState) { state.create_name.clear(); state.create_description.clear(); - state.create_target_kind = tui_kit_runtime::CreateTargetKind::Memory; } fn reset_insert_fields(state: &mut CoreState) { diff --git a/tui/crates/tui-kit-host/src/lib.rs b/tui/crates/tui-kit-host/src/lib.rs index 20f4fb1..b18ed01 100644 --- a/tui/crates/tui-kit-host/src/lib.rs +++ b/tui/crates/tui-kit-host/src/lib.rs @@ -362,8 +362,7 @@ pub fn execute_effects_to_status(state: &mut CoreState, effects: Vec state.create_submit_state = CreateSubmitState::Idle; state.create_spinner_frame = 0; state.create_error = None; - state.create_target_kind = tui_kit_runtime::CreateTargetKind::Memory; - state.create_focus = CreateModalFocus::Type; + state.create_focus = CreateModalFocus::Name; } CoreEffect::ResetInsertFormForRepeat => { if state.insert_tag_is_auto { diff --git a/tui/crates/tui-kit-host/src/runtime_loop.rs b/tui/crates/tui-kit-host/src/runtime_loop.rs index f2e253b..d18e485 100644 --- a/tui/crates/tui-kit-host/src/runtime_loop.rs +++ b/tui/crates/tui-kit-host/src/runtime_loop.rs @@ -212,7 +212,6 @@ pub fn run_provider_app_with_hooks>( .show_create_modal(false) .create_name(&state.create_name) .create_description(&state.create_description) - .create_target_kind(state.create_target_kind) .create_description_cursor(textarea_cursor( active_textarea(&state), ActiveTextarea::CreateDescription, @@ -698,7 +697,6 @@ fn build_ui<'a>( .show_create_modal(false) .create_name(&state.create_name) .create_description(&state.create_description) - .create_target_kind(state.create_target_kind) .create_description_cursor(textarea_cursor( active_textarea(state), ActiveTextarea::CreateDescription, diff --git a/tui/crates/tui-kit-render/src/ui/app/screens/create/mod.rs b/tui/crates/tui-kit-render/src/ui/app/screens/create/mod.rs index fd9f8b1..acedddf 100644 --- a/tui/crates/tui-kit-render/src/ui/app/screens/create/mod.rs +++ b/tui/crates/tui-kit-render/src/ui/app/screens/create/mod.rs @@ -146,13 +146,11 @@ impl CreateFormLines<'_> { } fn create_form_lines<'a>(ui: &'a TuiKitUi<'a>, layout: CreateScreenLayout) -> CreateFormLines<'a> { - let type_style = create_field_style(ui, CreateModalFocus::Type); let name_style = create_field_style(ui, CreateModalFocus::Name); let description_style = create_field_style(ui, CreateModalFocus::Description); let submit_style = create_field_style(ui, CreateModalFocus::Submit); let close_hint = create_close_hint(ui); let submit_text = create_submit_text(ui); - let type_hint = next_entry_hint(ui, CreateModalFocus::Type); let name_hint = next_entry_hint(ui, CreateModalFocus::Name); let description_hint = next_entry_hint(ui, CreateModalFocus::Description); let submit_hint = next_entry_hint(ui, CreateModalFocus::Submit); @@ -178,19 +176,6 @@ fn create_form_lines<'a>(ui: &'a TuiKitUi<'a>, layout: CreateScreenLayout) -> Cr let mut lines = Vec::with_capacity(if ui.create_error.is_some() { 20 } else { 18 }); let mut rows = FormRows::default(); - let type_value = format!("< {} >", ui.create_target_kind.label()); - rows.push_labeled_row( - &mut lines, - Line::from(Span::styled("Type", ui.theme.style_dim())), - CreateModalFocus::Type, - Line::from(vec![ - create_input_indent(), - Span::styled(type_value.clone(), type_style), - type_hint, - ]), - type_value.as_str(), - ); - lines.push(Line::from("")); rows.push_labeled_row( &mut lines, Line::from(Span::styled( @@ -460,14 +445,10 @@ fn display_create_value<'a>(value: &'a str, placeholder: &'a str) -> &'a str { } fn create_submit_text(ui: &TuiKitUi<'_>) -> String { - let idle_label = match ui.create_target_kind { - tui_kit_runtime::CreateTargetKind::Memory => ui.ui_config.create.submit_label.as_str(), - tui_kit_runtime::CreateTargetKind::Wiki => "Create Wiki", - }; submit_button_text( &ui.create_submit_state, ui.create_spinner_frame, - idle_label, + ui.ui_config.create.submit_label.as_str(), ui.ui_config.create.submit_pending_label.as_str(), ) } @@ -492,7 +473,7 @@ fn fit_single_line(value: &str, max_width: u16, keep_end: bool) -> String { } } fn first_create_focus() -> CreateModalFocus { - CreateModalFocus::Type + CreateModalFocus::Name } fn take_prefix_by_width(value: &str, max_width: u16) -> String { let mut width = 0; diff --git a/tui/crates/tui-kit-render/src/ui/app/ui_builder.rs b/tui/crates/tui-kit-render/src/ui/app/ui_builder.rs index d74cbad..00c6731 100644 --- a/tui/crates/tui-kit-render/src/ui/app/ui_builder.rs +++ b/tui/crates/tui-kit-render/src/ui/app/ui_builder.rs @@ -4,10 +4,9 @@ use crate::ui::animation::AnimationState; use crate::ui::model::{UiContextNode, UiItemContent, UiItemSummary}; use crate::ui::search::CompletionCandidate; use tui_kit_runtime::{ - AccessControlModalState, CreateCostState, CreateModalFocus, CreateSubmitState, - CreateTargetKind, InsertFormFocus, InsertMode, MemorySelection, PickerState, - RemoveMemoryModalState, RenameMemoryModalState, SearchScope, SettingsSnapshot, - TextInputModalState, TransferModalState, + AccessControlModalState, CreateCostState, CreateModalFocus, CreateSubmitState, InsertFormFocus, + InsertMode, MemorySelection, PickerState, RemoveMemoryModalState, RenameMemoryModalState, + SearchScope, SettingsSnapshot, TextInputModalState, TransferModalState, }; use super::{Focus, TabId, TabSpec, TuiKitUi, UiConfig}; @@ -176,12 +175,6 @@ impl<'a> TuiKitUi<'a> { self } - #[must_use] - pub fn create_target_kind(mut self, value: CreateTargetKind) -> Self { - self.create_target_kind = value; - self - } - #[must_use] pub fn create_description_cursor(mut self, value: Option<(usize, usize)>) -> Self { self.create_description_cursor = value; diff --git a/tui/crates/tui-kit-render/src/ui/app/ui_state.rs b/tui/crates/tui-kit-render/src/ui/app/ui_state.rs index 8246e7f..f2b72c9 100644 --- a/tui/crates/tui-kit-render/src/ui/app/ui_state.rs +++ b/tui/crates/tui-kit-render/src/ui/app/ui_state.rs @@ -6,9 +6,8 @@ use crate::ui::search::CompletionCandidate; use crate::ui::theme::Theme; use tui_kit_runtime::{ AccessControlModalState, ChatScope, CreateCostState, CreateModalFocus, CreateSubmitState, - CreateTargetKind, InsertFormFocus, InsertMode, MemorySelection, PickerState, - RemoveMemoryModalState, RenameMemoryModalState, SearchScope, SettingsSnapshot, - TextInputModalState, TransferModalState, + InsertFormFocus, InsertMode, MemorySelection, PickerState, RemoveMemoryModalState, + RenameMemoryModalState, SearchScope, SettingsSnapshot, TextInputModalState, TransferModalState, }; use super::{Focus, TabId, TabSpec, UiConfig, default_tab_specs}; @@ -42,7 +41,6 @@ pub struct TuiKitUi<'a> { pub(super) show_create_modal: bool, pub(super) create_name: &'a str, pub(super) create_description: &'a str, - pub(super) create_target_kind: CreateTargetKind, pub(super) create_description_cursor: Option<(usize, usize)>, pub(super) create_submit_state: CreateSubmitState, pub(super) create_spinner_frame: usize, @@ -119,7 +117,6 @@ impl<'a> TuiKitUi<'a> { show_create_modal: false, create_name: "", create_description: "", - create_target_kind: CreateTargetKind::Memory, create_description_cursor: None, create_submit_state: CreateSubmitState::Idle, create_spinner_frame: 0, diff --git a/tui/crates/tui-kit-runtime/src/form_descriptor.rs b/tui/crates/tui-kit-runtime/src/form_descriptor.rs index 7d4bf4d..ef16679 100644 --- a/tui/crates/tui-kit-runtime/src/form_descriptor.rs +++ b/tui/crates/tui-kit-runtime/src/form_descriptor.rs @@ -88,7 +88,7 @@ struct InsertFieldSpec { } const CREATE_DESCRIPTOR: FormDescriptor = FormDescriptor { kind: FormKind::Create, - first_focus: FormFocus::Create(CreateModalFocus::Type), + first_focus: FormFocus::Create(CreateModalFocus::Name), reset_kind: FormResetKind::Create, }; const INSERT_DESCRIPTOR: FormDescriptor = FormDescriptor { @@ -96,13 +96,7 @@ const INSERT_DESCRIPTOR: FormDescriptor = FormDescriptor { first_focus: FormFocus::Insert(InsertFormFocus::Mode), reset_kind: FormResetKind::Insert, }; -const CREATE_FIELDS: [FormFieldSpec; 4] = [ - FormFieldSpec { - focus: FormFocus::Create(CreateModalFocus::Type), - enter_command: FormCommand::NextField, - accepts_input: false, - supports_horizontal_change: true, - }, +const CREATE_FIELDS: [FormFieldSpec; 3] = [ FormFieldSpec { focus: FormFocus::Create(CreateModalFocus::Name), enter_command: FormCommand::NextField, @@ -243,8 +237,6 @@ pub fn form_command_to_action(kind: FormKind, command: FormCommand) -> Option Some(CoreAction::CreateNextField), (FormKind::Create, FormCommand::PrevField) => Some(CoreAction::CreatePrevField), (FormKind::Create, FormCommand::Submit) => Some(CoreAction::CreateSubmit), - (FormKind::Create, FormCommand::HorizontalChangePrev) => Some(CoreAction::CreatePrevType), - (FormKind::Create, FormCommand::HorizontalChangeNext) => Some(CoreAction::CreateNextType), (FormKind::Insert, FormCommand::Input(c)) => Some(CoreAction::InsertInput(c)), (FormKind::Insert, FormCommand::Backspace) => Some(CoreAction::InsertBackspace), (FormKind::Insert, FormCommand::NextField) => Some(CoreAction::InsertNextField), @@ -277,8 +269,6 @@ pub fn core_action_to_form_command(action: &CoreAction) -> Option<(FormKind, For CoreAction::CreateNextField => Some((FormKind::Create, FormCommand::NextField)), CoreAction::CreatePrevField => Some((FormKind::Create, FormCommand::PrevField)), CoreAction::CreateSubmit => Some((FormKind::Create, FormCommand::Submit)), - CoreAction::CreatePrevType => Some((FormKind::Create, FormCommand::HorizontalChangePrev)), - CoreAction::CreateNextType => Some((FormKind::Create, FormCommand::HorizontalChangeNext)), _ => None, } } diff --git a/tui/crates/tui-kit-runtime/src/lib.rs b/tui/crates/tui-kit-runtime/src/lib.rs index 1e73486..0cacafb 100644 --- a/tui/crates/tui-kit-runtime/src/lib.rs +++ b/tui/crates/tui-kit-runtime/src/lib.rs @@ -143,39 +143,11 @@ pub fn tab_entry_focus(tab_id: &str) -> Option { #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum CreateModalFocus { #[default] - Type, Name, Description, Submit, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum CreateTargetKind { - #[default] - Memory, - Wiki, -} - -impl CreateTargetKind { - pub fn label(self) -> &'static str { - match self { - Self::Memory => "Memory", - Self::Wiki => "Wiki", - } - } - - pub fn next(self) -> Self { - match self { - Self::Memory => Self::Wiki, - Self::Wiki => Self::Memory, - } - } - - pub fn prev(self) -> Self { - self.next() - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum InsertMode { #[default] @@ -687,7 +659,6 @@ pub struct CoreState { pub chat_scope_label: Option, pub create_name: String, pub create_description: String, - pub create_target_kind: CreateTargetKind, pub create_submit_state: CreateSubmitState, pub create_spinner_frame: usize, pub create_error: Option, @@ -744,7 +715,6 @@ impl Default for CoreState { chat_scope_label: None, create_name: String::new(), create_description: String::new(), - create_target_kind: CreateTargetKind::default(), create_submit_state: CreateSubmitState::default(), create_spinner_frame: 0, create_error: None, @@ -868,8 +838,6 @@ pub enum CoreAction { CreateBackspace, CreateNextField, CreatePrevField, - CreatePrevType, - CreateNextType, CreateRefresh, RefreshCurrentView, CreateSubmit, @@ -1443,7 +1411,6 @@ pub fn apply_core_action(state: &mut CoreState, action: &CoreAction) { }, CoreAction::CreateInput(c) => { match state.create_focus { - CreateModalFocus::Type => {} CreateModalFocus::Name => state.create_name.push(*c), CreateModalFocus::Description => state.create_description.push(*c), CreateModalFocus::Submit => {} @@ -1454,7 +1421,6 @@ pub fn apply_core_action(state: &mut CoreState, action: &CoreAction) { } } CoreAction::CreateBackspace => match state.create_focus { - CreateModalFocus::Type => {} CreateModalFocus::Name => { state.create_name.pop(); } @@ -1465,32 +1431,18 @@ pub fn apply_core_action(state: &mut CoreState, action: &CoreAction) { }, CoreAction::CreateNextField => { state.create_focus = match state.create_focus { - CreateModalFocus::Type => CreateModalFocus::Name, CreateModalFocus::Name => CreateModalFocus::Description, CreateModalFocus::Description => CreateModalFocus::Submit, - CreateModalFocus::Submit => CreateModalFocus::Type, + CreateModalFocus::Submit => CreateModalFocus::Name, }; } CoreAction::CreatePrevField => { state.create_focus = match state.create_focus { - CreateModalFocus::Type => CreateModalFocus::Submit, - CreateModalFocus::Name => CreateModalFocus::Type, + CreateModalFocus::Name => CreateModalFocus::Submit, CreateModalFocus::Description => CreateModalFocus::Name, CreateModalFocus::Submit => CreateModalFocus::Description, }; } - CoreAction::CreatePrevType => { - if state.create_focus == CreateModalFocus::Type { - state.create_target_kind = state.create_target_kind.prev(); - state.create_error = None; - } - } - CoreAction::CreateNextType => { - if state.create_focus == CreateModalFocus::Type { - state.create_target_kind = state.create_target_kind.next(); - state.create_error = None; - } - } CoreAction::CreateRefresh => { state.create_cost_state = CreateCostState::Loading; state.create_spinner_frame = 0; @@ -1611,7 +1563,7 @@ pub fn apply_core_action(state: &mut CoreState, action: &CoreAction) { state.focus = PaneFocus::Form; match kinic_tabs::tab_kind(state.current_tab_id.as_str()) { kinic_tabs::TabKind::CreateForm => { - state.create_focus = CreateModalFocus::Type; + state.create_focus = CreateModalFocus::Name; } kinic_tabs::TabKind::InsertForm => { state.insert_focus = InsertFormFocus::Mode; @@ -1935,7 +1887,6 @@ fn start_insert_submit(state: &mut CoreState) { fn apply_create_text_input(state: &mut CoreState, c: char) { match state.create_focus { - CreateModalFocus::Type => {} CreateModalFocus::Name => state.create_name.push(c), CreateModalFocus::Description => state.create_description.push(c), CreateModalFocus::Submit => {} @@ -1945,7 +1896,6 @@ fn apply_create_text_input(state: &mut CoreState, c: char) { fn apply_create_backspace(state: &mut CoreState) { match state.create_focus { - CreateModalFocus::Type => {} CreateModalFocus::Name => { state.create_name.pop(); } @@ -1971,17 +1921,15 @@ fn start_create_submit(state: &mut CoreState) { fn next_create_focus(focus: CreateModalFocus) -> CreateModalFocus { match focus { - CreateModalFocus::Type => CreateModalFocus::Name, CreateModalFocus::Name => CreateModalFocus::Description, CreateModalFocus::Description => CreateModalFocus::Submit, - CreateModalFocus::Submit => CreateModalFocus::Type, + CreateModalFocus::Submit => CreateModalFocus::Name, } } fn prev_create_focus(focus: CreateModalFocus) -> CreateModalFocus { match focus { - CreateModalFocus::Type => CreateModalFocus::Submit, - CreateModalFocus::Name => CreateModalFocus::Type, + CreateModalFocus::Name => CreateModalFocus::Submit, CreateModalFocus::Description => CreateModalFocus::Name, CreateModalFocus::Submit => CreateModalFocus::Description, } From 484643b2c316659b53be506b88a1c94696938fe6 Mon Sep 17 00:00:00 2001 From: hude Date: Mon, 11 May 2026 14:20:53 +0900 Subject: [PATCH 03/10] Refine Wiki browser navigation and three-pane rendering --- README.md | 12 +- docs/cli.md | 26 + docs/tui.md | 4 +- rust/cli_defs.rs | 180 +++++- rust/cli_policy.rs | 11 + rust/commands/mod.rs | 2 + rust/commands/wiki.rs | 486 ++++++++++++++ rust/lib.rs | 1 + rust/tui/bridge.rs | 270 ++------ rust/tui/mod.rs | 25 +- rust/tui/provider/mod.rs | 530 +++++++++++++-- rust/tui/provider/tests.rs | 4 +- rust/tui/provider/tests/wiki.rs | 234 ++++++- rust/tui/ui_config.rs | 9 +- rust/wiki_bridge.rs | 379 +++++++++++ tests/fixtures/capabilities_golden.json | 605 ++++++++++++++++++ tui/crates/tui-kit-host/src/lib_tests.rs | 7 +- tui/crates/tui-kit-host/src/runtime_loop.rs | 2 + .../tui-kit-host/src/runtime_loop_tests.rs | 8 +- .../tui-kit-render/src/ui/app/screens/mod.rs | 28 +- .../src/ui/app/screens/placeholder/mod.rs | 54 -- .../tui-kit-render/src/ui/app/screens/wiki.rs | 264 ++++++++ .../src/ui/app/shared/status.rs | 5 +- .../tui-kit-render/src/ui/app/ui_builder.rs | 8 +- .../tui-kit-render/src/ui/app/ui_state.rs | 5 +- tui/crates/tui-kit-runtime/src/kinic_tabs.rs | 12 +- tui/crates/tui-kit-runtime/src/lib.rs | 125 +++- 27 files changed, 2875 insertions(+), 421 deletions(-) create mode 100644 rust/commands/wiki.rs create mode 100644 rust/wiki_bridge.rs delete mode 100644 tui/crates/tui-kit-render/src/ui/app/screens/placeholder/mod.rs create mode 100644 tui/crates/tui-kit-render/src/ui/app/screens/wiki.rs diff --git a/README.md b/README.md index cf8decc..57692f0 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ For the fastest first success, start with `Inline Text`. It avoids file chooser ### Step 1: Start the TUI and Learn the Layout -When the TUI starts, it opens on the `Memories` tab. Use `1` to `6` to switch tabs, `Tab` / `Shift+Tab` to move focus, and `?` to open help. In normal list navigation, `q` quits and `Ctrl+R` refreshes the current view. +When the TUI starts, it opens on the `Memories` tab. Use `1` to `5` to switch tabs, `Tab` / `Shift+Tab` to move focus, and `?` to open help. In normal list navigation, `q` quits and `Ctrl+R` refreshes the current view. ![Memories tab screenshot](./docs/images/tui-memories.png) @@ -129,9 +129,17 @@ What to understand first: - `Insert`: add files, text, or manual embeddings - `Create`: create a new memory - `Wiki`: browse databases from the configured wiki canister; `KINIC_WIKI_CANISTER_ID` can override the default -- `Market`: reserved and not implemented yet - `Settings`: principal, balance, default memory, saved tags, and retrieval settings +Wiki write/delete and database management are available from the CLI: + +```bash +kinic-cli --ic --identity alice wiki database list +kinic-cli --ic --identity alice wiki read --database-id DATABASE_ID --path /Wiki/index.md +kinic-cli --ic --identity alice wiki write --database-id DATABASE_ID --path /Wiki/new.md --input ./new.md +kinic-cli --ic --identity alice wiki delete --database-id DATABASE_ID --path /Wiki/new.md --yes +``` + ### Step 2: Confirm Identity and Balance Open `Settings` and confirm that `Principal ID` matches the identity you launched with. Then check whether `KINIC balance` is high enough to create a memory. If needed, you can transfer tokens from the transfer modal, and `Ctrl+R` refreshes both `Principal ID` and `KINIC balance`. diff --git a/docs/cli.md b/docs/cli.md index c11adb5..229029c 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -65,6 +65,7 @@ Agent-friendly discovery tips: - Start with `kinic-cli --help` for auth mode guidance and top-level entrypoints - Start with `kinic-cli capabilities` to get a JSON execution contract for global flags, auth sources, output behavior, major arguments, and arg-group constraints - Use `kinic-cli prefs --help` to inspect the JSON contract for shared local preferences +- Use `kinic-cli wiki --help` to inspect Wiki database, read/search, and write/delete operations against the configured Wiki canister - `capabilities` and `prefs` commands return JSON; `list`, `show`, and `search` also support `--json` for agent/script consumption while keeping human-friendly text output by default - Keychain failures use stable text prefixes such as `KEYCHAIN_LOOKUP_FAILED`, `KEYCHAIN_ACCESS_DENIED`, `KEYCHAIN_INTERACTION_NOT_ALLOWED`, and `KEYCHAIN_ERROR`; agents should branch on the leading `[KEYCHAIN_*]` code instead of parsing the rest of the sentence @@ -132,6 +133,31 @@ Notes: - Delegations are stored at `~/.config/kinic/identity.json`. - The login flow uses a local callback on port `8620`. +### Wiki CLI + +`wiki` operates the configured Wiki canister. It uses `xis3j-paaaa-aaaai-axumq-cai` by default and `KINIC_WIKI_CANISTER_ID` can override that target. + +```bash +cargo run -- --ic --identity alice wiki database list +cargo run -- --ic --identity alice wiki children --database-id DATABASE_ID --path /Wiki +cargo run -- --ic --identity alice wiki read --database-id DATABASE_ID --path /Wiki/index.md +cargo run -- --ic --identity alice wiki search --database-id DATABASE_ID "release notes" +``` + +Write operations are CLI-only: + +```bash +cargo run -- --ic --identity alice wiki write \ + --database-id DATABASE_ID \ + --path /Wiki/new.md \ + --input ./new.md + +cargo run -- --ic --identity alice wiki delete \ + --database-id DATABASE_ID \ + --path /Wiki/new.md \ + --yes +``` + ### Convert PDF to markdown (inspect only) ```bash diff --git a/docs/tui.md b/docs/tui.md index 2e0e3bd..3f6a506 100644 --- a/docs/tui.md +++ b/docs/tui.md @@ -75,13 +75,12 @@ If you want to try File mode, either install `yazi` first or type the file path ## Layout -The TUI has six tabs. +The TUI has five tabs. - `Memories`: list, search, and details - `Insert`: add data - `Create`: create a new memory - `Wiki`: browse databases from the configured wiki canister; `KINIC_WIKI_CANISTER_ID` can override the default -- `Market`: reserved for future use and currently not implemented - `Settings`: view and change current connection info and saved settings The `Memories` tab opens first when the TUI starts. @@ -309,7 +308,6 @@ The main saved values are: - `--identity` is required - `--ii` is not supported yet -- The `Market` tab is not implemented yet - `pdftotext` is required for PDF insertion - There is no dedicated copy shortcut for Principal ID - Local use requires a prepared local replica and supporting canisters diff --git a/rust/cli_defs.rs b/rust/cli_defs.rs index f76794f..ecc713c 100644 --- a/rust/cli_defs.rs +++ b/rust/cli_defs.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use clap::{ArgGroup, Args, Parser, Subcommand}; +use clap::{ArgGroup, Args, Parser, Subcommand, ValueEnum}; pub fn parse_identity_arg(value: &str) -> Result { if value.trim().is_empty() { @@ -153,6 +153,10 @@ pub enum Command { after_help = "Configuration:\n Set KINIC_TOOL_IDENTITY=\n Set KINIC_TOOL_NETWORK=local|mainnet\n\nNotes:\n tools serve does not accept global --identity, --ii, --ic, or --identity-path." )] Tools(ToolsArgs), + #[command( + about = "Operate the configured Wiki canister. Requires --identity or --ii. Returns text output by default." + )] + Wiki(WikiArgs), #[command( about = "Launch the Kinic terminal UI. Requires global --identity . --ii is not supported. Returns an interactive TUI, not JSON.", after_help = "Requires:\n kinic-cli --identity tui\n\nReturns:\n Interactive terminal UI.\n\nExample:\n kinic-cli --identity alice tui" @@ -175,6 +179,180 @@ pub struct CreateArgs { #[derive(Args, Debug, Default)] pub struct TuiArgs {} +#[derive(Args, Debug)] +pub struct WikiArgs { + #[command(subcommand)] + pub command: WikiCommand, +} + +#[derive(Subcommand, Debug)] +pub enum WikiCommand { + #[command(about = "Manage Wiki databases")] + Database(WikiDatabaseArgs), + #[command(about = "Read a Wiki node")] + Read(WikiReadArgs), + #[command(about = "List Wiki children under a path")] + Children(WikiChildrenArgs), + #[command(about = "Search Wiki nodes")] + Search(WikiSearchArgs), + #[command(about = "Write a Wiki node from a file")] + Write(WikiWriteArgs), + #[command(about = "Append file contents to a Wiki node")] + Append(WikiAppendArgs), + #[command(about = "Replace text in a Wiki node")] + Edit(WikiEditArgs), + #[command(about = "Delete a Wiki node. Requires --yes.")] + Delete(WikiDeleteArgs), +} + +#[derive(Args, Debug)] +pub struct WikiDatabaseArgs { + #[command(subcommand)] + pub command: WikiDatabaseCommand, +} + +#[derive(Subcommand, Debug)] +pub enum WikiDatabaseCommand { + #[command(about = "List databases visible to the caller")] + List(WikiJsonArgs), + #[command(about = "Create a new database")] + Create(WikiJsonArgs), + #[command(about = "Grant database access to a principal")] + Grant(WikiDatabaseGrantArgs), +} + +#[derive(Args, Debug, Default)] +pub struct WikiJsonArgs { + #[arg(long, help = "Return machine-readable JSON output")] + pub json: bool, +} + +#[derive(Args, Debug)] +pub struct WikiDatabaseGrantArgs { + pub database_id: String, + pub principal: String, + #[arg(value_enum)] + pub role: WikiDatabaseRoleArg, + #[arg(long, help = "Return machine-readable JSON output")] + pub json: bool, +} + +#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)] +pub enum WikiDatabaseRoleArg { + Owner, + Writer, + Reader, +} + +#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)] +pub enum WikiNodeKindArg { + File, + Source, +} + +#[derive(Args, Debug)] +pub struct WikiReadArgs { + #[arg(long, required = true)] + pub database_id: String, + #[arg(long, required = true)] + pub path: String, + #[arg(long, help = "Return machine-readable JSON output")] + pub json: bool, +} + +#[derive(Args, Debug)] +pub struct WikiChildrenArgs { + #[arg(long, required = true)] + pub database_id: String, + #[arg(long, default_value = "/Wiki")] + pub path: String, + #[arg(long, help = "Return machine-readable JSON output")] + pub json: bool, +} + +#[derive(Args, Debug)] +pub struct WikiSearchArgs { + #[arg(long, required = true)] + pub database_id: String, + pub query: String, + #[arg(long, default_value = "/Wiki")] + pub prefix: String, + #[arg(long, default_value_t = 10)] + pub top_k: u32, + #[arg(long, help = "Return machine-readable JSON output")] + pub json: bool, +} + +#[derive(Args, Debug)] +pub struct WikiWriteArgs { + #[arg(long, required = true)] + pub database_id: String, + #[arg(long, required = true)] + pub path: String, + #[arg(long, value_name = "PATH", required = true)] + pub input: PathBuf, + #[arg(long, value_enum, default_value_t = WikiNodeKindArg::File)] + pub kind: WikiNodeKindArg, + #[arg(long, default_value = "{}")] + pub metadata_json: String, + #[arg(long)] + pub expected_etag: Option, + #[arg(long, help = "Return machine-readable JSON output")] + pub json: bool, +} + +#[derive(Args, Debug)] +pub struct WikiAppendArgs { + #[arg(long, required = true)] + pub database_id: String, + #[arg(long, required = true)] + pub path: String, + #[arg(long, value_name = "PATH", required = true)] + pub input: PathBuf, + #[arg(long)] + pub expected_etag: Option, + #[arg(long)] + pub separator: Option, + #[arg(long, value_enum)] + pub kind: Option, + #[arg(long)] + pub metadata_json: Option, + #[arg(long, help = "Return machine-readable JSON output")] + pub json: bool, +} + +#[derive(Args, Debug)] +pub struct WikiEditArgs { + #[arg(long, required = true)] + pub database_id: String, + #[arg(long, required = true)] + pub path: String, + #[arg(long, required = true)] + pub old_text: String, + #[arg(long, required = true)] + pub new_text: String, + #[arg(long)] + pub replace_all: bool, + #[arg(long)] + pub expected_etag: Option, + #[arg(long, help = "Return machine-readable JSON output")] + pub json: bool, +} + +#[derive(Args, Debug)] +pub struct WikiDeleteArgs { + #[arg(long, required = true)] + pub database_id: String, + #[arg(long, required = true)] + pub path: String, + #[arg(long)] + pub expected_etag: Option, + #[arg(long, help = "Confirm deletion")] + pub yes: bool, + #[arg(long, help = "Return machine-readable JSON output")] + pub json: bool, +} + #[derive(Args, Debug)] pub struct ToolsArgs { #[command(subcommand)] diff --git a/rust/cli_policy.rs b/rust/cli_policy.rs index 55d2270..87cbae9 100644 --- a/rust/cli_policy.rs +++ b/rust/cli_policy.rs @@ -116,6 +116,17 @@ pub fn command_policy_for_path(path: &str) -> CommandPolicy { }; } + if path == "wiki" || path.starts_with("wiki.") { + return CommandPolicy { + auth_sources: &["global_identity", "global_ii"], + conditional_auth: &[], + output_default: "text", + output_supported: &["text", "json"], + interactive: false, + global_flags_supported: GLOBAL_FLAGS_ALL, + }; + } + CommandPolicy { auth_sources: &["global_identity", "global_ii"], conditional_auth: &[], diff --git a/rust/commands/mod.rs b/rust/commands/mod.rs index d6ec654..cc708bb 100644 --- a/rust/commands/mod.rs +++ b/rust/commands/mod.rs @@ -25,6 +25,7 @@ pub mod show; pub mod tagged_embeddings; pub mod transfer; pub mod update; +pub mod wiki; #[derive(Clone)] pub struct CommandContext { @@ -58,6 +59,7 @@ pub async fn run_command(command: Command, ctx: CommandContext) -> Result<()> { Command::AskAi(args) => ask_ai::handle(args, &ctx).await, Command::Login(args) => ii_login::handle(args, &ctx).await, Command::Tools(_) => unreachable!("tools command is handled before agent setup"), + Command::Wiki(args) => wiki::handle(args, &ctx).await, Command::Tui(_) => unreachable!("TUI command is handled before command dispatch"), } } diff --git a/rust/commands/wiki.rs b/rust/commands/wiki.rs new file mode 100644 index 0000000..db71299 --- /dev/null +++ b/rust/commands/wiki.rs @@ -0,0 +1,486 @@ +use std::fs; + +use anyhow::{Result, bail}; + +use crate::{ + cli::{ + WikiAppendArgs, WikiArgs, WikiChildrenArgs, WikiCommand, WikiDatabaseCommand, + WikiDatabaseRoleArg, WikiDeleteArgs, WikiEditArgs, WikiJsonArgs, WikiNodeKindArg, + WikiReadArgs, WikiSearchArgs, WikiWriteArgs, + }, + commands::CommandContext, + wiki_bridge::{ + AppendNodeRequest, DatabaseRole, DeleteNodeRequest, EditNodeRequest, ListChildrenRequest, + NodeKind, SearchNodesRequest, SearchPreviewMode, WikiClient, WriteNodeRequest, + wiki_canister_id_from_env, + }, +}; + +pub async fn handle(args: WikiArgs, ctx: &CommandContext) -> Result<()> { + let agent = ctx.agent_factory.build().await?; + let client = WikiClient::new(agent, wiki_canister_id_from_env())?; + match args.command { + WikiCommand::Database(database) => match database.command { + WikiDatabaseCommand::List(args) => list_databases(&client, args).await, + WikiDatabaseCommand::Create(args) => create_database(&client, args).await, + WikiDatabaseCommand::Grant(args) => grant_database(&client, args).await, + }, + WikiCommand::Read(args) => read_node(&client, args).await, + WikiCommand::Children(args) => list_children(&client, args).await, + WikiCommand::Search(args) => search_nodes(&client, args).await, + WikiCommand::Write(args) => write_node(&client, args).await, + WikiCommand::Append(args) => append_node(&client, args).await, + WikiCommand::Edit(args) => edit_node(&client, args).await, + WikiCommand::Delete(args) => delete_node(&client, args).await, + } +} + +async fn list_databases(client: &WikiClient, args: WikiJsonArgs) -> Result<()> { + let databases = client.list_databases().await?; + if args.json { + println!("{}", serde_json::to_string_pretty(&databases)?); + } else { + for database in databases { + println!( + "{}\t{:?}\t{:?}\t{}", + database.database_id, database.status, database.role, database.logical_size_bytes + ); + } + } + Ok(()) +} + +async fn create_database(client: &WikiClient, args: WikiJsonArgs) -> Result<()> { + let database_id = client.create_database().await?; + if args.json { + println!( + "{}", + serde_json::to_string_pretty(&serde_json::json!({ "database_id": database_id }))? + ); + } else { + println!("{database_id}"); + } + Ok(()) +} + +async fn grant_database( + client: &WikiClient, + args: crate::cli::WikiDatabaseGrantArgs, +) -> Result<()> { + let role = database_role(args.role); + client + .grant_database_access(&args.database_id, &args.principal, role) + .await?; + if args.json { + println!( + "{}", + serde_json::to_string_pretty(&serde_json::json!({ + "database_id": args.database_id, + "principal": args.principal, + "role": format!("{role:?}"), + }))? + ); + } else { + println!( + "granted {:?} {} on {}", + role, args.principal, args.database_id + ); + } + Ok(()) +} + +async fn read_node(client: &WikiClient, args: WikiReadArgs) -> Result<()> { + let Some(node) = client.read_node(&args.database_id, &args.path).await? else { + bail!("node not found: {}", args.path); + }; + if args.json { + println!("{}", serde_json::to_string_pretty(&node)?); + } else { + println!("{}", node.content); + } + Ok(()) +} + +async fn list_children(client: &WikiClient, args: WikiChildrenArgs) -> Result<()> { + let children = client + .list_children(ListChildrenRequest { + database_id: args.database_id, + path: args.path, + }) + .await?; + if args.json { + println!("{}", serde_json::to_string_pretty(&children)?); + } else { + for child in children { + println!( + "{}\t{:?}\t{}", + child.path, + child.kind, + child.etag.unwrap_or_default() + ); + } + } + Ok(()) +} + +async fn search_nodes(client: &WikiClient, args: WikiSearchArgs) -> Result<()> { + let hits = client + .search_nodes(SearchNodesRequest { + database_id: args.database_id, + query_text: args.query, + prefix: Some(args.prefix), + top_k: args.top_k, + preview_mode: Some(SearchPreviewMode::ContentStart), + }) + .await?; + if args.json { + println!("{}", serde_json::to_string_pretty(&hits)?); + } else { + for hit in hits { + let snippet = hit + .preview + .and_then(|preview| preview.excerpt) + .or(hit.snippet) + .unwrap_or_default(); + println!("{:.3}\t{}\t{}", hit.score, hit.path, snippet); + } + } + Ok(()) +} + +async fn write_node(client: &WikiClient, args: WikiWriteArgs) -> Result<()> { + let kind = node_kind(args.kind); + validate_source_path(&args.path, &kind)?; + let content = fs::read_to_string(&args.input)?; + let result = client + .write_node(WriteNodeRequest { + database_id: args.database_id, + path: args.path, + kind, + content, + metadata_json: args.metadata_json, + expected_etag: args.expected_etag, + }) + .await?; + if args.json { + println!("{}", serde_json::to_string_pretty(&result)?); + } else { + println!("{}", result.node.etag); + } + Ok(()) +} + +async fn append_node(client: &WikiClient, args: WikiAppendArgs) -> Result<()> { + let kind = args.kind.map(node_kind); + if let Some(kind) = kind.as_ref() { + validate_source_path(&args.path, kind)?; + } + let json = args.json; + let content = fs::read_to_string(&args.input)?; + let result = client + .append_node(append_request(args, content, kind)) + .await?; + if json { + println!("{}", serde_json::to_string_pretty(&result)?); + } else { + println!("{}", result.node.etag); + } + Ok(()) +} + +fn append_request( + args: WikiAppendArgs, + content: String, + kind: Option, +) -> AppendNodeRequest { + AppendNodeRequest { + database_id: args.database_id, + path: args.path, + content, + expected_etag: args.expected_etag, + separator: args.separator, + metadata_json: args.metadata_json, + kind, + } +} + +async fn edit_node(client: &WikiClient, args: WikiEditArgs) -> Result<()> { + let result = client + .edit_node(EditNodeRequest { + database_id: args.database_id, + path: args.path, + old_text: args.old_text, + new_text: args.new_text, + expected_etag: args.expected_etag, + replace_all: args.replace_all, + }) + .await?; + if args.json { + println!("{}", serde_json::to_string_pretty(&result)?); + } else { + println!("{}\t{}", result.replacement_count, result.node.etag); + } + Ok(()) +} + +async fn delete_node(client: &WikiClient, args: WikiDeleteArgs) -> Result<()> { + if !args.yes { + bail!("--yes is required to delete a wiki node"); + } + let result = client + .delete_node(DeleteNodeRequest { + database_id: args.database_id, + path: args.path, + expected_etag: args.expected_etag, + }) + .await?; + if args.json { + println!("{}", serde_json::to_string_pretty(&result)?); + } else { + println!("{}", result.path); + } + Ok(()) +} + +fn database_role(role: WikiDatabaseRoleArg) -> DatabaseRole { + match role { + WikiDatabaseRoleArg::Owner => DatabaseRole::Owner, + WikiDatabaseRoleArg::Writer => DatabaseRole::Writer, + WikiDatabaseRoleArg::Reader => DatabaseRole::Reader, + } +} + +fn node_kind(kind: WikiNodeKindArg) -> NodeKind { + match kind { + WikiNodeKindArg::File => NodeKind::File, + WikiNodeKindArg::Source => NodeKind::Source, + } +} + +fn validate_source_path(path: &str, kind: &NodeKind) -> Result<()> { + let is_source_path = path_matches_prefix_boundary(path, "/Sources/raw") + || path_matches_prefix_boundary(path, "/Sources/sessions"); + if *kind != NodeKind::Source { + if is_source_path { + bail!( + "source path must use source kind under /Sources/raw or /Sources/sessions: {path}" + ); + } + return Ok(()); + } + if path_matches_prefix_boundary(path, "/Sources/raw") { + return validate_source_path_under_prefix(path, "/Sources/raw"); + } + if path_matches_prefix_boundary(path, "/Sources/sessions") { + return validate_source_path_under_prefix(path, "/Sources/sessions"); + } + bail!("source path must stay under /Sources/raw or /Sources/sessions: {path}"); +} + +fn path_matches_prefix_boundary(path: &str, prefix: &str) -> bool { + path == prefix + || path + .strip_prefix(prefix) + .is_some_and(|suffix| suffix.starts_with('/')) +} + +fn validate_source_path_under_prefix(path: &str, prefix: &str) -> Result<()> { + let relative = path + .strip_prefix(prefix) + .ok_or_else(|| anyhow::anyhow!("source path must stay under {prefix}: {path}"))?; + let segments = relative + .split('/') + .filter(|segment| !segment.is_empty()) + .collect::>(); + if segments.len() != 2 { + bail!("source path must use canonical form {prefix}//.md: {path}"); + } + let [directory_name, file_name] = segments.as_slice() else { + unreachable!(); + }; + if directory_name.is_empty() || *file_name != format!("{directory_name}.md") { + bail!("source path must use canonical form {prefix}//.md: {path}"); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cli::{Cli, Command, WikiDatabaseCommand}; + use clap::Parser; + + #[test] + fn wiki_read_command_parses() { + let cli = Cli::try_parse_from([ + "kinic-cli", + "--identity", + "alice", + "wiki", + "read", + "--database-id", + "db", + "--path", + "/Wiki/a.md", + ]) + .expect("wiki read should parse"); + + match cli.command { + Command::Wiki(args) => match args.command { + crate::cli::WikiCommand::Read(args) => { + assert_eq!(args.database_id, "db"); + assert_eq!(args.path, "/Wiki/a.md"); + } + _ => panic!("expected wiki read"), + }, + _ => panic!("expected wiki command"), + } + } + + #[test] + fn wiki_database_grant_command_parses_role() { + let cli = Cli::try_parse_from([ + "kinic-cli", + "--identity", + "alice", + "wiki", + "database", + "grant", + "db", + "aaaaa-aa", + "reader", + ]) + .expect("wiki grant should parse"); + + match cli.command { + Command::Wiki(args) => match args.command { + crate::cli::WikiCommand::Database(database) => match database.command { + WikiDatabaseCommand::Grant(args) => { + assert_eq!(args.role, WikiDatabaseRoleArg::Reader); + assert!(!args.json); + } + _ => panic!("expected wiki database grant"), + }, + _ => panic!("expected wiki database"), + }, + _ => panic!("expected wiki command"), + } + } + + #[test] + fn wiki_database_grant_accepts_json() { + let cli = Cli::try_parse_from([ + "kinic-cli", + "--identity", + "alice", + "wiki", + "database", + "grant", + "db", + "aaaaa-aa", + "reader", + "--json", + ]) + .expect("wiki grant json should parse"); + + match cli.command { + Command::Wiki(args) => match args.command { + crate::cli::WikiCommand::Database(database) => match database.command { + WikiDatabaseCommand::Grant(args) => assert!(args.json), + _ => panic!("expected wiki database grant"), + }, + _ => panic!("expected wiki database"), + }, + _ => panic!("expected wiki command"), + } + } + + #[test] + fn wiki_append_accepts_kind_and_metadata() { + let cli = Cli::try_parse_from([ + "kinic-cli", + "--identity", + "alice", + "wiki", + "append", + "--database-id", + "db", + "--path", + "/Sources/raw/a/a.md", + "--input", + "note.md", + "--kind", + "source", + "--metadata-json", + "{}", + ]) + .expect("wiki append should parse"); + + match cli.command { + Command::Wiki(args) => match args.command { + crate::cli::WikiCommand::Append(args) => { + assert_eq!(args.kind, Some(WikiNodeKindArg::Source)); + assert_eq!(args.metadata_json.as_deref(), Some("{}")); + } + _ => panic!("expected wiki append"), + }, + _ => panic!("expected wiki command"), + } + } + + #[test] + fn append_request_keeps_kind_and_metadata() { + let request = append_request( + WikiAppendArgs { + database_id: "db".to_string(), + path: "/Sources/raw/a/a.md".to_string(), + input: "unused.md".into(), + expected_etag: Some("etag".to_string()), + separator: Some("\n".to_string()), + kind: Some(WikiNodeKindArg::Source), + metadata_json: Some("{}".to_string()), + json: false, + }, + "body".to_string(), + Some(NodeKind::Source), + ); + + assert_eq!(request.kind, Some(NodeKind::Source)); + assert_eq!(request.metadata_json.as_deref(), Some("{}")); + } + + #[test] + fn source_path_validation_matches_wiki_domain_rules() { + assert!(validate_source_path("/Sources/raw/a/a.md", &NodeKind::Source).is_ok()); + assert!(validate_source_path("/Sources/sessions/a/a.md", &NodeKind::Source).is_ok()); + assert!(validate_source_path("/Sources/raw/a/b.md", &NodeKind::Source).is_err()); + assert!(validate_source_path("/Sources/raw/a.md", &NodeKind::Source).is_err()); + assert!(validate_source_path("/Sources/rawfoo/a/a.md", &NodeKind::Source).is_err()); + assert!(validate_source_path("/Sources/raw/a/a.md", &NodeKind::File).is_err()); + assert!(validate_source_path("/Wiki/a.md", &NodeKind::File).is_ok()); + } + + #[test] + fn wiki_delete_parses_without_yes_for_runtime_rejection() { + let cli = Cli::try_parse_from([ + "kinic-cli", + "--identity", + "alice", + "wiki", + "delete", + "--database-id", + "db", + "--path", + "/Wiki/a.md", + ]) + .expect("delete command should parse so handler can reject missing --yes"); + + match cli.command { + Command::Wiki(args) => match args.command { + crate::cli::WikiCommand::Delete(args) => assert!(!args.yes), + _ => panic!("expected wiki delete"), + }, + _ => panic!("expected wiki command"), + } + } +} diff --git a/rust/lib.rs b/rust/lib.rs index 00ef50a..c02ef54 100644 --- a/rust/lib.rs +++ b/rust/lib.rs @@ -21,6 +21,7 @@ mod python; pub(crate) mod shared; pub mod tools; pub mod tui; +pub mod wiki_bridge; use anyhow::{Result, anyhow}; use clap::{CommandFactory, Parser, error::ErrorKind}; diff --git a/rust/tui/bridge.rs b/rust/tui/bridge.rs index 9d785e9..35c9e79 100644 --- a/rust/tui/bridge.rs +++ b/rust/tui/bridge.rs @@ -23,13 +23,14 @@ use crate::{ }; use anyhow::{Context, Result}; -use candid::{CandidType, Decode, Encode}; +#[cfg(test)] +use candid::{Decode, Encode}; use ic_agent::{Agent, export::Principal}; use kinic_core::amount::format_e8s_to_kinic_string_nat; -use serde::{Deserialize, Serialize}; use tui_kit_runtime::{AccessControlAction, AccessControlRole, ChatScope, SessionAccountOverview}; pub(crate) use crate::shared::memory_metadata::DescriptionUpdate; +pub use crate::wiki_bridge::{DatabaseStatus, DatabaseSummary, NodeEntryKind}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct MemorySummary { @@ -73,115 +74,6 @@ pub struct WikiSearchHit { pub snippet: Option, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] -pub enum DatabaseRole { - Owner, - Writer, - Reader, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] -pub enum DatabaseStatus { - Hot, - Restoring, - Archiving, - Archived, - Deleted, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] -pub struct DatabaseSummary { - pub database_id: String, - pub status: DatabaseStatus, - pub role: DatabaseRole, - pub logical_size_bytes: u64, - pub archived_at_ms: Option, - pub deleted_at_ms: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] -struct ListChildrenRequest { - database_id: String, - path: String, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] -enum NodeEntryKind { - File, - Source, - Directory, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] -struct ChildNode { - path: String, - name: String, - kind: NodeEntryKind, - updated_at: Option, - etag: Option, - size_bytes: Option, - is_virtual: bool, - has_children: bool, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] -enum NodeKind { - File, - Source, - Directory, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] -struct Node { - path: String, - kind: NodeKind, - content: String, - created_at: i64, - updated_at: i64, - etag: String, - metadata_json: String, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] -enum SearchPreviewMode { - Light, - ContentStart, - None, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] -enum SearchPreviewField { - Path, - Content, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] -struct SearchPreview { - field: SearchPreviewField, - char_offset: u32, - match_reason: String, - excerpt: Option, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, CandidType)] -struct SearchNodeHit { - path: String, - kind: NodeKind, - snippet: Option, - preview: Option, - score: f32, - match_reasons: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] -struct SearchNodesRequest { - database_id: String, - query_text: String, - prefix: Option, - top_k: u32, - preview_mode: Option, -} - pub type SearchResultItem = SearchHit; #[derive(Debug, Clone, PartialEq)] @@ -461,15 +353,21 @@ pub async fn create_instance( }) } -pub async fn list_wiki_root_children( +pub async fn list_wiki_children( use_mainnet: bool, auth: TuiAuth, wiki_id: String, database_id: String, + path: String, ) -> Result> { let agent = build_search_agent(use_mainnet, auth).await?; - let client = VfsClient::new(agent, wiki_id)?; - client.list_children(&database_id, "/Wiki").await + let client = crate::wiki_bridge::WikiClient::new(agent, wiki_id)?; + Ok(client + .list_children(crate::wiki_bridge::ListChildrenRequest { database_id, path }) + .await? + .into_iter() + .map(WikiChildNode::from) + .collect()) } pub async fn read_wiki_node( @@ -480,8 +378,11 @@ pub async fn read_wiki_node( path: String, ) -> Result> { let agent = build_search_agent(use_mainnet, auth).await?; - let client = VfsClient::new(agent, wiki_id)?; - client.read_node(&database_id, &path).await + let client = crate::wiki_bridge::WikiClient::new(agent, wiki_id)?; + Ok(client + .read_node(&database_id, &path) + .await? + .map(WikiNode::from)) } pub async fn search_wiki_nodes( @@ -492,8 +393,19 @@ pub async fn search_wiki_nodes( query: String, ) -> Result> { let agent = build_search_agent(use_mainnet, auth).await?; - let client = VfsClient::new(agent, wiki_id)?; - client.search_nodes(&database_id, &query, "/Wiki", 20).await + let client = crate::wiki_bridge::WikiClient::new(agent, wiki_id)?; + Ok(client + .search_nodes(crate::wiki_bridge::SearchNodesRequest { + database_id, + query_text: query, + prefix: Some("/Wiki".to_string()), + top_k: 20, + preview_mode: Some(crate::wiki_bridge::SearchPreviewMode::ContentStart), + }) + .await? + .into_iter() + .map(WikiSearchHit::from) + .collect()) } pub async fn list_wiki_databases( @@ -502,7 +414,9 @@ pub async fn list_wiki_databases( wiki_id: String, ) -> Result> { let agent = build_search_agent(use_mainnet, auth).await?; - VfsClient::new(agent, wiki_id)?.list_databases().await + crate::wiki_bridge::WikiClient::new(agent, wiki_id)? + .list_databases() + .await } pub async fn load_session_account_overview( @@ -969,111 +883,8 @@ fn role_code(role: AccessControlRole, principal_id: &str) -> Result { Ok(memory_role.code()) } -#[derive(Clone)] -struct VfsClient { - agent: Agent, - canister_id: Principal, -} - -impl VfsClient { - fn new(agent: Agent, canister_id: String) -> Result { - Ok(Self { - agent, - canister_id: Principal::from_text(canister_id) - .context("failed to parse wiki canister principal")?, - }) - } - - async fn query(&self, method: &str, arg: &Arg) -> Result - where - Arg: CandidType, - Out: for<'de> candid::Deserialize<'de> + CandidType, - { - let bytes = self - .agent - .query(&self.canister_id, method) - .with_arg(Encode!(arg).context("failed to encode wiki query args")?) - .call() - .await - .with_context(|| format!("wiki query failed for {method}"))?; - Decode!(&bytes, Out).with_context(|| format!("failed to decode wiki response for {method}")) - } - - async fn query2(&self, method: &str, a: &A, b: &B) -> Result - where - A: CandidType, - B: CandidType, - Out: for<'de> candid::Deserialize<'de> + CandidType, - { - let bytes = self - .agent - .query(&self.canister_id, method) - .with_arg(Encode!(a, b).context("failed to encode wiki query args")?) - .call() - .await - .with_context(|| format!("wiki query failed for {method}"))?; - Decode!(&bytes, Out).with_context(|| format!("failed to decode wiki response for {method}")) - } - - async fn list_databases(&self) -> Result> { - let result: Result, String> = - self.query("list_databases", &()).await?; - result.map_err(anyhow::Error::msg) - } - - async fn list_children(&self, database_id: &str, path: &str) -> Result> { - let result: Result, String> = self - .query( - "list_children", - &ListChildrenRequest { - database_id: database_id.to_string(), - path: path.to_string(), - }, - ) - .await?; - Ok(result - .map_err(anyhow::Error::msg)? - .into_iter() - .map(WikiChildNode::from) - .collect()) - } - - async fn read_node(&self, database_id: &str, path: &str) -> Result> { - let result: Result, String> = self - .query2("read_node", &database_id.to_string(), &path.to_string()) - .await?; - Ok(result.map_err(anyhow::Error::msg)?.map(WikiNode::from)) - } - - async fn search_nodes( - &self, - database_id: &str, - query_text: &str, - prefix: &str, - top_k: u32, - ) -> Result> { - let result: Result, String> = self - .query( - "search_nodes", - &SearchNodesRequest { - database_id: database_id.to_string(), - query_text: query_text.to_string(), - prefix: Some(prefix.to_string()), - top_k, - preview_mode: Some(SearchPreviewMode::ContentStart), - }, - ) - .await?; - Ok(result - .map_err(anyhow::Error::msg)? - .into_iter() - .map(WikiSearchHit::from) - .collect()) - } -} - -impl From for WikiChildNode { - fn from(node: ChildNode) -> Self { +impl From for WikiChildNode { + fn from(node: crate::wiki_bridge::ChildNode) -> Self { Self { path: node.path, name: node.name, @@ -1088,8 +899,8 @@ impl From for WikiChildNode { } } -impl From for WikiNode { - fn from(node: Node) -> Self { +impl From for WikiNode { + fn from(node: crate::wiki_bridge::Node) -> Self { Self { path: node.path, content: node.content, @@ -1098,8 +909,8 @@ impl From for WikiNode { } } -impl From for WikiSearchHit { - fn from(hit: SearchNodeHit) -> Self { +impl From for WikiSearchHit { + fn from(hit: crate::wiki_bridge::SearchNodeHit) -> Self { Self { path: hit.path, score: hit.score, @@ -1121,6 +932,9 @@ fn format_insert_execute_error(message: &str) -> String { #[cfg(test)] mod tests { use super::*; + use crate::wiki_bridge::{ + ChildNode, DatabaseRole, Node, NodeKind, SearchNodeHit, SearchPreview, SearchPreviewField, + }; use candid::Nat; use ic_agent::identity::AnonymousIdentity; use std::sync::Arc; diff --git a/rust/tui/mod.rs b/rust/tui/mod.rs index 8714962..2b204e1 100644 --- a/rust/tui/mod.rs +++ b/rust/tui/mod.rs @@ -119,9 +119,6 @@ pub struct TuiLaunchConfig { pub wiki_canister_id: Option, } -const WIKI_CANISTER_ID_ENV_VAR: &str = "KINIC_WIKI_CANISTER_ID"; -const DEFAULT_WIKI_CANISTER_ID: &str = "xis3j-paaaa-aaaai-axumq-cai"; - pub fn run(global: &GlobalOpts) -> Result<()> { run_with_config(build_launch_config_from_global(global)?) } @@ -143,13 +140,7 @@ pub fn build_launch_config_from_global(global: &GlobalOpts) -> Result Option { - Some( - std::env::var(WIKI_CANISTER_ID_ENV_VAR) - .ok() - .map(|value| value.trim().to_string()) - .filter(|value| !value.is_empty()) - .unwrap_or_else(|| DEFAULT_WIKI_CANISTER_ID.to_string()), - ) + Some(crate::wiki_bridge::wiki_canister_id_from_env()) } pub fn run_with_config(config: TuiLaunchConfig) -> Result<()> { @@ -227,7 +218,7 @@ mod tests { assert!(config.use_mainnet); assert_eq!( config.wiki_canister_id.as_deref(), - Some(DEFAULT_WIKI_CANISTER_ID) + Some(crate::wiki_bridge::DEFAULT_WIKI_CANISTER_ID) ); } @@ -262,4 +253,16 @@ mod tests { assert_eq!(config.tab_ids, &kinic_tabs::KINIC_TAB_IDS); assert_eq!(config.initial_focus, PaneFocus::Search); } + + #[test] + fn ui_config_omits_market_tab() { + let config = ui_config::kinic_ui_config(); + let titles = config + .tabs + .iter() + .map(|tab| tab.title.as_str()) + .collect::>(); + + assert_eq!(titles, ["Memories", "Insert", "Create", "Wiki", "Settings"]); + } } diff --git a/rust/tui/provider/mod.rs b/rust/tui/provider/mod.rs index c13db09..15c3a8f 100644 --- a/rust/tui/provider/mod.rs +++ b/rust/tui/provider/mod.rs @@ -41,13 +41,14 @@ use tokio::sync::Semaphore; use tokio_util::sync::CancellationToken; use tui_kit_runtime::{ AccessControlAction, AccessControlMode, AccessControlRole, ChatScope, CoreAction, CoreEffect, - CoreResult, CoreState, CreateCostState, DataProvider, FILE_MODE_ALLOWED_EXTENSIONS, InsertMode, - LoadedCreateCost, MemorySelection, PaneFocus, PickerConfirmKind, PickerContext, PickerItem, - PickerItemKind, PickerListMode, PickerState, ProviderOutput, ProviderSnapshot, SearchScope, - SessionAccountOverview, SessionSettingsSnapshot, TransferModalMode, + CoreResult, CoreState, CreateCostState, DataProvider, DiagnosticSnapshot, DocumentSnapshot, + FILE_MODE_ALLOWED_EXTENSIONS, InsertMode, LoadedCreateCost, MemorySelection, PaneFocus, + PaneRow, PaneSnapshot, PickerConfirmKind, PickerContext, PickerItem, PickerItemKind, + PickerListMode, PickerState, ProviderOutput, ProviderSnapshot, SearchScope, + SessionAccountOverview, SessionSettingsSnapshot, ThreePaneSnapshot, TransferModalMode, kinic_tabs::{ - KINIC_CREATE_TAB_ID, KINIC_INSERT_TAB_ID, KINIC_MARKET_TAB_ID, KINIC_MEMORIES_TAB_ID, - KINIC_SETTINGS_TAB_ID, KINIC_WIKI_TAB_ID, + KINIC_CREATE_TAB_ID, KINIC_INSERT_TAB_ID, KINIC_MEMORIES_TAB_ID, KINIC_SETTINGS_TAB_ID, + KINIC_WIKI_TAB_ID, }, }; @@ -165,6 +166,7 @@ pub struct KinicProvider { memory_records: Vec, wiki_databases: Vec, wiki_records: Vec, + wiki_load_error: Option, result_records: Vec, memories_mode: MemoriesMode, pending_initial_memories: Option>, @@ -208,6 +210,9 @@ pub struct KinicProvider { wiki_children_task: RequestTaskState, next_wiki_children_request_id: u64, pending_wiki_children_database_id: Option, + pending_wiki_children_path: Option, + wiki_current_path: String, + selected_wiki_browser_index: usize, wiki_search_task: RequestTaskState, next_wiki_search_request_id: u64, wiki_databases_task: RequestTaskState, @@ -232,13 +237,31 @@ struct SearchTaskOutput { #[derive(Debug, Clone, PartialEq, Eq)] struct WikiChildrenContent { + entries: Vec, body_lines: Vec, index_preview: Option>, } +#[derive(Debug, Clone, PartialEq, Eq)] +struct WikiBrowserEntry { + path: String, + name: String, + kind: WikiBrowserEntryKind, + size_bytes: Option, + has_children: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum WikiBrowserEntryKind { + Directory, + File, + Source, +} + struct WikiChildrenTaskOutput { request_id: u64, database_id: String, + path: String, result: Result, } @@ -992,44 +1015,132 @@ fn load_wiki_children_content( auth: TuiAuth, wiki_id: String, database_id: String, + path: String, ) -> Result { let runtime = Runtime::new().expect("failed to create tokio runtime for wiki browser load"); - let children = runtime - .block_on(bridge::list_wiki_root_children( + if path != "/" + && let Some(node) = runtime + .block_on(bridge::read_wiki_node( + use_mainnet, + auth.clone(), + wiki_id.clone(), + database_id.clone(), + path.clone(), + )) + .map_err(|error| error.to_string())? + { + let preview = node.content.lines().map(str::to_string).collect(); + return Ok(WikiChildrenContent { + entries: Vec::new(), + body_lines: Vec::new(), + index_preview: Some(preview), + }); + } + + let entries = if path == "/" { + wiki_root_entries() + } else { + let children = runtime + .block_on(bridge::list_wiki_children( + use_mainnet, + auth.clone(), + wiki_id.clone(), + database_id.clone(), + path.clone(), + )) + .map_err(|error| error.to_string())?; + children.into_iter().map(WikiBrowserEntry::from).collect() + }; + let index = runtime + .block_on(bridge::read_wiki_node( use_mainnet, auth.clone(), wiki_id.clone(), database_id.clone(), - )) - .map_err(|error| error.to_string())?; - let index = runtime - .block_on(bridge::read_wiki_node( - use_mainnet, - auth, - wiki_id, - database_id, "/Wiki/index.md".to_string(), )) .map_err(|error| error.to_string())?; - let body_lines = if children.is_empty() { - vec!["No /Wiki children found.".to_string()] + let body_lines = if entries.is_empty() { + vec!["No /Wiki or /Sources children found.".to_string()] } else { - children - .into_iter() - .map(|child| { - let marker = if child.has_children { "+" } else { "-" }; - format!("{marker} {} ({})", child.path, child.kind) - }) - .collect() + entries.iter().map(wiki_entry_line).collect() }; let index_preview = index.map(|node| node.content.lines().take(8).map(str::to_string).collect()); Ok(WikiChildrenContent { + entries, body_lines, index_preview, }) } +fn wiki_entry_line(entry: &WikiBrowserEntry) -> String { + let marker = if entry.has_children { "+" } else { "-" }; + format!( + "{marker} {} ({})", + entry.path, + wiki_entry_kind_label(entry.kind) + ) +} + +fn wiki_root_entries() -> Vec { + ["/Wiki", "/Sources"] + .into_iter() + .map(|path| WikiBrowserEntry { + path: path.to_string(), + name: path.trim_start_matches('/').to_string(), + kind: WikiBrowserEntryKind::Directory, + size_bytes: None, + has_children: true, + }) + .collect() +} + +fn wiki_entry_kind_label(kind: WikiBrowserEntryKind) -> &'static str { + match kind { + WikiBrowserEntryKind::Directory => "directory", + WikiBrowserEntryKind::File => "file", + WikiBrowserEntryKind::Source => "source", + } +} + +impl From for WikiBrowserEntry { + fn from(child: bridge::WikiChildNode) -> Self { + Self { + path: child.path, + name: child.name, + kind: match child.kind.as_str() { + "source" => WikiBrowserEntryKind::Source, + "directory" => WikiBrowserEntryKind::Directory, + _ => WikiBrowserEntryKind::File, + }, + size_bytes: None, + has_children: child.has_children, + } + } +} + +fn wiki_children_cache_key(database_id: &str, path: &str) -> String { + format!("{database_id}\n{path}") +} + +fn wiki_parent_path(path: &str) -> String { + let trimmed = path.trim_end_matches('/'); + if trimmed == "/Wiki" || trimmed == "/Sources" || trimmed.is_empty() { + return "/".to_string(); + } + trimmed + .rsplit_once('/') + .map(|(parent, _)| { + if parent.is_empty() { + "/".to_string() + } else { + parent.to_string() + } + }) + .unwrap_or_else(|| "/".to_string()) +} + #[derive(Debug, Clone, PartialEq, Eq)] enum MemoryContentSelection<'a> { RenameMemory, @@ -1075,6 +1186,7 @@ impl KinicProvider { memory_records: Vec::new(), wiki_databases: Vec::new(), wiki_records: Vec::new(), + wiki_load_error: None, result_records: Vec::new(), memories_mode: MemoriesMode::Browser, pending_initial_memories: None, @@ -1118,6 +1230,9 @@ impl KinicProvider { wiki_children_task: RequestTaskState::default(), next_wiki_children_request_id: 0, pending_wiki_children_database_id: None, + pending_wiki_children_path: None, + wiki_current_path: "/".to_string(), + selected_wiki_browser_index: 0, wiki_search_task: RequestTaskState::default(), next_wiki_search_request_id: 0, wiki_databases_task: RequestTaskState::default(), @@ -1143,6 +1258,7 @@ impl KinicProvider { self.query.clear(); } self.result_records.clear(); + self.wiki_load_error = None; self.invalidate_pending_search(); self.all = vec![loading_memories_record()]; @@ -1203,10 +1319,14 @@ impl KinicProvider { reset_request_task(&mut self.wiki_children_task); reset_request_task(&mut self.wiki_search_task); self.pending_wiki_children_database_id = None; + self.pending_wiki_children_path = None; + self.wiki_current_path = "/".to_string(); + self.selected_wiki_browser_index = 0; let Some(wiki_canister_id) = self.config.wiki_canister_id.clone() else { self.wiki_databases.clear(); self.wiki_records = vec![wiki_not_configured_record()]; + self.wiki_load_error = Some("Wiki canister is not configured.".to_string()); self.wiki_children_cache.clear(); return CoreEffect::Notify("Wiki canister is not configured.".to_string()); }; @@ -1252,6 +1372,297 @@ impl KinicProvider { base.iter().collect() } + fn build_wiki_three_pane_snapshot(&self, state: &CoreState) -> ThreePaneSnapshot { + if self.tab_id != KINIC_WIKI_TAB_ID { + return ThreePaneSnapshot::default(); + } + let selected_index = state.selected_index.unwrap_or(0); + let database_rows = self + .wiki_records + .iter() + .enumerate() + .map(|(index, record)| { + let database = self.wiki_databases.iter().find(|database| { + Some(database.database_id.as_str()) == record.source_wiki_database_id.as_deref() + }); + let status = database + .map(|database| format!("{:?}", database.status)) + .unwrap_or_else(|| "-".to_string()); + let role = database + .map(|database| format!("{:?}", database.role)) + .unwrap_or_else(|| "-".to_string()); + let size = database + .map(|database| database.logical_size_bytes.to_string()) + .unwrap_or_else(|| "0".to_string()); + PaneRow { + label: record.title.clone(), + detail: format!("{status} {role} {size} bytes"), + selected: index == selected_index, + } + }) + .collect::>(); + let document_lines = self.wiki_document_lines(selected_index); + ThreePaneSnapshot { + left: PaneSnapshot { + title: "Databases".to_string(), + rows: database_rows, + empty_message: "No databases".to_string(), + loading: self.wiki_databases_task.in_flight, + }, + middle: PaneSnapshot { + title: "Browser".to_string(), + rows: self.wiki_browser_rows(selected_index), + empty_message: "Select a database or run search.".to_string(), + loading: self.wiki_children_task.in_flight || self.wiki_search_task.in_flight, + }, + document: DocumentSnapshot { + title: if self.result_records.is_empty() { + self.wiki_current_path.clone() + } else { + self.result_records + .get(self.selected_wiki_browser_index) + .map(|record| record.title.clone()) + .unwrap_or_else(|| "Wiki search".to_string()) + }, + lines: document_lines, + }, + diagnostic: self.wiki_diagnostic(), + middle_mode: if self.result_records.is_empty() { + "browse".to_string() + } else { + "search".to_string() + }, + } + } + + fn wiki_browser_rows(&self, selected_index: usize) -> Vec { + if !self.result_records.is_empty() { + return self + .result_records + .iter() + .enumerate() + .map(|(index, record)| PaneRow { + label: record.title.clone(), + detail: record.summary.clone(), + selected: index == self.selected_wiki_browser_index, + }) + .collect(); + } + let Some(record) = self.wiki_records.get(selected_index) else { + return Vec::new(); + }; + let Some(database_id) = record.source_wiki_database_id.as_deref() else { + return Vec::new(); + }; + match self + .wiki_children_cache + .get(wiki_children_cache_key(database_id, self.wiki_current_path.as_str()).as_str()) + .or_else(|| self.wiki_children_cache.get(database_id)) + { + Some(content) => content + .entries + .iter() + .enumerate() + .map(|(index, entry)| PaneRow { + label: entry.path.clone(), + detail: match entry.size_bytes { + Some(size) => { + format!("{} {} bytes", wiki_entry_kind_label(entry.kind), size) + } + None => wiki_entry_kind_label(entry.kind).to_string(), + }, + selected: index == self.selected_wiki_browser_index, + }) + .collect(), + None if self.pending_wiki_children_database_id.as_deref() == Some(database_id) => { + vec![PaneRow { + label: "Loading /Wiki and /Sources...".to_string(), + detail: database_id.to_string(), + selected: false, + }] + } + None => vec![ + PaneRow { + label: "/Wiki".to_string(), + detail: "directory".to_string(), + selected: false, + }, + PaneRow { + label: "/Sources".to_string(), + detail: "directory".to_string(), + selected: false, + }, + ], + } + } + + fn wiki_document_lines(&self, selected_index: usize) -> Vec { + if self.result_records.is_empty() + && let Some(database_id) = self + .wiki_records + .get(selected_index) + .and_then(|record| record.source_wiki_database_id.as_deref()) + && let Some(content) = self + .wiki_children_cache + .get(wiki_children_cache_key(database_id, self.wiki_current_path.as_str()).as_str()) + && let Some(lines) = &content.index_preview + { + return lines.clone(); + } + let records = self.current_records(); + let Some(record) = records.get(selected_index) else { + return vec!["No wiki database selected.".to_string()]; + }; + let content = self.selected_content_for_record(record, &CoreState::default()); + let mut lines = Vec::new(); + if !content.definition.trim().is_empty() { + lines.extend(content.definition.lines().map(str::to_string)); + } + for section in content.sections { + lines.push(String::new()); + lines.push(format!("## {}", section.heading)); + lines.extend(section.body_lines); + } + lines + } + + fn wiki_diagnostic(&self) -> Option { + let error = self.wiki_load_error.as_ref()?; + let principal = match self.session_overview.session.principal_id.trim() { + "" | "unavailable" => None, + value => Some(value.to_string()), + }; + let mut rows = vec![ + PaneRow { + label: "canister".to_string(), + detail: self + .config + .wiki_canister_id + .clone() + .unwrap_or_else(|| "not configured".to_string()), + selected: false, + }, + PaneRow { + label: "network".to_string(), + detail: self.network_label().to_string(), + selected: false, + }, + PaneRow { + label: "auth".to_string(), + detail: self.config.auth.identity_label().to_string(), + selected: false, + }, + ]; + if let Some(principal) = principal { + rows.push(PaneRow { + label: "principal".to_string(), + detail: principal, + selected: false, + }); + } + Some(DiagnosticSnapshot { + rows, + message: short_error(error.as_str()), + }) + } + + fn navigate_wiki_browser(&mut self, state: &CoreState, action: &CoreAction) { + let len = if self.result_records.is_empty() { + self.current_wiki_entries(state).len() + } else { + self.result_records.len() + }; + if len == 0 { + self.selected_wiki_browser_index = 0; + return; + } + let last = len.saturating_sub(1); + self.selected_wiki_browser_index = match action { + CoreAction::MoveNext => (self.selected_wiki_browser_index + 1).min(last), + CoreAction::MovePrev => self.selected_wiki_browser_index.saturating_sub(1), + CoreAction::MoveHome => 0, + CoreAction::MoveEnd => last, + CoreAction::MovePageDown => (self.selected_wiki_browser_index + 10).min(last), + CoreAction::MovePageUp => self.selected_wiki_browser_index.saturating_sub(10), + _ => self.selected_wiki_browser_index.min(last), + }; + } + + fn current_wiki_entries(&self, state: &CoreState) -> Vec { + let selected_index = state.selected_index.unwrap_or(0); + let Some(database_id) = self + .wiki_records + .get(selected_index) + .and_then(|record| record.source_wiki_database_id.as_deref()) + else { + return Vec::new(); + }; + self.wiki_children_cache + .get(wiki_children_cache_key(database_id, self.wiki_current_path.as_str()).as_str()) + .map(|content| content.entries.clone()) + .unwrap_or_default() + } + + fn open_selected_wiki_browser_entry(&mut self, state: &CoreState) -> Vec { + if !self.result_records.is_empty() { + let Some(record) = self.result_records.get(self.selected_wiki_browser_index) else { + return vec![CoreEffect::Notify( + "Select a wiki search result.".to_string(), + )]; + }; + let title = record.title.clone(); + if let Some(database_id) = record.source_wiki_database_id.clone() { + self.wiki_current_path = title.clone(); + self.wiki_children_cache.remove( + wiki_children_cache_key(database_id.as_str(), self.wiki_current_path.as_str()) + .as_str(), + ); + self.start_selected_wiki_children_load(state); + return vec![CoreEffect::Notify(format!("Loading wiki node {title}"))]; + } + return Vec::new(); + } + + let entries = self.current_wiki_entries(state); + let Some(entry) = entries.get(self.selected_wiki_browser_index) else { + self.start_selected_wiki_children_load(state); + return Vec::new(); + }; + self.wiki_current_path = entry.path.clone(); + self.selected_wiki_browser_index = 0; + self.start_selected_wiki_children_load(state); + match entry.kind { + WikiBrowserEntryKind::Directory => { + vec![CoreEffect::Notify(format!("Opened {}", entry.path))] + } + WikiBrowserEntryKind::File | WikiBrowserEntryKind::Source => { + vec![CoreEffect::Notify(format!( + "Loading wiki node {}", + entry.path + ))] + } + } + } + + fn back_wiki_browser(&mut self) -> Vec { + if !self.result_records.is_empty() { + self.result_records.clear(); + self.selected_wiki_browser_index = 0; + return vec![CoreEffect::Notify( + "Closed wiki search results.".to_string(), + )]; + } + if self.wiki_current_path == "/" { + return Vec::new(); + } + self.wiki_current_path = wiki_parent_path(self.wiki_current_path.as_str()); + self.selected_wiki_browser_index = 0; + vec![CoreEffect::Notify(format!( + "Opened {}", + self.wiki_current_path + ))] + } + fn visible_memory_records(&self) -> Vec<&KinicRecord> { if self.memories_mode != MemoriesMode::Browser { return Vec::new(); @@ -1908,8 +2319,11 @@ impl KinicProvider { let Some((wiki_id, database_id)) = self.selected_wiki_target(state) else { return; }; - if self.wiki_children_cache.contains_key(database_id.as_str()) - || self.pending_wiki_children_database_id.as_deref() == Some(database_id.as_str()) + let path = self.wiki_current_path.clone(); + let cache_key = wiki_children_cache_key(database_id.as_str(), path.as_str()); + if self.wiki_children_cache.contains_key(cache_key.as_str()) + || (self.pending_wiki_children_database_id.as_deref() == Some(database_id.as_str()) + && self.pending_wiki_children_path.as_deref() == Some(path.as_str())) { return; } @@ -1917,15 +2331,19 @@ impl KinicProvider { let auth = self.config.auth.clone(); let use_mainnet = self.config.use_mainnet; self.pending_wiki_children_database_id = Some(database_id.clone()); + self.pending_wiki_children_path = Some(path.clone()); spawn_request_task( &mut self.next_wiki_children_request_id, &mut self.wiki_children_task, move |request_id, tx| { let requested_database_id = database_id.clone(); - let result = load_wiki_children_content(use_mainnet, auth, wiki_id, database_id); + let requested_path = path.clone(); + let result = + load_wiki_children_content(use_mainnet, auth, wiki_id, database_id, path); let _ = tx.send(WikiChildrenTaskOutput { request_id, database_id: requested_database_id, + path: requested_path, result, }); }, @@ -2145,8 +2563,12 @@ impl KinicProvider { .iter() .filter_map(|record| record.source_wiki_database_id.clone()) .collect::>(); - self.wiki_children_cache - .retain(|database_id, _| database_ids.contains(database_id)); + self.wiki_children_cache.retain(|cache_key, _| { + cache_key + .split_once('\n') + .map(|(database_id, _)| database_ids.contains(database_id)) + .unwrap_or_else(|| database_ids.contains(cache_key)) + }); } fn apply_instance_summaries(&mut self, instances: bridge::InstanceSummaries) { @@ -2578,6 +3000,7 @@ impl KinicProvider { selected_index, selected_content, selected_context: None, + three_pane: self.build_wiki_three_pane_snapshot(state), total_count: filtered.len(), status_message: Some(self.status_message(state, filtered.len())), selected_memory: self.active_memory.clone(), @@ -2928,6 +3351,7 @@ impl KinicProvider { let auth = self.config.auth.clone(); let use_mainnet = self.config.use_mainnet; let query = query.to_string(); + self.selected_wiki_browser_index = 0; spawn_request_task( &mut self.next_wiki_search_request_id, &mut self.wiki_search_task, @@ -3323,9 +3747,6 @@ impl KinicProvider { if self.tab_id == KINIC_WIKI_TAB_ID { return "Browse wiki databases and search wiki nodes.".to_string(); } - if self.tab_id == KINIC_MARKET_TAB_ID { - return "Market is not implemented yet.".to_string(); - } let base = match self.memories_mode { MemoriesMode::Browser => self.browser_status_message(state), MemoriesMode::Results => match self.last_search_state.as_ref() { @@ -4525,6 +4946,7 @@ impl KinicProvider { PendingTaskPoll::Disconnected => { reset_request_task(&mut self.wiki_children_task); self.pending_wiki_children_database_id = None; + self.pending_wiki_children_path = None; return Some(self.disconnected_request_output( state, CoreEffect::Notify("Wiki browser load failed unexpectedly.".to_string()), @@ -4534,13 +4956,17 @@ impl KinicProvider { let is_current = finish_request_task(&mut self.wiki_children_task, output.request_id); self.pending_wiki_children_database_id = None; + self.pending_wiki_children_path = None; if !is_current { return Some(self.stale_request_output(state)); } let effects = match output.result { Ok(content) => { - self.wiki_children_cache.insert(output.database_id, content); + self.wiki_children_cache.insert( + wiki_children_cache_key(output.database_id.as_str(), output.path.as_str()), + content, + ); Vec::new() } Err(error) => vec![CoreEffect::Notify(format!( @@ -4559,9 +4985,8 @@ impl KinicProvider { PendingTaskPoll::Ready(output) => output, PendingTaskPoll::Disconnected => { reset_request_task(&mut self.wiki_databases_task); - self.wiki_records = vec![load_error_record( - "Wiki databases load failed unexpectedly.".to_string(), - )]; + self.wiki_records.clear(); + self.wiki_load_error = Some("Wiki databases load failed unexpectedly.".to_string()); return Some(self.disconnected_request_output( state, CoreEffect::Notify("Wiki databases load failed unexpectedly.".to_string()), @@ -4576,6 +5001,7 @@ impl KinicProvider { let effects = match output.result { Ok(databases) => { + self.wiki_load_error = None; self.wiki_databases = databases; self.refresh_wiki_records_from_databases(); if self.wiki_records.is_empty() { @@ -4592,7 +5018,8 @@ impl KinicProvider { } Err(error) => { self.wiki_databases.clear(); - self.wiki_records = vec![load_error_record(error.clone())]; + self.wiki_records.clear(); + self.wiki_load_error = Some(error.clone()); vec![CoreEffect::Notify(format!( "Wiki databases load failed: {}", short_error(error.as_str()) @@ -4791,11 +5218,6 @@ impl KinicProvider { self.result_records.clear(); vec![self.start_wiki_databases_load()] } - KINIC_MARKET_TAB_ID => { - vec![CoreEffect::Notify( - "Market is not implemented yet.".to_string(), - )] - } KINIC_SETTINGS_TAB_ID => self.start_session_settings_refresh().into_iter().collect(), _ => vec![CoreEffect::Notify(format!("Switched kinic tab: {tab_id}"))], } @@ -4945,12 +5367,32 @@ impl DataProvider for KinicProvider { CoreAction::MovePageUp if self.should_handle_memory_navigation(state) => { self.navigate_active_memory(state, action) } + CoreAction::MoveNext + | CoreAction::MovePrev + | CoreAction::MoveHome + | CoreAction::MoveEnd + | CoreAction::MovePageDown + | CoreAction::MovePageUp + if self.tab_id == KINIC_WIKI_TAB_ID && state.focus == PaneFocus::Content => + { + self.navigate_wiki_browser(state, action); + } CoreAction::OpenSelected => { - if self.is_add_memory_action_selected(state) { + if self.tab_id == KINIC_WIKI_TAB_ID && state.focus == PaneFocus::Content { + effects.extend(self.open_selected_wiki_browser_entry(state)); + } else if self.tab_id == KINIC_WIKI_TAB_ID { + self.wiki_current_path = "/".to_string(); + self.selected_wiki_browser_index = 0; + self.start_selected_wiki_children_load(state); + effects.push(CoreEffect::FocusPane(PaneFocus::Content)); + } else if self.is_add_memory_action_selected(state) { effects.push(CoreEffect::OpenAddMemory); effects.push(CoreEffect::FocusPane(PaneFocus::Items)); } } + CoreAction::Back if self.tab_id == KINIC_WIKI_TAB_ID => { + effects.extend(self.back_wiki_browser()); + } CoreAction::SetTab(id) => { effects.extend(self.set_tab(id.0.as_str())); if id.0.as_str() == KINIC_INSERT_TAB_ID { diff --git a/rust/tui/provider/tests.rs b/rust/tui/provider/tests.rs index ead759a..8415259 100644 --- a/rust/tui/provider/tests.rs +++ b/rust/tui/provider/tests.rs @@ -6,8 +6,8 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; use tui_kit_runtime::{ - CreateCostState, LoadedCreateCost, PickerConfirmKind, PickerContext, PickerItem, - PickerListMode, PickerState, RenameMemoryModalState, SessionAccountOverview, + CoreEffect, CreateCostState, LoadedCreateCost, PaneFocus, PickerConfirmKind, PickerContext, + PickerItem, PickerListMode, PickerState, RenameMemoryModalState, SessionAccountOverview, TextInputModalState, TransferModalState, }; diff --git a/rust/tui/provider/tests/wiki.rs b/rust/tui/provider/tests/wiki.rs index 04d6bdd..56e3c14 100644 --- a/rust/tui/provider/tests/wiki.rs +++ b/rust/tui/provider/tests/wiki.rs @@ -4,7 +4,7 @@ fn wiki_database(database_id: &str, status: bridge::DatabaseStatus) -> bridge::D bridge::DatabaseSummary { database_id: database_id.to_string(), status, - role: bridge::DatabaseRole::Owner, + role: crate::wiki_bridge::DatabaseRole::Owner, logical_size_bytes: 42, archived_at_ms: None, deleted_at_ms: None, @@ -60,6 +60,231 @@ fn wiki_database_records_sort_hot_database_first() { ); } +#[test] +fn wiki_three_pane_exposes_database_rows_separately_from_memory_items() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_databases = vec![wiki_database("db-a", bridge::DatabaseStatus::Hot)]; + provider.refresh_wiki_records_from_databases(); + + let snapshot = provider.build_snapshot(&CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + selected_index: Some(0), + ..CoreState::default() + }); + + assert_eq!(snapshot.three_pane.left.rows.len(), 1); + assert_eq!(snapshot.three_pane.left.rows[0].label, "db-a"); + assert!(snapshot.three_pane.left.rows[0].selected); + assert_eq!(snapshot.items.len(), 1); +} + +#[test] +fn wiki_database_load_error_uses_diagnostic_not_error_record() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_load_error = Some("wiki query failed for list_databases".to_string()); + provider.wiki_records.clear(); + + let snapshot = provider.build_snapshot(&CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + ..CoreState::default() + }); + + assert!(snapshot.items.is_empty()); + assert!(snapshot.three_pane.diagnostic.is_some()); + assert_eq!( + snapshot + .three_pane + .diagnostic + .as_ref() + .and_then(|d| d.rows.iter().find(|row| row.label == "canister")) + .map(|row| row.detail.as_str()), + Some("aaaaa-aa") + ); +} + +#[test] +fn wiki_diagnostic_uses_session_principal_without_resolving_auth() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.session_overview.session.principal_id = "principal-from-session".to_string(); + provider.wiki_load_error = Some("wiki query failed for list_databases".to_string()); + + let snapshot = provider.build_snapshot(&CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + ..CoreState::default() + }); + + assert_eq!( + snapshot + .three_pane + .diagnostic + .and_then(|d| d.rows.into_iter().find(|row| row.label == "principal")) + .map(|row| row.detail), + Some("principal-from-session".to_string()) + ); +} + +#[test] +fn wiki_browser_enter_on_directory_updates_current_path() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_records = vec![record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-a", bridge::DatabaseStatus::Hot), + )]; + provider.wiki_children_cache.insert( + wiki_children_cache_key("db-a", "/"), + WikiChildrenContent { + entries: vec![WikiBrowserEntry { + path: "/Wiki".to_string(), + name: "Wiki".to_string(), + kind: WikiBrowserEntryKind::Directory, + size_bytes: None, + has_children: true, + }], + body_lines: vec!["+ /Wiki (directory)".to_string()], + index_preview: None, + }, + ); + provider.wiki_children_cache.insert( + wiki_children_cache_key("db-a", "/Wiki"), + WikiChildrenContent { + entries: Vec::new(), + body_lines: Vec::new(), + index_preview: Some(vec!["cached wiki".to_string()]), + }, + ); + + let effects = provider.open_selected_wiki_browser_entry(&CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + focus: PaneFocus::Content, + selected_index: Some(0), + ..CoreState::default() + }); + + assert_eq!(provider.wiki_current_path, "/Wiki"); + assert!(effects.iter().any(|effect| { + matches!(effect, CoreEffect::Notify(message) if message.contains("Opened /Wiki")) + })); +} + +#[test] +fn wiki_root_entries_only_include_top_level_directories() { + let entries = wiki_root_entries(); + + assert_eq!(entries.len(), 2); + assert_eq!(entries[0].path, "/Wiki"); + assert_eq!(entries[0].kind, WikiBrowserEntryKind::Directory); + assert_eq!(entries[1].path, "/Sources"); + assert_eq!(entries[1].kind, WikiBrowserEntryKind::Directory); +} + +#[test] +fn wiki_browser_can_move_to_sources_and_enter_directory() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_records = vec![record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-a", bridge::DatabaseStatus::Hot), + )]; + provider.wiki_children_cache.insert( + wiki_children_cache_key("db-a", "/"), + WikiChildrenContent { + entries: wiki_root_entries(), + body_lines: vec![ + "+ /Wiki (directory)".to_string(), + "+ /Sources (directory)".to_string(), + ], + index_preview: None, + }, + ); + provider.wiki_children_cache.insert( + wiki_children_cache_key("db-a", "/Sources"), + WikiChildrenContent { + entries: Vec::new(), + body_lines: Vec::new(), + index_preview: None, + }, + ); + let state = CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + focus: PaneFocus::Content, + selected_index: Some(0), + ..CoreState::default() + }; + + provider.navigate_wiki_browser(&state, &CoreAction::MoveNext); + let effects = provider.open_selected_wiki_browser_entry(&state); + + assert_eq!(provider.selected_wiki_browser_index, 0); + assert_eq!(provider.wiki_current_path, "/Sources"); + assert!(effects.iter().any(|effect| { + matches!(effect, CoreEffect::Notify(message) if message.contains("Opened /Sources")) + })); +} + +#[test] +fn wiki_search_results_use_browser_selection_for_navigation() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.result_records = vec![ + record_from_wiki_search_hit( + "aaaaa-aa", + "db-a", + 0, + bridge::WikiSearchHit { + path: "/Wiki/a.md".to_string(), + score: 1.0, + snippet: None, + }, + ), + record_from_wiki_search_hit( + "aaaaa-aa", + "db-a", + 1, + bridge::WikiSearchHit { + path: "/Wiki/b.md".to_string(), + score: 0.8, + snippet: None, + }, + ), + ]; + let state = CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + focus: PaneFocus::Content, + selected_index: Some(0), + ..CoreState::default() + }; + + provider.navigate_wiki_browser(&state, &CoreAction::MoveNext); + let rows = provider.wiki_browser_rows(0); + + assert_eq!(provider.selected_wiki_browser_index, 1); + assert!(!rows[0].selected); + assert!(rows[1].selected); + assert_eq!(rows[1].label, "/Wiki/b.md"); +} + #[test] fn selected_wiki_target_follows_displayed_search_results() { let mut provider = KinicProvider::new(TuiConfig { @@ -115,6 +340,13 @@ fn wiki_content_render_uses_database_cache_without_starting_query() { provider.wiki_children_cache.insert( "db-a".to_string(), WikiChildrenContent { + entries: vec![WikiBrowserEntry { + path: "/Wiki/index.md".to_string(), + name: "index.md".to_string(), + kind: WikiBrowserEntryKind::File, + size_bytes: None, + has_children: false, + }], body_lines: vec!["- /Wiki/index.md (file)".to_string()], index_preview: Some(vec!["cached preview".to_string()]), }, diff --git a/rust/tui/ui_config.rs b/rust/tui/ui_config.rs index e8665ad..c982d56 100644 --- a/rust/tui/ui_config.rs +++ b/rust/tui/ui_config.rs @@ -1,7 +1,7 @@ use tui_kit_render::ui::{BrandingText, HeaderText, TabId, TabSpec, UiConfig}; pub use tui_kit_runtime::kinic_tabs::{ - KINIC_CREATE_TAB_ID, KINIC_INSERT_TAB_ID, KINIC_MARKET_TAB_ID, KINIC_MEMORIES_TAB_ID, - KINIC_SETTINGS_TAB_ID, KINIC_WIKI_TAB_ID, + KINIC_CREATE_TAB_ID, KINIC_INSERT_TAB_ID, KINIC_MEMORIES_TAB_ID, KINIC_SETTINGS_TAB_ID, + KINIC_WIKI_TAB_ID, }; pub fn kinic_ui_config() -> UiConfig { @@ -46,11 +46,6 @@ pub fn kinic_ui_config() -> UiConfig { title: "Wiki".to_string(), search_placeholder: "Search wiki nodes...".to_string(), }, - TabSpec { - id: TabId::new(KINIC_MARKET_TAB_ID), - title: "Market".to_string(), - search_placeholder: "Market is coming soon...".to_string(), - }, TabSpec { id: TabId::new(KINIC_SETTINGS_TAB_ID), title: "Settings".to_string(), diff --git a/rust/wiki_bridge.rs b/rust/wiki_bridge.rs new file mode 100644 index 0000000..fcf6361 --- /dev/null +++ b/rust/wiki_bridge.rs @@ -0,0 +1,379 @@ +use anyhow::{Context, Result}; +use candid::{CandidType, Decode, Encode}; +use ic_agent::{Agent, export::Principal}; +use serde::{Deserialize, Serialize}; + +pub const WIKI_CANISTER_ID_ENV_VAR: &str = "KINIC_WIKI_CANISTER_ID"; +pub const DEFAULT_WIKI_CANISTER_ID: &str = "xis3j-paaaa-aaaai-axumq-cai"; + +pub fn wiki_canister_id_from_env() -> String { + std::env::var(WIKI_CANISTER_ID_ENV_VAR) + .ok() + .map(|value| value.trim().to_string()) + .filter(|value| !value.is_empty()) + .unwrap_or_else(|| DEFAULT_WIKI_CANISTER_ID.to_string()) +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub enum DatabaseRole { + Owner, + Writer, + Reader, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub enum DatabaseStatus { + Hot, + Restoring, + Archiving, + Archived, + Deleted, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct DatabaseSummary { + pub database_id: String, + pub status: DatabaseStatus, + pub role: DatabaseRole, + pub logical_size_bytes: u64, + pub archived_at_ms: Option, + pub deleted_at_ms: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct ListChildrenRequest { + pub database_id: String, + pub path: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub enum NodeEntryKind { + File, + Source, + Directory, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct ChildNode { + pub path: String, + pub name: String, + pub kind: NodeEntryKind, + pub updated_at: Option, + pub etag: Option, + pub size_bytes: Option, + pub is_virtual: bool, + pub has_children: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub enum NodeKind { + File, + Source, + Directory, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct Node { + pub path: String, + pub kind: NodeKind, + pub content: String, + pub created_at: i64, + pub updated_at: i64, + pub etag: String, + pub metadata_json: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub enum SearchPreviewMode { + Light, + ContentStart, + None, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub enum SearchPreviewField { + Path, + Content, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct SearchPreview { + pub field: SearchPreviewField, + pub char_offset: u32, + pub match_reason: String, + pub excerpt: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, CandidType)] +pub struct SearchNodeHit { + pub path: String, + pub kind: NodeKind, + pub snippet: Option, + pub preview: Option, + pub score: f32, + pub match_reasons: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct SearchNodesRequest { + pub database_id: String, + pub query_text: String, + pub prefix: Option, + pub top_k: u32, + pub preview_mode: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct WriteNodeRequest { + pub database_id: String, + pub path: String, + pub kind: NodeKind, + pub content: String, + pub metadata_json: String, + pub expected_etag: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct AppendNodeRequest { + pub database_id: String, + pub path: String, + pub content: String, + pub expected_etag: Option, + pub separator: Option, + pub metadata_json: Option, + pub kind: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct EditNodeRequest { + pub database_id: String, + pub path: String, + pub old_text: String, + pub new_text: String, + pub expected_etag: Option, + pub replace_all: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct DeleteNodeRequest { + pub database_id: String, + pub path: String, + pub expected_etag: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct RecentNodeHit { + pub path: String, + pub kind: NodeKind, + pub etag: String, + pub updated_at: i64, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct WriteNodeResult { + pub created: bool, + pub node: RecentNodeHit, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct EditNodeResult { + pub replacement_count: u32, + pub node: RecentNodeHit, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)] +pub struct DeleteNodeResult { + pub path: String, +} + +#[derive(Clone)] +pub struct WikiClient { + agent: Agent, + canister_id: Principal, +} + +impl WikiClient { + pub fn new(agent: Agent, canister_id: impl AsRef) -> Result { + Ok(Self { + agent, + canister_id: Principal::from_text(canister_id.as_ref()) + .context("failed to parse wiki canister principal")?, + }) + } + + async fn query(&self, method: &str, arg: &Arg) -> Result + where + Arg: CandidType, + Out: for<'de> candid::Deserialize<'de> + CandidType, + { + let bytes = self + .agent + .query(&self.canister_id, method) + .with_arg(Encode!(arg).context("failed to encode wiki query args")?) + .call() + .await + .with_context(|| format!("wiki query failed for {method}"))?; + Decode!(&bytes, Out).with_context(|| format!("failed to decode wiki response for {method}")) + } + + async fn query2(&self, method: &str, a: &A, b: &B) -> Result + where + A: CandidType, + B: CandidType, + Out: for<'de> candid::Deserialize<'de> + CandidType, + { + let bytes = self + .agent + .query(&self.canister_id, method) + .with_arg(Encode!(a, b).context("failed to encode wiki query args")?) + .call() + .await + .with_context(|| format!("wiki query failed for {method}"))?; + Decode!(&bytes, Out).with_context(|| format!("failed to decode wiki response for {method}")) + } + + async fn update(&self, method: &str, arg: &Arg) -> Result + where + Arg: CandidType, + Out: for<'de> candid::Deserialize<'de> + CandidType, + { + let bytes = self + .agent + .update(&self.canister_id, method) + .with_arg(Encode!(arg).context("failed to encode wiki update args")?) + .call_and_wait() + .await + .with_context(|| format!("wiki update failed for {method}"))?; + Decode!(&bytes, Out).with_context(|| format!("failed to decode wiki response for {method}")) + } + + async fn update3(&self, method: &str, a: &A, b: &B, c: &C) -> Result + where + A: CandidType, + B: CandidType, + C: CandidType, + Out: for<'de> candid::Deserialize<'de> + CandidType, + { + let bytes = self + .agent + .update(&self.canister_id, method) + .with_arg(Encode!(a, b, c).context("failed to encode wiki update args")?) + .call_and_wait() + .await + .with_context(|| format!("wiki update failed for {method}"))?; + Decode!(&bytes, Out).with_context(|| format!("failed to decode wiki response for {method}")) + } + + pub async fn list_databases(&self) -> Result> { + let result: Result, String> = + self.query("list_databases", &()).await?; + result.map_err(anyhow::Error::msg) + } + + pub async fn create_database(&self) -> Result { + let result: Result = self.update("create_database", &()).await?; + result.map_err(anyhow::Error::msg) + } + + pub async fn grant_database_access( + &self, + database_id: &str, + principal: &str, + role: DatabaseRole, + ) -> Result<()> { + let result: Result<(), String> = self + .update3( + "grant_database_access", + &database_id.to_string(), + &principal.to_string(), + &role, + ) + .await?; + result.map_err(anyhow::Error::msg) + } + + pub async fn list_children(&self, request: ListChildrenRequest) -> Result> { + let result: Result, String> = self.query("list_children", &request).await?; + result.map_err(anyhow::Error::msg) + } + + pub async fn read_node(&self, database_id: &str, path: &str) -> Result> { + let result: Result, String> = self + .query2("read_node", &database_id.to_string(), &path.to_string()) + .await?; + result.map_err(anyhow::Error::msg) + } + + pub async fn search_nodes(&self, request: SearchNodesRequest) -> Result> { + let result: Result, String> = + self.query("search_nodes", &request).await?; + result.map_err(anyhow::Error::msg) + } + + pub async fn write_node(&self, request: WriteNodeRequest) -> Result { + let result: Result = self.update("write_node", &request).await?; + result.map_err(anyhow::Error::msg) + } + + pub async fn append_node(&self, request: AppendNodeRequest) -> Result { + let result: Result = self.update("append_node", &request).await?; + result.map_err(anyhow::Error::msg) + } + + pub async fn edit_node(&self, request: EditNodeRequest) -> Result { + let result: Result = self.update("edit_node", &request).await?; + result.map_err(anyhow::Error::msg) + } + + pub async fn delete_node(&self, request: DeleteNodeRequest) -> Result { + let result: Result = self.update("delete_node", &request).await?; + result.map_err(anyhow::Error::msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn database_summary_decodes_list_databases_shape() { + let bytes = Encode!(&Ok::<_, String>(vec![DatabaseSummary { + database_id: "default".to_string(), + status: DatabaseStatus::Hot, + role: DatabaseRole::Owner, + logical_size_bytes: 42, + archived_at_ms: None, + deleted_at_ms: None, + }])) + .expect("database list should encode"); + let decoded = Decode!(&bytes, Result, String>) + .expect("database list should decode"); + + assert_eq!(decoded.unwrap()[0].database_id, "default"); + } + + #[test] + fn write_delete_and_grant_shapes_encode() { + Encode!(&WriteNodeRequest { + database_id: "db".to_string(), + path: "/Wiki/a.md".to_string(), + kind: NodeKind::File, + content: "body".to_string(), + metadata_json: "{}".to_string(), + expected_etag: Some("etag".to_string()), + }) + .expect("write request should encode"); + Encode!(&DeleteNodeRequest { + database_id: "db".to_string(), + path: "/Wiki/a.md".to_string(), + expected_etag: None, + }) + .expect("delete request should encode"); + Encode!( + &"db".to_string(), + &"aaaaa-aa".to_string(), + &DatabaseRole::Reader + ) + .expect("grant args should encode"); + } +} diff --git a/tests/fixtures/capabilities_golden.json b/tests/fixtures/capabilities_golden.json index df036ed..187fb41 100644 --- a/tests/fixtures/capabilities_golden.json +++ b/tests/fixtures/capabilities_golden.json @@ -1380,6 +1380,611 @@ } ] }, + { + "name": "wiki", + "summary": "Operate the configured Wiki canister. Requires --identity or --ii. Returns text output by default.", + "auth": { + "required": true, + "sources": [ + "global_identity", + "global_ii" + ] + }, + "output": { + "default": "text", + "supported": [ + "text", + "json" + ], + "interactive": false + }, + "global_flags_supported": [ + "verbose", + "ic", + "identity", + "ii", + "identity_path" + ], + "arguments": [], + "subcommands": [ + { + "name": "database", + "summary": "Manage Wiki databases", + "auth": { + "required": true, + "sources": [ + "global_identity", + "global_ii" + ] + }, + "output": { + "default": "text", + "supported": [ + "text", + "json" + ], + "interactive": false + }, + "global_flags_supported": [ + "verbose", + "ic", + "identity", + "ii", + "identity_path" + ], + "arguments": [], + "subcommands": [ + { + "name": "list", + "summary": "List databases visible to the caller", + "auth": { + "required": true, + "sources": [ + "global_identity", + "global_ii" + ] + }, + "output": { + "default": "text", + "supported": [ + "text", + "json" + ], + "interactive": false + }, + "global_flags_supported": [ + "verbose", + "ic", + "identity", + "ii", + "identity_path" + ], + "arguments": [ + { + "name": "json", + "required": false, + "input_shape": "flag", + "value_kind": "boolean" + } + ] + }, + { + "name": "create", + "summary": "Create a new database", + "auth": { + "required": true, + "sources": [ + "global_identity", + "global_ii" + ] + }, + "output": { + "default": "text", + "supported": [ + "text", + "json" + ], + "interactive": false + }, + "global_flags_supported": [ + "verbose", + "ic", + "identity", + "ii", + "identity_path" + ], + "arguments": [ + { + "name": "json", + "required": false, + "input_shape": "flag", + "value_kind": "boolean" + } + ] + }, + { + "name": "grant", + "summary": "Grant database access to a principal", + "auth": { + "required": true, + "sources": [ + "global_identity", + "global_ii" + ] + }, + "output": { + "default": "text", + "supported": [ + "text", + "json" + ], + "interactive": false + }, + "global_flags_supported": [ + "verbose", + "ic", + "identity", + "ii", + "identity_path" + ], + "arguments": [ + { + "name": "json", + "required": false, + "input_shape": "flag", + "value_kind": "boolean" + } + ], + "arg_groups": [ + { + "id": "WikiDatabaseGrantArgs", + "required": false, + "multiple": true, + "members": [ + "database_id", + "principal", + "role", + "json" + ] + } + ] + } + ] + }, + { + "name": "read", + "summary": "Read a Wiki node", + "auth": { + "required": true, + "sources": [ + "global_identity", + "global_ii" + ] + }, + "output": { + "default": "text", + "supported": [ + "text", + "json" + ], + "interactive": false + }, + "global_flags_supported": [ + "verbose", + "ic", + "identity", + "ii", + "identity_path" + ], + "arguments": [ + { + "name": "database_id", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "path", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "json", + "required": false, + "input_shape": "flag", + "value_kind": "boolean" + } + ] + }, + { + "name": "children", + "summary": "List Wiki children under a path", + "auth": { + "required": true, + "sources": [ + "global_identity", + "global_ii" + ] + }, + "output": { + "default": "text", + "supported": [ + "text", + "json" + ], + "interactive": false + }, + "global_flags_supported": [ + "verbose", + "ic", + "identity", + "ii", + "identity_path" + ], + "arguments": [ + { + "name": "database_id", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "path", + "required": false, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "json", + "required": false, + "input_shape": "flag", + "value_kind": "boolean" + } + ] + }, + { + "name": "search", + "summary": "Search Wiki nodes", + "auth": { + "required": true, + "sources": [ + "global_identity", + "global_ii" + ] + }, + "output": { + "default": "text", + "supported": [ + "text", + "json" + ], + "interactive": false + }, + "global_flags_supported": [ + "verbose", + "ic", + "identity", + "ii", + "identity_path" + ], + "arguments": [ + { + "name": "database_id", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "prefix", + "required": false, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "top_k", + "required": false, + "input_shape": "single_value", + "value_kind": "integer" + }, + { + "name": "json", + "required": false, + "input_shape": "flag", + "value_kind": "boolean" + } + ], + "arg_groups": [ + { + "id": "WikiSearchArgs", + "required": false, + "multiple": true, + "members": [ + "database_id", + "query", + "prefix", + "top_k", + "json" + ] + } + ] + }, + { + "name": "write", + "summary": "Write a Wiki node from a file", + "auth": { + "required": true, + "sources": [ + "global_identity", + "global_ii" + ] + }, + "output": { + "default": "text", + "supported": [ + "text", + "json" + ], + "interactive": false + }, + "global_flags_supported": [ + "verbose", + "ic", + "identity", + "ii", + "identity_path" + ], + "arguments": [ + { + "name": "database_id", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "path", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "input", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "kind", + "required": false, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "metadata_json", + "required": false, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "expected_etag", + "required": false, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "json", + "required": false, + "input_shape": "flag", + "value_kind": "boolean" + } + ] + }, + { + "name": "append", + "summary": "Append file contents to a Wiki node", + "auth": { + "required": true, + "sources": [ + "global_identity", + "global_ii" + ] + }, + "output": { + "default": "text", + "supported": [ + "text", + "json" + ], + "interactive": false + }, + "global_flags_supported": [ + "verbose", + "ic", + "identity", + "ii", + "identity_path" + ], + "arguments": [ + { + "name": "database_id", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "path", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "input", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "expected_etag", + "required": false, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "separator", + "required": false, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "kind", + "required": false, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "metadata_json", + "required": false, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "json", + "required": false, + "input_shape": "flag", + "value_kind": "boolean" + } + ] + }, + { + "name": "edit", + "summary": "Replace text in a Wiki node", + "auth": { + "required": true, + "sources": [ + "global_identity", + "global_ii" + ] + }, + "output": { + "default": "text", + "supported": [ + "text", + "json" + ], + "interactive": false + }, + "global_flags_supported": [ + "verbose", + "ic", + "identity", + "ii", + "identity_path" + ], + "arguments": [ + { + "name": "database_id", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "path", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "old_text", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "new_text", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "replace_all", + "required": false, + "input_shape": "flag", + "value_kind": "boolean" + }, + { + "name": "expected_etag", + "required": false, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "json", + "required": false, + "input_shape": "flag", + "value_kind": "boolean" + } + ] + }, + { + "name": "delete", + "summary": "Delete a Wiki node. Requires --yes.", + "auth": { + "required": true, + "sources": [ + "global_identity", + "global_ii" + ] + }, + "output": { + "default": "text", + "supported": [ + "text", + "json" + ], + "interactive": false + }, + "global_flags_supported": [ + "verbose", + "ic", + "identity", + "ii", + "identity_path" + ], + "arguments": [ + { + "name": "database_id", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "path", + "required": true, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "expected_etag", + "required": false, + "input_shape": "single_value", + "value_kind": "string" + }, + { + "name": "yes", + "required": false, + "input_shape": "flag", + "value_kind": "boolean" + }, + { + "name": "json", + "required": false, + "input_shape": "flag", + "value_kind": "boolean" + } + ] + } + ] + }, { "name": "tui", "summary": "Launch the Kinic terminal UI. Requires global --identity . --ii is not supported. Returns an interactive TUI, not JSON.", diff --git a/tui/crates/tui-kit-host/src/lib_tests.rs b/tui/crates/tui-kit-host/src/lib_tests.rs index 8086a3c..fdc7238 100644 --- a/tui/crates/tui-kit-host/src/lib_tests.rs +++ b/tui/crates/tui-kit-host/src/lib_tests.rs @@ -1,6 +1,6 @@ use super::*; use tui_kit_runtime::kinic_tabs::{ - KINIC_CREATE_TAB_ID, KINIC_MARKET_TAB_ID, KINIC_MEMORIES_TAB_ID, KINIC_SETTINGS_TAB_ID, + KINIC_CREATE_TAB_ID, KINIC_MEMORIES_TAB_ID, KINIC_SETTINGS_TAB_ID, }; use tui_kit_runtime::{CoreState, ProviderSnapshot, TransferModalState, apply_snapshot}; @@ -415,11 +415,6 @@ mod global_commands { KINIC_CREATE_TAB_ID, HostGlobalCommand::BackFromFormToTabs, ), - ( - PaneFocus::Content, - KINIC_MARKET_TAB_ID, - HostGlobalCommand::BackToTabs, - ), ( PaneFocus::Content, KINIC_SETTINGS_TAB_ID, diff --git a/tui/crates/tui-kit-host/src/runtime_loop.rs b/tui/crates/tui-kit-host/src/runtime_loop.rs index d18e485..da39dfe 100644 --- a/tui/crates/tui-kit-host/src/runtime_loop.rs +++ b/tui/crates/tui-kit-host/src/runtime_loop.rs @@ -197,6 +197,7 @@ pub fn run_provider_app_with_hooks>( .ui_config(ui_config()) .ui_summaries(&state.list_items) .ui_selected_content(state.selected_content.as_ref()) + .three_pane_snapshot(&state.three_pane) .ui_total_count(state.total_count) .list_selected(state.selected_index) .list_scroll(list_scroll_offset) @@ -682,6 +683,7 @@ fn build_ui<'a>( .ui_config((cfg.ui_config)()) .ui_summaries(&state.list_items) .ui_selected_content(state.selected_content.as_ref()) + .three_pane_snapshot(&state.three_pane) .ui_total_count(state.total_count) .list_selected(state.selected_index) .list_scroll(list_scroll_offset) diff --git a/tui/crates/tui-kit-host/src/runtime_loop_tests.rs b/tui/crates/tui-kit-host/src/runtime_loop_tests.rs index 5d44b6f..ce5b134 100644 --- a/tui/crates/tui-kit-host/src/runtime_loop_tests.rs +++ b/tui/crates/tui-kit-host/src/runtime_loop_tests.rs @@ -3,7 +3,7 @@ use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; use std::path::PathBuf; use tui_kit_render::ui::UiConfig; use tui_kit_runtime::kinic_tabs::{ - KINIC_CREATE_TAB_ID, KINIC_INSERT_TAB_ID, KINIC_MARKET_TAB_ID, KINIC_MEMORIES_TAB_ID, + KINIC_CREATE_TAB_ID, KINIC_INSERT_TAB_ID, KINIC_MEMORIES_TAB_ID, KINIC_SETTINGS_TAB_ID, }; use tui_kit_runtime::{ CoreError, CoreResult, InsertFormFocus, InsertMode, PaneFocus, PickerContext, PickerListMode, @@ -53,7 +53,7 @@ fn test_runtime_config() -> RuntimeLoopConfig { KINIC_MEMORIES_TAB_ID, KINIC_CREATE_TAB_ID, KINIC_INSERT_TAB_ID, - KINIC_MARKET_TAB_ID, + KINIC_SETTINGS_TAB_ID, ], initial_focus: PaneFocus::Form, ui_config: test_ui_config, @@ -122,7 +122,7 @@ fn normalize_focus_resets_insert_tab_to_tabs_and_mode_field() { #[test] fn normalize_focus_keeps_placeholder_tabs_on_tabs() { let mut state = CoreState { - current_tab_id: KINIC_MARKET_TAB_ID.to_string(), + current_tab_id: KINIC_SETTINGS_TAB_ID.to_string(), focus: PaneFocus::Content, ..CoreState::default() }; @@ -1833,7 +1833,7 @@ fn switch_to_tab_failure_keeps_existing_focus_when_target_tab_allows_it() { let mut provider = TestProvider::err("tab failed"); let mut hooks = NoopRuntimeHooks; let mut state = CoreState { - current_tab_id: KINIC_MARKET_TAB_ID.to_string(), + current_tab_id: KINIC_SETTINGS_TAB_ID.to_string(), focus: PaneFocus::Content, ..CoreState::default() }; diff --git a/tui/crates/tui-kit-render/src/ui/app/screens/mod.rs b/tui/crates/tui-kit-render/src/ui/app/screens/mod.rs index a9fbc04..f580fff 100644 --- a/tui/crates/tui-kit-render/src/ui/app/screens/mod.rs +++ b/tui/crates/tui-kit-render/src/ui/app/screens/mod.rs @@ -3,8 +3,8 @@ pub mod create; pub mod insert; pub mod memories; -pub mod placeholder; pub mod settings; +pub mod wiki; use ratatui::{buffer::Buffer, layout::Rect, text::Line}; use tui_kit_runtime::CreateSubmitState; @@ -13,24 +13,6 @@ use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; use crate::ui::app::TuiKitUi; -struct PlaceholderScreenSpec<'a> { - title: &'a str, - lead: &'a str, - detail: &'a str, -} - -fn placeholder_screen_spec(kind: TabKind) -> Option> { - match kind { - TabKind::PlaceholderMarket => Some(PlaceholderScreenSpec { - title: "Market", - lead: "Market tab is reserved for future discovery and purchase flows.", - detail: "Use Memories to browse and Create to provision a new memory today.", - }), - TabKind::PlaceholderSettings => None, - _ => None, - } -} - impl<'a> TuiKitUi<'a> { pub(crate) fn render_tab_screen(&self, area: Rect, buf: &mut Buffer) -> bool { match tab_kind(self.current_tab_id.0.as_str()) { @@ -46,13 +28,11 @@ impl<'a> TuiKitUi<'a> { self.render_settings_screen(area, buf); true } - kind => { - let Some(spec) = placeholder_screen_spec(kind) else { - return false; - }; - self.render_placeholder_screen(area, buf, spec.title, spec.lead, spec.detail); + TabKind::Wiki => { + self.render_wiki_screen(area, buf); true } + _ => false, } } } diff --git a/tui/crates/tui-kit-render/src/ui/app/screens/placeholder/mod.rs b/tui/crates/tui-kit-render/src/ui/app/screens/placeholder/mod.rs deleted file mode 100644 index b2fa3e9..0000000 --- a/tui/crates/tui-kit-render/src/ui/app/screens/placeholder/mod.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Placeholder bodies for non-interactive tabs. - -use ratatui::{ - buffer::Buffer, - style::Style, - text::{Line, Span}, - widgets::{Block, Borders, Paragraph, Widget, Wrap}, -}; - -use crate::ui::app::{Focus, TuiKitUi}; - -impl<'a> TuiKitUi<'a> { - pub(crate) fn render_placeholder_screen( - &self, - area: ratatui::layout::Rect, - buf: &mut Buffer, - title: &str, - lead: &str, - detail: &str, - ) { - let body_area = crate::ui::app::shared::layout::body_rect_for_area_with_tabs( - area, - !self.tab_specs.is_empty(), - ); - let body = vec![ - Line::from(""), - Line::from(Span::styled(lead, self.theme.style_normal())), - Line::from(""), - Line::from(Span::styled(detail, self.theme.style_muted())), - Line::from(""), - Line::from(vec![ - Span::styled(" 1 ", self.theme.style_accent()), - Span::styled("Memories", self.theme.style_muted()), - Span::styled(" │ ", self.theme.style_dim()), - Span::styled(" 2 ", self.theme.style_accent()), - Span::styled("Create", self.theme.style_muted()), - ]), - ]; - Paragraph::new(body) - .block( - Block::default() - .borders(Borders::ALL) - .border_style(if self.focus == Focus::Tabs { - self.theme.style_border() - } else { - self.theme.style_border_focused() - }) - .title(format!(" {} ", title)) - .style(Style::default().bg(self.theme.bg_panel)), - ) - .wrap(Wrap { trim: false }) - .render(body_area, buf); - } -} diff --git a/tui/crates/tui-kit-render/src/ui/app/screens/wiki.rs b/tui/crates/tui-kit-render/src/ui/app/screens/wiki.rs new file mode 100644 index 0000000..7aa90c4 --- /dev/null +++ b/tui/crates/tui-kit-render/src/ui/app/screens/wiki.rs @@ -0,0 +1,264 @@ +//! Wiki browser screen composition. + +use ratatui::{ + buffer::Buffer, + layout::{Constraint, Direction, Layout, Rect}, + style::{Modifier, Style}, + text::{Line, Span}, + widgets::{Block, Borders, List, ListItem, Paragraph, Widget, Wrap}, +}; +use tui_kit_runtime::{PaneRow, ThreePaneSnapshot}; + +use crate::ui::app::{Focus, TuiKitUi, shared}; + +impl<'a> TuiKitUi<'a> { + pub(crate) fn render_wiki_screen(&self, area: Rect, buf: &mut Buffer) { + let body = shared::layout::body_rect_for_area_with_tabs(area, !self.tab_specs.is_empty()); + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Length(30), + Constraint::Length(36), + Constraint::Min(24), + ]) + .split(body); + let default_snapshot = ThreePaneSnapshot::default(); + let snapshot = self.three_pane.unwrap_or(&default_snapshot); + + self.render_wiki_databases(chunks[0], buf, snapshot); + self.render_wiki_browser(chunks[1], buf, snapshot); + self.render_wiki_document(chunks[2], buf, snapshot); + } + + fn render_wiki_databases(&self, area: Rect, buf: &mut Buffer, snapshot: &ThreePaneSnapshot) { + let title = if snapshot.left.loading { + " Databases (loading) " + } else { + " Databases " + }; + let items = if snapshot.left.rows.is_empty() { + vec![ListItem::new(Line::from(Span::styled( + format!(" {}", snapshot.left.empty_message), + self.theme.style_dim(), + )))] + } else { + snapshot + .left + .rows + .iter() + .map(|row| self.render_wiki_database_row(row)) + .collect() + }; + let block = Block::default() + .borders(Borders::ALL) + .border_style(if self.focus == Focus::Items { + self.theme.style_border_focused() + } else { + self.theme.style_border() + }) + .style(Style::default().bg(self.theme.bg_panel)) + .title(title); + List::new(items).block(block).render(area, buf); + } + + fn render_wiki_database_row(&self, row: &PaneRow) -> ListItem<'static> { + let marker = if row.selected { "▸" } else { " " }; + let style = if row.selected { + self.theme.style_normal().add_modifier(Modifier::BOLD) + } else { + self.theme.style_dim() + }; + ListItem::new(vec![ + Line::from(vec![ + Span::styled(format!("{marker} "), self.theme.style_accent()), + Span::styled(row.label.clone(), style), + ]), + Line::from(vec![Span::styled( + format!(" {}", row.detail), + self.theme.style_muted(), + )]), + ]) + } + + fn render_wiki_browser(&self, area: Rect, buf: &mut Buffer, snapshot: &ThreePaneSnapshot) { + let title = if snapshot.middle_mode == "search" { + " Browser (search) " + } else { + " Browser " + }; + let rows = if let Some(diagnostic) = &snapshot.diagnostic { + vec![PaneRow { + label: "list_databases failed".to_string(), + detail: diagnostic.message.clone(), + selected: false, + }] + } else if snapshot.middle.rows.is_empty() { + vec![PaneRow { + label: "No entries".to_string(), + detail: snapshot.middle.empty_message.clone(), + selected: false, + }] + } else { + snapshot.middle.rows.clone() + }; + let items = rows + .iter() + .map(|row| { + let marker = if row.selected { "▸" } else { " " }; + let style = if row.selected { + self.theme.style_normal().add_modifier(Modifier::BOLD) + } else { + self.theme.style_dim() + }; + ListItem::new(vec![ + Line::from(vec![ + Span::styled(format!("{marker} "), self.theme.style_accent()), + Span::styled(row.label.clone(), style), + ]), + Line::from(Span::styled( + format!(" {}", row.detail), + self.theme.style_muted(), + )), + ]) + }) + .collect::>(); + let block = Block::default() + .borders(Borders::ALL) + .border_style(self.theme.style_border()) + .style(Style::default().bg(self.theme.bg_panel)) + .title(title); + List::new(items).block(block).render(area, buf); + } + + fn render_wiki_document(&self, area: Rect, buf: &mut Buffer, snapshot: &ThreePaneSnapshot) { + let mut lines = Vec::new(); + if let Some(diagnostic) = &snapshot.diagnostic { + for row in &diagnostic.rows { + lines.push(Line::from(vec![ + Span::styled(format!("{}: ", row.label), self.theme.style_muted()), + Span::raw(row.detail.clone()), + ])); + } + lines.push(Line::from("")); + lines.push(Line::from(diagnostic.message.clone())); + } else if snapshot.document.lines.is_empty() { + lines.push(Line::from( + "Select a wiki database to browse /Wiki and /Sources.", + )); + } else { + lines.extend(snapshot.document.lines.iter().cloned().map(Line::from)); + } + let title = if snapshot.document.title.trim().is_empty() { + " Document ".to_string() + } else { + format!(" Document: {} ", snapshot.document.title) + }; + let block = Block::default() + .borders(Borders::ALL) + .border_style(if self.focus == Focus::Content { + self.theme.style_border_focused() + } else { + self.theme.style_border() + }) + .style(Style::default().bg(self.theme.bg_panel)) + .title(title); + Paragraph::new(lines) + .block(block) + .wrap(Wrap { trim: false }) + .scroll((self.inspector_scroll as u16, 0)) + .render(area, buf); + } +} + +#[cfg(test)] +mod tests { + use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; + use tui_kit_runtime::{ + DiagnosticSnapshot, DocumentSnapshot, PaneRow, PaneSnapshot, ThreePaneSnapshot, + kinic_tabs::KINIC_WIKI_TAB_ID, + }; + + use crate::ui::{app::TabId, theme::Theme}; + + use super::*; + + fn rendered(buf: &Buffer) -> String { + buf.content + .iter() + .map(|cell| cell.symbol()) + .collect::>() + .join("") + } + + #[test] + fn wiki_screen_renders_dedicated_panes() { + let theme = Theme::default(); + let snapshot = ThreePaneSnapshot { + left: PaneSnapshot { + rows: vec![PaneRow { + label: "db-a".to_string(), + detail: "Hot Owner 42 bytes".to_string(), + selected: true, + }], + ..PaneSnapshot::default() + }, + middle: PaneSnapshot { + rows: vec![PaneRow { + label: "/Wiki/index.md".to_string(), + detail: "file".to_string(), + selected: false, + }], + ..PaneSnapshot::default() + }, + document: DocumentSnapshot { + title: "/Wiki/index.md".to_string(), + lines: vec!["hello wiki".to_string()], + }, + ..ThreePaneSnapshot::default() + }; + let mut buf = Buffer::empty(Rect::new(0, 0, 110, 30)); + TuiKitUi::new(&theme) + .current_tab_id(TabId::new(KINIC_WIKI_TAB_ID)) + .three_pane_snapshot(&snapshot) + .render(Rect::new(0, 0, 110, 30), &mut buf); + let text = rendered(&buf); + assert!(text.contains("Databases")); + assert!(text.contains("Browser")); + assert!(text.contains("Document")); + assert!(text.contains("db-a")); + assert!(text.contains("/Wiki/index.md")); + assert!(text.contains("hello wiki")); + } + + #[test] + fn wiki_screen_renders_diagnostic_panel() { + let theme = Theme::default(); + let snapshot = ThreePaneSnapshot { + diagnostic: Some(DiagnosticSnapshot { + rows: vec![ + PaneRow { + label: "canister".to_string(), + detail: "aaaaa-aa".to_string(), + selected: false, + }, + PaneRow { + label: "principal".to_string(), + detail: "2vxsx-fae".to_string(), + selected: false, + }, + ], + message: "wiki query failed for list_databases".to_string(), + }), + ..ThreePaneSnapshot::default() + }; + let mut buf = Buffer::empty(Rect::new(0, 0, 110, 30)); + TuiKitUi::new(&theme) + .current_tab_id(TabId::new(KINIC_WIKI_TAB_ID)) + .three_pane_snapshot(&snapshot) + .render(Rect::new(0, 0, 110, 30), &mut buf); + let text = rendered(&buf); + assert!(text.contains("list_databases failed")); + assert!(text.contains("aaaaa-aa")); + assert!(text.contains("2vxsx-fae")); + } +} diff --git a/tui/crates/tui-kit-render/src/ui/app/shared/status.rs b/tui/crates/tui-kit-render/src/ui/app/shared/status.rs index 81da985..af9c696 100644 --- a/tui/crates/tui-kit-render/src/ui/app/shared/status.rs +++ b/tui/crates/tui-kit-render/src/ui/app/shared/status.rs @@ -28,10 +28,7 @@ impl<'a> TuiKitUi<'a> { let status_line = if matches!(tab_kind(tab_id), TabKind::InsertForm | TabKind::CreateForm) { self.form_status_line(tab_id) - } else if matches!( - tab_kind(tab_id), - TabKind::PlaceholderMarket | TabKind::PlaceholderSettings - ) { + } else if matches!(tab_kind(tab_id), TabKind::PlaceholderSettings) { self.placeholder_status_line(tab_id) } else if self.show_context_panel && self.in_context_items_view { self.context_items_status_line() diff --git a/tui/crates/tui-kit-render/src/ui/app/ui_builder.rs b/tui/crates/tui-kit-render/src/ui/app/ui_builder.rs index 00c6731..4a6b87d 100644 --- a/tui/crates/tui-kit-render/src/ui/app/ui_builder.rs +++ b/tui/crates/tui-kit-render/src/ui/app/ui_builder.rs @@ -6,7 +6,7 @@ use crate::ui::search::CompletionCandidate; use tui_kit_runtime::{ AccessControlModalState, CreateCostState, CreateModalFocus, CreateSubmitState, InsertFormFocus, InsertMode, MemorySelection, PickerState, RemoveMemoryModalState, RenameMemoryModalState, - SearchScope, SettingsSnapshot, TextInputModalState, TransferModalState, + SearchScope, SettingsSnapshot, TextInputModalState, ThreePaneSnapshot, TransferModalState, }; use super::{Focus, TabId, TabSpec, TuiKitUi, UiConfig}; @@ -30,6 +30,12 @@ impl<'a> TuiKitUi<'a> { self } + #[must_use] + pub fn three_pane_snapshot(mut self, snapshot: &'a ThreePaneSnapshot) -> Self { + self.three_pane = Some(snapshot); + self + } + #[must_use] pub fn ui_total_count(mut self, count: usize) -> Self { self.ui_total_count = count; diff --git a/tui/crates/tui-kit-render/src/ui/app/ui_state.rs b/tui/crates/tui-kit-render/src/ui/app/ui_state.rs index f2b72c9..e40849c 100644 --- a/tui/crates/tui-kit-render/src/ui/app/ui_state.rs +++ b/tui/crates/tui-kit-render/src/ui/app/ui_state.rs @@ -7,7 +7,8 @@ use crate::ui::theme::Theme; use tui_kit_runtime::{ AccessControlModalState, ChatScope, CreateCostState, CreateModalFocus, CreateSubmitState, InsertFormFocus, InsertMode, MemorySelection, PickerState, RemoveMemoryModalState, - RenameMemoryModalState, SearchScope, SettingsSnapshot, TextInputModalState, TransferModalState, + RenameMemoryModalState, SearchScope, SettingsSnapshot, TextInputModalState, ThreePaneSnapshot, + TransferModalState, }; use super::{Focus, TabId, TabSpec, UiConfig, default_tab_specs}; @@ -22,6 +23,7 @@ pub struct TuiKitUi<'a> { pub(super) ui_summaries: &'a [UiItemSummary], pub(super) ui_selected_content: Option<&'a UiItemContent>, pub(super) ui_context_node: Option<&'a UiContextNode>, + pub(super) three_pane: Option<&'a ThreePaneSnapshot>, pub(super) ui_total_count: usize, pub(super) in_context_items_view: bool, pub(super) show_context_panel: bool, @@ -98,6 +100,7 @@ impl<'a> TuiKitUi<'a> { ui_summaries: &[], ui_selected_content: None, ui_context_node: None, + three_pane: None, ui_total_count: 0, in_context_items_view: false, show_context_panel: false, diff --git a/tui/crates/tui-kit-runtime/src/kinic_tabs.rs b/tui/crates/tui-kit-runtime/src/kinic_tabs.rs index 42a500b..e1de990 100644 --- a/tui/crates/tui-kit-runtime/src/kinic_tabs.rs +++ b/tui/crates/tui-kit-runtime/src/kinic_tabs.rs @@ -4,15 +4,13 @@ pub const KINIC_MEMORIES_TAB_ID: &str = "kinic-memories"; pub const KINIC_INSERT_TAB_ID: &str = "kinic-insert"; pub const KINIC_CREATE_TAB_ID: &str = "kinic-create"; pub const KINIC_WIKI_TAB_ID: &str = "kinic-wiki"; -pub const KINIC_MARKET_TAB_ID: &str = "kinic-market"; pub const KINIC_SETTINGS_TAB_ID: &str = "kinic-settings"; -pub const KINIC_TAB_IDS: [&str; 6] = [ +pub const KINIC_TAB_IDS: [&str; 5] = [ KINIC_MEMORIES_TAB_ID, KINIC_INSERT_TAB_ID, KINIC_CREATE_TAB_ID, KINIC_WIKI_TAB_ID, - KINIC_MARKET_TAB_ID, KINIC_SETTINGS_TAB_ID, ]; @@ -22,7 +20,6 @@ pub enum TabKind { InsertForm, CreateForm, Wiki, - PlaceholderMarket, PlaceholderSettings, Unknown, } @@ -33,7 +30,6 @@ pub fn tab_kind(tab_id: &str) -> TabKind { KINIC_INSERT_TAB_ID => TabKind::InsertForm, KINIC_CREATE_TAB_ID => TabKind::CreateForm, KINIC_WIKI_TAB_ID => TabKind::Wiki, - KINIC_MARKET_TAB_ID => TabKind::PlaceholderMarket, KINIC_SETTINGS_TAB_ID => TabKind::PlaceholderSettings, _ => TabKind::Unknown, } @@ -59,10 +55,6 @@ pub fn is_kinic_wiki_tab(tab_id: &str) -> bool { matches!(tab_kind(tab_id), TabKind::Wiki) } -pub fn is_kinic_market_tab(tab_id: &str) -> bool { - matches!(tab_kind(tab_id), TabKind::PlaceholderMarket) -} - pub fn is_kinic_settings_tab(tab_id: &str) -> bool { matches!(tab_kind(tab_id), TabKind::PlaceholderSettings) } @@ -77,7 +69,6 @@ mod tests { assert_eq!(tab_kind(KINIC_INSERT_TAB_ID), TabKind::InsertForm); assert_eq!(tab_kind(KINIC_CREATE_TAB_ID), TabKind::CreateForm); assert_eq!(tab_kind(KINIC_WIKI_TAB_ID), TabKind::Wiki); - assert_eq!(tab_kind(KINIC_MARKET_TAB_ID), TabKind::PlaceholderMarket); assert_eq!( tab_kind(KINIC_SETTINGS_TAB_ID), TabKind::PlaceholderSettings @@ -89,7 +80,6 @@ mod tests { assert!(is_form_tab(KINIC_CREATE_TAB_ID)); assert!(is_form_tab(KINIC_INSERT_TAB_ID)); assert!(is_kinic_memories_tab(KINIC_MEMORIES_TAB_ID)); - assert!(is_kinic_market_tab(KINIC_MARKET_TAB_ID)); assert!(is_kinic_settings_tab(KINIC_SETTINGS_TAB_ID)); assert!(!is_form_tab(KINIC_MEMORIES_TAB_ID)); } diff --git a/tui/crates/tui-kit-runtime/src/lib.rs b/tui/crates/tui-kit-runtime/src/lib.rs index 0cacafb..2c8d587 100644 --- a/tui/crates/tui-kit-runtime/src/lib.rs +++ b/tui/crates/tui-kit-runtime/src/lib.rs @@ -112,17 +112,15 @@ pub fn tab_focus_policy(tab_id: &str) -> TabFocusPolicy { allows_form: true, allows_chat: false, }, - kinic_tabs::TabKind::PlaceholderMarket | kinic_tabs::TabKind::PlaceholderSettings => { - TabFocusPolicy { - default_focus: PaneFocus::Tabs, - allows_search: false, - allows_items: false, - allows_tabs: true, - allows_content: true, - allows_form: false, - allows_chat: false, - } - } + kinic_tabs::TabKind::PlaceholderSettings => TabFocusPolicy { + default_focus: PaneFocus::Tabs, + allows_search: false, + allows_items: false, + allows_tabs: true, + allows_content: true, + allows_form: false, + allows_chat: false, + }, } } @@ -134,9 +132,9 @@ pub fn tab_entry_focus(tab_id: &str) -> Option { match kinic_tabs::tab_kind(tab_id) { kinic_tabs::TabKind::Memories | kinic_tabs::TabKind::Wiki => Some(PaneFocus::Search), kinic_tabs::TabKind::InsertForm | kinic_tabs::TabKind::CreateForm => Some(PaneFocus::Form), - kinic_tabs::TabKind::PlaceholderMarket - | kinic_tabs::TabKind::PlaceholderSettings - | kinic_tabs::TabKind::Unknown => Some(PaneFocus::Content), + kinic_tabs::TabKind::PlaceholderSettings | kinic_tabs::TabKind::Unknown => { + Some(PaneFocus::Content) + } } } @@ -631,6 +629,42 @@ pub struct SettingsSnapshot { pub sections: Vec, } +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct PaneRow { + pub label: String, + pub detail: String, + pub selected: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct PaneSnapshot { + pub title: String, + pub rows: Vec, + pub empty_message: String, + pub loading: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct DocumentSnapshot { + pub title: String, + pub lines: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct DiagnosticSnapshot { + pub rows: Vec, + pub message: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct ThreePaneSnapshot { + pub left: PaneSnapshot, + pub middle: PaneSnapshot, + pub document: DocumentSnapshot, + pub diagnostic: Option, + pub middle_mode: String, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct MemorySelection { pub id: String, @@ -647,6 +681,7 @@ pub struct CoreState { pub list_items: Vec, pub selected_content: Option, pub selected_context: Option, + pub three_pane: ThreePaneSnapshot, pub total_count: usize, pub status_message: Option, pub persistent_status_message: Option, @@ -703,6 +738,7 @@ impl Default for CoreState { list_items: Vec::new(), selected_content: None, selected_context: None, + three_pane: ThreePaneSnapshot::default(), total_count: 0, status_message: None, persistent_status_message: None, @@ -989,6 +1025,7 @@ pub struct ProviderSnapshot { pub selected_index: Option, pub selected_content: Option, pub selected_context: Option, + pub three_pane: ThreePaneSnapshot, pub total_count: usize, pub status_message: Option, pub selected_memory: Option, @@ -2552,6 +2589,33 @@ pub fn action_for_key(key: CoreKey, focus: PaneFocus, current_tab_id: &str) -> O }, PaneFocus::Tabs => None, PaneFocus::Content => match key { + CoreKey::Down if current_tab_id == kinic_tabs::KINIC_WIKI_TAB_ID => { + Some(CoreAction::MoveNext) + } + CoreKey::Up if current_tab_id == kinic_tabs::KINIC_WIKI_TAB_ID => { + Some(CoreAction::MovePrev) + } + CoreKey::PageDown if current_tab_id == kinic_tabs::KINIC_WIKI_TAB_ID => { + Some(CoreAction::MovePageDown) + } + CoreKey::PageUp if current_tab_id == kinic_tabs::KINIC_WIKI_TAB_ID => { + Some(CoreAction::MovePageUp) + } + CoreKey::Home | CoreKey::Char('g') + if current_tab_id == kinic_tabs::KINIC_WIKI_TAB_ID => + { + Some(CoreAction::MoveHome) + } + CoreKey::End | CoreKey::Char('G') + if current_tab_id == kinic_tabs::KINIC_WIKI_TAB_ID => + { + Some(CoreAction::MoveEnd) + } + CoreKey::Enter | CoreKey::Right | CoreKey::Char('l') + if current_tab_id == kinic_tabs::KINIC_WIKI_TAB_ID => + { + Some(CoreAction::OpenSelected) + } CoreKey::Enter if is_settings_content(current_tab_id, PaneFocus::Content) => None, CoreKey::Enter if current_tab_id == kinic_tabs::KINIC_MEMORIES_TAB_ID => { Some(CoreAction::MemoryContentOpenSelected) @@ -2606,6 +2670,7 @@ pub fn apply_snapshot(state: &mut CoreState, snapshot: ProviderSnapshot) -> Prov let snapshot_selected_index = snapshot.selected_index; state.selected_content = snapshot.selected_content; state.selected_context = snapshot.selected_context; + state.three_pane = snapshot.three_pane; state.total_count = snapshot.total_count; if state.persistent_status_message.is_none() { state.status_message = snapshot.status_message; @@ -3222,7 +3287,7 @@ mod tests { #[test] fn focus_next_stays_visible_on_placeholder_tabs() { let mut state = CoreState { - current_tab_id: kinic_tabs::KINIC_MARKET_TAB_ID.to_string(), + current_tab_id: kinic_tabs::KINIC_SETTINGS_TAB_ID.to_string(), focus: PaneFocus::Tabs, ..CoreState::default() }; @@ -3448,7 +3513,7 @@ mod tests { action_for_key( CoreKey::Enter, PaneFocus::Tabs, - kinic_tabs::KINIC_MARKET_TAB_ID + kinic_tabs::KINIC_SETTINGS_TAB_ID ), Some(CoreAction::FocusContent) ); @@ -3460,7 +3525,7 @@ mod tests { action_for_key( CoreKey::Tab, PaneFocus::Tabs, - kinic_tabs::KINIC_MARKET_TAB_ID + kinic_tabs::KINIC_SETTINGS_TAB_ID ), Some(CoreAction::FocusContent) ); @@ -3545,6 +3610,32 @@ mod tests { ); } + #[test] + fn wiki_content_keys_move_browser_selection() { + let cases = [ + (CoreKey::Down, CoreAction::MoveNext), + (CoreKey::Up, CoreAction::MovePrev), + (CoreKey::PageDown, CoreAction::MovePageDown), + (CoreKey::PageUp, CoreAction::MovePageUp), + (CoreKey::Home, CoreAction::MoveHome), + (CoreKey::Char('g'), CoreAction::MoveHome), + (CoreKey::End, CoreAction::MoveEnd), + (CoreKey::Char('G'), CoreAction::MoveEnd), + (CoreKey::Enter, CoreAction::OpenSelected), + (CoreKey::Right, CoreAction::OpenSelected), + (CoreKey::Char('l'), CoreAction::OpenSelected), + (CoreKey::Left, CoreAction::Back), + (CoreKey::Char('h'), CoreAction::Back), + ]; + + for (key, action) in cases { + assert_eq!( + action_for_key(key, PaneFocus::Content, kinic_tabs::KINIC_WIKI_TAB_ID), + Some(action) + ); + } + } + #[test] fn memories_items_tab_moves_focus_to_content() { let mut state = CoreState { From a2325ca9dcee47ea6ee50c2c4f865c028fde5d45 Mon Sep 17 00:00:00 2001 From: hude Date: Tue, 12 May 2026 08:58:03 +0900 Subject: [PATCH 04/10] Refactor kinic CLI flow and update tests --- README.md | 2 + dfx.json | 5 + docs/cli.md | 1 + docs/tui.md | 1 + rust/tui/bridge.rs | 11 + rust/tui/provider/mod.rs | 453 +++++++++++-- rust/tui/provider/tests/wiki.rs | 625 +++++++++++++++++- scripts/setup.sh | 1 + tui/crates/tui-kit-host/src/lib.rs | 12 + tui/crates/tui-kit-host/src/lib_tests.rs | 35 +- .../tui-kit-render/src/ui/app/screens/mod.rs | 2 +- .../tui-kit-render/src/ui/app/screens/wiki.rs | 244 +++++-- tui/crates/tui-kit-runtime/src/lib.rs | 51 +- wasm/wiki/vfs.did | 347 ++++++++++ wasm/wiki/vfs_canister_nowasi.wasm | Bin 0 -> 4202071 bytes 15 files changed, 1684 insertions(+), 106 deletions(-) create mode 100644 wasm/wiki/vfs.did create mode 100644 wasm/wiki/vfs_canister_nowasi.wasm diff --git a/README.md b/README.md index 57692f0..247df20 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,8 @@ What to understand first: - `Wiki`: browse databases from the configured wiki canister; `KINIC_WIKI_CANISTER_ID` can override the default - `Settings`: principal, balance, default memory, saved tags, and retrieval settings +For local deploys, `scripts/setup.sh` deploys the bundled Wiki wasm at the default Wiki canister id. + Wiki write/delete and database management are available from the CLI: ```bash diff --git a/dfx.json b/dfx.json index 84879cf..76ddc67 100644 --- a/dfx.json +++ b/dfx.json @@ -15,6 +15,11 @@ "type": "custom", "candid": "https://github.com/dfinity/internet-identity/releases/latest/download/internet_identity.did", "wasm": "https://github.com/dfinity/internet-identity/releases/latest/download/internet_identity_dev.wasm.gz" + }, + "wiki": { + "type": "custom", + "candid": "wasm/wiki/vfs.did", + "wasm": "wasm/wiki/vfs_canister_nowasi.wasm" } }, "defaults": { diff --git a/docs/cli.md b/docs/cli.md index 229029c..2748336 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -136,6 +136,7 @@ Notes: ### Wiki CLI `wiki` operates the configured Wiki canister. It uses `xis3j-paaaa-aaaai-axumq-cai` by default and `KINIC_WIKI_CANISTER_ID` can override that target. +For local deploys, `scripts/setup.sh` deploys the bundled Wiki wasm at that same canister id. ```bash cargo run -- --ic --identity alice wiki database list diff --git a/docs/tui.md b/docs/tui.md index 3f6a506..c8a45c9 100644 --- a/docs/tui.md +++ b/docs/tui.md @@ -85,6 +85,7 @@ The TUI has five tabs. The `Memories` tab opens first when the TUI starts. By default, the Wiki tab uses `xis3j-paaaa-aaaai-axumq-cai`. +For local deploys, `scripts/setup.sh` deploys the bundled Wiki wasm at that same canister id. The local preferences shown in `Settings`, including the default memory, saved tags, and manually tracked memories, can also be managed from the CLI with `kinic-cli prefs ...`. ## Basic Controls diff --git a/rust/tui/bridge.rs b/rust/tui/bridge.rs index 35c9e79..25524b1 100644 --- a/rust/tui/bridge.rs +++ b/rust/tui/bridge.rs @@ -419,6 +419,17 @@ pub async fn list_wiki_databases( .await } +pub async fn create_wiki_database( + use_mainnet: bool, + auth: TuiAuth, + wiki_id: String, +) -> Result { + let agent = build_search_agent(use_mainnet, auth).await?; + crate::wiki_bridge::WikiClient::new(agent, wiki_id)? + .create_database() + .await +} + pub async fn load_session_account_overview( use_mainnet: bool, auth: TuiAuth, diff --git a/rust/tui/provider/mod.rs b/rust/tui/provider/mod.rs index 15c3a8f..5d5c037 100644 --- a/rust/tui/provider/mod.rs +++ b/rust/tui/provider/mod.rs @@ -45,7 +45,8 @@ use tui_kit_runtime::{ FILE_MODE_ALLOWED_EXTENSIONS, InsertMode, LoadedCreateCost, MemorySelection, PaneFocus, PaneRow, PaneSnapshot, PickerConfirmKind, PickerContext, PickerItem, PickerItemKind, PickerListMode, PickerState, ProviderOutput, ProviderSnapshot, SearchScope, - SessionAccountOverview, SessionSettingsSnapshot, ThreePaneSnapshot, TransferModalMode, + SessionAccountOverview, SessionSettingsSnapshot, ThreePaneMode, ThreePaneSnapshot, + TransferModalMode, kinic_tabs::{ KINIC_CREATE_TAB_ID, KINIC_INSERT_TAB_ID, KINIC_MEMORIES_TAB_ID, KINIC_SETTINGS_TAB_ID, KINIC_WIKI_TAB_ID, @@ -167,6 +168,9 @@ pub struct KinicProvider { wiki_databases: Vec, wiki_records: Vec, wiki_load_error: Option, + wiki_view_mode: WikiViewMode, + active_wiki_database_id: Option, + pending_wiki_reload: Option, result_records: Vec, memories_mode: MemoriesMode, pending_initial_memories: Option>, @@ -212,11 +216,14 @@ pub struct KinicProvider { pending_wiki_children_database_id: Option, pending_wiki_children_path: Option, wiki_current_path: String, + wiki_preview_path: Option, selected_wiki_browser_index: usize, wiki_search_task: RequestTaskState, next_wiki_search_request_id: u64, wiki_databases_task: RequestTaskState, next_wiki_databases_request_id: u64, + wiki_create_database_task: RequestTaskState, + next_wiki_create_database_request_id: u64, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -258,6 +265,22 @@ enum WikiBrowserEntryKind { Source, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum WikiViewMode { + DatabaseList, + DatabaseBrowser, + Diagnostic, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct WikiReloadState { + database_id: Option, + view_mode: WikiViewMode, + current_path: String, + browser_index: usize, + had_search_results: bool, +} + struct WikiChildrenTaskOutput { request_id: u64, database_id: String, @@ -277,6 +300,11 @@ struct WikiDatabasesTaskOutput { result: Result, String>, } +struct WikiCreateDatabaseTaskOutput { + request_id: u64, + result: Result, +} + /// In-flight memory search with explicit cancellation. Kept separate from /// `RequestTaskState` because workers use `CancellationToken` and batching /// differs from other request/response tasks. @@ -1187,6 +1215,9 @@ impl KinicProvider { wiki_databases: Vec::new(), wiki_records: Vec::new(), wiki_load_error: None, + wiki_view_mode: WikiViewMode::DatabaseList, + active_wiki_database_id: None, + pending_wiki_reload: None, result_records: Vec::new(), memories_mode: MemoriesMode::Browser, pending_initial_memories: None, @@ -1232,11 +1263,14 @@ impl KinicProvider { pending_wiki_children_database_id: None, pending_wiki_children_path: None, wiki_current_path: "/".to_string(), + wiki_preview_path: None, selected_wiki_browser_index: 0, wiki_search_task: RequestTaskState::default(), next_wiki_search_request_id: 0, wiki_databases_task: RequestTaskState::default(), next_wiki_databases_request_id: 0, + wiki_create_database_task: RequestTaskState::default(), + next_wiki_create_database_request_id: 0, } } @@ -1303,7 +1337,7 @@ impl KinicProvider { match self.tab_id.as_str() { KINIC_CREATE_TAB_ID => self.start_create_cost_refresh().into_iter().collect(), KINIC_INSERT_TAB_ID => Vec::new(), - KINIC_WIKI_TAB_ID => vec![self.start_wiki_databases_load()], + KINIC_WIKI_TAB_ID => vec![self.start_wiki_databases_load(false)], KINIC_MEMORIES_TAB_ID => self .start_live_memories_load(None, true) .into_iter() @@ -1313,20 +1347,48 @@ impl KinicProvider { } } - fn start_wiki_databases_load(&mut self) -> CoreEffect { - self.result_records.clear(); + fn enter_wiki_tab(&mut self) -> Vec { + if self.wiki_databases_task.in_flight + || self.wiki_load_error.is_some() + || !self.wiki_records.is_empty() + || !self.wiki_databases.is_empty() + { + return Vec::new(); + } + vec![self.start_wiki_databases_load(true)] + } + + fn start_wiki_databases_load(&mut self, reset_view: bool) -> CoreEffect { + if reset_view { + self.result_records.clear(); + self.pending_wiki_reload = None; + self.active_wiki_database_id = None; + self.wiki_current_path = "/".to_string(); + self.wiki_preview_path = None; + self.selected_wiki_browser_index = 0; + self.wiki_view_mode = WikiViewMode::DatabaseList; + } else { + self.pending_wiki_reload = Some(WikiReloadState { + database_id: self.active_wiki_database_id.clone(), + view_mode: self.wiki_view_mode, + current_path: self.wiki_current_path.clone(), + browser_index: self.selected_wiki_browser_index, + had_search_results: !self.result_records.is_empty(), + }); + } self.invalidate_pending_search(); reset_request_task(&mut self.wiki_children_task); reset_request_task(&mut self.wiki_search_task); self.pending_wiki_children_database_id = None; self.pending_wiki_children_path = None; - self.wiki_current_path = "/".to_string(); - self.selected_wiki_browser_index = 0; let Some(wiki_canister_id) = self.config.wiki_canister_id.clone() else { self.wiki_databases.clear(); self.wiki_records = vec![wiki_not_configured_record()]; self.wiki_load_error = Some("Wiki canister is not configured.".to_string()); + self.wiki_view_mode = WikiViewMode::Diagnostic; + self.active_wiki_database_id = None; + self.pending_wiki_reload = None; self.wiki_children_cache.clear(); return CoreEffect::Notify("Wiki canister is not configured.".to_string()); }; @@ -1352,6 +1414,34 @@ impl KinicProvider { CoreEffect::Notify("Refreshing wiki databases...".to_string()) } + fn start_wiki_create_database(&mut self) -> CoreEffect { + if self.wiki_create_database_task.in_flight { + return CoreEffect::Notify("Creating wiki database...".to_string()); + } + let Some(wiki_canister_id) = self.config.wiki_canister_id.clone() else { + return CoreEffect::Notify("Wiki canister is not configured.".to_string()); + }; + let auth = self.config.auth.clone(); + let use_mainnet = self.config.use_mainnet; + spawn_request_task( + &mut self.next_wiki_create_database_request_id, + &mut self.wiki_create_database_task, + move |request_id, tx| { + let runtime = Runtime::new() + .expect("failed to create tokio runtime for wiki database create"); + let result = runtime + .block_on(bridge::create_wiki_database( + use_mainnet, + auth, + wiki_canister_id, + )) + .map_err(|error| error.to_string()); + let _ = tx.send(WikiCreateDatabaseTaskOutput { request_id, result }); + }, + ); + CoreEffect::Notify("Creating wiki database...".to_string()) + } + fn current_records(&self) -> Vec<&KinicRecord> { if self.tab_id == KINIC_WIKI_TAB_ID { if self.result_records.is_empty() { @@ -1376,8 +1466,9 @@ impl KinicProvider { if self.tab_id != KINIC_WIKI_TAB_ID { return ThreePaneSnapshot::default(); } - let selected_index = state.selected_index.unwrap_or(0); - let database_rows = self + let mode = self.wiki_snapshot_mode(); + let selected_index = self.wiki_database_index_for_state(state, mode).unwrap_or(0); + let mut database_rows = self .wiki_records .iter() .enumerate() @@ -1401,7 +1492,22 @@ impl KinicProvider { } }) .collect::>(); - let document_lines = self.wiki_document_lines(selected_index); + if mode == WikiViewMode::DatabaseList { + database_rows.push(PaneRow { + label: "+ Create database".to_string(), + detail: if self.wiki_create_database_task.in_flight { + "creating database".to_string() + } else { + "create new database".to_string() + }, + selected: self.is_wiki_create_database_action_selected(state), + }); + } + let document_lines = match mode { + WikiViewMode::DatabaseList => Vec::new(), + WikiViewMode::DatabaseBrowser => self.wiki_document_lines(selected_index), + WikiViewMode::Diagnostic => self.wiki_diagnostic_lines(), + }; ThreePaneSnapshot { left: PaneSnapshot { title: "Databases".to_string(), @@ -1411,27 +1517,99 @@ impl KinicProvider { }, middle: PaneSnapshot { title: "Browser".to_string(), - rows: self.wiki_browser_rows(selected_index), + rows: if mode == WikiViewMode::DatabaseBrowser { + self.wiki_browser_rows(selected_index) + } else { + Vec::new() + }, empty_message: "Select a database or run search.".to_string(), loading: self.wiki_children_task.in_flight || self.wiki_search_task.in_flight, }, document: DocumentSnapshot { - title: if self.result_records.is_empty() { - self.wiki_current_path.clone() - } else { - self.result_records - .get(self.selected_wiki_browser_index) - .map(|record| record.title.clone()) - .unwrap_or_else(|| "Wiki search".to_string()) - }, + title: self.wiki_document_title(mode, selected_index), lines: document_lines, }, diagnostic: self.wiki_diagnostic(), - middle_mode: if self.result_records.is_empty() { - "browse".to_string() - } else { - "search".to_string() - }, + mode: self.wiki_three_pane_mode(mode), + } + } + + fn wiki_snapshot_mode(&self) -> WikiViewMode { + if self.wiki_load_error.is_some() { + WikiViewMode::Diagnostic + } else { + self.wiki_view_mode + } + } + + fn wiki_database_index_for_state( + &self, + state: &CoreState, + mode: WikiViewMode, + ) -> Option { + if mode == WikiViewMode::DatabaseBrowser + && let Some(database_id) = self.active_wiki_database_id.as_deref() + && let Some(index) = self + .wiki_records + .iter() + .position(|record| record.source_wiki_database_id.as_deref() == Some(database_id)) + { + return Some(index); + } + state.selected_index + } + + fn wiki_selected_database_index(&self, state: &CoreState) -> Option { + if self.is_wiki_create_database_action_selected(state) { + return None; + } + self.wiki_database_index_for_state(state, self.wiki_snapshot_mode()) + .filter(|index| *index < self.wiki_records.len()) + } + + fn active_wiki_database_index(&self) -> Option { + let database_id = self.active_wiki_database_id.as_deref()?; + self.wiki_records + .iter() + .position(|record| record.source_wiki_database_id.as_deref() == Some(database_id)) + } + + fn is_wiki_create_database_action_selected(&self, state: &CoreState) -> bool { + self.tab_id == KINIC_WIKI_TAB_ID + && self.wiki_snapshot_mode() == WikiViewMode::DatabaseList + && state.selected_index == Some(self.wiki_records.len()) + } + + fn wiki_three_pane_mode(&self, mode: WikiViewMode) -> ThreePaneMode { + match mode { + WikiViewMode::DatabaseList => ThreePaneMode::List, + WikiViewMode::DatabaseBrowser if self.result_records.is_empty() => { + ThreePaneMode::Browse + } + WikiViewMode::DatabaseBrowser => ThreePaneMode::Search, + WikiViewMode::Diagnostic => ThreePaneMode::Diagnostic, + } + } + + fn wiki_document_title(&self, mode: WikiViewMode, selected_index: usize) -> String { + match mode { + WikiViewMode::DatabaseList => "Databases".to_string(), + WikiViewMode::Diagnostic => "Diagnostics".to_string(), + WikiViewMode::DatabaseBrowser if self.result_records.is_empty() => { + let path = self + .wiki_preview_path + .as_deref() + .unwrap_or(self.wiki_current_path.as_str()); + self.wiki_records + .get(selected_index) + .map(|record| format!("{} {}", record.title, path)) + .unwrap_or_else(|| path.to_string()) + } + WikiViewMode::DatabaseBrowser => self + .result_records + .get(self.selected_wiki_browser_index) + .map(|record| record.title.clone()) + .unwrap_or_else(|| "Wiki search".to_string()), } } @@ -1497,6 +1675,10 @@ impl KinicProvider { } fn wiki_document_lines(&self, selected_index: usize) -> Vec { + let document_path = self + .wiki_preview_path + .as_deref() + .unwrap_or(self.wiki_current_path.as_str()); if self.result_records.is_empty() && let Some(database_id) = self .wiki_records @@ -1504,7 +1686,7 @@ impl KinicProvider { .and_then(|record| record.source_wiki_database_id.as_deref()) && let Some(content) = self .wiki_children_cache - .get(wiki_children_cache_key(database_id, self.wiki_current_path.as_str()).as_str()) + .get(wiki_children_cache_key(database_id, document_path).as_str()) && let Some(lines) = &content.index_preview { return lines.clone(); @@ -1566,6 +1748,36 @@ impl KinicProvider { }) } + fn wiki_diagnostic_lines(&self) -> Vec { + let Some(diagnostic) = self.wiki_diagnostic() else { + return Vec::new(); + }; + let mut lines = diagnostic + .rows + .into_iter() + .map(|row| format!("{}: {}", row.label, row.detail)) + .collect::>(); + lines.push(String::new()); + lines.push(diagnostic.message); + lines + } + + fn enter_selected_wiki_database(&mut self, state: &CoreState) -> Vec { + let Some(selected_index) = self.wiki_selected_database_index(state) else { + return Vec::new(); + }; + self.result_records.clear(); + self.active_wiki_database_id = self + .wiki_records + .get(selected_index) + .and_then(|record| record.source_wiki_database_id.clone()); + self.wiki_view_mode = WikiViewMode::DatabaseBrowser; + self.wiki_current_path = "/".to_string(); + self.selected_wiki_browser_index = 0; + self.start_selected_wiki_children_load(state); + vec![CoreEffect::FocusPane(PaneFocus::Content)] + } + fn navigate_wiki_browser(&mut self, state: &CoreState, action: &CoreAction) { let len = if self.result_records.is_empty() { self.current_wiki_entries(state).len() @@ -1589,7 +1801,9 @@ impl KinicProvider { } fn current_wiki_entries(&self, state: &CoreState) -> Vec { - let selected_index = state.selected_index.unwrap_or(0); + let selected_index = self + .wiki_database_index_for_state(state, self.wiki_snapshot_mode()) + .unwrap_or(0); let Some(database_id) = self .wiki_records .get(selected_index) @@ -1628,14 +1842,17 @@ impl KinicProvider { self.start_selected_wiki_children_load(state); return Vec::new(); }; - self.wiki_current_path = entry.path.clone(); - self.selected_wiki_browser_index = 0; - self.start_selected_wiki_children_load(state); match entry.kind { WikiBrowserEntryKind::Directory => { + self.wiki_current_path = entry.path.clone(); + self.wiki_preview_path = None; + self.selected_wiki_browser_index = 0; + self.start_selected_wiki_children_load(state); vec![CoreEffect::Notify(format!("Opened {}", entry.path))] } WikiBrowserEntryKind::File | WikiBrowserEntryKind::Source => { + self.wiki_preview_path = Some(entry.path.clone()); + self.start_wiki_children_load(state, entry.path.clone()); vec![CoreEffect::Notify(format!( "Loading wiki node {}", entry.path @@ -1645,17 +1862,28 @@ impl KinicProvider { } fn back_wiki_browser(&mut self) -> Vec { + if self.wiki_view_mode == WikiViewMode::DatabaseList { + return vec![CoreEffect::FocusPane(PaneFocus::Tabs)]; + } if !self.result_records.is_empty() { self.result_records.clear(); self.selected_wiki_browser_index = 0; + self.wiki_view_mode = WikiViewMode::DatabaseBrowser; return vec![CoreEffect::Notify( "Closed wiki search results.".to_string(), )]; } if self.wiki_current_path == "/" { - return Vec::new(); + self.wiki_view_mode = WikiViewMode::DatabaseList; + self.wiki_preview_path = None; + let mut effects = vec![CoreEffect::FocusPane(PaneFocus::Items)]; + if let Some(index) = self.active_wiki_database_index() { + effects.push(CoreEffect::SelectListItem(index)); + } + return effects; } self.wiki_current_path = wiki_parent_path(self.wiki_current_path.as_str()); + self.wiki_preview_path = None; self.selected_wiki_browser_index = 0; vec![CoreEffect::Notify(format!( "Opened {}", @@ -2313,13 +2541,16 @@ impl KinicProvider { } fn start_selected_wiki_children_load(&mut self, state: &CoreState) { + self.start_wiki_children_load(state, self.wiki_current_path.clone()); + } + + fn start_wiki_children_load(&mut self, state: &CoreState, path: String) { if self.tab_id != KINIC_WIKI_TAB_ID { return; } let Some((wiki_id, database_id)) = self.selected_wiki_target(state) else { return; }; - let path = self.wiki_current_path.clone(); let cache_key = wiki_children_cache_key(database_id.as_str(), path.as_str()); if self.wiki_children_cache.contains_key(cache_key.as_str()) || (self.pending_wiki_children_database_id.as_deref() == Some(database_id.as_str()) @@ -2571,6 +2802,44 @@ impl KinicProvider { }); } + fn restore_wiki_reload_state(&mut self, reload_state: Option) { + let Some(reload_state) = reload_state else { + if self.active_wiki_database_id.is_none() { + self.wiki_view_mode = WikiViewMode::DatabaseList; + } + return; + }; + let Some(database_id) = reload_state.database_id else { + self.active_wiki_database_id = None; + self.wiki_view_mode = WikiViewMode::DatabaseList; + self.wiki_current_path = "/".to_string(); + self.wiki_preview_path = None; + self.selected_wiki_browser_index = 0; + self.result_records.clear(); + return; + }; + let database_still_exists = self + .wiki_records + .iter() + .any(|record| record.source_wiki_database_id.as_deref() == Some(database_id.as_str())); + if !database_still_exists { + self.active_wiki_database_id = None; + self.wiki_view_mode = WikiViewMode::DatabaseList; + self.wiki_current_path = "/".to_string(); + self.wiki_preview_path = None; + self.selected_wiki_browser_index = 0; + self.result_records.clear(); + return; + } + self.active_wiki_database_id = Some(database_id); + self.wiki_view_mode = reload_state.view_mode; + self.wiki_current_path = reload_state.current_path; + self.selected_wiki_browser_index = reload_state.browser_index; + if !reload_state.had_search_results { + self.result_records.clear(); + } + } + fn apply_instance_summaries(&mut self, instances: bridge::InstanceSummaries) { self.memory_summaries = instances.memories; self.refresh_memory_records_from_summaries(); @@ -2958,9 +3227,8 @@ impl KinicProvider { let selected_content = if state.current_tab_id == KINIC_SETTINGS_TAB_ID { None } else if state.current_tab_id == KINIC_WIKI_TAB_ID { - let sel = state.selected_index.unwrap_or(0); - filtered - .get(sel) + self.wiki_selected_database_index(state) + .and_then(|index| filtered.get(index)) .map(|record| self.selected_content_for_record(record, state)) } else if self.memories_mode == MemoriesMode::Browser { if self.active_memory.is_none() && self.memory_records.is_empty() { @@ -2982,7 +3250,13 @@ impl KinicProvider { None } else if state.current_tab_id == KINIC_WIKI_TAB_ID { let current = state.selected_index.unwrap_or(0); - (!filtered.is_empty()).then_some(current.min(filtered.len().saturating_sub(1))) + let max_index = if self.wiki_snapshot_mode() == WikiViewMode::DatabaseList { + filtered.len() + } else { + filtered.len().saturating_sub(1) + }; + (self.wiki_snapshot_mode() == WikiViewMode::DatabaseList || !filtered.is_empty()) + .then_some(current.min(max_index)) } else if self.is_add_memory_action_selected(state) { Some(filtered.len()) } else if self.memories_mode == MemoriesMode::Browser { @@ -3352,6 +3626,7 @@ impl KinicProvider { let use_mainnet = self.config.use_mainnet; let query = query.to_string(); self.selected_wiki_browser_index = 0; + self.wiki_view_mode = WikiViewMode::DatabaseBrowser; spawn_request_task( &mut self.next_wiki_search_request_id, &mut self.wiki_search_task, @@ -3382,9 +3657,18 @@ impl KinicProvider { } fn selected_wiki_target(&self, state: &CoreState) -> Option<(String, String)> { - let index = state.selected_index.unwrap_or(0); - let records = self.current_records(); - let record = records.get(index)?; + if !self.result_records.is_empty() { + let record = self + .result_records + .get(self.selected_wiki_browser_index) + .or_else(|| self.result_records.first())?; + return Some(( + record.source_wiki_id.clone()?, + record.source_wiki_database_id.clone()?, + )); + } + let index = self.wiki_selected_database_index(state)?; + let record = self.wiki_records.get(index)?; Some(( record.source_wiki_id.clone()?, record.source_wiki_database_id.clone()?, @@ -3745,6 +4029,9 @@ impl KinicProvider { return "Review session details and default memory settings here.".to_string(); } if self.tab_id == KINIC_WIKI_TAB_ID { + if self.wiki_snapshot_mode() == WikiViewMode::DatabaseList { + return "Enter: open/create wiki database.".to_string(); + } return "Browse wiki databases and search wiki nodes.".to_string(); } let base = match self.memories_mode { @@ -4985,8 +5272,9 @@ impl KinicProvider { PendingTaskPoll::Ready(output) => output, PendingTaskPoll::Disconnected => { reset_request_task(&mut self.wiki_databases_task); - self.wiki_records.clear(); + self.pending_wiki_reload = None; self.wiki_load_error = Some("Wiki databases load failed unexpectedly.".to_string()); + self.wiki_view_mode = WikiViewMode::Diagnostic; return Some(self.disconnected_request_output( state, CoreEffect::Notify("Wiki databases load failed unexpectedly.".to_string()), @@ -5001,14 +5289,20 @@ impl KinicProvider { let effects = match output.result { Ok(databases) => { + let reload_state = self.pending_wiki_reload.take(); self.wiki_load_error = None; self.wiki_databases = databases; self.refresh_wiki_records_from_databases(); + self.restore_wiki_reload_state(reload_state); if self.wiki_records.is_empty() { vec![CoreEffect::Notify("No wiki databases found.".to_string())] } else { + let selection_effect = self + .active_wiki_database_index() + .map(CoreEffect::SelectListItem) + .unwrap_or(CoreEffect::SelectFirstListItem); vec![ - CoreEffect::SelectFirstListItem, + selection_effect, CoreEffect::Notify(format!( "Loaded {} wiki databases.", self.wiki_records.len() @@ -5017,9 +5311,9 @@ impl KinicProvider { } } Err(error) => { - self.wiki_databases.clear(); - self.wiki_records.clear(); + self.pending_wiki_reload = None; self.wiki_load_error = Some(error.clone()); + self.wiki_view_mode = WikiViewMode::Diagnostic; vec![CoreEffect::Notify(format!( "Wiki databases load failed: {}", short_error(error.as_str()) @@ -5030,6 +5324,50 @@ impl KinicProvider { Some(self.snapshot_output(state, effects)) } + fn poll_wiki_create_database_background( + &mut self, + state: &CoreState, + ) -> Option { + let receiver = self.wiki_create_database_task.receiver.as_ref()?; + let output = match poll_pending_task(receiver) { + PendingTaskPoll::Pending => return None, + PendingTaskPoll::Ready(output) => output, + PendingTaskPoll::Disconnected => { + reset_request_task(&mut self.wiki_create_database_task); + return Some(self.disconnected_request_output( + state, + CoreEffect::Notify("Wiki database create failed unexpectedly.".to_string()), + )); + } + }; + + let is_current = + finish_request_task(&mut self.wiki_create_database_task, output.request_id); + if !is_current { + return Some(self.stale_request_output(state)); + } + + let effects = match output.result { + Ok(database_id) => { + self.active_wiki_database_id = Some(database_id.clone()); + self.wiki_view_mode = WikiViewMode::DatabaseList; + self.wiki_current_path = "/".to_string(); + self.wiki_preview_path = None; + self.selected_wiki_browser_index = 0; + vec![ + self.start_wiki_databases_load(false), + CoreEffect::Notify(format!("Created wiki database {database_id}.")), + ] + } + Err(error) => vec![CoreEffect::Notify(format!( + "Wiki database create failed: {}", + short_error(error.as_str()) + ))], + }; + + Some(self.snapshot_output(state, effects)) + } + fn poll_wiki_search_background(&mut self, state: &CoreState) -> Option { let receiver = self.wiki_search_task.receiver.as_ref()?; let output = match poll_pending_task(receiver) { @@ -5214,10 +5552,7 @@ impl KinicProvider { } effects } - KINIC_WIKI_TAB_ID => { - self.result_records.clear(); - vec![self.start_wiki_databases_load()] - } + KINIC_WIKI_TAB_ID => self.enter_wiki_tab(), KINIC_SETTINGS_TAB_ID => self.start_session_settings_refresh().into_iter().collect(), _ => vec![CoreEffect::Notify(format!("Switched kinic tab: {tab_id}"))], } @@ -5373,18 +5708,29 @@ impl DataProvider for KinicProvider { | CoreAction::MoveEnd | CoreAction::MovePageDown | CoreAction::MovePageUp - if self.tab_id == KINIC_WIKI_TAB_ID && state.focus == PaneFocus::Content => + if self.tab_id == KINIC_WIKI_TAB_ID + && self.wiki_snapshot_mode() == WikiViewMode::DatabaseBrowser => { self.navigate_wiki_browser(state, action); + if let Some(index) = self.active_wiki_database_index() { + effects.push(CoreEffect::SelectListItem(index)); + } } CoreAction::OpenSelected => { - if self.tab_id == KINIC_WIKI_TAB_ID && state.focus == PaneFocus::Content { - effects.extend(self.open_selected_wiki_browser_entry(state)); - } else if self.tab_id == KINIC_WIKI_TAB_ID { - self.wiki_current_path = "/".to_string(); - self.selected_wiki_browser_index = 0; - self.start_selected_wiki_children_load(state); - effects.push(CoreEffect::FocusPane(PaneFocus::Content)); + if self.tab_id == KINIC_WIKI_TAB_ID { + match self.wiki_snapshot_mode() { + WikiViewMode::DatabaseList => { + if self.is_wiki_create_database_action_selected(state) { + effects.push(self.start_wiki_create_database()); + } else { + effects.extend(self.enter_selected_wiki_database(state)); + } + } + WikiViewMode::DatabaseBrowser => { + effects.extend(self.open_selected_wiki_browser_entry(state)); + } + WikiViewMode::Diagnostic => {} + } } else if self.is_add_memory_action_selected(state) { effects.push(CoreEffect::OpenAddMemory); effects.push(CoreEffect::FocusPane(PaneFocus::Items)); @@ -6021,6 +6367,7 @@ impl DataProvider for KinicProvider { .or_else(|| self.poll_insert_submit_background(state)) .or_else(|| self.poll_create_cost_background(state)) .or_else(|| self.poll_session_settings_background(state)) + .or_else(|| self.poll_wiki_create_database_background(state)) .or_else(|| self.poll_wiki_databases_background(state)) .or_else(|| self.poll_wiki_children_background(state)) .or_else(|| self.poll_wiki_search_background(state)) diff --git a/rust/tui/provider/tests/wiki.rs b/rust/tui/provider/tests/wiki.rs index 56e3c14..17d2551 100644 --- a/rust/tui/provider/tests/wiki.rs +++ b/rust/tui/provider/tests/wiki.rs @@ -76,12 +76,63 @@ fn wiki_three_pane_exposes_database_rows_separately_from_memory_items() { ..CoreState::default() }); - assert_eq!(snapshot.three_pane.left.rows.len(), 1); + assert_eq!(snapshot.three_pane.left.rows.len(), 2); assert_eq!(snapshot.three_pane.left.rows[0].label, "db-a"); assert!(snapshot.three_pane.left.rows[0].selected); + assert_eq!(snapshot.three_pane.left.rows[1].label, "+ Create database"); + assert_eq!(snapshot.three_pane.mode, ThreePaneMode::List); assert_eq!(snapshot.items.len(), 1); } +#[test] +fn wiki_database_list_includes_create_database_action_row() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_databases = vec![wiki_database("db-a", bridge::DatabaseStatus::Hot)]; + provider.refresh_wiki_records_from_databases(); + let snapshot = provider.build_snapshot(&CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + selected_index: Some(1), + ..CoreState::default() + }); + + assert_eq!(snapshot.three_pane.left.rows.len(), 2); + assert_eq!(snapshot.three_pane.left.rows[1].label, "+ Create database"); + assert_eq!( + snapshot.three_pane.left.rows[1].detail, + "create new database" + ); + assert!(snapshot.three_pane.left.rows[1].selected); +} + +#[test] +fn wiki_create_database_action_is_not_treated_as_database_selection() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_databases = vec![wiki_database("db-a", bridge::DatabaseStatus::Hot)]; + provider.refresh_wiki_records_from_databases(); + let state = CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + selected_index: Some(1), + ..CoreState::default() + }; + let snapshot = provider.build_snapshot(&state); + + assert_eq!(provider.wiki_selected_database_index(&state), None); + assert_eq!(provider.selected_wiki_target(&state), None); + assert!(snapshot.selected_content.is_none()); + assert_eq!( + snapshot.status_message.as_deref(), + Some("Enter: open/create wiki database.") + ); +} + #[test] fn wiki_database_load_error_uses_diagnostic_not_error_record() { let mut provider = KinicProvider::new(TuiConfig { @@ -98,6 +149,7 @@ fn wiki_database_load_error_uses_diagnostic_not_error_record() { }); assert!(snapshot.items.is_empty()); + assert_eq!(snapshot.three_pane.mode, ThreePaneMode::Diagnostic); assert!(snapshot.three_pane.diagnostic.is_some()); assert_eq!( snapshot @@ -135,6 +187,295 @@ fn wiki_diagnostic_uses_session_principal_without_resolving_auth() { ); } +#[test] +fn wiki_database_list_enter_drills_into_browser() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_records = vec![record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-a", bridge::DatabaseStatus::Hot), + )]; + let state = CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + selected_index: Some(0), + ..CoreState::default() + }; + + let effects = provider.enter_selected_wiki_database(&state); + let snapshot = provider.build_snapshot(&state); + + assert_eq!(provider.wiki_view_mode, WikiViewMode::DatabaseBrowser); + assert_eq!(provider.wiki_current_path, "/"); + assert_eq!(provider.selected_wiki_browser_index, 0); + assert!(provider.wiki_children_task.in_flight); + assert_eq!(snapshot.three_pane.mode, ThreePaneMode::Browse); + assert!( + effects + .iter() + .any(|effect| matches!(effect, CoreEffect::FocusPane(PaneFocus::Content))) + ); +} + +#[test] +fn wiki_database_list_enter_uses_database_even_when_focus_is_content() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_view_mode = WikiViewMode::DatabaseList; + provider.wiki_records = vec![record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-a", bridge::DatabaseStatus::Hot), + )]; + provider.wiki_children_cache.insert( + wiki_children_cache_key("db-a", "/"), + WikiChildrenContent { + entries: wiki_root_entries(), + body_lines: Vec::new(), + index_preview: None, + }, + ); + let state = CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + focus: PaneFocus::Content, + selected_index: Some(0), + ..CoreState::default() + }; + + let output = provider + .handle_action(&CoreAction::OpenSelected, &state) + .expect("open selected should build output"); + + assert_eq!(provider.wiki_view_mode, WikiViewMode::DatabaseBrowser); + assert_eq!(provider.wiki_current_path, "/"); + assert_eq!( + output + .snapshot + .as_ref() + .map(|snapshot| snapshot.three_pane.mode), + Some(ThreePaneMode::Browse) + ); + assert!( + output + .effects + .iter() + .any(|effect| matches!(effect, CoreEffect::FocusPane(PaneFocus::Content))) + ); +} + +#[test] +fn wiki_database_list_enter_on_create_action_starts_create_task() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_records = vec![record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-a", bridge::DatabaseStatus::Hot), + )]; + let state = CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + selected_index: Some(1), + ..CoreState::default() + }; + + let output = provider + .handle_action(&CoreAction::OpenSelected, &state) + .expect("open selected should build output"); + + assert!(provider.wiki_create_database_task.in_flight); + assert!(output.effects.iter().any(|effect| { + matches!(effect, CoreEffect::Notify(message) if message == "Creating wiki database...") + })); +} + +#[test] +fn wiki_database_create_action_does_not_start_duplicate_task() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_create_database_task.in_flight = true; + let state = CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + selected_index: Some(0), + ..CoreState::default() + }; + + let output = provider + .handle_action(&CoreAction::OpenSelected, &state) + .expect("open selected should build output"); + + assert!(provider.wiki_create_database_task.in_flight); + assert!(output.effects.iter().any(|effect| { + matches!(effect, CoreEffect::Notify(message) if message == "Creating wiki database...") + })); +} + +#[test] +fn wiki_database_create_success_refreshes_and_preserves_created_database() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_view_mode = WikiViewMode::DatabaseList; + let (tx, rx) = std::sync::mpsc::channel(); + provider.wiki_create_database_task.receiver = Some(rx); + provider.wiki_create_database_task.in_flight = true; + provider.wiki_create_database_task.request_id = Some(7); + tx.send(WikiCreateDatabaseTaskOutput { + request_id: 7, + result: Ok("db-new".to_string()), + }) + .expect("send create result"); + + let output = provider + .poll_wiki_create_database_background(&CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + selected_index: Some(0), + ..CoreState::default() + }) + .expect("create result should produce output"); + + assert!(!provider.wiki_create_database_task.in_flight); + assert!(provider.wiki_databases_task.in_flight); + assert_eq!(provider.active_wiki_database_id.as_deref(), Some("db-new")); + assert_eq!(provider.wiki_view_mode, WikiViewMode::DatabaseList); + assert!(output.effects.iter().any(|effect| { + matches!(effect, CoreEffect::Notify(message) if message == "Created wiki database db-new.") + })); +} + +#[test] +fn wiki_database_create_failure_keeps_database_list_state() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_view_mode = WikiViewMode::DatabaseList; + let (tx, rx) = std::sync::mpsc::channel(); + provider.wiki_create_database_task.receiver = Some(rx); + provider.wiki_create_database_task.in_flight = true; + provider.wiki_create_database_task.request_id = Some(3); + tx.send(WikiCreateDatabaseTaskOutput { + request_id: 3, + result: Err("caller is not allowed".to_string()), + }) + .expect("send create result"); + + let output = provider + .poll_wiki_create_database_background(&CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + selected_index: Some(0), + ..CoreState::default() + }) + .expect("create result should produce output"); + + assert!(!provider.wiki_create_database_task.in_flight); + assert!(!provider.wiki_databases_task.in_flight); + assert_eq!(provider.wiki_view_mode, WikiViewMode::DatabaseList); + assert!(output.effects.iter().any(|effect| { + matches!(effect, CoreEffect::Notify(message) if message == "Wiki database create failed: caller is not allowed") + })); +} + +#[test] +fn wiki_browser_back_at_root_returns_to_database_list() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_view_mode = WikiViewMode::DatabaseBrowser; + provider.active_wiki_database_id = Some("db-a".to_string()); + provider.wiki_records = vec![record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-a", bridge::DatabaseStatus::Hot), + )]; + provider.wiki_current_path = "/".to_string(); + + let effects = provider.back_wiki_browser(); + + assert_eq!(provider.wiki_view_mode, WikiViewMode::DatabaseList); + assert!( + effects + .iter() + .any(|effect| matches!(effect, CoreEffect::FocusPane(PaneFocus::Items))) + ); + assert!( + effects + .iter() + .any(|effect| matches!(effect, CoreEffect::SelectListItem(0))) + ); +} + +#[test] +fn wiki_database_list_back_focuses_tabs() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_view_mode = WikiViewMode::DatabaseList; + + let effects = provider.back_wiki_browser(); + + assert!( + effects + .iter() + .any(|effect| matches!(effect, CoreEffect::FocusPane(PaneFocus::Tabs))) + ); +} + +#[test] +fn wiki_browser_back_inside_directory_moves_to_parent_path() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_view_mode = WikiViewMode::DatabaseBrowser; + provider.wiki_current_path = "/Wiki/docs".to_string(); + + provider.back_wiki_browser(); + + assert_eq!(provider.wiki_view_mode, WikiViewMode::DatabaseBrowser); + assert_eq!(provider.wiki_current_path, "/Wiki"); +} + +#[test] +fn wiki_search_back_only_closes_results() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_view_mode = WikiViewMode::DatabaseBrowser; + provider.result_records = vec![record_from_wiki_search_hit( + "aaaaa-aa", + "db-a", + 0, + bridge::WikiSearchHit { + path: "/Wiki/a.md".to_string(), + score: 1.0, + snippet: None, + }, + )]; + + provider.back_wiki_browser(); + + assert_eq!(provider.wiki_view_mode, WikiViewMode::DatabaseBrowser); + assert!(provider.result_records.is_empty()); +} + #[test] fn wiki_browser_enter_on_directory_updates_current_path() { let mut provider = KinicProvider::new(TuiConfig { @@ -240,6 +581,70 @@ fn wiki_browser_can_move_to_sources_and_enter_directory() { })); } +#[test] +fn wiki_browser_enter_on_file_keeps_directory_list_visible() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_records = vec![record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-a", bridge::DatabaseStatus::Hot), + )]; + provider.wiki_children_cache.insert( + wiki_children_cache_key("db-a", "/Wiki"), + WikiChildrenContent { + entries: vec![ + WikiBrowserEntry { + path: "/Wiki/a.md".to_string(), + name: "a.md".to_string(), + kind: WikiBrowserEntryKind::File, + size_bytes: Some(12), + has_children: false, + }, + WikiBrowserEntry { + path: "/Wiki/b.md".to_string(), + name: "b.md".to_string(), + kind: WikiBrowserEntryKind::File, + size_bytes: Some(34), + has_children: false, + }, + ], + body_lines: Vec::new(), + index_preview: None, + }, + ); + provider.wiki_children_cache.insert( + wiki_children_cache_key("db-a", "/Wiki/b.md"), + WikiChildrenContent { + entries: Vec::new(), + body_lines: Vec::new(), + index_preview: Some(vec!["preview b".to_string()]), + }, + ); + provider.wiki_view_mode = WikiViewMode::DatabaseBrowser; + provider.wiki_current_path = "/Wiki".to_string(); + provider.selected_wiki_browser_index = 1; + let state = CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + focus: PaneFocus::Content, + selected_index: Some(0), + ..CoreState::default() + }; + + provider.open_selected_wiki_browser_entry(&state); + let rows = provider.wiki_browser_rows(0); + let snapshot = provider.build_snapshot(&state); + + assert_eq!(provider.wiki_current_path, "/Wiki"); + assert_eq!(provider.selected_wiki_browser_index, 1); + assert_eq!(provider.wiki_preview_path.as_deref(), Some("/Wiki/b.md")); + assert_eq!(rows.len(), 2); + assert!(rows[1].selected); + assert_eq!(snapshot.three_pane.document.lines, vec!["preview b"]); +} + #[test] fn wiki_search_results_use_browser_selection_for_navigation() { let mut provider = KinicProvider::new(TuiConfig { @@ -391,3 +796,221 @@ fn set_wiki_tab_starts_database_load_once() { .count(); assert_eq!(refresh_count, 1); } + +#[test] +fn set_wiki_tab_keeps_existing_browser_state() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_MEMORIES_TAB_ID.to_string(); + provider.wiki_view_mode = WikiViewMode::DatabaseBrowser; + provider.active_wiki_database_id = Some("db-a".to_string()); + provider.wiki_current_path = "/Wiki/docs".to_string(); + provider.selected_wiki_browser_index = 2; + provider.wiki_records = vec![record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-a", bridge::DatabaseStatus::Hot), + )]; + let state = CoreState { + current_tab_id: KINIC_MEMORIES_TAB_ID.to_string(), + ..CoreState::default() + }; + + let output = provider + .handle_action(&CoreAction::SetTab(KINIC_WIKI_TAB_ID.into()), &state) + .expect("set tab should build output"); + + assert_eq!(provider.wiki_view_mode, WikiViewMode::DatabaseBrowser); + assert_eq!(provider.active_wiki_database_id.as_deref(), Some("db-a")); + assert_eq!(provider.wiki_current_path, "/Wiki/docs"); + assert_eq!(provider.selected_wiki_browser_index, 2); + assert!(!provider.wiki_databases_task.in_flight); + assert!(!output.effects.iter().any(|effect| { + matches!( + effect, + CoreEffect::Notify(message) if message == "Refreshing wiki databases..." + ) + })); +} + +#[test] +fn wiki_reload_restores_database_browser_state_when_database_remains() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_databases = vec![ + wiki_database("db-a", bridge::DatabaseStatus::Hot), + wiki_database("db-b", bridge::DatabaseStatus::Hot), + ]; + provider.refresh_wiki_records_from_databases(); + provider.restore_wiki_reload_state(Some(WikiReloadState { + database_id: Some("db-b".to_string()), + view_mode: WikiViewMode::DatabaseBrowser, + current_path: "/Wiki/docs".to_string(), + browser_index: 3, + had_search_results: false, + })); + + assert_eq!(provider.wiki_view_mode, WikiViewMode::DatabaseBrowser); + assert_eq!(provider.active_wiki_database_id.as_deref(), Some("db-b")); + assert_eq!(provider.wiki_current_path, "/Wiki/docs"); + assert_eq!(provider.selected_wiki_browser_index, 3); +} + +#[test] +fn wiki_refresh_starts_reload_without_clearing_current_browser_state() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_view_mode = WikiViewMode::DatabaseBrowser; + provider.active_wiki_database_id = Some("db-a".to_string()); + provider.wiki_current_path = "/Wiki/docs".to_string(); + provider.selected_wiki_browser_index = 1; + provider.wiki_records = vec![record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-a", bridge::DatabaseStatus::Hot), + )]; + let state = CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + focus: PaneFocus::Content, + selected_index: Some(0), + ..CoreState::default() + }; + + provider + .handle_action(&CoreAction::RefreshCurrentView, &state) + .expect("refresh should build output"); + + assert!(provider.wiki_databases_task.in_flight); + assert_eq!(provider.wiki_view_mode, WikiViewMode::DatabaseBrowser); + assert_eq!(provider.wiki_current_path, "/Wiki/docs"); + assert_eq!(provider.selected_wiki_browser_index, 1); + assert_eq!( + provider + .pending_wiki_reload + .as_ref() + .and_then(|reload| reload.database_id.as_deref()), + Some("db-a") + ); +} + +#[test] +fn wiki_reload_returns_to_database_list_when_selected_database_disappears() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_databases = vec![wiki_database("db-a", bridge::DatabaseStatus::Hot)]; + provider.refresh_wiki_records_from_databases(); + provider.restore_wiki_reload_state(Some(WikiReloadState { + database_id: Some("db-missing".to_string()), + view_mode: WikiViewMode::DatabaseBrowser, + current_path: "/Wiki/docs".to_string(), + browser_index: 3, + had_search_results: true, + })); + + assert_eq!(provider.wiki_view_mode, WikiViewMode::DatabaseList); + assert_eq!(provider.active_wiki_database_id, None); + assert_eq!(provider.wiki_current_path, "/"); + assert_eq!(provider.selected_wiki_browser_index, 0); + assert!(provider.result_records.is_empty()); +} + +#[test] +fn wiki_browser_items_focus_enter_opens_browser_entry() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_view_mode = WikiViewMode::DatabaseBrowser; + provider.active_wiki_database_id = Some("db-a".to_string()); + provider.wiki_records = vec![record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-a", bridge::DatabaseStatus::Hot), + )]; + provider.wiki_children_cache.insert( + wiki_children_cache_key("db-a", "/"), + WikiChildrenContent { + entries: wiki_root_entries(), + body_lines: Vec::new(), + index_preview: None, + }, + ); + provider.wiki_children_cache.insert( + wiki_children_cache_key("db-a", "/Wiki"), + WikiChildrenContent { + entries: Vec::new(), + body_lines: Vec::new(), + index_preview: None, + }, + ); + let state = CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + focus: PaneFocus::Items, + selected_index: Some(0), + ..CoreState::default() + }; + + provider + .handle_action(&CoreAction::OpenSelected, &state) + .expect("open selected should build output"); + + assert_eq!(provider.wiki_view_mode, WikiViewMode::DatabaseBrowser); + assert_eq!(provider.wiki_current_path, "/Wiki"); + assert_eq!(provider.selected_wiki_browser_index, 0); +} + +#[test] +fn wiki_browser_navigation_restores_database_list_selection() { + let mut provider = KinicProvider::new(TuiConfig { + wiki_canister_id: Some("aaaaa-aa".to_string()), + ..live_config() + }); + provider.tab_id = KINIC_WIKI_TAB_ID.to_string(); + provider.wiki_view_mode = WikiViewMode::DatabaseBrowser; + provider.active_wiki_database_id = Some("db-b".to_string()); + provider.wiki_records = vec![ + record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-a", bridge::DatabaseStatus::Hot), + ), + record_from_wiki_database( + "aaaaa-aa", + wiki_database("db-b", bridge::DatabaseStatus::Hot), + ), + ]; + provider.wiki_children_cache.insert( + wiki_children_cache_key("db-b", "/"), + WikiChildrenContent { + entries: wiki_root_entries(), + body_lines: Vec::new(), + index_preview: None, + }, + ); + let state = CoreState { + current_tab_id: KINIC_WIKI_TAB_ID.to_string(), + focus: PaneFocus::Items, + selected_index: Some(0), + ..CoreState::default() + }; + + let output = provider + .handle_action(&CoreAction::MoveNext, &state) + .expect("move should build output"); + + assert_eq!(provider.selected_wiki_browser_index, 1); + assert!( + output + .effects + .iter() + .any(|effect| matches!(effect, CoreEffect::SelectListItem(1))) + ); +} diff --git a/scripts/setup.sh b/scripts/setup.sh index 6bb71d1..36d8da4 100644 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -53,6 +53,7 @@ dfx identity use "${USER_NAME}" dfx deploy internet_identity --specified-id rdmx6-jaaaa-aaaaa-aaadq-cai dfx deploy launcher --specified-id xfug4-5qaaa-aaaak-afowa-cai --argument='(variant {minor})' +dfx deploy wiki --specified-id xis3j-paaaa-aaaai-axumq-cai # dfx canister call launcher change_key_id '("test_key_1")' dfx ledger fabricate-cycles --cycles 100T --canister $(dfx canister id launcher) diff --git a/tui/crates/tui-kit-host/src/lib.rs b/tui/crates/tui-kit-host/src/lib.rs index b18ed01..280d242 100644 --- a/tui/crates/tui-kit-host/src/lib.rs +++ b/tui/crates/tui-kit-host/src/lib.rs @@ -57,6 +57,7 @@ pub fn key_to_core_key(code: KeyCode) -> Option { KeyCode::Char(c) => Some(CoreKey::Char(c)), KeyCode::Tab => Some(CoreKey::Tab), KeyCode::BackTab => Some(CoreKey::BackTab), + KeyCode::Esc => Some(CoreKey::Esc), KeyCode::Backspace => Some(CoreKey::Backspace), KeyCode::Enter => Some(CoreKey::Enter), KeyCode::Down => Some(CoreKey::Down), @@ -189,6 +190,10 @@ pub fn global_command_for_key( if code == KeyCode::Esc { let tab_specific = if focus == PaneFocus::Form && focus_policy.allows_form { HostGlobalCommand::BackFromFormToTabs + } else if current_tab_id == tui_kit_runtime::kinic_tabs::KINIC_WIKI_TAB_ID + && matches!(focus, PaneFocus::Items | PaneFocus::Content) + { + HostGlobalCommand::None } else if current_tab_id == tui_kit_runtime::kinic_tabs::KINIC_MEMORIES_TAB_ID && focus == PaneFocus::Content { @@ -341,6 +346,13 @@ pub fn execute_effects_to_status(state: &mut CoreState, effects: Vec Some(0) }; } + CoreEffect::SelectListItem(index) => { + state.selected_index = if state.list_items.is_empty() { + None + } else { + Some(index.min(state.list_items.len().saturating_sub(1))) + }; + } CoreEffect::FocusPane(pane) => { let focus_policy = tab_focus_policy(state.current_tab_id.as_str()); let allows_focus = match pane { diff --git a/tui/crates/tui-kit-host/src/lib_tests.rs b/tui/crates/tui-kit-host/src/lib_tests.rs index fdc7238..111823a 100644 --- a/tui/crates/tui-kit-host/src/lib_tests.rs +++ b/tui/crates/tui-kit-host/src/lib_tests.rs @@ -1,6 +1,7 @@ use super::*; +use tui_kit_render::{UiItemKind, UiItemSummary, UiVisibility}; use tui_kit_runtime::kinic_tabs::{ - KINIC_CREATE_TAB_ID, KINIC_MEMORIES_TAB_ID, KINIC_SETTINGS_TAB_ID, + KINIC_CREATE_TAB_ID, KINIC_MEMORIES_TAB_ID, KINIC_SETTINGS_TAB_ID, KINIC_WIKI_TAB_ID, }; use tui_kit_runtime::{CoreState, ProviderSnapshot, TransferModalState, apply_snapshot}; @@ -147,6 +148,32 @@ mod effect_application { assert_eq!(state.focus, PaneFocus::Form); } + #[test] + fn select_list_item_effect_clamps_to_visible_items() { + let mut state = CoreState { + list_items: vec![test_item("a"), test_item("b")], + selected_index: Some(0), + ..CoreState::default() + }; + + execute_effects_to_status(&mut state, vec![CoreEffect::SelectListItem(3)]); + + assert_eq!(state.selected_index, Some(1)); + } + + fn test_item(id: &str) -> UiItemSummary { + UiItemSummary { + id: id.to_string(), + name: id.to_string(), + leading_marker: None, + kind: UiItemKind::Custom("test".to_string()), + visibility: UiVisibility::Private, + qualified_name: None, + subtitle: None, + tags: Vec::new(), + } + } + #[test] fn set_insert_tag_effect_updates_insert_tag() { let mut state = CoreState { @@ -420,6 +447,12 @@ mod global_commands { KINIC_SETTINGS_TAB_ID, HostGlobalCommand::BackToTabs, ), + ( + PaneFocus::Content, + KINIC_WIKI_TAB_ID, + HostGlobalCommand::None, + ), + (PaneFocus::Items, KINIC_WIKI_TAB_ID, HostGlobalCommand::None), ]; for (focus, tab_id, expected) in cases { diff --git a/tui/crates/tui-kit-render/src/ui/app/screens/mod.rs b/tui/crates/tui-kit-render/src/ui/app/screens/mod.rs index f580fff..36bc2fc 100644 --- a/tui/crates/tui-kit-render/src/ui/app/screens/mod.rs +++ b/tui/crates/tui-kit-render/src/ui/app/screens/mod.rs @@ -51,7 +51,7 @@ fn submit_button_text( } } -fn spinner_frame(frame: usize) -> &'static str { +pub(crate) fn spinner_frame(frame: usize) -> &'static str { const FRAMES: [&str; 4] = ["|", "/", "-", "\\"]; FRAMES[frame % FRAMES.len()] } diff --git a/tui/crates/tui-kit-render/src/ui/app/screens/wiki.rs b/tui/crates/tui-kit-render/src/ui/app/screens/wiki.rs index 7aa90c4..7cd0c74 100644 --- a/tui/crates/tui-kit-render/src/ui/app/screens/wiki.rs +++ b/tui/crates/tui-kit-render/src/ui/app/screens/wiki.rs @@ -7,38 +7,52 @@ use ratatui::{ text::{Line, Span}, widgets::{Block, Borders, List, ListItem, Paragraph, Widget, Wrap}, }; -use tui_kit_runtime::{PaneRow, ThreePaneSnapshot}; +use tui_kit_runtime::{PaneRow, ThreePaneMode, ThreePaneSnapshot}; +use super::spinner_frame; use crate::ui::app::{Focus, TuiKitUi, shared}; impl<'a> TuiKitUi<'a> { pub(crate) fn render_wiki_screen(&self, area: Rect, buf: &mut Buffer) { let body = shared::layout::body_rect_for_area_with_tabs(area, !self.tab_specs.is_empty()); + let default_snapshot = ThreePaneSnapshot::default(); + let snapshot = self.three_pane.unwrap_or(&default_snapshot); + match snapshot.mode { + ThreePaneMode::List => { + self.render_wiki_databases(body, buf, snapshot); + return; + } + ThreePaneMode::Diagnostic => { + self.render_wiki_diagnostic(body, buf, snapshot); + return; + } + _ => {} + } let chunks = Layout::default() .direction(Direction::Horizontal) - .constraints([ - Constraint::Length(30), - Constraint::Length(36), - Constraint::Min(24), - ]) + .constraints([Constraint::Length(38), Constraint::Min(36)]) .split(body); - let default_snapshot = ThreePaneSnapshot::default(); - let snapshot = self.three_pane.unwrap_or(&default_snapshot); - self.render_wiki_databases(chunks[0], buf, snapshot); - self.render_wiki_browser(chunks[1], buf, snapshot); - self.render_wiki_document(chunks[2], buf, snapshot); + self.render_wiki_browser(chunks[0], buf, snapshot); + self.render_wiki_document(chunks[1], buf, snapshot); } fn render_wiki_databases(&self, area: Rect, buf: &mut Buffer, snapshot: &ThreePaneSnapshot) { let title = if snapshot.left.loading { - " Databases (loading) " + format!( + " Databases | {} Loading... ", + spinner_frame(self.insert_spinner_frame) + ) } else { - " Databases " + " Databases ".to_string() }; let items = if snapshot.left.rows.is_empty() { vec![ListItem::new(Line::from(Span::styled( - format!(" {}", snapshot.left.empty_message), + if snapshot.left.loading { + format!(" {} Loading...", spinner_frame(self.insert_spinner_frame)) + } else { + format!(" {}", snapshot.left.empty_message) + }, self.theme.style_dim(), )))] } else { @@ -51,11 +65,13 @@ impl<'a> TuiKitUi<'a> { }; let block = Block::default() .borders(Borders::ALL) - .border_style(if self.focus == Focus::Items { - self.theme.style_border_focused() - } else { - self.theme.style_border() - }) + .border_style( + if self.focus == Focus::Items || self.focus == Focus::Content { + self.theme.style_border_focused() + } else { + self.theme.style_border() + }, + ) .style(Style::default().bg(self.theme.bg_panel)) .title(title); List::new(items).block(block).render(area, buf); @@ -81,20 +97,24 @@ impl<'a> TuiKitUi<'a> { } fn render_wiki_browser(&self, area: Rect, buf: &mut Buffer, snapshot: &ThreePaneSnapshot) { - let title = if snapshot.middle_mode == "search" { - " Browser (search) " + let path = selected_browser_path(snapshot); + let title = if snapshot.middle.loading { + format!( + " Browser | {} Loading... ", + spinner_frame(self.insert_spinner_frame) + ) + } else if snapshot.mode == ThreePaneMode::Search { + " Browser (search) ".to_string() } else { - " Browser " + format!(" Browser | {path} ") }; - let rows = if let Some(diagnostic) = &snapshot.diagnostic { + let rows = if snapshot.middle.rows.is_empty() { vec![PaneRow { - label: "list_databases failed".to_string(), - detail: diagnostic.message.clone(), - selected: false, - }] - } else if snapshot.middle.rows.is_empty() { - vec![PaneRow { - label: "No entries".to_string(), + label: if snapshot.middle.loading { + format!("{} Loading...", spinner_frame(self.insert_spinner_frame)) + } else { + "No entries".to_string() + }, detail: snapshot.middle.empty_message.clone(), selected: false, }] @@ -104,19 +124,21 @@ impl<'a> TuiKitUi<'a> { let items = rows .iter() .map(|row| { - let marker = if row.selected { "▸" } else { " " }; + let marker = if row.selected { ">" } else { " " }; + let icon = wiki_browser_icon(row.detail.as_str()); let style = if row.selected { - self.theme.style_normal().add_modifier(Modifier::BOLD) + self.theme.style_selected() } else { self.theme.style_dim() }; ListItem::new(vec![ Line::from(vec![ Span::styled(format!("{marker} "), self.theme.style_accent()), + Span::styled(format!("{icon} "), style), Span::styled(row.label.clone(), style), ]), Line::from(Span::styled( - format!(" {}", row.detail), + format!(" {}", row.detail), self.theme.style_muted(), )), ]) @@ -130,6 +152,31 @@ impl<'a> TuiKitUi<'a> { List::new(items).block(block).render(area, buf); } + fn render_wiki_diagnostic(&self, area: Rect, buf: &mut Buffer, snapshot: &ThreePaneSnapshot) { + let mut lines = Vec::new(); + if let Some(diagnostic) = &snapshot.diagnostic { + for row in &diagnostic.rows { + lines.push(Line::from(vec![ + Span::styled(format!("{}: ", row.label), self.theme.style_muted()), + Span::raw(row.detail.clone()), + ])); + } + lines.push(Line::from("")); + lines.push(Line::from(diagnostic.message.clone())); + } else { + lines.push(Line::from("No diagnostics.")); + } + let block = Block::default() + .borders(Borders::ALL) + .border_style(self.theme.style_border_focused()) + .style(Style::default().bg(self.theme.bg_panel)) + .title(" Wiki diagnostics "); + Paragraph::new(lines) + .block(block) + .wrap(Wrap { trim: false }) + .render(area, buf); + } + fn render_wiki_document(&self, area: Rect, buf: &mut Buffer, snapshot: &ThreePaneSnapshot) { let mut lines = Vec::new(); if let Some(diagnostic) = &snapshot.diagnostic { @@ -170,12 +217,35 @@ impl<'a> TuiKitUi<'a> { } } +fn selected_browser_path(snapshot: &ThreePaneSnapshot) -> String { + snapshot + .document + .title + .split_once(' ') + .map(|(_, path)| path) + .filter(|path| path.starts_with('/')) + .unwrap_or("/") + .to_string() +} + +fn wiki_browser_icon(detail: &str) -> &'static str { + if detail.starts_with("directory") { + "[D]" + } else if detail.starts_with("source") { + "[S]" + } else if detail.starts_with("file") { + "[F]" + } else { + "[ ]" + } +} + #[cfg(test)] mod tests { use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; use tui_kit_runtime::{ - DiagnosticSnapshot, DocumentSnapshot, PaneRow, PaneSnapshot, ThreePaneSnapshot, - kinic_tabs::KINIC_WIKI_TAB_ID, + DiagnosticSnapshot, DocumentSnapshot, PaneRow, PaneSnapshot, ThreePaneMode, + ThreePaneSnapshot, kinic_tabs::KINIC_WIKI_TAB_ID, }; use crate::ui::{app::TabId, theme::Theme}; @@ -191,29 +261,58 @@ mod tests { } #[test] - fn wiki_screen_renders_dedicated_panes() { + fn wiki_screen_renders_database_list_mode_as_single_pane() { let theme = Theme::default(); let snapshot = ThreePaneSnapshot { left: PaneSnapshot { - rows: vec![PaneRow { - label: "db-a".to_string(), - detail: "Hot Owner 42 bytes".to_string(), - selected: true, - }], + rows: vec![ + PaneRow { + label: "db-a".to_string(), + detail: "Hot Owner 42 bytes".to_string(), + selected: false, + }, + PaneRow { + label: "+ Create database".to_string(), + detail: "create new database".to_string(), + selected: true, + }, + ], ..PaneSnapshot::default() }, + mode: ThreePaneMode::List, + ..ThreePaneSnapshot::default() + }; + let mut buf = Buffer::empty(Rect::new(0, 0, 110, 30)); + TuiKitUi::new(&theme) + .current_tab_id(TabId::new(KINIC_WIKI_TAB_ID)) + .three_pane_snapshot(&snapshot) + .render(Rect::new(0, 0, 110, 30), &mut buf); + let text = rendered(&buf); + assert!(text.contains("Databases")); + assert!(text.contains("db-a")); + assert!(text.contains("+ Create database")); + assert!(text.contains("create new database")); + assert!(!text.contains("Browser")); + assert!(!text.contains("Document")); + } + + #[test] + fn wiki_screen_renders_browser_mode_as_two_panes() { + let theme = Theme::default(); + let snapshot = ThreePaneSnapshot { middle: PaneSnapshot { rows: vec![PaneRow { label: "/Wiki/index.md".to_string(), detail: "file".to_string(), - selected: false, + selected: true, }], ..PaneSnapshot::default() }, document: DocumentSnapshot { - title: "/Wiki/index.md".to_string(), + title: "db-a /Wiki/index.md".to_string(), lines: vec!["hello wiki".to_string()], }, + mode: ThreePaneMode::Browse, ..ThreePaneSnapshot::default() }; let mut buf = Buffer::empty(Rect::new(0, 0, 110, 30)); @@ -222,12 +321,63 @@ mod tests { .three_pane_snapshot(&snapshot) .render(Rect::new(0, 0, 110, 30), &mut buf); let text = rendered(&buf); - assert!(text.contains("Databases")); assert!(text.contains("Browser")); assert!(text.contains("Document")); - assert!(text.contains("db-a")); assert!(text.contains("/Wiki/index.md")); assert!(text.contains("hello wiki")); + assert!(!text.contains("Databases")); + } + + #[test] + fn wiki_screen_renders_database_loading_spinner() { + let theme = Theme::default(); + let snapshot = ThreePaneSnapshot { + left: PaneSnapshot { + empty_message: "No databases".to_string(), + loading: true, + ..PaneSnapshot::default() + }, + mode: ThreePaneMode::List, + ..ThreePaneSnapshot::default() + }; + let mut buf = Buffer::empty(Rect::new(0, 0, 110, 30)); + TuiKitUi::new(&theme) + .current_tab_id(TabId::new(KINIC_WIKI_TAB_ID)) + .insert_spinner_frame(1) + .three_pane_snapshot(&snapshot) + .render(Rect::new(0, 0, 110, 30), &mut buf); + let text = rendered(&buf); + assert!(text.contains("Databases")); + assert!(text.contains("Loading")); + assert!(text.contains("/")); + } + + #[test] + fn wiki_screen_renders_browser_loading_spinner() { + let theme = Theme::default(); + let snapshot = ThreePaneSnapshot { + middle: PaneSnapshot { + empty_message: "Loading /Wiki".to_string(), + loading: true, + ..PaneSnapshot::default() + }, + document: DocumentSnapshot { + title: "db-a /".to_string(), + lines: Vec::new(), + }, + mode: ThreePaneMode::Browse, + ..ThreePaneSnapshot::default() + }; + let mut buf = Buffer::empty(Rect::new(0, 0, 110, 30)); + TuiKitUi::new(&theme) + .current_tab_id(TabId::new(KINIC_WIKI_TAB_ID)) + .insert_spinner_frame(1) + .three_pane_snapshot(&snapshot) + .render(Rect::new(0, 0, 110, 30), &mut buf); + let text = rendered(&buf); + assert!(text.contains("Browser")); + assert!(text.contains("Loading")); + assert!(text.contains("/")); } #[test] @@ -249,6 +399,7 @@ mod tests { ], message: "wiki query failed for list_databases".to_string(), }), + mode: ThreePaneMode::Diagnostic, ..ThreePaneSnapshot::default() }; let mut buf = Buffer::empty(Rect::new(0, 0, 110, 30)); @@ -257,7 +408,8 @@ mod tests { .three_pane_snapshot(&snapshot) .render(Rect::new(0, 0, 110, 30), &mut buf); let text = rendered(&buf); - assert!(text.contains("list_databases failed")); + assert!(text.contains("Wiki diagnostics")); + assert!(text.contains("wiki query failed for list_databases")); assert!(text.contains("aaaaa-aa")); assert!(text.contains("2vxsx-fae")); } diff --git a/tui/crates/tui-kit-runtime/src/lib.rs b/tui/crates/tui-kit-runtime/src/lib.rs index 2c8d587..bf6a4f7 100644 --- a/tui/crates/tui-kit-runtime/src/lib.rs +++ b/tui/crates/tui-kit-runtime/src/lib.rs @@ -656,13 +656,22 @@ pub struct DiagnosticSnapshot { pub message: String, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum ThreePaneMode { + #[default] + List, + Browse, + Search, + Diagnostic, +} + #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct ThreePaneSnapshot { pub left: PaneSnapshot, pub middle: PaneSnapshot, pub document: DocumentSnapshot, pub diagnostic: Option, - pub middle_mode: String, + pub mode: ThreePaneMode, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -961,6 +970,8 @@ pub enum CoreEffect { InsertFormError(Option), /// Select the first row in the list (no-op when empty). SelectFirstListItem, + /// Select a specific row in the list (clamped by the host after snapshots apply). + SelectListItem(usize), /// Move keyboard focus to a pane. FocusPane(PaneFocus), /// Clear create form fields and switch the active tab (e.g. after successful create). @@ -1062,6 +1073,7 @@ pub enum CoreKey { Slash, Tab, BackTab, + Esc, Backspace, Enter, Down, @@ -2576,6 +2588,9 @@ pub fn action_for_key(key: CoreKey, focus: PaneFocus, current_tab_id: &str) -> O _ => None, }, PaneFocus::Items => match key { + CoreKey::Esc if current_tab_id == kinic_tabs::KINIC_WIKI_TAB_ID => { + Some(CoreAction::Back) + } CoreKey::Down => Some(CoreAction::MoveNext), CoreKey::Up => Some(CoreAction::MovePrev), CoreKey::PageDown => Some(CoreAction::MovePageDown), @@ -2589,6 +2604,9 @@ pub fn action_for_key(key: CoreKey, focus: PaneFocus, current_tab_id: &str) -> O }, PaneFocus::Tabs => None, PaneFocus::Content => match key { + CoreKey::Esc if current_tab_id == kinic_tabs::KINIC_WIKI_TAB_ID => { + Some(CoreAction::Back) + } CoreKey::Down if current_tab_id == kinic_tabs::KINIC_WIKI_TAB_ID => { Some(CoreAction::MoveNext) } @@ -2626,7 +2644,11 @@ pub fn action_for_key(key: CoreKey, focus: PaneFocus, current_tab_id: &str) -> O CoreKey::Up if current_tab_id == kinic_tabs::KINIC_MEMORIES_TAB_ID => { Some(CoreAction::MemoryContentMovePrev) } - CoreKey::Left | CoreKey::Char('h') => Some(CoreAction::Back), + CoreKey::Left | CoreKey::Char('h') + if current_tab_id != kinic_tabs::KINIC_WIKI_TAB_ID => + { + Some(CoreAction::Back) + } _ if is_settings_content(current_tab_id, PaneFocus::Content) => { settings_content_action_for_key(key) } @@ -3624,8 +3646,7 @@ mod tests { (CoreKey::Enter, CoreAction::OpenSelected), (CoreKey::Right, CoreAction::OpenSelected), (CoreKey::Char('l'), CoreAction::OpenSelected), - (CoreKey::Left, CoreAction::Back), - (CoreKey::Char('h'), CoreAction::Back), + (CoreKey::Esc, CoreAction::Back), ]; for (key, action) in cases { @@ -3636,6 +3657,28 @@ mod tests { } } + #[test] + fn wiki_content_left_and_h_do_not_go_back() { + for key in [CoreKey::Left, CoreKey::Char('h')] { + assert_eq!( + action_for_key(key, PaneFocus::Content, kinic_tabs::KINIC_WIKI_TAB_ID), + None + ); + } + } + + #[test] + fn wiki_items_esc_goes_back() { + assert_eq!( + action_for_key( + CoreKey::Esc, + PaneFocus::Items, + kinic_tabs::KINIC_WIKI_TAB_ID + ), + Some(CoreAction::Back) + ); + } + #[test] fn memories_items_tab_moves_focus_to_content() { let mut state = CoreState { diff --git a/wasm/wiki/vfs.did b/wasm/wiki/vfs.did new file mode 100644 index 0000000..5d8224b --- /dev/null +++ b/wasm/wiki/vfs.did @@ -0,0 +1,347 @@ +type AppendNodeRequest = record { + content : text; + separator : opt text; + kind : opt NodeKind; + path : text; + expected_etag : opt text; + metadata_json : opt text; + database_id : text; +}; +type CanisterHealth = record { cycles_balance : nat }; +type CanonicalRole = record { + name : text; + path_pattern : text; + purpose : text; +}; +type ChildNode = record { + updated_at : opt int64; + etag : opt text; + kind : NodeEntryKind; + name : text; + size_bytes : opt nat64; + path : text; + has_children : bool; + is_virtual : bool; +}; +type DatabaseArchiveChunk = record { bytes : blob }; +type DatabaseArchiveInfo = record { size_bytes : nat64; database_id : text }; +type DatabaseMember = record { + "principal" : text; + role : DatabaseRole; + created_at_ms : int64; + database_id : text; +}; +type DatabaseRestoreChunkRequest = record { + offset : nat64; + database_id : text; + bytes : blob; +}; +type DatabaseRole = variant { Reader; Writer; Owner }; +type DatabaseStatus = variant { Hot; Restoring; Archiving; Archived; Deleted }; +type DatabaseSummary = record { + status : DatabaseStatus; + role : DatabaseRole; + logical_size_bytes : nat64; + database_id : text; + archived_at_ms : opt int64; + deleted_at_ms : opt int64; +}; +type DeleteNodeRequest = record { + path : text; + expected_etag : opt text; + database_id : text; +}; +type DeleteNodeResult = record { path : text }; +type EditNodeRequest = record { + path : text; + old_text : text; + replace_all : bool; + expected_etag : opt text; + new_text : text; + database_id : text; +}; +type EditNodeResult = record { + node : NodeMutationAck; + replacement_count : nat32; +}; +type ExportSnapshotRequest = record { + snapshot_revision : opt text; + cursor : opt text; + limit : nat32; + database_id : text; + prefix : opt text; + snapshot_session_id : opt text; +}; +type ExportSnapshotResponse = record { + snapshot_revision : text; + nodes : vec Node; + next_cursor : opt text; + snapshot_session_id : opt text; +}; +type FetchUpdatesRequest = record { + known_snapshot_revision : text; + cursor : opt text; + limit : nat32; + database_id : text; + prefix : opt text; + target_snapshot_revision : opt text; +}; +type FetchUpdatesResponse = record { + removed_paths : vec text; + snapshot_revision : text; + changed_nodes : vec Node; + next_cursor : opt text; +}; +type GlobNodeHit = record { + kind : NodeEntryKind; + path : text; + has_children : bool; +}; +type GlobNodeType = variant { Any; File; Directory }; +type GlobNodesRequest = record { + node_type : opt GlobNodeType; + pattern : text; + path : opt text; + database_id : text; +}; +type GraphLinksRequest = record { + limit : nat32; + database_id : text; + prefix : text; +}; +type GraphNeighborhoodRequest = record { + center_path : text; + limit : nat32; + database_id : text; + depth : nat32; +}; +type IncomingLinksRequest = record { + path : text; + limit : nat32; + database_id : text; +}; +type OutgoingLinksRequest = record { path : text; limit : nat32; database_id : text }; +type LinkEdge = record { + updated_at : int64; + link_kind : text; + link_text : text; + source_path : text; + raw_href : text; + target_path : text; +}; +type ListChildrenRequest = record { path : text; database_id : text }; +type ListNodesRequest = record { + recursive : bool; + database_id : text; + prefix : text; +}; +type MemoryCapability = record { name : text; description : text }; +type MemoryManifest = record { + recommended_entrypoint : text; + api_version : text; + capabilities : vec MemoryCapability; + write_policy : text; + budget_unit : text; + canonical_roles : vec CanonicalRole; + max_depth : nat32; + max_query_limit : nat32; + purpose : text; + roots : vec MemoryRoot; +}; +type MemoryRoot = record { kind : text; path : text }; +type MkdirNodeRequest = record { path : text; database_id : text }; +type MkdirNodeResult = record { created : bool; path : text }; +type MoveNodeRequest = record { + from_path : text; + to_path : text; + expected_etag : opt text; + overwrite : bool; + database_id : text; +}; +type MoveNodeResult = record { + from_path : text; + node : NodeMutationAck; + overwrote : bool; +}; +type MultiEdit = record { old_text : text; new_text : text }; +type MultiEditNodeRequest = record { + path : text; + edits : vec MultiEdit; + expected_etag : opt text; + database_id : text; +}; +type Node = record { + updated_at : int64; + content : text; + etag : text; + kind : NodeKind; + path : text; + created_at : int64; + metadata_json : text; +}; +type NodeContext = record { + incoming_links : vec LinkEdge; + node : Node; + outgoing_links : vec LinkEdge; +}; +type NodeContextRequest = record { + link_limit : nat32; + path : text; + database_id : text; +}; +type NodeEntry = record { + updated_at : int64; + etag : text; + kind : NodeEntryKind; + path : text; + has_children : bool; +}; +type NodeEntryKind = variant { File; Source; Directory }; +type NodeKind = variant { File; Source }; +type NodeMutationAck = record { + updated_at : int64; + etag : text; + kind : NodeKind; + path : text; +}; +type QueryContext = record { + truncated : bool; + task : text; + evidence : vec SourceEvidence; + nodes : vec NodeContext; + graph_links : vec LinkEdge; + search_hits : vec SearchNodeHit; + namespace : text; +}; +type QueryContextRequest = record { + task : text; + include_evidence : bool; + entities : vec text; + budget_tokens : nat32; + database_id : text; + depth : nat32; + namespace : opt text; +}; +type RecentNodeHit = record { + updated_at : int64; + etag : text; + kind : NodeKind; + path : text; +}; +type RecentNodesRequest = record { + path : opt text; + limit : nat32; + database_id : text; +}; +type Result = variant { Ok : WriteNodeResult; Err : text }; +type Result_1 = variant { Ok : DatabaseArchiveInfo; Err : text }; +type Result_10 = variant { Ok : vec ChildNode; Err : text }; +type Result_11 = variant { Ok : vec DatabaseMember; Err : text }; +type Result_12 = variant { Ok : vec DatabaseSummary; Err : text }; +type Result_13 = variant { Ok : vec NodeEntry; Err : text }; +type Result_14 = variant { Ok : MkdirNodeResult; Err : text }; +type Result_15 = variant { Ok : MoveNodeResult; Err : text }; +type Result_16 = variant { Ok : QueryContext; Err : text }; +type Result_17 = variant { Ok : DatabaseArchiveChunk; Err : text }; +type Result_18 = variant { Ok : opt Node; Err : text }; +type Result_19 = variant { Ok : opt NodeContext; Err : text }; +type Result_2 = variant { Ok; Err : text }; +type Result_20 = variant { Ok : vec RecentNodeHit; Err : text }; +type Result_21 = variant { Ok : vec SearchNodeHit; Err : text }; +type Result_22 = variant { Ok : SourceEvidence; Err : text }; +type Result_3 = variant { Ok : text; Err : text }; +type Result_4 = variant { Ok : DeleteNodeResult; Err : text }; +type Result_5 = variant { Ok : EditNodeResult; Err : text }; +type Result_6 = variant { Ok : ExportSnapshotResponse; Err : text }; +type Result_7 = variant { Ok : FetchUpdatesResponse; Err : text }; +type Result_8 = variant { Ok : vec GlobNodeHit; Err : text }; +type Result_9 = variant { Ok : vec LinkEdge; Err : text }; +type SearchNodeHit = record { + preview : opt SearchPreview; + kind : NodeKind; + path : text; + match_reasons : vec text; + snippet : opt text; + score : float32; +}; +type SearchNodePathsRequest = record { + top_k : nat32; + database_id : text; + preview_mode : opt SearchPreviewMode; + prefix : opt text; + query_text : text; +}; +type SearchNodesRequest = record { + top_k : nat32; + database_id : text; + preview_mode : opt SearchPreviewMode; + prefix : opt text; + query_text : text; +}; +type SearchPreview = record { + field : SearchPreviewField; + char_offset : nat32; + match_reason : text; + excerpt : opt text; +}; +type SearchPreviewField = variant { Path; Content }; +type SearchPreviewMode = variant { Light; ContentStart; None }; +type SourceEvidence = record { node_path : text; refs : vec SourceEvidenceRef }; +type SourceEvidenceRef = record { + link_text : text; + via_path : text; + source_path : text; + raw_href : text; +}; +type SourceEvidenceRequest = record { node_path : text; database_id : text }; +type Status = record { source_count : nat64; file_count : nat64 }; +type WriteNodeRequest = record { + content : text; + kind : NodeKind; + path : text; + expected_etag : opt text; + metadata_json : text; + database_id : text; +}; +type WriteNodeResult = record { created : bool; node : RecentNodeHit }; +service : () -> { + append_node : (AppendNodeRequest) -> (Result); + begin_database_archive : (text) -> (Result_1); + begin_database_restore : (text, blob, nat64) -> (Result_2); + cancel_database_archive : (text) -> (Result_2); + canister_health : () -> (CanisterHealth) query; + create_database : () -> (Result_3); + delete_database : (text) -> (Result_2); + delete_node : (DeleteNodeRequest) -> (Result_4); + edit_node : (EditNodeRequest) -> (Result_5); + export_snapshot : (ExportSnapshotRequest) -> (Result_6) query; + fetch_updates : (FetchUpdatesRequest) -> (Result_7) query; + finalize_database_archive : (text, blob) -> (Result_2); + finalize_database_restore : (text) -> (Result_2); + glob_nodes : (GlobNodesRequest) -> (Result_8) query; + grant_database_access : (text, text, DatabaseRole) -> (Result_2); + graph_links : (GraphLinksRequest) -> (Result_9) query; + graph_neighborhood : (GraphNeighborhoodRequest) -> (Result_9) query; + incoming_links : (IncomingLinksRequest) -> (Result_9) query; + list_children : (ListChildrenRequest) -> (Result_10) query; + list_database_members : (text) -> (Result_11) query; + list_databases : () -> (Result_12) query; + list_nodes : (ListNodesRequest) -> (Result_13) query; + memory_manifest : () -> (MemoryManifest) query; + mkdir_node : (MkdirNodeRequest) -> (Result_14); + move_node : (MoveNodeRequest) -> (Result_15); + multi_edit_node : (MultiEditNodeRequest) -> (Result_5); + outgoing_links : (OutgoingLinksRequest) -> (Result_9) query; + query_context : (QueryContextRequest) -> (Result_16) query; + read_database_archive_chunk : (text, nat64, nat32) -> (Result_17) query; + read_node : (text, text) -> (Result_18) query; + read_node_context : (NodeContextRequest) -> (Result_19) query; + recent_nodes : (RecentNodesRequest) -> (Result_20) query; + revoke_database_access : (text, text) -> (Result_2); + search_node_paths : (SearchNodePathsRequest) -> (Result_21) query; + search_nodes : (SearchNodesRequest) -> (Result_21) query; + source_evidence : (SourceEvidenceRequest) -> (Result_22) query; + status : (text) -> (Status) query; + write_database_restore_chunk : (DatabaseRestoreChunkRequest) -> (Result_2); + write_node : (WriteNodeRequest) -> (Result); +} diff --git a/wasm/wiki/vfs_canister_nowasi.wasm b/wasm/wiki/vfs_canister_nowasi.wasm new file mode 100644 index 0000000000000000000000000000000000000000..19e21925976e114c6866e66fba99348cdae1d204 GIT binary patch literal 4202071 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

S6H4HH44MB0P2RY2p(1o^4 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%0D1CP$U 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ɦ%sCaNAEP*hzuCLYG5 z>mOHA!t15yG&(F?!Lqb*8Df`>3xD=J`;AX zGRe+0quATIjD9r<|k$|C9WNZ<#}E23TnFpdCF# z%-GW^lF@)KG6w!p1hLy$#1vC=dZ-~EAu6Z_s)kBbH<;>}{OHns%sPJ|fU%R<7=t7Z z65~}-eH)j}fXrl-bJqJJ&1YNN@6XxbR10d~m`l!$7mCqq?NH9S@jQ`=kda<>!Mmof=Lvu~_j2r#Fl6DF4Mo`msS55v25Y5a9fl#;8#sca!}By@o+h z7XWAMT`u!@7g4>HZhjSsJxvK*j!D6zN^GLyFy0lQvuM-`7ebuFbD9-TnBJB$+}^0EWwtS@4_D0v5TS!i*#utvtQT+Ud=(p~u_aF8IU zsr@+DeyFE>*$y=HOF0=`;5~#lomUfy;7Z6sKxg~L0K3`FLZ7&H( zSl}Xv@4q^L_)GKTHRd zF(drdNgervw>_}#Mn^#TW))DL326FAaA-)n-whU}+vdPnnvrvtaVXrYrYqU2VQFB+ zbANi$xI?Z;7Y*@=ykEm>QUwKBwWOWbpIY-;qx}uc_z0-=Xifd@l5zutlNo ziTd8hXpyY0&vz96C;h!-IO(i_K7Z@)PEj1z)l)p~DgK*({IZI_KgH&;{3~Nud6@=WX6g@#0%-O zdE!-_K`o4;*kpYrM~uQ*?2ZtPkVqBKI4R&5c4?kFtsu_Unr^XRJyJg45WE^i6t9T(!RjM&DocWL5|AyP?7|RWDRvmuChQD^NnEuB$@; zvKzoqgC~1(Lwwo&3EH4=f-N{i?o1zI0Y%Q7y>?NGDa06c=?4KIrSw-)zrv?B+*BFT z&fu1uDctC63Ag^!f}2{C?CFM^?%M6aCh+vk;ARC%xIsp?Y%HYO-r_dTHlr{W66msD zblDG8DxRz6^qSmr8zagR(>k$?iYwY{o+-5GVhOF&qdm2DTC@lCoEfyNKnX2o(bJwY z&eQJ(J;8OeAQ+gCu@f4`iGtB}#+-;Ql+b9N7VfAu#WwH89U06$xbyNjk%$Z@EmsoR zLbn(miV^O^r3`fjE|`ndVqTyp@_L>H@@%)&ZqZ=QVvfFws^m_72B!aJ_?IVFX~K`< z6}nUyw_P>plNEzrxk_EJ?nw&Ndn&pOus>tEJ!9A#-JTBY!SOT0L@Q8XqNiI!$n7oe zm2cB6{9fMe{$kD``*wEkNU4m94_8XC^jwxEa+aPEhzP|2APs828Pw${;9kFx>vepP zPUBYhTC-YGq8g30tP@&3PmJ_r+JDOD!BY z-x``aq+j~Uscb^-0Xl%?``oy=v-?OjmcDcXW>|>;h8mccy@pi@Np>STSpGVr3>(&{M}IZe ziTNh4N9AzwH^8yS%#eOW9b6IVsbEBCpLQ7RBh_q=$Y59V>V-TcnnpS;qg^2Bpa;65 z-=c>x>v`&^r+vfP2(BmS0qrAEc;q=fM|Fc5ECPX|*|VE8u6B^GEh7N~DZYK_Z)n-F zOuE8v>e5G;0}5YJXWEC0yX~%xCU=>EK8&_EgAza$v>IjQc{x-xwwB!F)t1()VRCHt zr9U>3{$fD{l=!}|b!qtmBu=#QgI3vy`l~!e|J!hz zv8gaBlF}eZ?n(cCSYX5`Sduykqa`jX_<~(jV+jLS7<+47eOGeXLHX@aU5q)xf6d8E zk7B1w1{Y(#6aJZic~sY##*YbgJN?pt!C!-bbft|~w>g3&m9RtJ=ohzvPC5m^v z$d=vPlyDPl^}5~cvd|1OZB~L6T0BRQ7)l#GVbVfR%nGiuz|#lA8Q>YGFKRDzwd+Uj zBZ{M{%uz#B{OPkN8K`_tc$gUQBw|z;)gX8cKU-Qb$DL

j9;t>2Iv{{H-m_ONz$< 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

R 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⋙smDgp$w+~omm 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 QADRl)XcJA^g{5Z7WfaE>|ud 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