diff --git a/Cargo.lock b/Cargo.lock
index 55f8459..21d624a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -32,6 +32,19 @@ dependencies = [
"libc",
]
+[[package]]
+name = "ansi-to-tui"
+version = "8.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdfd3cbf4843347ca072771a797484f1c3434a14d57f39d31c92dfb93a8799a8"
+dependencies = [
+ "nom 8.0.0",
+ "ratatui-core",
+ "simdutf8",
+ "smallvec",
+ "thiserror 2.0.17",
+]
+
[[package]]
name = "anstream"
version = "0.6.21"
@@ -256,6 +269,15 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "bitflags"
version = "2.10.0"
@@ -265,6 +287,12 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -383,6 +411,28 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
+[[package]]
+name = "clipboard"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7"
+dependencies = [
+ "clipboard-win",
+ "objc",
+ "objc-foundation",
+ "objc_id",
+ "x11-clipboard",
+]
+
+[[package]]
+name = "clipboard-win"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b"
+dependencies = [
+ "winapi",
+]
+
[[package]]
name = "colorchoice"
version = "1.0.4"
@@ -413,6 +463,20 @@ dependencies = [
"static_assertions",
]
+[[package]]
+name = "compact_str"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "static_assertions",
+]
+
[[package]]
name = "config"
version = "0.15.18"
@@ -420,7 +484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e549344080374f9b32ed41bf3b6b57885ff6a289367b3dbc10eea8acc1918"
dependencies = [
"async-trait",
- "convert_case 0.6.0",
+ "convert_case",
"json5",
"pathdiff",
"ron",
@@ -475,15 +539,6 @@ dependencies = [
"unicode-segmentation",
]
-[[package]]
-name = "convert_case"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
-dependencies = [
- "unicode-segmentation",
-]
-
[[package]]
name = "core-foundation"
version = "0.9.4"
@@ -640,25 +695,36 @@ dependencies = [
[[package]]
name = "cortex-mem-tars"
-version = "1.0.0"
+version = "1.1.0"
dependencies = [
+ "anyhow",
"async-stream",
"async-trait",
"chrono",
- "clap",
+ "clipboard",
"cortex-mem-config",
"cortex-mem-core",
"cortex-mem-rig",
- "crossterm 0.29.0",
+ "crossterm",
+ "directories",
+ "env_logger",
"futures",
+ "log",
"once_cell",
"ratatui",
+ "ratatui-core",
+ "reqwest",
"rig-core",
+ "serde",
"serde_json",
"tokio",
+ "toml",
"tracing",
"tracing-subscriber",
- "unicode-width 0.1.14",
+ "tui-markdown",
+ "tui-textarea",
+ "unicode-width 0.2.0",
+ "uuid",
]
[[package]]
@@ -728,24 +794,6 @@ dependencies = [
"winapi",
]
-[[package]]
-name = "crossterm"
-version = "0.29.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
-dependencies = [
- "bitflags",
- "crossterm_winapi",
- "derive_more",
- "document-features",
- "mio",
- "parking_lot",
- "rustix 1.1.2",
- "signal-hook",
- "signal-hook-mio",
- "winapi",
-]
-
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
@@ -881,27 +929,6 @@ dependencies = [
"syn 2.0.108",
]
-[[package]]
-name = "derive_more"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
-dependencies = [
- "derive_more-impl",
-]
-
-[[package]]
-name = "derive_more-impl"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
-dependencies = [
- "convert_case 0.7.1",
- "proc-macro2",
- "quote",
- "syn 2.0.108",
-]
-
[[package]]
name = "dialoguer"
version = "0.11.0"
@@ -915,6 +942,12 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "diff"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
+
[[package]]
name = "digest"
version = "0.10.7"
@@ -925,13 +958,22 @@ dependencies = [
"crypto-common",
]
+[[package]]
+name = "directories"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
+dependencies = [
+ "dirs-sys 0.5.0",
+]
+
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
- "dirs-sys",
+ "dirs-sys 0.4.1",
]
[[package]]
@@ -942,10 +984,22 @@ checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
- "redox_users",
+ "redox_users 0.4.6",
"windows-sys 0.48.0",
]
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users 0.5.2",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "displaydoc"
version = "0.2.5"
@@ -966,15 +1020,6 @@ dependencies = [
"const-random",
]
-[[package]]
-name = "document-features"
-version = "0.2.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
-dependencies = [
- "litrs",
-]
-
[[package]]
name = "dyn-clone"
version = "1.0.20"
@@ -1002,6 +1047,29 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "env_filter"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "env_filter",
+ "jiff",
+ "log",
+]
+
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -1036,7 +1104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab"
dependencies = [
"futures-core",
- "nom",
+ "nom 7.1.3",
"pin-project-lite",
]
@@ -1074,6 +1142,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
[[package]]
name = "foreign-types"
version = "0.3.2"
@@ -1203,6 +1277,15 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "getopts"
+version = "0.2.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
+dependencies = [
+ "unicode-width 0.2.0",
+]
+
[[package]]
name = "getrandom"
version = "0.2.16"
@@ -1275,7 +1358,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
- "foldhash",
+ "foldhash 0.1.5",
]
[[package]]
@@ -1283,6 +1366,11 @@ name = "hashbrown"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
+]
[[package]]
name = "hashlink"
@@ -1666,6 +1754,30 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+[[package]]
+name = "jiff"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a87d9b8105c23642f50cbbae03d1f75d8422c5cb98ce7ee9271f7ff7505be6b8"
+dependencies = [
+ "jiff-static",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde_core",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b787bebb543f8969132630c51fd0afab173a86c6abae56ff3b9e5e3e3f9f6e58"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.108",
+]
+
[[package]]
name = "js-sys"
version = "0.3.82"
@@ -1687,6 +1799,16 @@ dependencies = [
"serde",
]
+[[package]]
+name = "kasuari"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b"
+dependencies = [
+ "hashbrown 0.16.0",
+ "thiserror 2.0.17",
+]
+
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -1709,6 +1831,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
@@ -1727,12 +1855,6 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
-[[package]]
-name = "litrs"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
-
[[package]]
name = "lock_api"
version = "0.4.14"
@@ -1757,12 +1879,30 @@ dependencies = [
"hashbrown 0.15.5",
]
+[[package]]
+name = "lru"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f"
+dependencies = [
+ "hashbrown 0.16.0",
+]
+
[[package]]
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "matchers"
version = "0.2.0"
@@ -1867,6 +2007,15 @@ dependencies = [
"minimal-lexical",
]
+[[package]]
+name = "nom"
+version = "8.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
@@ -1891,6 +2040,35 @@ dependencies = [
"autocfg",
]
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+]
+
+[[package]]
+name = "objc-foundation"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
+dependencies = [
+ "block",
+ "objc",
+ "objc_id",
+]
+
+[[package]]
+name = "objc_id"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
+dependencies = [
+ "objc",
+]
+
[[package]]
name = "once_cell"
version = "1.21.3"
@@ -1903,6 +2081,28 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+[[package]]
+name = "onig"
+version = "6.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0"
+dependencies = [
+ "bitflags",
+ "libc",
+ "once_cell",
+ "onig_sys",
+]
+
+[[package]]
+name = "onig_sys"
+version = "69.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
[[package]]
name = "openssl"
version = "0.10.74"
@@ -2111,6 +2311,34 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+[[package]]
+name = "plist"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
+dependencies = [
+ "base64 0.22.1",
+ "indexmap 2.12.0",
+ "quick-xml",
+ "serde",
+ "time",
+]
+
+[[package]]
+name = "portable-atomic"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
+dependencies = [
+ "portable-atomic",
+]
+
[[package]]
name = "potential_utf"
version = "0.1.4"
@@ -2135,6 +2363,25 @@ dependencies = [
"zerocopy",
]
+[[package]]
+name = "pretty_assertions"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
+dependencies = [
+ "diff",
+ "yansi",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
+dependencies = [
+ "toml_edit",
+]
+
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@@ -2200,6 +2447,25 @@ dependencies = [
"prost",
]
+[[package]]
+name = "pulldown-cmark"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0"
+dependencies = [
+ "bitflags",
+ "getopts",
+ "memchr",
+ "pulldown-cmark-escape",
+ "unicase",
+]
+
+[[package]]
+name = "pulldown-cmark-escape"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
+
[[package]]
name = "qdrant-client"
version = "1.15.0"
@@ -2221,6 +2487,15 @@ dependencies = [
"tonic",
]
+[[package]]
+name = "quick-xml"
+version = "0.38.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "quinn"
version = "0.11.9"
@@ -2358,16 +2633,36 @@ checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
dependencies = [
"bitflags",
"cassowary",
- "compact_str",
- "crossterm 0.28.1",
+ "compact_str 0.8.1",
+ "crossterm",
"indoc",
"instability",
"itertools 0.13.0",
- "lru",
+ "lru 0.12.5",
"paste",
- "strum",
+ "strum 0.26.3",
+ "unicode-segmentation",
+ "unicode-truncate 1.1.0",
+ "unicode-width 0.2.0",
+]
+
+[[package]]
+name = "ratatui-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293"
+dependencies = [
+ "bitflags",
+ "compact_str 0.9.0",
+ "hashbrown 0.16.0",
+ "indoc",
+ "itertools 0.14.0",
+ "kasuari",
+ "lru 0.16.2",
+ "strum 0.27.2",
+ "thiserror 2.0.17",
"unicode-segmentation",
- "unicode-truncate",
+ "unicode-truncate 2.0.0",
"unicode-width 0.2.0",
]
@@ -2391,6 +2686,17 @@ dependencies = [
"thiserror 1.0.69",
]
+[[package]]
+name = "redox_users"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
+dependencies = [
+ "getrandom 0.2.16",
+ "libredox",
+ "thiserror 2.0.17",
+]
+
[[package]]
name = "ref-cast"
version = "1.0.25"
@@ -2440,6 +2746,12 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
+[[package]]
+name = "relative-path"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
+
[[package]]
name = "reqwest"
version = "0.12.24"
@@ -2578,6 +2890,35 @@ dependencies = [
"serde_derive",
]
+[[package]]
+name = "rstest"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49"
+dependencies = [
+ "futures-timer",
+ "futures-util",
+ "rstest_macros",
+]
+
+[[package]]
+name = "rstest_macros"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0"
+dependencies = [
+ "cfg-if",
+ "glob",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "relative-path",
+ "rustc_version",
+ "syn 2.0.108",
+ "unicode-ident",
+]
+
[[package]]
name = "rust-ini"
version = "0.21.3"
@@ -2594,6 +2935,15 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
[[package]]
name = "rustix"
version = "0.38.44"
@@ -2689,6 +3039,15 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
[[package]]
name = "schannel"
version = "0.1.28"
@@ -2938,6 +3297,12 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+[[package]]
+name = "simdutf8"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+
[[package]]
name = "slab"
version = "0.4.11"
@@ -2994,7 +3359,16 @@ version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [
- "strum_macros",
+ "strum_macros 0.26.4",
+]
+
+[[package]]
+name = "strum"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
+dependencies = [
+ "strum_macros 0.27.2",
]
[[package]]
@@ -3010,6 +3384,18 @@ dependencies = [
"syn 2.0.108",
]
+[[package]]
+name = "strum_macros"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.108",
+]
+
[[package]]
name = "subtle"
version = "2.6.1"
@@ -3058,6 +3444,27 @@ dependencies = [
"syn 2.0.108",
]
+[[package]]
+name = "syntect"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925"
+dependencies = [
+ "bincode",
+ "flate2",
+ "fnv",
+ "once_cell",
+ "onig",
+ "plist",
+ "regex-syntax",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "thiserror 2.0.17",
+ "walkdir",
+ "yaml-rust",
+]
+
[[package]]
name = "system-configuration"
version = "0.6.1"
@@ -3319,18 +3726,30 @@ dependencies = [
[[package]]
name = "toml_datetime"
-version = "0.7.3"
+version = "0.7.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
+checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
dependencies = [
"serde_core",
]
+[[package]]
+name = "toml_edit"
+version = "0.23.10+spec-1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
+dependencies = [
+ "indexmap 2.12.0",
+ "toml_datetime",
+ "toml_parser",
+ "winnow",
+]
+
[[package]]
name = "toml_parser"
-version = "1.0.4"
+version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
+checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
dependencies = [
"winnow",
]
@@ -3444,9 +3863,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
-version = "0.1.41"
+version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"log",
"pin-project-lite",
@@ -3468,9 +3887,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
-version = "0.1.30"
+version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
@@ -3479,9 +3898,9 @@ dependencies = [
[[package]]
name = "tracing-core"
-version = "0.1.34"
+version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
"valuable",
@@ -3535,6 +3954,33 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+[[package]]
+name = "tui-markdown"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e766339aabad4528c3fccddf4acf03bc2b7ae6642def41e43c7af1a11f183122"
+dependencies = [
+ "ansi-to-tui",
+ "itertools 0.14.0",
+ "pretty_assertions",
+ "pulldown-cmark",
+ "ratatui-core",
+ "rstest",
+ "syntect",
+ "tracing",
+]
+
+[[package]]
+name = "tui-textarea"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae"
+dependencies = [
+ "crossterm",
+ "ratatui",
+ "unicode-width 0.2.0",
+]
+
[[package]]
name = "typeid"
version = "1.0.3"
@@ -3582,6 +4028,17 @@ dependencies = [
"unicode-width 0.1.14",
]
+[[package]]
+name = "unicode-truncate"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fbf03860ff438702f3910ca5f28f8dac63c1c11e7efb5012b8b175493606330"
+dependencies = [
+ "itertools 0.13.0",
+ "unicode-segmentation",
+ "unicode-width 0.2.0",
+]
+
[[package]]
name = "unicode-width"
version = "0.1.14"
@@ -3654,6 +4111,16 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
[[package]]
name = "want"
version = "0.3.1"
@@ -3794,6 +4261,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@@ -4146,6 +4622,34 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
+[[package]]
+name = "x11-clipboard"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89bd49c06c9eb5d98e6ba6536cf64ac9f7ee3a009b2f53996d405b3944f6bcea"
+dependencies = [
+ "xcb",
+]
+
+[[package]]
+name = "xcb"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de"
+dependencies = [
+ "libc",
+ "log",
+]
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
+
[[package]]
name = "yaml-rust2"
version = "0.10.4"
@@ -4157,6 +4661,12 @@ dependencies = [
"hashlink",
]
+[[package]]
+name = "yansi"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+
[[package]]
name = "yoke"
version = "0.8.1"
diff --git a/README.md b/README.md
index 788b359..9110ea9 100644
--- a/README.md
+++ b/README.md
@@ -201,8 +201,8 @@ Cortex Memory has been rigorously evaluated against LangMem using the **LOCOMO d
- Cortex Memory Evaluation: Excellent retrieval performance with 93.33% Recall@1 and 93.72% MRR
- | LangMem Evaluation: Modest performance with 26.32% Recall@1 and 38.83% MRR
+ | Cortex Memory Evaluation: Excellent retrieval performance with 93.33% Recall@1 and 93.72% MRR
+ | LangMem Evaluation: Modest performance with 26.32% Recall@1 and 38.83% MRR
|
 |
diff --git a/examples/cortex-mem-tars/.gitignore b/examples/cortex-mem-tars/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/examples/cortex-mem-tars/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/examples/cortex-mem-tars/Cargo.lock b/examples/cortex-mem-tars/Cargo.lock
new file mode 100644
index 0000000..2a84d57
--- /dev/null
+++ b/examples/cortex-mem-tars/Cargo.lock
@@ -0,0 +1,2079 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "ansi-to-tui"
+version = "8.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdfd3cbf4843347ca072771a797484f1c3434a14d57f39d31c92dfb93a8799a8"
+dependencies = [
+ "nom",
+ "ratatui-core",
+ "simdutf8",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
+
+[[package]]
+name = "async-trait"
+version = "0.1.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
+name = "bumpalo"
+version = "3.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
+
+[[package]]
+name = "bytes"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
+
+[[package]]
+name = "cassowary"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
+
+[[package]]
+name = "castaway"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "chrono"
+version = "0.4.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "serde",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "clipboard"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7"
+dependencies = [
+ "clipboard-win",
+ "objc",
+ "objc-foundation",
+ "objc_id",
+ "x11-clipboard",
+]
+
+[[package]]
+name = "clipboard-win"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+
+[[package]]
+name = "compact_str"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "static_assertions",
+]
+
+[[package]]
+name = "compact_str"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "static_assertions",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cortex-mem-tars"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "chrono",
+ "clipboard",
+ "crossterm",
+ "directories",
+ "env_logger",
+ "log",
+ "ratatui",
+ "ratatui-core",
+ "serde",
+ "serde_json",
+ "tokio",
+ "toml",
+ "tui-markdown",
+ "tui-textarea",
+ "unicode-width 0.2.0",
+ "uuid",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossterm"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
+dependencies = [
+ "bitflags",
+ "crossterm_winapi",
+ "mio",
+ "parking_lot",
+ "rustix",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "darling"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "deranged"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "diff"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
+
+[[package]]
+name = "directories"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "env_filter"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "env_filter",
+ "jiff",
+ "log",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
+
+[[package]]
+name = "flate2"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-task"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+
+[[package]]
+name = "futures-timer"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
+
+[[package]]
+name = "futures-util"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+dependencies = [
+ "futures-core",
+ "futures-macro",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "getopts"
+version = "0.2.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
+dependencies = [
+ "unicode-width 0.2.0",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.1.5",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "indexmap"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.16.1",
+]
+
+[[package]]
+name = "indoc"
+version = "2.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "instability"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6778b0196eefee7df739db78758e5cf9b37412268bfa5650bfeed028aed20d9c"
+dependencies = [
+ "darling",
+ "indoc",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
+
+[[package]]
+name = "jiff"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a87d9b8105c23642f50cbbae03d1f75d8422c5cb98ce7ee9271f7ff7505be6b8"
+dependencies = [
+ "jiff-static",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde_core",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b787bebb543f8969132630c51fd0afab173a86c6abae56ff3b9e5e3e3f9f6e58"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "kasuari"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b"
+dependencies = [
+ "hashbrown 0.16.1",
+ "thiserror",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.178"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
+
+[[package]]
+name = "libredox"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+
+[[package]]
+name = "lru"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
+dependencies = [
+ "hashbrown 0.15.5",
+]
+
+[[package]]
+name = "lru"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f"
+dependencies = [
+ "hashbrown 0.16.1",
+]
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
+name = "mio"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "nom"
+version = "8.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+]
+
+[[package]]
+name = "objc-foundation"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
+dependencies = [
+ "block",
+ "objc",
+ "objc_id",
+]
+
+[[package]]
+name = "objc_id"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
+dependencies = [
+ "objc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
+[[package]]
+name = "onig"
+version = "6.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0"
+dependencies = [
+ "bitflags",
+ "libc",
+ "once_cell",
+ "onig_sys",
+]
+
+[[package]]
+name = "onig_sys"
+version = "69.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "plist"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
+dependencies = [
+ "base64",
+ "indexmap",
+ "quick-xml",
+ "serde",
+ "time",
+]
+
+[[package]]
+name = "portable-atomic"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
+dependencies = [
+ "portable-atomic",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "pretty_assertions"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
+dependencies = [
+ "diff",
+ "yansi",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "pulldown-cmark"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0"
+dependencies = [
+ "bitflags",
+ "getopts",
+ "memchr",
+ "pulldown-cmark-escape",
+ "unicase",
+]
+
+[[package]]
+name = "pulldown-cmark-escape"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
+
+[[package]]
+name = "quick-xml"
+version = "0.38.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "ratatui"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
+dependencies = [
+ "bitflags",
+ "cassowary",
+ "compact_str 0.8.1",
+ "crossterm",
+ "indoc",
+ "instability",
+ "itertools 0.13.0",
+ "lru 0.12.5",
+ "paste",
+ "strum 0.26.3",
+ "unicode-segmentation",
+ "unicode-truncate 1.1.0",
+ "unicode-width 0.2.0",
+]
+
+[[package]]
+name = "ratatui-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293"
+dependencies = [
+ "bitflags",
+ "compact_str 0.9.0",
+ "hashbrown 0.16.1",
+ "indoc",
+ "itertools 0.14.0",
+ "kasuari",
+ "lru 0.16.2",
+ "strum 0.27.2",
+ "thiserror",
+ "unicode-segmentation",
+ "unicode-truncate 2.0.0",
+ "unicode-width 0.2.0",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
+dependencies = [
+ "getrandom 0.2.16",
+ "libredox",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
+
+[[package]]
+name = "relative-path"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
+
+[[package]]
+name = "rstest"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49"
+dependencies = [
+ "futures-timer",
+ "futures-util",
+ "rstest_macros",
+]
+
+[[package]]
+name = "rstest_macros"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0"
+dependencies = [
+ "cfg-if",
+ "glob",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "relative-path",
+ "rustc_version",
+ "syn",
+ "unicode-ident",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "semver"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.148"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "signal-hook"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-mio"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
+dependencies = [
+ "libc",
+ "mio",
+ "signal-hook",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
+
+[[package]]
+name = "simdutf8"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+
+[[package]]
+name = "slab"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "socket2"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
+dependencies = [
+ "libc",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "strum"
+version = "0.26.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
+dependencies = [
+ "strum_macros 0.26.4",
+]
+
+[[package]]
+name = "strum"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
+dependencies = [
+ "strum_macros 0.27.2",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.112"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syntect"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925"
+dependencies = [
+ "bincode",
+ "flate2",
+ "fnv",
+ "once_cell",
+ "onig",
+ "plist",
+ "regex-syntax",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "thiserror",
+ "walkdir",
+ "yaml-rust",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.3.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
+
+[[package]]
+name = "time-macros"
+version = "0.2.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tokio"
+version = "1.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "toml"
+version = "0.9.10+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48"
+dependencies = [
+ "indexmap",
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.7.5+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.23.10+spec-1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "toml_parser",
+ "winnow",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.0.6+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.0.6+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "tui-markdown"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e766339aabad4528c3fccddf4acf03bc2b7ae6642def41e43c7af1a11f183122"
+dependencies = [
+ "ansi-to-tui",
+ "itertools 0.14.0",
+ "pretty_assertions",
+ "pulldown-cmark",
+ "ratatui-core",
+ "rstest",
+ "syntect",
+ "tracing",
+]
+
+[[package]]
+name = "tui-textarea"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae"
+dependencies = [
+ "crossterm",
+ "ratatui",
+ "unicode-width 0.2.0",
+]
+
+[[package]]
+name = "unicase"
+version = "2.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+
+[[package]]
+name = "unicode-truncate"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
+dependencies = [
+ "itertools 0.13.0",
+ "unicode-segmentation",
+ "unicode-width 0.1.14",
+]
+
+[[package]]
+name = "unicode-truncate"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fbf03860ff438702f3910ca5f28f8dac63c1c11e7efb5012b8b175493606330"
+dependencies = [
+ "itertools 0.13.0",
+ "unicode-segmentation",
+ "unicode-width 0.2.0",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+
+[[package]]
+name = "unicode-width"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "uuid"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
+dependencies = [
+ "getrandom 0.3.4",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.1+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
+dependencies = [
+ "windows-link",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "winnow"
+version = "0.7.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
+
+[[package]]
+name = "x11-clipboard"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89bd49c06c9eb5d98e6ba6536cf64ac9f7ee3a009b2f53996d405b3944f6bcea"
+dependencies = [
+ "xcb",
+]
+
+[[package]]
+name = "xcb"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de"
+dependencies = [
+ "libc",
+ "log",
+]
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
+
+[[package]]
+name = "yansi"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+
+[[package]]
+name = "zmij"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3280a1b827474fcd5dbef4b35a674deb52ba5c312363aef9135317df179d81b"
diff --git a/examples/cortex-mem-tars/Cargo.toml b/examples/cortex-mem-tars/Cargo.toml
index e0073db..19d70e7 100644
--- a/examples/cortex-mem-tars/Cargo.toml
+++ b/examples/cortex-mem-tars/Cargo.toml
@@ -1,42 +1,55 @@
[package]
name = "cortex-mem-tars"
-version = "1.0.0"
+version = "1.1.0"
edition = "2024"
-description = "A TUI demo application for demonstrating and testing the core features of Cortex Memory"
-license = "MIT"
[dependencies]
+# Cortex Memory dependencies
cortex-mem-config = { path = "../../cortex-mem-config" }
-clap = { workspace = true, features = ["derive"] }
-# Workspace dependencies from parent
cortex-mem-core = { path = "../../cortex-mem-core" }
cortex-mem-rig = { path = "../../cortex-mem-rig" }
-# Runtime
-tokio = { workspace = true, features = ["full"] }
-
# LLM framework
rig-core = "0.23"
# TUI
ratatui = "0.29"
-crossterm = "0.29"
-unicode-width = "0.1"
+tui-markdown = "0.3.7"
+ratatui-core = "0.1"
+crossterm = "0.28"
+tui-textarea = "0.7"
+unicode-width = "0.2"
+
+# Serialization
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+toml = "0.9"
+
+# Error handling
+anyhow = "1.0"
+
+# Time handling
+chrono = { version = "0.4", features = ["serde"] }
+
+# File system
+directories = "6.0"
# Logging
+log = "0.4"
+env_logger = "0.11"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
once_cell = "1.21"
-# Async traits
+# Async
async-trait = "0.1"
-
-# JSON处理
-serde_json = "1.0"
-
-# Stream processing
+tokio = { version = "1.40", features = ["full"] }
futures = "0.3"
async-stream = "0.3"
-# 时间处理
-chrono = { version = "0.4", features = ["serde"] }
+# HTTP client
+reqwest = { version = "0.12", features = ["json"] }
+
+# Utilities
+uuid = { version = "1.10", features = ["v4"] }
+clipboard = "0.5"
diff --git a/examples/cortex-mem-tars/README.md b/examples/cortex-mem-tars/README.md
new file mode 100644
index 0000000..408c55e
--- /dev/null
+++ b/examples/cortex-mem-tars/README.md
@@ -0,0 +1,184 @@
+# Cortex Memory TARS
+
+这是一个基于 Cortex Memory 的 TUI(终端用户界面)聊天应用,具有记忆功能。它能够记住用户的对话历史和个人信息,提供更智能的对话体验。
+
+## 功能特性
+
+- 🧠 **记忆功能**:自动记忆用户的对话历史和个人信息
+- 🤖 **智能 AI 助手**:支持多个机器人配置,每个机器人可以有不同的系统提示词
+- 📝 **Markdown 渲染**:支持 Markdown 格式的消息显示
+- 💾 **对话导出**:可以将对话导出到剪贴板
+- 🔧 **灵活配置**:支持自定义 LLM API、向量存储等配置
+- 🎨 **现代化 TUI**:基于 ratatui 的美观终端界面
+
+## 安装
+
+### 前置要求
+
+- Rust 1.70 或更高版本
+- Qdrant 向量数据库(可选,用于记忆功能)
+- OpenAI API 密钥或其他兼容的 LLM API
+
+### 构建项目
+
+```bash
+cd examples/cortex-mem-tars-new
+cargo build --release
+```
+
+## 配置
+
+### 1. 创建配置文件
+
+将 `config.example.toml` 复制为 `config.toml` 并修改相应的配置:
+
+```bash
+cp config.example.toml config.toml
+```
+
+### 2. 修改配置
+
+编辑 `config.toml` 文件,至少需要配置以下内容:
+
+```toml
+[llm]
+api_base_url = "https://api.openai.com/v1"
+api_key = "your-actual-api-key"
+model_efficient = "gpt-4o-mini"
+
+[embedding]
+api_base_url = "https://api.openai.com/v1"
+api_key = "your-actual-api-key"
+model_name = "text-embedding-3-small"
+
+[qdrant]
+url = "http://localhost:6334"
+```
+
+### 3. 启动 Qdrant(可选,用于记忆功能)
+
+如果你使用记忆功能,需要启动 Qdrant 向量数据库:
+
+```bash
+# 使用 Docker
+docker run -p 6334:6334 qdrant/qdrant
+
+# 或使用本地安装
+qdrant
+```
+
+## 使用方法
+
+### 运行应用
+
+```bash
+cargo run --release
+```
+
+### 基本操作
+
+- **Enter**:发送消息
+- **Shift+Enter**:换行
+- **Ctrl+L**:打开/关闭日志面板
+- **Esc**:关闭日志面板
+- **Ctrl+H**:显示帮助信息
+- **Ctrl+C**:清空会话
+- **Ctrl+D**:导出对话到剪贴板
+- **q**:退出程序
+
+### 命令
+
+在输入框中输入以下命令:
+
+- `/quit`:退出程序
+- `/clear`:清空会话
+- `/help`:显示帮助信息
+- `/dump`:导出对话到剪贴板
+
+## 项目结构
+
+```
+cortex-mem-tars-new/
+├── src/
+│ ├── main.rs # 主程序入口
+│ ├── app.rs # 应用程序主逻辑
+│ ├── agent.rs # Agent 实现(包括记忆功能)
+│ ├── config.rs # 配置管理
+│ ├── infrastructure.rs # 基础设施(LLM、向量存储、记忆管理器)
+│ ├── logger.rs # 日志系统
+│ └── ui.rs # TUI 界面
+├── config.example.toml # 配置文件示例
+└── README.md # 本文件
+```
+
+## 核心功能
+
+### 1. 记忆功能
+
+应用会自动:
+
+- 在启动时加载用户的基本信息(个人特征、事实信息等)
+- 在对话过程中使用记忆工具检索相关信息
+- 在退出时将对话历史保存到记忆系统
+
+### 2. 多机器人支持
+
+可以在配置目录中创建多个机器人配置,每个机器人可以有:
+
+- 不同的名称
+- 不同的系统提示词
+- 不同的访问密码
+
+### 3. 流式响应
+
+支持实时的流式 AI 响应,提供更流畅的对话体验。
+
+## 故障排除
+
+### 1. 无法连接到 Qdrant
+
+确保 Qdrant 正在运行并且 URL 配置正确:
+
+```bash
+curl http://localhost:6334/health
+```
+
+### 2. API 密钥错误
+
+检查 `config.toml` 中的 API 密钥是否正确。
+
+### 3. 记忆功能不工作
+
+- 确保 Qdrant 正在运行
+- 检查 API 密钥是否正确
+- 查看日志面板获取详细错误信息
+
+## 开发
+
+### 运行测试
+
+```bash
+cargo test
+```
+
+### 检查代码
+
+```bash
+cargo check
+```
+
+### 格式化代码
+
+```bash
+cargo fmt
+```
+
+## 许可证
+
+MIT
+
+## 致谢
+
+- [Cortex Memory](https://github.com/sopaco/cortex-mem) - 记忆管理系统
+- [RatATUI](https://github.com/ratatui-org/ratatui) - TUI 框架
+- [Rig](https://github.com/0xPlaygrounds/rig) - LLM Agent 框架
diff --git a/examples/cortex-mem-tars/config.example.toml b/examples/cortex-mem-tars/config.example.toml
new file mode 100644
index 0000000..ce1475d
--- /dev/null
+++ b/examples/cortex-mem-tars/config.example.toml
@@ -0,0 +1,72 @@
+# Cortex Memory TARS 配置文件示例
+# 将此文件复制为 config.toml 并修改相应的配置
+
+[qdrant]
+# Qdrant 向量数据库地址
+url = "http://localhost:6334"
+# 集合名称
+collection_name = "cortex_mem"
+# 嵌入维度(可选,默认为 1536)
+embedding_dim = 1536
+# 超时时间(秒)
+timeout_secs = 30
+
+[llm]
+# LLM API 基础 URL
+api_base_url = "https://api.openai.com/v1"
+# API 密钥
+api_key = "your-api-key-here"
+# 使用的模型
+model_efficient = "gpt-4o-mini"
+# 温度参数
+temperature = 0.7
+# 最大令牌数
+max_tokens = 2000
+
+[server]
+# 服务器主机
+host = "127.0.0.1"
+# 服务器端口
+port = 8080
+# CORS 允许的来源
+cors_origins = ["*"]
+
+[embedding]
+# 嵌入服务 API 基础 URL
+api_base_url = "https://api.openai.com/v1"
+# 嵌入模型名称
+model_name = "text-embedding-3-small"
+# API 密钥
+api_key = "your-api-key-here"
+# 批处理大小
+batch_size = 100
+# 超时时间(秒)
+timeout_secs = 30
+
+[memory]
+# 最大记忆数量
+max_memories = 10000
+# 相似度阈值
+similarity_threshold = 0.65
+# 最大搜索结果数
+max_search_results = 50
+# 记忆 TTL(小时,可选)
+memory_ttl_hours = null
+# 自动摘要阈值
+auto_summary_threshold = 32768
+# 自动增强
+auto_enhance = true
+# 去重
+deduplicate = true
+# 合并阈值
+merge_threshold = 0.75
+# 搜索相似度阈值(可选)
+search_similarity_threshold = 0.70
+
+[logging]
+# 是否启用日志
+enabled = false
+# 日志目录
+log_directory = "logs"
+# 日志级别
+level = "info"
\ No newline at end of file
diff --git a/examples/cortex-mem-tars/src/agent.rs b/examples/cortex-mem-tars/src/agent.rs
index 384d5cb..f94e6c0 100644
--- a/examples/cortex-mem-tars/src/agent.rs
+++ b/examples/cortex-mem-tars/src/agent.rs
@@ -1,25 +1,66 @@
+use anyhow::Result;
+use chrono::{DateTime, Local};
use cortex_mem_config::Config;
use cortex_mem_core::memory::MemoryManager;
use cortex_mem_rig::{ListMemoriesArgs, create_memory_tools, tool::MemoryToolConfig};
+use futures::StreamExt;
use rig::{
- agent::Agent,
+ agent::Agent as RigAgent,
client::CompletionClient,
providers::openai::{Client, CompletionModel},
tool::Tool,
};
-
-use chrono::Local;
+use rig::agent::MultiTurnStreamItem;
+use rig::completion::Message;
+use rig::streaming::{StreamedAssistantContent, StreamingChat};
use std::sync::Arc;
+use tokio::sync::mpsc;
+
+/// 消息角色
+#[derive(Debug, Clone, PartialEq)]
+pub enum MessageRole {
+ System,
+ User,
+ Assistant,
+}
+
+/// 聊天消息
+#[derive(Debug, Clone)]
+pub struct ChatMessage {
+ pub role: MessageRole,
+ pub content: String,
+ pub timestamp: DateTime,
+}
-// 导入日志重定向函数
-use crate::app::redirect_log_to_ui;
+impl ChatMessage {
+ pub fn new(role: MessageRole, content: String) -> Self {
+ Self {
+ role,
+ content,
+ timestamp: Local::now(),
+ }
+ }
+
+ #[allow(dead_code)]
+ pub fn system(content: impl Into) -> Self {
+ Self::new(MessageRole::System, content.into())
+ }
+
+ pub fn user(content: impl Into) -> Self {
+ Self::new(MessageRole::User, content.into())
+ }
+
+ pub fn assistant(content: impl Into) -> Self {
+ Self::new(MessageRole::Assistant, content.into())
+ }
+}
/// 创建带记忆功能的Agent
pub async fn create_memory_agent(
memory_manager: Arc,
memory_tool_config: MemoryToolConfig,
config: &Config,
-) -> Result, Box> {
+) -> Result, Box> {
// 创建记忆工具
let memory_tools =
create_memory_tools(memory_manager.clone(), &config, Some(memory_tool_config));
@@ -42,13 +83,6 @@ pub async fn create_memory_agent(
此会话发生的初始时间:{current_time}
-你的工具:
-- CortexMemoryTool: 可以存储、搜索和检索记忆。支持以下操作:
- * store_memory: 存储新记忆
- * query_memory: 搜索相关记忆
- * list_memories: 获取一系列的记忆集合
- * get_memory: 获取特定记忆
-
重要指令:
- 对话历史将作为上下文提供,请使用这些信息来理解当前的对话流程
- 用户基本信息将在上下文中提供一次,请不要再使用memory工具来创建或更新用户基本信息
@@ -138,15 +172,9 @@ pub async fn extract_user_basic_info(
}
}
-use futures::StreamExt;
-use rig::agent::MultiTurnStreamItem;
-use rig::completion::Message;
-use rig::streaming::{StreamedAssistantContent, StreamingChat};
-use tokio::sync::mpsc;
-
/// Agent回复函数 - 基于tool call的记忆引擎使用(真实流式版本)
pub async fn agent_reply_with_memory_retrieval_streaming(
- agent: &Agent,
+ agent: &RigAgent,
_memory_manager: Arc,
user_input: &str,
_user_id: &str,
@@ -154,9 +182,6 @@ pub async fn agent_reply_with_memory_retrieval_streaming(
conversations: &[(String, String)],
stream_sender: mpsc::UnboundedSender,
) -> Result> {
- // 记录开始处理
- redirect_log_to_ui("DEBUG", &format!("开始处理用户请求: {}", user_input));
-
// 构建对话历史 - 转换为rig的Message格式
let mut chat_history = Vec::new();
for (user_msg, assistant_msg) in conversations {
@@ -180,17 +205,15 @@ pub async fn agent_reply_with_memory_retrieval_streaming(
// 构建完整的prompt
let prompt_content = if let Some(info) = user_info {
- redirect_log_to_ui("DEBUG", "已添加用户基本信息和对话历史到上下文");
format!(
"{}\n\n用户基本信息:\n{}\n\n当前用户输入: {}",
system_prompt, info, user_input
)
} else {
- redirect_log_to_ui("DEBUG", "已添加对话历史到上下文");
format!("{}\n\n当前用户输入: {}", system_prompt, user_input)
};
- redirect_log_to_ui("DEBUG", "正在生成AI回复(真实流式模式)...");
+ log::debug!("正在生成AI回复(真实流式模式)...");
// 使用rig的真实流式API
let prompt_message = Message::user(&prompt_content);
@@ -223,45 +246,42 @@ pub async fn agent_reply_with_memory_retrieval_streaming(
}
StreamedAssistantContent::ToolCall(_) => {
// 处理工具调用(如果需要)
- redirect_log_to_ui("DEBUG", "收到工具调用");
+ log::debug!("收到工具调用");
}
StreamedAssistantContent::Reasoning(_) => {
// 处理推理过程(如果需要)
- redirect_log_to_ui("DEBUG", "收到推理过程");
+ log::debug!("收到推理过程");
}
StreamedAssistantContent::Final(_) => {
// 处理最终响应
- redirect_log_to_ui("DEBUG", "收到最终响应");
+ log::debug!("收到最终响应");
}
StreamedAssistantContent::ToolCallDelta { .. } => {
// 处理工具调用增量
- redirect_log_to_ui("DEBUG", "收到工具调用增量");
+ log::debug!("收到工具调用增量");
}
}
}
MultiTurnStreamItem::FinalResponse(final_response) => {
// 处理最终响应
- redirect_log_to_ui(
- "DEBUG",
- &format!("收到最终响应: {}", final_response.response()),
- );
+ log::debug!("收到最终响应: {}", final_response.response());
full_response = final_response.response().to_string();
break;
}
_ => {
// 处理其他未知的流式项目类型
- redirect_log_to_ui("DEBUG", "收到未知的流式项目类型");
+ log::debug!("收到未知的流式项目类型");
}
}
}
Err(e) => {
- redirect_log_to_ui("ERROR", &format!("流式处理错误: {}", e));
+ log::error!("流式处理错误: {}", e);
return Err(format!("Streaming error: {}", e).into());
}
}
}
- redirect_log_to_ui("DEBUG", "AI回复生成完成");
+ log::debug!("AI回复生成完成");
Ok(full_response.trim().to_string())
}
diff --git a/examples/cortex-mem-tars/src/app.rs b/examples/cortex-mem-tars/src/app.rs
index 557afb2..1046ca8 100644
--- a/examples/cortex-mem-tars/src/app.rs
+++ b/examples/cortex-mem-tars/src/app.rs
@@ -1,366 +1,567 @@
-use ratatui::widgets::ScrollbarState;
-use std::collections::VecDeque;
+use crate::agent::{ChatMessage, create_memory_agent, extract_user_basic_info, store_conversations_batch, agent_reply_with_memory_retrieval_streaming};
+use crate::config::{BotConfig, ConfigManager};
+use crate::infrastructure::Infrastructure;
+use crate::logger::LogManager;
+use crate::ui::{AppState, AppUi};
+use anyhow::{Context, Result};
+use crossterm::{
+ event::{self, DisableMouseCapture, EnableMouseCapture, Event},
+ execute,
+ terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
+};
+use ratatui::backend::CrosstermBackend;
+use ratatui::layout::Rect;
+use rig::agent::Agent as RigAgent;
+use rig::providers::openai::CompletionModel;
+use std::io;
+use std::sync::Arc;
+use std::time::{Duration, Instant};
use tokio::sync::mpsc;
-use chrono::{DateTime, Local};
-// 全局消息发送器,用于日志重定向
-use once_cell::sync::OnceCell;
-use std::sync::Mutex;
-
-static LOG_SENDER: OnceCell>>> = OnceCell::new();
-
-// 设置全局日志发送器 (crate可见性)
-pub(crate) fn set_global_log_sender(sender: mpsc::UnboundedSender) {
- LOG_SENDER
- .get_or_init(|| Mutex::new(None))
- .lock()
- .unwrap()
- .replace(sender);
-}
-
-// 获取全局日志发送器 (crate可见性)
-pub(crate) fn get_global_log_sender() -> Option> {
- LOG_SENDER
- .get()
- .and_then(|mutex| mutex.lock().unwrap().clone())
-}
-
-// 简单的日志重定向函数
-pub fn redirect_log_to_ui(level: &str, message: &str) {
- if let Some(sender) = get_global_log_sender() {
- let full_message = format!("[{}] {}", level, message);
- let _ = sender.send(AppMessage::Log(full_message));
- }
+/// 应用程序
+pub struct App {
+ #[allow(dead_code)]
+ config_manager: ConfigManager,
+ log_manager: Arc,
+ ui: AppUi,
+ current_bot: Option,
+ rig_agent: Option>,
+ infrastructure: Option>,
+ user_id: String,
+ user_info: Option,
+ should_quit: bool,
+ message_sender: mpsc::UnboundedSender,
+ message_receiver: mpsc::UnboundedReceiver,
}
-#[derive(Debug)]
+/// 应用消息类型
+#[derive(Debug, Clone)]
pub enum AppMessage {
+ #[allow(dead_code)]
Log(String),
- Conversation {
- user: String,
- assistant: String,
- },
StreamingChunk {
+ #[allow(dead_code)]
user: String,
chunk: String,
},
StreamingComplete {
+ #[allow(dead_code)]
user: String,
full_response: String,
},
- #[allow(dead_code)]
- MemoryIterationCompleted,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum FocusArea {
- Input, // 输入框
- Conversation, // 对话区域
- Logs, // 日志区域
}
-/// 应用状态
-pub struct App {
- // 对话历史 - 包含时间戳
- pub conversations: VecDeque<(String, String, DateTime)>,
- // 当前输入
- pub current_input: String,
- // 光标位置(以字符为单位)
- pub cursor_position: usize,
- // 日志信息
- pub logs: VecDeque,
- // Agent 是否正在处理
- pub is_processing: bool,
- // 用户信息
- pub user_info: Option,
- // 是否需要退出
- pub should_quit: bool,
- // 是否在shut down过程中
- pub is_shutting_down: bool,
- // 记忆迭代是否完成
- pub memory_iteration_completed: bool,
- // 消息发送器
- pub message_sender: Option>,
- // 日志滚动偏移
- pub log_scroll_offset: usize,
- // 对话滚动偏移
- pub conversation_scroll_offset: usize,
- // 当前焦点区域
- pub focus_area: FocusArea,
- // 用户是否手动滚动过日志(用于决定是否自动滚动到底部)
- pub user_scrolled_logs: bool,
- // 用户是否手动滚动过对话(用于决定是否自动滚动到底部)
- pub user_scrolled_conversations: bool,
- // 滚动条状态
- pub conversation_scrollbar_state: ScrollbarState,
- pub log_scrollbar_state: ScrollbarState,
- // 当前正在流式生成的回复
- pub current_streaming_response: Option<(String, String)>, // (user_input, partial_response)
-}
-
-impl Default for App {
- fn default() -> Self {
- Self {
- conversations: VecDeque::with_capacity(100),
- current_input: String::new(),
- cursor_position: 0,
- logs: VecDeque::with_capacity(50),
- is_processing: false,
+impl App {
+ /// 创建新的应用
+ pub fn new(config_manager: ConfigManager, log_manager: Arc, infrastructure: Option>) -> Result {
+ let mut ui = AppUi::new();
+
+ // 加载机器人列表
+ let bots = config_manager.get_bots()?;
+ ui.set_bot_list(bots);
+
+ // 创建消息通道
+ let (msg_tx, msg_rx) = mpsc::unbounded_channel::();
+
+ log::info!("应用程序初始化完成");
+
+ Ok(Self {
+ config_manager,
+ log_manager,
+ ui,
+ current_bot: None,
+ rig_agent: None,
+ infrastructure,
+ user_id: "tars_user".to_string(),
user_info: None,
should_quit: false,
- is_shutting_down: false,
- memory_iteration_completed: false,
- message_sender: None,
- log_scroll_offset: 0,
- conversation_scroll_offset: 0,
- focus_area: FocusArea::Input,
- user_scrolled_logs: false,
- user_scrolled_conversations: false,
- conversation_scrollbar_state: ScrollbarState::default(),
- log_scrollbar_state: ScrollbarState::default(),
- current_streaming_response: None,
- }
+ message_sender: msg_tx,
+ message_receiver: msg_rx,
+ })
}
-}
-impl App {
- pub fn new(message_sender: mpsc::UnboundedSender) -> Self {
- Self {
- message_sender: Some(message_sender),
- current_streaming_response: None,
- ..Default::default()
+ /// 设置用户信息
+ pub async fn load_user_info(&mut self) -> Result<()> {
+ if let Some(infrastructure) = &self.infrastructure {
+ let user_info = extract_user_basic_info(
+ infrastructure.config(),
+ infrastructure.memory_manager().clone(),
+ &self.user_id,
+ ).await.map_err(|e| anyhow::anyhow!("加载用户信息失败: {}", e))?;
+
+ if let Some(info) = user_info {
+ log::info!("已加载用户基本信息");
+ self.user_info = Some(info);
+ } else {
+ log::info!("未找到用户基本信息");
+ }
}
+ Ok(())
}
- pub fn add_log(&mut self, log: String) {
- self.logs.push_back(log);
- if self.logs.len() > 50 {
- self.logs.pop_front();
+ /// 检查服务可用性
+ pub async fn check_service_status(&mut self) -> Result<()> {
+ use reqwest::Method;
+
+ if let Some(infrastructure) = &self.infrastructure {
+ let api_base_url = &infrastructure.config().llm.api_base_url;
+ // 拼接完整的 API 地址
+ let check_url = format!("{}/chat/completions", api_base_url.trim_end_matches('/'));
+
+ log::info!("检查服务可用性: {}", check_url);
+
+ let client = reqwest::Client::builder()
+ .timeout(Duration::from_secs(5))
+ .build()
+ .context("无法创建 HTTP 客户端")?;
+
+ match client
+ .request(Method::OPTIONS, &check_url)
+ .send()
+ .await
+ {
+ Ok(response) => {
+ if response.status().is_success() || response.status().as_u16() == 405 {
+ // 200 OK 或 405 Method Not Allowed 都表示服务可用
+ log::info!("服务可用,状态码: {}", response.status());
+ self.ui.service_status = crate::ui::ServiceStatus::Active;
+ } else {
+ log::warn!("服务不可用,状态码: {}", response.status());
+ self.ui.service_status = crate::ui::ServiceStatus::Inactive;
+ }
+ }
+ Err(e) => {
+ log::error!("服务检查失败: {}", e);
+ self.ui.service_status = crate::ui::ServiceStatus::Inactive;
+ }
+ }
+ } else {
+ log::warn!("基础设施未初始化,无法检查服务状态");
+ self.ui.service_status = crate::ui::ServiceStatus::Inactive;
}
- // 如果用户没有手动滚动过,自动滚动到最新日志
- if !self.user_scrolled_logs {
- self.scroll_logs_to_bottom();
- }
+ Ok(())
}
- pub fn add_conversation(&mut self, user: String, assistant: String) {
- let timestamp = Local::now();
- self.conversations.push_back((user, assistant, timestamp));
- if self.conversations.len() > 100 {
- self.conversations.pop_front();
- }
+ /// 运行应用
+ pub async fn run(&mut self) -> Result<()> {
+ enable_raw_mode().context("无法启用原始模式")?;
+
+ let mut stdout = io::stdout();
+ execute!(
+ stdout,
+ EnterAlternateScreen,
+ EnableMouseCapture,
+ crossterm::terminal::DisableLineWrap
+ )
+ .context("无法设置终端")?;
+
+ let backend = CrosstermBackend::new(stdout);
+ let mut terminal = ratatui::Terminal::new(backend).context("无法创建终端")?;
+
+ let mut last_log_update = Instant::now();
+ let mut last_service_check = Instant::now();
+ let tick_rate = Duration::from_millis(100);
+
+ loop {
+ // 更新日志
+ if last_log_update.elapsed() > Duration::from_secs(1) {
+ self.update_logs();
+ last_log_update = Instant::now();
+ }
- // 如果用户没有手动滚动过,自动滚动到最新对话
- if !self.user_scrolled_conversations {
- self.scroll_conversations_to_bottom();
- }
- }
+ // 定期检查服务状态(每5秒)
+ if last_service_check.elapsed() > Duration::from_secs(5) {
+ // 在后台检查服务状态,不阻塞主循环
+ let _ = self.check_service_status().await;
+ last_service_check = Instant::now();
+ }
- /// 开始流式回复
- pub fn start_streaming_response(&mut self, user_input: String) {
- self.current_streaming_response = Some((user_input, String::new()));
- self.is_processing = true;
- }
+ // 处理流式消息
+ if let Ok(msg) = self.message_receiver.try_recv() {
+ match msg {
+ AppMessage::StreamingChunk { user: _, chunk } => {
+ // 添加流式内容到当前正在生成的消息
+ if let Some(last_msg) = self.ui.messages.last_mut() {
+ if last_msg.role == crate::agent::MessageRole::Assistant {
+ last_msg.content.push_str(&chunk);
+ } else {
+ // 如果最后一条不是助手消息,创建新的助手消息
+ self.ui.messages.push(ChatMessage::assistant(chunk));
+ }
+ } else {
+ // 如果没有消息,创建新的助手消息
+ self.ui.messages.push(ChatMessage::assistant(chunk));
+ }
+ // 确保自动滚动启用
+ self.ui.auto_scroll = true;
+ }
+ AppMessage::StreamingComplete { user: _, full_response } => {
+ // 流式完成,确保完整响应已保存
+ if let Some(last_msg) = self.ui.messages.last_mut() {
+ if last_msg.role == crate::agent::MessageRole::Assistant {
+ last_msg.content = full_response;
+ } else {
+ self.ui.messages.push(ChatMessage::assistant(full_response));
+ }
+ } else {
+ self.ui.messages.push(ChatMessage::assistant(full_response));
+ }
+ // 确保自动滚动启用
+ self.ui.auto_scroll = true;
+ }
+ AppMessage::Log(_) => {
+ // 日志消息暂时忽略
+ }
+ }
+ }
- /// 添加流式内容块
- pub fn add_streaming_chunk(&mut self, chunk: String) {
- if let Some((_, ref mut response)) = self.current_streaming_response {
- response.push_str(&chunk);
-
- // 如果用户没有手动滚动过,自动滚动到最新对话
- if !self.user_scrolled_conversations {
- self.scroll_conversations_to_bottom();
+ // 渲染 UI
+ terminal.draw(|f| self.ui.render(f)).context("渲染失败")?;
+
+ // 处理事件
+ if event::poll(tick_rate).context("事件轮询失败")? {
+ let event = event::read().context("读取事件失败")?;
+ log::trace!("收到事件: {:?}", event);
+
+ match event {
+ Event::Key(key) => {
+ let action = self.ui.handle_key_event(key);
+
+ match action {
+ crate::ui::KeyAction::Quit => {
+ self.should_quit = true;
+ break;
+ }
+ crate::ui::KeyAction::SendMessage => {
+ if self.ui.state == AppState::Chat {
+ self.send_message().await?;
+ }
+ }
+ crate::ui::KeyAction::ClearChat => {
+ if self.ui.state == AppState::Chat {
+ self.clear_chat();
+ }
+ }
+ crate::ui::KeyAction::ShowHelp => {
+ if self.ui.state == AppState::Chat {
+ self.show_help();
+ }
+ }
+ crate::ui::KeyAction::DumpChats => {
+ if self.ui.state == AppState::Chat {
+ self.dump_chats();
+ }
+ }
+ crate::ui::KeyAction::Continue => {}
+ }
+ }
+ Event::Mouse(mouse) => {
+ let size = terminal.size()?;
+ self.ui
+ .handle_mouse_event(mouse, Rect::new(0, 0, size.width, size.height));
+ }
+ _ => {}
+ }
}
- }
- }
- /// 完成流式回复
- pub fn complete_streaming_response(&mut self) {
- if let Some((user_input, full_response)) = self.current_streaming_response.take() {
- self.add_conversation(user_input, full_response);
+ if self.should_quit {
+ break;
+ }
}
- self.is_processing = false;
- }
- /// 获取当前显示的对话(包括正在流式生成的)
- pub fn get_display_conversations(&self) -> Vec<(String, String, Option>)> {
- let mut conversations: Vec<(String, String, Option>)> = self.conversations
- .iter()
- .map(|(user, assistant, timestamp)| (user.clone(), assistant.clone(), Some(*timestamp)))
- .collect();
-
- // 如果有正在流式生成的回复,添加到显示列表(没有时间戳)
- if let Some((ref user_input, ref partial_response)) = self.current_streaming_response {
- conversations.push((user_input.clone(), partial_response.clone(), None));
- }
-
- conversations
- }
+ disable_raw_mode().context("无法禁用原始模式")?;
+ execute!(
+ terminal.backend_mut(),
+ LeaveAlternateScreen,
+ DisableMouseCapture
+ )
+ .context("无法恢复终端")?;
- /// 在光标位置插入字符
- pub fn insert_char_at_cursor(&mut self, c: char) {
- // 将光标位置转换为字节索引
- let byte_pos = self
- .current_input
- .chars()
- .take(self.cursor_position)
- .map(|ch| ch.len_utf8())
- .sum();
-
- self.current_input.insert(byte_pos, c);
- self.cursor_position += 1;
+ terminal.show_cursor().context("无法显示光标")?;
+
+ log::info!("应用程序退出");
+ Ok(())
}
- /// 在光标位置删除字符(退格键)
- pub fn delete_char_at_cursor(&mut self) {
- if self.cursor_position > 0 {
- // 将光标位置转换为字节索引
- let chars: Vec = self.current_input.chars().collect();
- if self.cursor_position <= chars.len() {
- // 找到要删除字符的字节范围
- let byte_start: usize = chars
- .iter()
- .take(self.cursor_position - 1)
- .map(|ch| ch.len_utf8())
- .sum();
-
- let byte_end: usize = chars
- .iter()
- .take(self.cursor_position)
- .map(|ch| ch.len_utf8())
- .sum();
-
- // 安全地删除字符
- self.current_input.drain(byte_start..byte_end);
- self.cursor_position -= 1;
+ /// 更新日志
+ fn update_logs(&mut self) {
+ match self.log_manager.read_logs(1000) {
+ Ok(logs) => {
+ self.ui.log_lines = logs;
+ }
+ Err(e) => {
+ log::error!("读取日志失败: {}", e);
}
}
}
- /// 将光标向左移动一个字符
- pub fn move_cursor_left(&mut self) {
- if self.cursor_position > 0 {
- self.cursor_position -= 1;
- }
- }
+ /// 发送消息
+ async fn send_message(&mut self) -> Result<()> {
+ let input_text = self.ui.get_input_text();
+ let input_text = input_text.trim();
+
+ log::debug!("准备发送消息,长度: {}", input_text.len());
- /// 将光标向右移动一个字符
- pub fn move_cursor_right(&mut self) {
- let input_len = self.current_input.chars().count();
- if self.cursor_position < input_len {
- self.cursor_position += 1;
+ if input_text.is_empty() {
+ log::debug!("消息为空,忽略");
+ return Ok(());
}
- }
- /// 重置光标位置到末尾
- pub fn reset_cursor_to_end(&mut self) {
- self.cursor_position = self.current_input.chars().count();
- }
+ // 检查是否是命令
+ if let Some(command_action) = self.ui.parse_and_execute_command(input_text) {
+ self.ui.clear_input();
- /// 滚动到日志底部(最新日志)
- pub fn scroll_logs_to_bottom(&mut self) {
- self.log_scroll_offset = 0;
- }
+ match command_action {
+ crate::ui::KeyAction::Quit => {
+ self.should_quit = true;
+ }
+ crate::ui::KeyAction::ClearChat => {
+ self.clear_chat();
+ }
+ crate::ui::KeyAction::ShowHelp => {
+ self.show_help();
+ }
+ crate::ui::KeyAction::DumpChats => {
+ self.dump_chats();
+ }
+ _ => {}
+ }
+ return Ok(());
+ }
- /// 滚动到对话底部(最新对话)
- pub fn scroll_conversations_to_bottom(&mut self) {
- self.conversation_scroll_offset = 0;
- }
+ // 检查是否刚进入聊天模式
+ if self.current_bot.is_none() {
+ if let Some(bot) = self.ui.selected_bot() {
+ self.current_bot = Some(bot.clone());
+
+ // 如果有基础设施,创建真实的带记忆的 Agent
+ if let Some(infrastructure) = &self.infrastructure {
+ let memory_tool_config = cortex_mem_rig::tool::MemoryToolConfig {
+ default_user_id: Some(self.user_id.clone()),
+ ..Default::default()
+ };
+
+ match create_memory_agent(
+ infrastructure.memory_manager().clone(),
+ memory_tool_config,
+ infrastructure.config(),
+ ).await {
+ Ok(rig_agent) => {
+ self.rig_agent = Some(rig_agent);
+ log::info!("已创建带记忆功能的真实 Agent");
+ }
+ Err(e) => {
+ log::error!("创建真实 Agent 失败,使用 Mock Agent: {}", e);
+ }
+ }
+ }
- /// 向前滚动日志(查看更早日志)
- pub fn scroll_logs_forward(&mut self) {
- if self.logs.is_empty() {
- return;
+ log::info!("选择机器人: {}", bot.name);
+ } else {
+ log::warn!("没有选中的机器人");
+ return Ok(());
+ }
}
- let page_size = 10; // 每次翻页的行数
+ // 添加用户消息
+ let user_message = ChatMessage::user(input_text);
+ self.ui.messages.push(user_message.clone());
+ self.ui.clear_input();
+
+ // 用户发送新消息,重新启用自动滚动
+ self.ui.auto_scroll = true;
+
+ log::info!("用户发送消息: {}", input_text);
+ log::debug!("当前消息总数: {}", self.ui.messages.len());
+
+ // 使用真实的带记忆的 Agent 或 Mock Agent
+ if let Some(rig_agent) = &self.rig_agent {
+ // 使用真实 Agent 进行流式响应
+ let current_conversations: Vec<(String, String)> = self.ui.messages
+ .iter()
+ .filter_map(|msg| match msg.role {
+ crate::agent::MessageRole::User => Some((msg.content.clone(), String::new())),
+ crate::agent::MessageRole::Assistant => {
+ if let Some(last) = self.ui.messages.iter().rev().find(|m| m.role == crate::agent::MessageRole::User) {
+ Some((last.content.clone(), msg.content.clone()))
+ } else {
+ None
+ }
+ }
+ _ => None
+ })
+ .collect();
+
+ let user_info_clone = self.user_info.clone();
+ let infrastructure_clone = self.infrastructure.clone();
+ let rig_agent_clone = rig_agent.clone();
+ let msg_tx = self.message_sender.clone();
+ let user_input = input_text.to_string();
+ let user_id = self.user_id.clone();
+ let user_input_for_stream = user_input.clone();
+
+ tokio::spawn(async move {
+ let (stream_tx, mut stream_rx) = mpsc::unbounded_channel::();
+
+ let generation_task = tokio::spawn(async move {
+ agent_reply_with_memory_retrieval_streaming(
+ &rig_agent_clone,
+ infrastructure_clone.unwrap().memory_manager().clone(),
+ &user_input,
+ &user_id,
+ user_info_clone.as_deref(),
+ ¤t_conversations,
+ stream_tx,
+ ).await
+ });
+
+ while let Some(chunk) = stream_rx.recv().await {
+ if let Err(_) = msg_tx.send(AppMessage::StreamingChunk {
+ user: user_input_for_stream.clone(),
+ chunk,
+ }) {
+ break;
+ }
+ }
- // 简单增加偏移量,让UI层处理边界
- self.log_scroll_offset += page_size;
- self.user_scrolled_logs = true;
- }
+ match generation_task.await {
+ Ok(Ok(full_response)) => {
+ let _ = msg_tx.send(AppMessage::StreamingComplete {
+ user: user_input_for_stream.clone(),
+ full_response,
+ });
+ }
+ Ok(Err(e)) => {
+ log::error!("生成回复失败: {}", e);
+ }
+ Err(e) => {
+ log::error!("任务执行失败: {}", e);
+ }
+ }
+ });
- /// 向后滚动日志(查看更新日志)
- pub fn scroll_logs_backward(&mut self) {
- if self.logs.is_empty() {
- return;
+ } else {
+ log::warn!("Agent 未初始化");
}
- let page_size = 10; // 每次翻页的行数
+ // 滚动到底部 - 将在渲染时自动计算
+ self.ui.auto_scroll = true;
- // 向后翻页(减少偏移量,查看更新的日志)
- if self.log_scroll_offset >= page_size {
- self.log_scroll_offset -= page_size;
- } else {
- self.log_scroll_offset = 0;
- self.user_scrolled_logs = false;
- }
+ Ok(())
}
- /// 向前滚动对话(查看更早内容)
- pub fn scroll_conversations_forward(&mut self) {
- if self.conversations.is_empty() {
- return;
- }
-
- let page_size = 5; // 每次翻页的行数
+ /// 清空会话
+ fn clear_chat(&mut self) {
+ log::info!("清空会话");
+ self.ui.messages.clear();
+ self.ui.scroll_offset = 0;
+ self.ui.auto_scroll = true;
+ }
- // 简单增加偏移量,让UI层处理边界
- self.conversation_scroll_offset += page_size;
- self.user_scrolled_conversations = true;
+ /// 显示帮助信息
+ fn show_help(&mut self) {
+ log::info!("显示帮助信息");
+ let help_message = ChatMessage::assistant(AppUi::get_help_message());
+ self.ui.messages.push(help_message);
+ self.ui.auto_scroll = true;
}
- /// 向后滚动对话(查看更新内容)
- pub fn scroll_conversations_backward(&mut self) {
- if self.conversations.is_empty() {
- return;
+ /// 导出会话到剪贴板
+ fn dump_chats(&mut self) {
+ match self.ui.dump_chats_to_clipboard() {
+ Ok(msg) => {
+ log::info!("{}", msg);
+ let success_message = ChatMessage::assistant(msg);
+ self.ui.messages.push(success_message);
+ }
+ Err(e) => {
+ log::error!("{}", e);
+ let error_message = ChatMessage::assistant(format!("❌ {}", e));
+ self.ui.messages.push(error_message);
+ }
}
+ self.ui.auto_scroll = true;
+ }
- let page_size = 5; // 每次翻页的行数
-
- // 向后翻页(减少偏移量,查看更新的内容)
- if self.conversation_scroll_offset >= page_size {
- self.conversation_scroll_offset -= page_size;
- } else {
- self.conversation_scroll_offset = 0;
- self.user_scrolled_conversations = false;
+ /// 退出时保存对话到记忆系统
+ pub async fn save_conversations_to_memory(&self) -> Result<()> {
+ if let Some(infrastructure) = &self.infrastructure {
+ let conversations: Vec<(String, String)> = self.ui.messages
+ .iter()
+ .filter_map(|msg| match msg.role {
+ crate::agent::MessageRole::User => Some((msg.content.clone(), String::new())),
+ crate::agent::MessageRole::Assistant => {
+ if let Some(last) = self.ui.messages.iter().rev().find(|m| m.role == crate::agent::MessageRole::User) {
+ Some((last.content.clone(), msg.content.clone()))
+ } else {
+ None
+ }
+ },
+ _ => None
+ })
+ .filter(|(user, assistant)| !user.is_empty() && !assistant.is_empty())
+ .collect();
+
+ if !conversations.is_empty() {
+ log::info!("正在保存 {} 条对话到记忆系统...", conversations.len());
+ store_conversations_batch(
+ infrastructure.memory_manager().clone(),
+ &conversations,
+ &self.user_id,
+ ).await.map_err(|e| anyhow::anyhow!("保存对话到记忆系统失败: {}", e))?;
+ log::info!("对话保存完成");
+ }
}
+ Ok(())
}
- /// 切换焦点到下一个区域
- pub fn next_focus(&mut self) {
- self.focus_area = match self.focus_area {
- FocusArea::Input => {
- if self.is_shutting_down {
- // 在退出过程中,跳过输入框,直接到对话区域
- FocusArea::Conversation
- } else {
- FocusArea::Conversation
- }
- }
- FocusArea::Conversation => {
- if self.is_shutting_down {
- // 在退出过程中,从对话区域切换到日志区域
- FocusArea::Logs
- } else {
- FocusArea::Logs
- }
- }
- FocusArea::Logs => {
- if self.is_shutting_down {
- // 在退出过程中,从日志区域切换回对话区域
- FocusArea::Conversation
- } else {
- FocusArea::Input
- }
- }
- };
+ /// 获取所有对话
+ pub fn get_conversations(&self) -> Vec<(String, String)> {
+ self.ui.messages
+ .iter()
+ .filter_map(|msg| match msg.role {
+ crate::agent::MessageRole::User => Some((msg.content.clone(), String::new())),
+ crate::agent::MessageRole::Assistant => {
+ if let Some(last) = self.ui.messages.iter().rev().find(|m| m.role == crate::agent::MessageRole::User) {
+ Some((last.content.clone(), msg.content.clone()))
+ } else {
+ None
+ }
+ },
+ _ => None,
+ })
+ .collect()
}
- pub fn log_info(&self, message: &str) {
- if let Some(sender) = &self.message_sender {
- let _ = sender.send(AppMessage::Log(format!("[INFO] {}", message)));
- }
+ /// 获取用户ID
+ pub fn get_user_id(&self) -> String {
+ self.user_id.clone()
}
}
+
+/// 创建默认机器人
+pub fn create_default_bots(config_manager: &ConfigManager) -> Result<()> {
+ let bots = config_manager.get_bots()?;
+
+ if bots.is_empty() {
+ // 创建默认机器人
+ let default_bot = BotConfig::new(
+ "助手",
+ "你是一个有用的 AI 助手,能够回答各种问题并提供帮助。",
+ "password",
+ );
+ config_manager.add_bot(default_bot)?;
+
+ let coder_bot = BotConfig::new(
+ "程序员",
+ "你是一个经验丰富的程序员,精通多种编程语言,能够帮助解决编程问题。",
+ "password",
+ );
+ config_manager.add_bot(coder_bot)?;
+
+ log::info!("已创建默认机器人");
+ }
+
+ Ok(())
+}
diff --git a/examples/cortex-mem-tars/src/config.rs b/examples/cortex-mem-tars/src/config.rs
new file mode 100644
index 0000000..79307af
--- /dev/null
+++ b/examples/cortex-mem-tars/src/config.rs
@@ -0,0 +1,197 @@
+use anyhow::{Context, Result};
+use chrono::{DateTime, Utc};
+use cortex_mem_config::Config as CortexConfig;
+use serde::{Deserialize, Serialize};
+use std::fs;
+use std::path::{Path, PathBuf};
+
+/// 机器人配置
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct BotConfig {
+ pub id: String,
+ pub name: String,
+ pub system_prompt: String,
+ pub access_password: String,
+ pub created_at: DateTime,
+}
+
+impl BotConfig {
+ pub fn new(name: impl Into, system_prompt: impl Into, access_password: impl Into) -> Self {
+ Self {
+ id: uuid::Uuid::new_v4().to_string(),
+ name: name.into(),
+ system_prompt: system_prompt.into(),
+ access_password: access_password.into(),
+ created_at: Utc::now(),
+ }
+ }
+}
+
+/// 配置管理器
+pub struct ConfigManager {
+ config_dir: PathBuf,
+ bots_file: PathBuf,
+ cortex_config: CortexConfig,
+}
+
+impl ConfigManager {
+ /// 创建新的配置管理器
+ pub fn new() -> Result {
+ // 获取当前工作目录
+ let current_dir = std::env::current_dir().context("无法获取当前工作目录")?;
+
+ // 系统配置目录(用于 bots.json)
+ let config_dir = directories::ProjectDirs::from("com", "cortex", "mem-tars")
+ .context("无法获取项目目录")?
+ .config_dir()
+ .to_path_buf();
+
+ fs::create_dir_all(&config_dir).context("无法创建配置目录")?;
+
+ let bots_file = config_dir.join("bots.json");
+
+ // cortex-mem 配置文件:优先从当前目录读取
+ let local_config_file = current_dir.join("config.toml");
+ let system_config_file = config_dir.join("config.toml");
+
+ // 确定使用哪个配置文件
+ let cortex_config_file = if local_config_file.exists() {
+ log::info!("使用当前目录的配置文件: {:?}", local_config_file);
+ local_config_file
+ } else {
+ log::info!("使用系统配置目录的配置文件: {:?}", system_config_file);
+ system_config_file
+ };
+
+ // 加载或创建 cortex-mem 配置
+ let cortex_config = if cortex_config_file.exists() {
+ CortexConfig::load(&cortex_config_file).context("无法加载 cortex-mem 配置")?
+ } else {
+ // 创建默认配置
+ let default_config = CortexConfig {
+ qdrant: cortex_mem_config::QdrantConfig {
+ url: "http://localhost:6334".to_string(),
+ collection_name: "cortex_mem".to_string(),
+ embedding_dim: Some(1536),
+ timeout_secs: 30,
+ },
+ llm: cortex_mem_config::LLMConfig {
+ api_base_url: "https://api.openai.com/v1".to_string(),
+ api_key: "".to_string(),
+ model_efficient: "gpt-4o-mini".to_string(),
+ temperature: 0.7,
+ max_tokens: 2000,
+ },
+ server: cortex_mem_config::ServerConfig {
+ host: "127.0.0.1".to_string(),
+ port: 8080,
+ cors_origins: vec!["*".to_string()],
+ },
+ embedding: cortex_mem_config::EmbeddingConfig {
+ api_base_url: "https://api.openai.com/v1".to_string(),
+ model_name: "text-embedding-3-small".to_string(),
+ api_key: "".to_string(),
+ batch_size: 100,
+ timeout_secs: 30,
+ },
+ memory: cortex_mem_config::MemoryConfig::default(),
+ logging: cortex_mem_config::LoggingConfig::default(),
+ };
+ let content = toml::to_string_pretty(&default_config).context("无法序列化默认配置")?;
+ fs::write(&cortex_config_file, content).context("无法写入默认配置文件")?;
+ log::info!("已创建默认 cortex-mem 配置文件: {:?}", cortex_config_file);
+ default_config
+ };
+
+ Ok(Self {
+ config_dir,
+ bots_file,
+ cortex_config,
+ })
+ }
+
+ /// 获取所有机器人配置
+ pub fn get_bots(&self) -> Result> {
+ if !self.bots_file.exists() {
+ return Ok(vec![]);
+ }
+
+ let content = fs::read_to_string(&self.bots_file).context("无法读取配置文件")?;
+ let bots: Vec = serde_json::from_str(&content).context("无法解析配置文件")?;
+
+ Ok(bots)
+ }
+
+ /// 保存所有机器人配置
+ fn save_bots(&self, bots: &[BotConfig]) -> Result<()> {
+ let content = serde_json::to_string_pretty(bots).context("无法序列化配置")?;
+ fs::write(&self.bots_file, content).context("无法写入配置文件")?;
+ Ok(())
+ }
+
+ /// 添加机器人
+ pub fn add_bot(&self, bot: BotConfig) -> Result<()> {
+ let bot_name = bot.name.clone();
+ let bot_id = bot.id.clone();
+ let mut bots = self.get_bots()?;
+ bots.push(bot);
+ self.save_bots(&bots)?;
+ log::info!("添加机器人: {} (ID: {})", bot_name, bot_id);
+ Ok(())
+ }
+
+ /// 删除机器人
+ #[allow(dead_code)]
+ pub fn remove_bot(&self, bot_id: &str) -> Result {
+ let mut bots = self.get_bots()?;
+ let original_len = bots.len();
+ bots.retain(|bot| bot.id != bot_id);
+
+ if bots.len() < original_len {
+ self.save_bots(&bots)?;
+ log::info!("删除机器人 ID: {}", bot_id);
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+
+ /// 更新机器人
+ #[allow(dead_code)]
+ pub fn update_bot(&self, bot_id: &str, updated_bot: BotConfig) -> Result {
+ let bot_name = updated_bot.name.clone();
+ let mut bots = self.get_bots()?;
+ if let Some(bot) = bots.iter_mut().find(|b| b.id == bot_id) {
+ *bot = updated_bot;
+ self.save_bots(&bots)?;
+ log::info!("更新机器人: {} (ID: {})", bot_name, bot_id);
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+
+ /// 根据 ID 获取机器人
+ #[allow(dead_code)]
+ pub fn get_bot(&self, bot_id: &str) -> Result