mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-16 19:59:56 +02:00
Lot 11 : fichiers de la discussion tenus par la façade (LOKI_FILES=rust)
La façade sert le dépôt des pièces jointes (morceaux base64, 1 Gio au plus, espace disque vérifié), le panneau Fichiers, la suppression, le téléchargement (fiche, tranches base64 pour le tunnel E2E, brut avec plage d'octets), l'affichage des images (type reniflé, CSP fermée, vignette redressée et réduite par moyennage) et l'archive .zip, quand LOKI_FILES=rust (expérimental ; le Go reste le défaut). Côté Go : la nouvelle route /internal/v1/conv/workspace donne le dossier de la discussion active (le Go la connaît toujours), facade_files.go remet ces routes à la façade quand elles arrivent par le tunnel, et les .part d'envois ne sont plus vidés par le Go au démarrage. Parité : parity_files_test.go tire du Go les noms, slugs, en-têtes EXIF, orientations et réductions (pixel pour pixel), reniflages, puis 42 appels de route sur une arborescence fixe, rejoués par tests/convfiles.rs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
20 files changed
+3751
-8
No files matched your search
Generated
+175
-1
@@ -17,12 +17,27 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.23.1"
|
||||
@@ -50,6 +65,18 @@ version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.12.1"
|
||||
@@ -92,6 +119,12 @@ dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.8"
|
||||
@@ -142,6 +175,12 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.2"
|
||||
@@ -157,6 +196,17 @@ version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b034bd7d5f032402a2479444dcc6f74e36a03f31854d41680fb240ef682a1ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.3"
|
||||
@@ -201,6 +251,15 @@ version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.29"
|
||||
@@ -224,7 +283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.9.1",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
@@ -326,6 +385,16 @@ dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"
|
||||
dependencies = [
|
||||
"color_quant",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.20"
|
||||
@@ -576,6 +645,23 @@ dependencies = [
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"color_quant",
|
||||
"gif",
|
||||
"moxcms",
|
||||
"num-traits",
|
||||
"png",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.2"
|
||||
@@ -692,6 +778,7 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
name = "loki-rs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"bzip2",
|
||||
"flate2",
|
||||
@@ -701,6 +788,7 @@ dependencies = [
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"image",
|
||||
"libc",
|
||||
"regex",
|
||||
"reqwest",
|
||||
@@ -714,6 +802,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"url",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -722,6 +811,16 @@ version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[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 = "miniz_oxide"
|
||||
version = "0.9.1"
|
||||
@@ -743,6 +842,25 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"pxfm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
@@ -767,6 +885,19 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.6"
|
||||
@@ -785,6 +916,12 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
@@ -1576,6 +1713,12 @@ dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
@@ -1762,6 +1905,22 @@ dependencies = [
|
||||
"syn 3.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"displaydoc",
|
||||
"flate2",
|
||||
"indexmap",
|
||||
"memchr",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.8"
|
||||
@@ -1773,3 +1932,18 @@ name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b"
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
@@ -4,6 +4,7 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.23.1"
|
||||
bytes = "1.12.1"
|
||||
bzip2 = "0.6.1"
|
||||
flate2 = "1.1.10"
|
||||
@@ -11,10 +12,12 @@ futures-util = "0.3.34"
|
||||
getrandom = "0.4.3"
|
||||
hex = "0.4.3"
|
||||
http-body-util = "0.1.5"
|
||||
image = { version = "0.25.10", default-features = false, features = ["jpeg", "png", "gif"] }
|
||||
hyper = { version = "1.12.0", features = ["http1", "server", "client"] }
|
||||
hyper-util = { version = "0.1.21", features = ["tokio", "server", "client-legacy", "http1"] }
|
||||
regex = "1.13.1"
|
||||
url = "2.5.8"
|
||||
zip = { version = "2.4.2", default-features = false, features = ["deflate-flate2", "flate2"] }
|
||||
reqwest = { version = "0.13.5", default-features = false, features = ["rustls-no-provider", "stream", "http2"] }
|
||||
rustls = { version = "0.23.46", default-features = false, features = ["ring", "std", "tls12"], optional = true }
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
|
||||
@@ -34,6 +34,8 @@ pub struct Ctx {
|
||||
pub dictate: Option<std::sync::Arc<crate::dictate::Dictation>>,
|
||||
/// Réglages partagés tenus par la façade (`LOKI_SETTINGS=rust`).
|
||||
pub settings: bool,
|
||||
/// Fichiers de la discussion tenus par la façade (`LOKI_FILES=rust`).
|
||||
pub files: Option<std::sync::Arc<crate::convfiles::ConvFiles>>,
|
||||
/// Jeton de l'API interne : une requête qui le porte vient du Go.
|
||||
pub internal_token: String,
|
||||
}
|
||||
@@ -42,7 +44,11 @@ impl Ctx {
|
||||
/// La façade tient-elle au moins un lot (le Go lui remet alors le trafic
|
||||
/// de ce lot qui lui arrive par le tunnel) ?
|
||||
pub fn holds_any(&self) -> bool {
|
||||
self.chat.is_some() || self.models.is_some() || self.dictate.is_some() || self.settings
|
||||
self.chat.is_some()
|
||||
|| self.models.is_some()
|
||||
|| self.dictate.is_some()
|
||||
|| self.settings
|
||||
|| self.files.is_some()
|
||||
}
|
||||
|
||||
/// La requête vient du Go (jeton interne valide) : déjà authentifiée par
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -15,6 +15,7 @@ pub mod auth;
|
||||
pub mod chat;
|
||||
pub mod chatapi;
|
||||
pub mod child;
|
||||
pub mod convfiles;
|
||||
pub mod dictate;
|
||||
pub mod downloads;
|
||||
pub mod engine;
|
||||
|
||||
@@ -127,6 +127,9 @@ fn serve() -> ExitCode {
|
||||
// LOKI_SETTINGS=rust : les réglages partagés (préférences, prompt
|
||||
// système, intensité du raisonnement, adresse publique).
|
||||
let settings_rust = held_by_facade("LOKI_SETTINGS");
|
||||
// LOKI_FILES=rust : les fichiers de la discussion (dépôt, panneau
|
||||
// Fichiers, archive, téléchargement, images).
|
||||
let files_rust = held_by_facade("LOKI_FILES");
|
||||
let mut env = vec![(auth::INTERNAL_ENV.to_string(), token.clone())];
|
||||
if chat_rust {
|
||||
env.push(("LOKI_CHAT".into(), "rust".into()));
|
||||
@@ -140,7 +143,10 @@ fn serve() -> ExitCode {
|
||||
if settings_rust {
|
||||
env.push(("LOKI_SETTINGS".into(), "rust".into()));
|
||||
}
|
||||
if chat_rust || models_rust || dictate_rust || settings_rust {
|
||||
if files_rust {
|
||||
env.push(("LOKI_FILES".into(), "rust".into()));
|
||||
}
|
||||
if chat_rust || models_rust || dictate_rust || settings_rust || files_rust {
|
||||
env.push(("LOKI_FACADE_ADDR".into(), format!("127.0.0.1:{web_port}")));
|
||||
}
|
||||
let sup = child::Supervisor::spawn(go_bin, go_port, env);
|
||||
@@ -184,6 +190,12 @@ fn serve() -> ExitCode {
|
||||
std::sync::Arc::new(loki_rs::dictate::Dictation::new(&engine_paths.home))
|
||||
});
|
||||
|
||||
let files = if files_rust {
|
||||
Some(start_files(backend, &token).await)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], web_port));
|
||||
let listener = match tokio::net::TcpListener::bind(addr).await {
|
||||
Ok(l) => l,
|
||||
@@ -203,6 +215,7 @@ fn serve() -> ExitCode {
|
||||
models,
|
||||
dictate,
|
||||
settings: settings_rust,
|
||||
files,
|
||||
};
|
||||
let res = proxy::serve(listener, cfg, shutdown_signal()).await;
|
||||
eprintln!("[loki-rs] arrêt demandé — arrêt du serveur Go");
|
||||
@@ -258,6 +271,33 @@ async fn start_models(
|
||||
std::sync::Arc::new(loki_rs::models::Models::new())
|
||||
}
|
||||
|
||||
/// Les envois interrompus (process arrêté en plein dépôt) ne reprendront
|
||||
/// jamais : leurs `.part` sont vidés au démarrage (le Go ne le fait plus quand
|
||||
/// la façade tient les fichiers).
|
||||
async fn start_files(
|
||||
backend: SocketAddr,
|
||||
token: &str,
|
||||
) -> std::sync::Arc<loki_rs::convfiles::ConvFiles> {
|
||||
let st = loki_rs::store::Store::new(backend, token.to_string());
|
||||
let root = st
|
||||
.internal_json(
|
||||
hyper::Method::POST,
|
||||
"/internal/v1/conv/workspace",
|
||||
Some(&serde_json::json!({})),
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|v| v.get("root").and_then(|r| r.as_str()).map(str::to_string));
|
||||
if let Some(root) = root {
|
||||
let _ = tokio::task::spawn_blocking(move || {
|
||||
loki_rs::convfiles::clean_stale_upload_parts(&root)
|
||||
})
|
||||
.await;
|
||||
}
|
||||
eprintln!("[loki-rs] fichiers de la discussion tenus par la façade (LOKI_FILES=rust)");
|
||||
std::sync::Arc::new(loki_rs::convfiles::ConvFiles::default())
|
||||
}
|
||||
|
||||
/// Le Go garde-t-il la conversation ? (quelques essais : il vient de démarrer)
|
||||
async fn go_keeps_chat(backend: SocketAddr, token: &str) -> Result<Option<String>, String> {
|
||||
let store = loki_rs::store::Store::new(backend, token.to_string());
|
||||
|
||||
@@ -71,6 +71,8 @@ pub struct Config {
|
||||
pub dictate: Option<Arc<crate::dictate::Dictation>>,
|
||||
/// Réglages partagés tenus par la façade (`LOKI_SETTINGS=rust`).
|
||||
pub settings: bool,
|
||||
/// Fichiers de la discussion tenus par la façade (`LOKI_FILES=rust`).
|
||||
pub files: Option<Arc<crate::convfiles::ConvFiles>>,
|
||||
}
|
||||
|
||||
struct Proxy {
|
||||
@@ -101,6 +103,7 @@ pub async fn serve(
|
||||
models: cfg.models,
|
||||
dictate: cfg.dictate,
|
||||
settings: cfg.settings,
|
||||
files: cfg.files,
|
||||
internal_token: cfg.internal_token.clone(),
|
||||
},
|
||||
});
|
||||
@@ -182,6 +185,11 @@ impl Proxy {
|
||||
{
|
||||
return chatapi::serve(&self.ctx, chat, req, from_go).await;
|
||||
}
|
||||
if let Some(f) = &self.ctx.files
|
||||
&& crate::convfiles::handles(path)
|
||||
{
|
||||
return crate::convfiles::serve(&self.ctx, f, req, from_go).await;
|
||||
}
|
||||
if self.ctx.settings && crate::settings::handles(path) {
|
||||
return crate::settings::serve(&self.ctx, req, from_go).await;
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ pub struct FakeGo {
|
||||
pub turn_view: Mutex<Option<serde_json::Value>>,
|
||||
pub turn_verifier: Mutex<Option<serde_json::Value>>,
|
||||
pub tool_results: Mutex<HashMap<String, String>>,
|
||||
/// Réponse de /internal/v1/conv/workspace (None : 404).
|
||||
pub conv_workspace: Mutex<Option<serde_json::Value>>,
|
||||
}
|
||||
|
||||
impl FakeGo {
|
||||
@@ -291,6 +293,10 @@ async fn handle(st: Arc<FakeGo>, req: Request<Incoming>) -> Response<Full<Bytes>
|
||||
*st.go_busy.lock().unwrap() = None;
|
||||
resp(204, "")
|
||||
}
|
||||
"/internal/v1/conv/workspace" => match st.conv_workspace.lock().unwrap().clone() {
|
||||
Some(v) => resp(200, v.to_string()),
|
||||
None => resp(404, "404 page not found"),
|
||||
},
|
||||
"/internal/v1/tool/context" => match st.tool_ctx.lock().unwrap().clone() {
|
||||
Some(v) => resp(200, v.to_string()),
|
||||
None => resp(404, "404 page not found"),
|
||||
@@ -466,6 +472,7 @@ pub struct Opts {
|
||||
pub models_hf: Option<String>,
|
||||
pub dictate: Option<Arc<loki_rs::dictate::Dictation>>,
|
||||
pub settings: bool,
|
||||
pub files: Option<Arc<loki_rs::convfiles::ConvFiles>>,
|
||||
}
|
||||
|
||||
/// Comme [`start`], avec la conversation tenue par la façade
|
||||
@@ -558,6 +565,7 @@ pub async fn start_with(
|
||||
}),
|
||||
dictate: opts.dictate,
|
||||
settings: opts.settings,
|
||||
files: opts.files,
|
||||
};
|
||||
tokio::spawn(loki_rs::proxy::serve(fl, cfg, async move {
|
||||
let _ = rx.await;
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
//! Lot 11 : fichiers de la discussion tenus par la façade (`LOKI_FILES=rust`).
|
||||
//!
|
||||
//! Le journal tiré du Go (`internal/loki/parity_files_test.go` →
|
||||
//! `tests/parity_files/files.json`) est rejoué : noms de dépôt, slug,
|
||||
//! orientation EXIF, redressement et réduction des pixels, reniflage, puis
|
||||
//! 42 appels de route par la façade sur la même arborescence — panneau
|
||||
//! Fichiers, téléchargement, image et vignette, dépôt en morceaux,
|
||||
//! suppression, archive .zip — avec les fichiers restants après chaque POST.
|
||||
|
||||
mod common;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
|
||||
use bytes::Bytes;
|
||||
use common::fakego;
|
||||
use http_body_util::{BodyExt, Full};
|
||||
use hyper::{Request, StatusCode};
|
||||
use loki_rs::convfiles::{self, Rgba};
|
||||
use serde_json::Value;
|
||||
|
||||
fn fixture() -> Value {
|
||||
serde_json::from_str(include_str!("parity_files/files.json")).expect("journal")
|
||||
}
|
||||
|
||||
fn b64(s: &str) -> Vec<u8> {
|
||||
use base64::Engine;
|
||||
base64::engine::general_purpose::STANDARD.decode(s).unwrap()
|
||||
}
|
||||
|
||||
fn pix(v: &Value) -> Rgba {
|
||||
Rgba {
|
||||
w: v["w"].as_u64().unwrap() as usize,
|
||||
h: v["h"].as_u64().unwrap() as usize,
|
||||
pix: b64(v["pix"].as_str().unwrap_or("")),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parite_des_fonctions_pures() {
|
||||
let fx = fixture();
|
||||
for (n, want) in fx["upload_name"].as_object().unwrap() {
|
||||
assert_eq!(
|
||||
convfiles::safe_upload_name(n),
|
||||
want.as_str().unwrap(),
|
||||
"{n:?}"
|
||||
);
|
||||
}
|
||||
for (s, want) in fx["slug"].as_object().unwrap() {
|
||||
assert_eq!(convfiles::slugify(s), want.as_str().unwrap(), "{s:?}");
|
||||
}
|
||||
for c in fx["exif"].as_array().unwrap() {
|
||||
assert_eq!(
|
||||
convfiles::exif_orientation(&b64(c["in"].as_str().unwrap())),
|
||||
c["out"].as_i64().unwrap() as i32
|
||||
);
|
||||
}
|
||||
let src = pix(&fx["orient_in"]);
|
||||
for c in fx["orient"].as_array().unwrap() {
|
||||
let o = c["orient"].as_i64().unwrap() as i32;
|
||||
assert_eq!(
|
||||
convfiles::apply_orientation(&src, o),
|
||||
pix(&c["out"]),
|
||||
"orientation {o}"
|
||||
);
|
||||
}
|
||||
for c in fx["scale"].as_array().unwrap() {
|
||||
let max = c["max"].as_u64().unwrap() as usize;
|
||||
assert_eq!(
|
||||
convfiles::scale_down_to_max(&pix(&c["in"]), max),
|
||||
pix(&c["out"]),
|
||||
"{max}"
|
||||
);
|
||||
}
|
||||
for c in fx["sniff"].as_array().unwrap() {
|
||||
let want = c["out"].as_str().unwrap();
|
||||
let got = convfiles::sniff_image(&b64(c["in"].as_str().unwrap_or("")));
|
||||
if want.starts_with("image/") {
|
||||
assert_eq!(got, want);
|
||||
} else {
|
||||
assert_eq!(got, "", "{want}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_mtime(p: &Path, secs: i64) {
|
||||
let f = std::fs::File::open(p).unwrap();
|
||||
f.set_modified(UNIX_EPOCH + Duration::from_secs(secs as u64))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn tree(dir: &Path) -> BTreeMap<String, u64> {
|
||||
let mut out = BTreeMap::new();
|
||||
let mut stack = vec![dir.to_path_buf()];
|
||||
while let Some(d) = stack.pop() {
|
||||
let Ok(rd) = std::fs::read_dir(&d) else {
|
||||
continue;
|
||||
};
|
||||
for e in rd.filter_map(Result::ok) {
|
||||
let p = e.path();
|
||||
if p.is_dir() {
|
||||
stack.push(p);
|
||||
} else {
|
||||
let rel = p.strip_prefix(dir).unwrap().to_string_lossy().into_owned();
|
||||
let rel = mask_part(&rel);
|
||||
out.insert(rel, e.metadata().unwrap().len());
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn mask_part(s: &str) -> String {
|
||||
let re = regex::Regex::new(r"\.upload-[0-9]+\.part").unwrap();
|
||||
re.replace_all(s, ".upload-{{N}}.part").into_owned()
|
||||
}
|
||||
|
||||
async fn call(
|
||||
facade: SocketAddr,
|
||||
method: &str,
|
||||
url: &str,
|
||||
body: &str,
|
||||
) -> (StatusCode, hyper::HeaderMap, Bytes) {
|
||||
use hyper_util::client::legacy::Client;
|
||||
use hyper_util::rt::TokioExecutor;
|
||||
let client: Client<_, Full<Bytes>> = Client::builder(TokioExecutor::new()).build_http();
|
||||
let r = client
|
||||
.request(
|
||||
Request::builder()
|
||||
.method(method)
|
||||
.uri(format!("http://{facade}{url}"))
|
||||
.body(Full::new(Bytes::from(body.to_string())))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let (p, b) = r.into_parts();
|
||||
(p.status, p.headers, b.collect().await.unwrap().to_bytes())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn parite_des_routes() {
|
||||
let fx = fixture();
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let home = tmp.path().canonicalize().unwrap();
|
||||
let h = home.to_string_lossy().into_owned();
|
||||
// L'arborescence, fichiers profonds d'abord, puis les dates des dossiers.
|
||||
let mut nodes: Vec<(&String, &Value)> = fx["tree"].as_object().unwrap().iter().collect();
|
||||
nodes.sort_by_key(|(k, _)| std::cmp::Reverse(k.len()));
|
||||
for (k, n) in &nodes {
|
||||
let p = home.join(k);
|
||||
if n.get("dir").and_then(Value::as_bool).unwrap_or(false) {
|
||||
std::fs::create_dir_all(&p).unwrap();
|
||||
} else {
|
||||
std::fs::create_dir_all(p.parent().unwrap()).unwrap();
|
||||
std::fs::write(&p, b64(n.get("data").and_then(Value::as_str).unwrap_or(""))).unwrap();
|
||||
}
|
||||
}
|
||||
for (k, n) in &nodes {
|
||||
set_mtime(&home.join(k), n["mtime"].as_i64().unwrap());
|
||||
}
|
||||
let zips = home.join("tmp");
|
||||
std::fs::create_dir_all(&zips).unwrap();
|
||||
let opts = fakego::Opts {
|
||||
files: Some(Arc::new(convfiles::ConvFiles::new(zips))),
|
||||
..Default::default()
|
||||
};
|
||||
let (facade, go, _stop, _) = fakego::start_with(home.clone(), opts).await;
|
||||
let ws: Value =
|
||||
serde_json::from_str(&fx["workspace"].to_string().replace("{{HOME}}", &h)).unwrap();
|
||||
*go.conv_workspace.lock().unwrap() = Some(ws.clone());
|
||||
let conv_dir = Path::new(ws["dir"].as_str().unwrap()).to_path_buf();
|
||||
|
||||
let date_re = regex::Regex::new(r"[0-9]{4}-[0-9]{2}-[0-9]{2}\.zip").unwrap();
|
||||
let mut last_id = String::new();
|
||||
for (i, c) in fx["calls"].as_array().unwrap().iter().enumerate() {
|
||||
let (m, url) = (c["method"].as_str().unwrap(), c["url"].as_str().unwrap());
|
||||
let body = c
|
||||
.get("body")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.replace("{{ID}}", &last_id);
|
||||
let (code, headers, b) = call(facade, m, url, &body).await;
|
||||
let ctx = format!("appel {i} : {m} {url} {body}");
|
||||
assert_eq!(
|
||||
code.as_u16() as u64,
|
||||
c["status"].as_u64().unwrap(),
|
||||
"{ctx} : {b:?}"
|
||||
);
|
||||
if let Some(want) = c.get("out") {
|
||||
let txt = String::from_utf8_lossy(&b).replace(&h, "{{HOME}}");
|
||||
let txt = date_re.replace_all(&txt, "{{DATE}}.zip").into_owned();
|
||||
let mut got: Value = serde_json::from_str(&txt).unwrap();
|
||||
if let Some(id) = got.get("id").and_then(Value::as_str) {
|
||||
last_id = id.to_string();
|
||||
got["id"] = Value::String("{{ID}}".into());
|
||||
}
|
||||
if url.starts_with("/api/chat/files/zip") {
|
||||
if let Some(d) = got.get("data").and_then(Value::as_str) {
|
||||
let raw = b64(d);
|
||||
let mut zr = zip::ZipArchive::new(std::io::Cursor::new(raw)).unwrap();
|
||||
let mut entries = BTreeMap::new();
|
||||
for k in 0..zr.len() {
|
||||
let mut f = zr.by_index(k).unwrap();
|
||||
let mut v = Vec::new();
|
||||
std::io::Read::read_to_end(&mut f, &mut v).unwrap();
|
||||
entries.insert(f.name().to_string(), v);
|
||||
}
|
||||
let want_zip: BTreeMap<String, Vec<u8>> = c["zip"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), b64(v.as_str().unwrap_or(""))))
|
||||
.collect();
|
||||
assert_eq!(entries, want_zip, "{ctx} (contenu de l'archive)");
|
||||
got["data"] = Value::String("…".into());
|
||||
}
|
||||
if got.get("size").is_some() {
|
||||
got["size"] = Value::String("…".into());
|
||||
}
|
||||
}
|
||||
if let Some(p) = got.get("path").and_then(Value::as_str) {
|
||||
got["path"] = Value::String(mask_part(p));
|
||||
}
|
||||
assert_eq!(&got, want, "{ctx}");
|
||||
}
|
||||
if let Some(raw) = c.get("raw").and_then(Value::as_str) {
|
||||
assert_eq!(b.to_vec(), b64(raw), "{ctx} (corps)");
|
||||
}
|
||||
if let Some(dims) = c.get("thumb").and_then(Value::as_array) {
|
||||
let img = image::load_from_memory(&b).unwrap();
|
||||
assert_eq!(
|
||||
[img.width() as u64, img.height() as u64],
|
||||
[dims[0].as_u64().unwrap(), dims[1].as_u64().unwrap()],
|
||||
"{ctx} (vignette)"
|
||||
);
|
||||
}
|
||||
if let Some(hs) = c.get("headers").and_then(Value::as_object) {
|
||||
for (k, v) in hs {
|
||||
assert_eq!(
|
||||
headers.get(k.as_str()).and_then(|x| x.to_str().ok()),
|
||||
v.as_str(),
|
||||
"{ctx} (en-tête {k})"
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(want) = c.get("tree") {
|
||||
let want: BTreeMap<String, u64> = serde_json::from_value(want.clone()).unwrap();
|
||||
assert_eq!(tree(&conv_dir), want, "{ctx} (fichiers)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plages_et_liens() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let home = tmp.path().canonicalize().unwrap();
|
||||
let dir = home.join("ws/discussions/c1");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
std::fs::write(dir.join("a.txt"), b"0123456789").unwrap();
|
||||
// Un lien vers l'extérieur ne sert pas d'échappatoire.
|
||||
std::os::unix::fs::symlink("/etc", dir.join("dehors")).unwrap();
|
||||
let opts = fakego::Opts {
|
||||
files: Some(Arc::new(convfiles::ConvFiles::new(home.clone()))),
|
||||
..Default::default()
|
||||
};
|
||||
let (facade, go, _stop, _) = fakego::start_with(home.clone(), opts).await;
|
||||
*go.conv_workspace.lock().unwrap() = Some(serde_json::json!({
|
||||
"dir": dir, "root": home.join("ws"), "id": "c1", "title": "Ma discussion"
|
||||
}));
|
||||
use hyper_util::client::legacy::Client;
|
||||
use hyper_util::rt::TokioExecutor;
|
||||
let client: Client<_, Full<Bytes>> = Client::builder(TokioExecutor::new()).build_http();
|
||||
let r = client
|
||||
.request(
|
||||
Request::get(format!("http://{facade}/api/chat/file?path=a.txt"))
|
||||
.header("range", "bytes=2-5")
|
||||
.body(Full::new(Bytes::new()))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(r.status(), StatusCode::PARTIAL_CONTENT);
|
||||
assert_eq!(r.headers()["content-range"], "bytes 2-5/10");
|
||||
assert_eq!(
|
||||
&r.into_body().collect().await.unwrap().to_bytes()[..],
|
||||
b"2345"
|
||||
);
|
||||
let (code, _, _) = call(
|
||||
facade,
|
||||
"GET",
|
||||
"/api/chat/file?path=dehors/passwd&meta=1",
|
||||
"",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(code, StatusCode::FORBIDDEN);
|
||||
let (code, _, b) = call(facade, "GET", "/api/chat/files", "").await;
|
||||
assert_eq!(code, StatusCode::OK);
|
||||
let v: Value = serde_json::from_slice(&b).unwrap();
|
||||
// Le lien est listé (comme le Go), mais son contenu ne compte pas.
|
||||
assert_eq!(
|
||||
(v["total"].as_i64(), v["count"].as_i64()),
|
||||
(Some(10), Some(1))
|
||||
);
|
||||
// Nom d'archive tiré du titre.
|
||||
let (_, _, b) = call(facade, "GET", "/api/chat/files/zip?meta=1", "").await;
|
||||
let v: Value = serde_json::from_slice(&b).unwrap();
|
||||
assert!(
|
||||
v["name"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.starts_with("loki-ma-discussion-"),
|
||||
"{v}"
|
||||
);
|
||||
assert_eq!(v["files"], 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sans_la_variable_les_fichiers_restent_au_go() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let (facade, _go, _stop) = fakego::start(tmp.path().to_path_buf()).await;
|
||||
for p in [
|
||||
"/api/chat/files",
|
||||
"/api/chat/file",
|
||||
"/api/chat/image",
|
||||
"/api/chat/files/zip",
|
||||
] {
|
||||
let (_, _, b) = fakego::get(facade, p, &[]).await;
|
||||
assert_eq!(&b[..], format!("go:{p}").as_bytes());
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,7 @@ async fn banc(cle: Option<&str>) -> Banc {
|
||||
models: None,
|
||||
dictate: None,
|
||||
settings: false,
|
||||
files: None,
|
||||
};
|
||||
tokio::spawn(loki_rs::proxy::serve(l, cfg, async move {
|
||||
let _ = rx.await;
|
||||
|
||||
@@ -0,0 +1,975 @@
|
||||
{
|
||||
"upload_name": {
|
||||
"": "fichier",
|
||||
" .cache ": "cache",
|
||||
"..": "fichier",
|
||||
"../../etc/passwd": "passwd",
|
||||
"C:\\Users\\moi\\photo.jpg": "photo.jpg",
|
||||
"a:b*c?d\"e<f>g|h.txt": "a_b_c_d_e_f_g_h.txt",
|
||||
"nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn.eeeeeeeeeeeeeeeeeeee": "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn",
|
||||
"nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn.pdf": "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn.pdf",
|
||||
"rapport.pdf": "rapport.pdf",
|
||||
"x\u0001y.txt": "x_y.txt"
|
||||
},
|
||||
"slug": {
|
||||
"": "",
|
||||
" Rapport_Final.v2 ": "rapport-final-v2",
|
||||
"!!!": "",
|
||||
"--a--": "a",
|
||||
"Déjà vu": "dj-vu",
|
||||
"abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc ": "abc-abc-abc-abc-abc-abc-abc-abc-abc-abc"
|
||||
},
|
||||
"exif": [
|
||||
{
|
||||
"in": "/9j/4AAGSkZJRv/hACpFeGlmAABJSSoACAAAAAIAEgEDAAEAAAAGAAAADwECAAEAAAAAAAAA/9oAAg==",
|
||||
"out": 6
|
||||
},
|
||||
{
|
||||
"in": "/9j/4AAGSkZJRv/hACpFeGlmAABNTQAqAAAACAACAQ8AAgAAAAEAAAAAARIAAwAAAAEACAAA/9oAAg==",
|
||||
"out": 8
|
||||
},
|
||||
{
|
||||
"in": "/9j/4AAGSkZJRv/hACpFeGlmAABJSSoACAAAAAIADwECAAEAAAAAAAAAEgEDAAEAAAABAAAA/9oAAg==",
|
||||
"out": 1
|
||||
},
|
||||
{
|
||||
"in": "/9j/4AAGSkZJRv/hACpFeGlmAABJSSoACAAAAAIA",
|
||||
"out": 0
|
||||
},
|
||||
{
|
||||
"in": "/9j/2g==",
|
||||
"out": 0
|
||||
},
|
||||
{
|
||||
"in": "cGFzIHVuIGpwZWc=",
|
||||
"out": 0
|
||||
}
|
||||
],
|
||||
"orient": [
|
||||
{
|
||||
"orient": 1,
|
||||
"out": {
|
||||
"w": 5,
|
||||
"h": 3,
|
||||
"pix": "CzBVep/E6Q4zWH2ix+wRNluApcrvFDleg6jN8hc8YYar0PUaP2SJrtP4HUJnjLHW+yBFao+02f4jSG2S"
|
||||
}
|
||||
},
|
||||
{
|
||||
"orient": 2,
|
||||
"out": {
|
||||
"w": 5,
|
||||
"h": 3,
|
||||
"pix": "W4ClysfsETYzWH2in8TpDgswVXo/ZImuq9D1Ghc8YYaDqM3y7xQ5XiNIbZKPtNn++yBFameMsdbT+B1C"
|
||||
}
|
||||
},
|
||||
{
|
||||
"orient": 3,
|
||||
"out": {
|
||||
"w": 5,
|
||||
"h": 3,
|
||||
"pix": "I0htko+02f77IEVqZ4yx1tP4HUI/ZImuq9D1Ghc8YYaDqM3y7xQ5XluApcrH7BE2M1h9op/E6Q4LMFV6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"orient": 4,
|
||||
"out": {
|
||||
"w": 5,
|
||||
"h": 3,
|
||||
"pix": "0/gdQmeMsdb7IEVqj7TZ/iNIbZLvFDleg6jN8hc8YYar0PUaP2SJrgswVXqfxOkOM1h9osfsETZbgKXK"
|
||||
}
|
||||
},
|
||||
{
|
||||
"orient": 5,
|
||||
"out": {
|
||||
"w": 3,
|
||||
"h": 5,
|
||||
"pix": "CzBVeu8UOV7T+B1Cn8TpDoOozfJnjLHWM1h9ohc8YYb7IEVqx+wRNqvQ9RqPtNn+W4Clyj9kia4jSG2S"
|
||||
}
|
||||
},
|
||||
{
|
||||
"orient": 6,
|
||||
"out": {
|
||||
"w": 3,
|
||||
"h": 5,
|
||||
"pix": "0/gdQu8UOV4LMFV6Z4yx1oOozfKfxOkO+yBFahc8YYYzWH2ij7TZ/qvQ9RrH7BE2I0htkj9kia5bgKXK"
|
||||
}
|
||||
},
|
||||
{
|
||||
"orient": 7,
|
||||
"out": {
|
||||
"w": 3,
|
||||
"h": 5,
|
||||
"pix": "I0htkj9kia5bgKXKj7TZ/qvQ9RrH7BE2+yBFahc8YYYzWH2iZ4yx1oOozfKfxOkO0/gdQu8UOV4LMFV6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"orient": 8,
|
||||
"out": {
|
||||
"w": 3,
|
||||
"h": 5,
|
||||
"pix": "W4Clyj9kia4jSG2Sx+wRNqvQ9RqPtNn+M1h9ohc8YYb7IEVqn8TpDoOozfJnjLHWCzBVeu8UOV7T+B1C"
|
||||
}
|
||||
}
|
||||
],
|
||||
"orient_in": {
|
||||
"w": 5,
|
||||
"h": 3,
|
||||
"pix": "CzBVep/E6Q4zWH2ix+wRNluApcrvFDleg6jN8hc8YYar0PUaP2SJrtP4HUJnjLHW+yBFao+02f4jSG2S"
|
||||
},
|
||||
"scale": [
|
||||
{
|
||||
"in": {
|
||||
"w": 7,
|
||||
"h": 5,
|
||||
"pix": "gabL8BU6X4SpzvMYPWKHrNH2G0Bliq/U+R5DaI2y1/whRmuQtdr/JEluk7jdAidMcZa74AUqT3SZvuMILVJ3nMHmCzBVep/E6Q4zWH2ix+wRNluApcrvFDleg6jN8hc8YYar0PUaP2SJrtP4HUJnjLHW+yBFao+02f4jSG2St9wBJktwlbrfBClOc5g="
|
||||
},
|
||||
"max": 3,
|
||||
"out": {
|
||||
"w": 3,
|
||||
"h": 2,
|
||||
"pix": "UXabwHmew2iVZV+Eb5S5Xpe8YYZsWH2G"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": {
|
||||
"w": 5,
|
||||
"h": 9,
|
||||
"pix": "7xQ5XoOozfIXPGGGq9D1Gj9kia7T+B1CZ4yx1vsgRWqPtNn+I0htkrfcASZLcJW63wQpTnOYveIHLFF2m8DlCi9UeZ7D6A0yV3yhxusQNVp/pMnuEzhdgqfM8RY7YIWqz/QZPmOIrdL3HEFmi7DV+h9EaY6z2P0iR2yRttsAJUpvlLneAyhNcpe84QYrUHWav+QJLlN4ncLnDDFWe6DF6g80WX6jyO0SN1yBpsvwFTpfhKnO"
|
||||
},
|
||||
"max": 4,
|
||||
"out": {
|
||||
"w": 2,
|
||||
"h": 4,
|
||||
"pix": "q5B1mnJskYxzmH1ij19ZfntghaqCp6FxdW9pZHV9ho4="
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": {
|
||||
"w": 10,
|
||||
"h": 10,
|
||||
"pix": "THGWu+AFKk90mb7jCC1Sd5zB5gswVXqfxOkOM1h9osfsETZbgKXK7xQ5XoOozfIXPGGGq9D1Gj9kia7T+B1CZ4yx1vsgRWqPtNn+I0htkrfcASZLcJW63wQpTnOYveIHLFF2m8DlCi9UeZ7D6A0yV3yhxusQNVp/pMnuEzhdgqfM8RY7YIWqz/QZPmOIrdL3HEFmi7DV+h9EaY6z2P0iR2yRttsAJUpvlLneAyhNcpe84QYrUHWav+QJLlN4ncLnDDFWe6DF6g80WX6jyO0SN1yBpsvwFTpfhKnO8xg9Yoes0fYbQGWKr9T5HkNojbLX/CFGa5C12v8kSW6TuN0CJ0xxlrvgBSpPdJm+4wgtUnecweYLMFV6n8TpDjNYfaLH7BE2W4Clyu8UOV6DqM3yFzxhhqvQ9Ro/ZImu0/gdQmeMsdb7IEVqj7TZ/iNIbZK33AEmS3CVut8EKU5zmL3iByxRdpvA5QovVHmew+gNMld8ocbrEDVaf6TJ7hM4XYKnzPEWO2CFqs/0GT5jiK3S9w=="
|
||||
},
|
||||
"max": 3,
|
||||
"out": {
|
||||
"w": 3,
|
||||
"h": 3,
|
||||
"pix": "b1uAiICJdWF/eYmZcZaCb4Nvd5yCkYxxkXaGlXhygnx+g3h9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": {
|
||||
"w": 3,
|
||||
"h": 2,
|
||||
"pix": "QmeMsdb7IEVqj7TZ/iNIbZK33AEmS3CV"
|
||||
},
|
||||
"max": 5,
|
||||
"out": {
|
||||
"w": 3,
|
||||
"h": 2,
|
||||
"pix": "QmeMsdb7IEVqj7TZ/iNIbZK33AEmS3CV"
|
||||
}
|
||||
}
|
||||
],
|
||||
"sniff": [
|
||||
{
|
||||
"in": "iVBORw0KGgoAAAANSUhEUg==",
|
||||
"out": "image/png"
|
||||
},
|
||||
{
|
||||
"in": "/9j/4A==",
|
||||
"out": "image/jpeg"
|
||||
},
|
||||
{
|
||||
"in": "R0lGODlhAQA=",
|
||||
"out": "image/gif"
|
||||
},
|
||||
{
|
||||
"in": "UklGRhAAAABXRUJQVlA4IA==",
|
||||
"out": "image/webp"
|
||||
},
|
||||
{
|
||||
"in": "Qk0AAA==",
|
||||
"out": "image/bmp"
|
||||
},
|
||||
{
|
||||
"in": "AAABAAEA",
|
||||
"out": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"in": "PHN2ZyB4bWxucz0neCcvPg==",
|
||||
"out": "text/plain; charset=utf-8"
|
||||
},
|
||||
{
|
||||
"in": "PGh0bWw+PGJvZHk+eA==",
|
||||
"out": "text/html; charset=utf-8"
|
||||
},
|
||||
{
|
||||
"in": "Ym9uam91cg==",
|
||||
"out": "text/plain; charset=utf-8"
|
||||
},
|
||||
{
|
||||
"in": "",
|
||||
"out": "text/plain; charset=utf-8"
|
||||
}
|
||||
],
|
||||
"workspace": {
|
||||
"dir": "{{HOME}}/workspace/discussions/c-fichiers",
|
||||
"id": "c-fichiers",
|
||||
"root": "{{HOME}}/workspace",
|
||||
"title": ""
|
||||
},
|
||||
"tree": {
|
||||
"workspace/discussions/c-autre/captures/x.jpg": {
|
||||
"data": "anBlZz8=",
|
||||
"mtime": 1700000004
|
||||
},
|
||||
"workspace/discussions/c-fichiers/faux.png": {
|
||||
"data": "PGh0bWw+cGFzIHVuZSBpbWFnZTwvaHRtbD4=",
|
||||
"mtime": 1700000005
|
||||
},
|
||||
"workspace/discussions/c-fichiers/image.png": {
|
||||
"data": "iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAQ0lEQVR4nAAzAMz/AxY7YIWfssTX6fwOobPG2OsAZouw1fofRGmOs9j9IkdskQC22wAlSm+Uud4DKE1yl7zhAQAA//+6OxpmJmJbrAAAAABJRU5ErkJggg==",
|
||||
"mtime": 1700000040
|
||||
},
|
||||
"workspace/discussions/c-fichiers/notes": {
|
||||
"dir": true,
|
||||
"mtime": 1700000020
|
||||
},
|
||||
"workspace/discussions/c-fichiers/notes/a.txt": {
|
||||
"data": "YWxwaGE=",
|
||||
"mtime": 1700000010
|
||||
},
|
||||
"workspace/discussions/c-fichiers/notes/sous": {
|
||||
"dir": true,
|
||||
"mtime": 1700000012
|
||||
},
|
||||
"workspace/discussions/c-fichiers/notes/sous/b.txt": {
|
||||
"data": "YmV0YSEh",
|
||||
"mtime": 1700000011
|
||||
},
|
||||
"workspace/discussions/c-fichiers/rapport.md": {
|
||||
"data": "IyBSYXBwb3J0Cg==",
|
||||
"mtime": 1700000030
|
||||
},
|
||||
"workspace/uploads": {
|
||||
"dir": true,
|
||||
"mtime": 1700000003
|
||||
},
|
||||
"workspace/uploads/ancien.pdf": {
|
||||
"data": "JVBERg==",
|
||||
"mtime": 1700000002
|
||||
},
|
||||
"workspace/vieux.txt": {
|
||||
"data": "b2xk",
|
||||
"mtime": 1700000001
|
||||
}
|
||||
},
|
||||
"calls": [
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/files",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"at_root": true,
|
||||
"count": 5,
|
||||
"dir": "",
|
||||
"entries": [
|
||||
{
|
||||
"dir": true,
|
||||
"image": false,
|
||||
"items": 2,
|
||||
"mod": 1700000020,
|
||||
"name": "notes",
|
||||
"path": "notes",
|
||||
"size": 11
|
||||
},
|
||||
{
|
||||
"dir": false,
|
||||
"image": true,
|
||||
"mod": 1700000040,
|
||||
"name": "image.png",
|
||||
"path": "image.png",
|
||||
"size": 124
|
||||
},
|
||||
{
|
||||
"dir": false,
|
||||
"image": false,
|
||||
"mod": 1700000030,
|
||||
"name": "rapport.md",
|
||||
"path": "rapport.md",
|
||||
"size": 10
|
||||
},
|
||||
{
|
||||
"dir": false,
|
||||
"image": true,
|
||||
"mod": 1700000005,
|
||||
"name": "faux.png",
|
||||
"path": "faux.png",
|
||||
"size": 26
|
||||
}
|
||||
],
|
||||
"legacy": 2,
|
||||
"ok": true,
|
||||
"parent": "",
|
||||
"root": "{{HOME}}/workspace/discussions/c-fichiers",
|
||||
"scope": "",
|
||||
"total": 171
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/files?dir=notes",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"at_root": false,
|
||||
"count": 5,
|
||||
"dir": "notes",
|
||||
"entries": [
|
||||
{
|
||||
"dir": true,
|
||||
"image": false,
|
||||
"items": 1,
|
||||
"mod": 1700000012,
|
||||
"name": "sous",
|
||||
"path": "notes/sous",
|
||||
"size": 6
|
||||
},
|
||||
{
|
||||
"dir": false,
|
||||
"image": false,
|
||||
"mod": 1700000010,
|
||||
"name": "a.txt",
|
||||
"path": "notes/a.txt",
|
||||
"size": 5
|
||||
}
|
||||
],
|
||||
"legacy": 2,
|
||||
"ok": true,
|
||||
"parent": "",
|
||||
"root": "{{HOME}}/workspace/discussions/c-fichiers",
|
||||
"scope": "",
|
||||
"total": 171
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/files?dir=notes/sous/",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"at_root": false,
|
||||
"count": 5,
|
||||
"dir": "notes/sous",
|
||||
"entries": [
|
||||
{
|
||||
"dir": false,
|
||||
"image": false,
|
||||
"mod": 1700000011,
|
||||
"name": "b.txt",
|
||||
"path": "notes/sous/b.txt",
|
||||
"size": 6
|
||||
}
|
||||
],
|
||||
"legacy": 2,
|
||||
"ok": true,
|
||||
"parent": "notes",
|
||||
"root": "{{HOME}}/workspace/discussions/c-fichiers",
|
||||
"scope": "",
|
||||
"total": 171
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/files?dir=../../..",
|
||||
"status": 403,
|
||||
"out": {
|
||||
"error": "hors du dossier de travail",
|
||||
"ok": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/files?dir=rapport.md",
|
||||
"status": 404,
|
||||
"out": {
|
||||
"error": "dossier introuvable",
|
||||
"ok": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/files?scope=legacy",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"at_root": true,
|
||||
"count": 8,
|
||||
"dir": "",
|
||||
"entries": [
|
||||
{
|
||||
"dir": true,
|
||||
"image": false,
|
||||
"items": 1,
|
||||
"mod": 1700000003,
|
||||
"name": "uploads",
|
||||
"path": "uploads",
|
||||
"size": 4
|
||||
},
|
||||
{
|
||||
"dir": false,
|
||||
"image": false,
|
||||
"mod": 1700000001,
|
||||
"name": "vieux.txt",
|
||||
"path": "vieux.txt",
|
||||
"size": 3
|
||||
}
|
||||
],
|
||||
"legacy": 2,
|
||||
"ok": true,
|
||||
"parent": "",
|
||||
"root": "{{HOME}}/workspace",
|
||||
"scope": "legacy",
|
||||
"total": 183
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/file?path=rapport.md&meta=1",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"e2e": false,
|
||||
"name": "rapport.md",
|
||||
"ok": true,
|
||||
"size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/file?path=rapport.md&b64=1&offset=2&len=4",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"data": "UmFwcA==",
|
||||
"eof": false,
|
||||
"name": "rapport.md",
|
||||
"offset": 2,
|
||||
"ok": true,
|
||||
"size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/file?path=rapport.md&b64=1&offset=5",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"data": "cG9ydAo=",
|
||||
"eof": true,
|
||||
"name": "rapport.md",
|
||||
"offset": 5,
|
||||
"ok": true,
|
||||
"size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/file?path=rapport.md&b64=1&offset=99",
|
||||
"status": 400,
|
||||
"out": {
|
||||
"error": "position hors du fichier",
|
||||
"ok": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/file?path=../../vieux.txt",
|
||||
"status": 403,
|
||||
"out": {
|
||||
"error": "hors du dossier de travail",
|
||||
"ok": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/file?path=absent.txt",
|
||||
"status": 404,
|
||||
"out": {
|
||||
"error": "fichier introuvable",
|
||||
"ok": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/file?path=",
|
||||
"status": 400,
|
||||
"out": {
|
||||
"error": "chemin manquant",
|
||||
"ok": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/file?path=captures/c-autre/x.jpg&meta=1",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"e2e": false,
|
||||
"name": "x.jpg",
|
||||
"ok": true,
|
||||
"size": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/file?path=uploads/ancien.pdf&meta=1",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"e2e": false,
|
||||
"name": "ancien.pdf",
|
||||
"ok": true,
|
||||
"size": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/file?path=notes",
|
||||
"status": 404,
|
||||
"out": {
|
||||
"error": "fichier introuvable",
|
||||
"ok": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/file?path=/notes/a.txt",
|
||||
"status": 200,
|
||||
"raw": "YWxwaGE=",
|
||||
"headers": {
|
||||
"Content-Disposition": "attachment; filename*=UTF-8''a.txt",
|
||||
"Content-Type": "application/octet-stream",
|
||||
"X-Content-Type-Options": "nosniff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/image?path=faux.png",
|
||||
"status": 415,
|
||||
"out": {
|
||||
"error": "ce fichier n'est pas une image",
|
||||
"ok": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/image?path=image.png",
|
||||
"status": 200,
|
||||
"raw": "iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAQ0lEQVR4nAAzAMz/AxY7YIWfssTX6fwOobPG2OsAZouw1fofRGmOs9j9IkdskQC22wAlSm+Uud4DKE1yl7zhAQAA//+6OxpmJmJbrAAAAABJRU5ErkJggg==",
|
||||
"headers": {
|
||||
"Content-Disposition": "inline; filename*=UTF-8''image.png",
|
||||
"Content-Security-Policy": "default-src 'none'; sandbox",
|
||||
"Content-Type": "image/png",
|
||||
"X-Content-Type-Options": "nosniff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/image?path=image.png&thumb=2",
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Cache-Control": "private, max-age=3600",
|
||||
"Content-Security-Policy": "default-src 'none'; sandbox",
|
||||
"Content-Type": "image/jpeg",
|
||||
"X-Content-Type-Options": "nosniff"
|
||||
},
|
||||
"thumb": [
|
||||
4,
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/image?path=rapport.md&thumb=64",
|
||||
"status": 415,
|
||||
"out": {
|
||||
"error": "ce fichier n'est pas une image",
|
||||
"ok": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/upload",
|
||||
"body": "{\"name\":\"../Mon fichier?.txt\",\"data\":\"aGVsbG8=\"}",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"abs": "{{HOME}}/workspace/discussions/c-fichiers/uploads/Mon fichier_.txt",
|
||||
"ok": true,
|
||||
"path": "uploads/Mon fichier_.txt",
|
||||
"size": 5
|
||||
},
|
||||
"tree": {
|
||||
"faux.png": 26,
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"notes/sous/b.txt": 6,
|
||||
"rapport.md": 10,
|
||||
"uploads/Mon fichier_.txt": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/upload",
|
||||
"body": "{\"name\":\"Mon fichier?.txt\",\"data\":\"aGVsbG8gIQ==\"}",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"abs": "{{HOME}}/workspace/discussions/c-fichiers/uploads/Mon fichier_-2.txt",
|
||||
"ok": true,
|
||||
"path": "uploads/Mon fichier_-2.txt",
|
||||
"size": 7
|
||||
},
|
||||
"tree": {
|
||||
"faux.png": 26,
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"notes/sous/b.txt": 6,
|
||||
"rapport.md": 10,
|
||||
"uploads/Mon fichier_-2.txt": 7,
|
||||
"uploads/Mon fichier_.txt": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/upload",
|
||||
"body": "{\"name\":\"gros.bin\",\"data\":\"AAEC\",\"more\":true,\"size\":5}",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"id": "{{ID}}",
|
||||
"ok": true,
|
||||
"received": 3
|
||||
},
|
||||
"tree": {
|
||||
"faux.png": 26,
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"notes/sous/b.txt": 6,
|
||||
"rapport.md": 10,
|
||||
"uploads/.upload-{{N}}.part": 3,
|
||||
"uploads/Mon fichier_-2.txt": 7,
|
||||
"uploads/Mon fichier_.txt": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/upload",
|
||||
"body": "{\"id\":\"{{ID}}\",\"data\":\"data:application/octet-stream;base64,AwQ=\",\"more\":false}",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"abs": "{{HOME}}/workspace/discussions/c-fichiers/uploads/gros.bin",
|
||||
"ok": true,
|
||||
"path": "uploads/gros.bin",
|
||||
"size": 5
|
||||
},
|
||||
"tree": {
|
||||
"faux.png": 26,
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"notes/sous/b.txt": 6,
|
||||
"rapport.md": 10,
|
||||
"uploads/Mon fichier_-2.txt": 7,
|
||||
"uploads/Mon fichier_.txt": 5,
|
||||
"uploads/gros.bin": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/upload",
|
||||
"body": "{\"id\":\"inconnu\",\"data\":\"AA==\"}",
|
||||
"status": 409,
|
||||
"out": {
|
||||
"error": "envoi expiré — recommence le fichier",
|
||||
"ok": false
|
||||
},
|
||||
"tree": {
|
||||
"faux.png": 26,
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"notes/sous/b.txt": 6,
|
||||
"rapport.md": 10,
|
||||
"uploads/Mon fichier_-2.txt": 7,
|
||||
"uploads/Mon fichier_.txt": 5,
|
||||
"uploads/gros.bin": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/upload",
|
||||
"body": "{\"name\":\"vide.txt\",\"data\":\"\"}",
|
||||
"status": 400,
|
||||
"out": {
|
||||
"error": "fichier vide",
|
||||
"ok": false
|
||||
},
|
||||
"tree": {
|
||||
"faux.png": 26,
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"notes/sous/b.txt": 6,
|
||||
"rapport.md": 10,
|
||||
"uploads/Mon fichier_-2.txt": 7,
|
||||
"uploads/Mon fichier_.txt": 5,
|
||||
"uploads/gros.bin": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/upload",
|
||||
"body": "{\"name\":\"x\",\"data\":\"!!!\"}",
|
||||
"status": 400,
|
||||
"out": {
|
||||
"error": "contenu illisible (base64 attendu)",
|
||||
"ok": false
|
||||
},
|
||||
"tree": {
|
||||
"faux.png": 26,
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"notes/sous/b.txt": 6,
|
||||
"rapport.md": 10,
|
||||
"uploads/Mon fichier_-2.txt": 7,
|
||||
"uploads/Mon fichier_.txt": 5,
|
||||
"uploads/gros.bin": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/upload",
|
||||
"body": "pas du json",
|
||||
"status": 400,
|
||||
"out": {
|
||||
"error": "morceau trop gros ou requête invalide",
|
||||
"ok": false
|
||||
},
|
||||
"tree": {
|
||||
"faux.png": 26,
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"notes/sous/b.txt": 6,
|
||||
"rapport.md": 10,
|
||||
"uploads/Mon fichier_-2.txt": 7,
|
||||
"uploads/Mon fichier_.txt": 5,
|
||||
"uploads/gros.bin": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/file/delete",
|
||||
"body": "{\"path\":\"\"}",
|
||||
"status": 400,
|
||||
"out": {
|
||||
"error": "chemin manquant",
|
||||
"ok": false
|
||||
},
|
||||
"tree": {
|
||||
"faux.png": 26,
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"notes/sous/b.txt": 6,
|
||||
"rapport.md": 10,
|
||||
"uploads/Mon fichier_-2.txt": 7,
|
||||
"uploads/Mon fichier_.txt": 5,
|
||||
"uploads/gros.bin": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/file/delete",
|
||||
"body": "{\"path\":\".\"}",
|
||||
"status": 403,
|
||||
"out": {
|
||||
"error": "le dossier de travail lui-même ne se supprime pas",
|
||||
"ok": false
|
||||
},
|
||||
"tree": {
|
||||
"faux.png": 26,
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"notes/sous/b.txt": 6,
|
||||
"rapport.md": 10,
|
||||
"uploads/Mon fichier_-2.txt": 7,
|
||||
"uploads/Mon fichier_.txt": 5,
|
||||
"uploads/gros.bin": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/file/delete",
|
||||
"body": "{\"path\":\"../..\"}",
|
||||
"status": 403,
|
||||
"out": {
|
||||
"error": "hors du dossier de travail",
|
||||
"ok": false
|
||||
},
|
||||
"tree": {
|
||||
"faux.png": 26,
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"notes/sous/b.txt": 6,
|
||||
"rapport.md": 10,
|
||||
"uploads/Mon fichier_-2.txt": 7,
|
||||
"uploads/Mon fichier_.txt": 5,
|
||||
"uploads/gros.bin": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/file/delete",
|
||||
"body": "{\"path\":\"discussions\",\"scope\":\"legacy\"}",
|
||||
"status": 403,
|
||||
"out": {
|
||||
"error": "les dossiers des discussions se suppriment discussion par discussion",
|
||||
"ok": false
|
||||
},
|
||||
"tree": {
|
||||
"faux.png": 26,
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"notes/sous/b.txt": 6,
|
||||
"rapport.md": 10,
|
||||
"uploads/Mon fichier_-2.txt": 7,
|
||||
"uploads/Mon fichier_.txt": 5,
|
||||
"uploads/gros.bin": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/file/delete",
|
||||
"body": "{\"path\":\"absent\"}",
|
||||
"status": 404,
|
||||
"out": {
|
||||
"error": "introuvable",
|
||||
"ok": false
|
||||
},
|
||||
"tree": {
|
||||
"faux.png": 26,
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"notes/sous/b.txt": 6,
|
||||
"rapport.md": 10,
|
||||
"uploads/Mon fichier_-2.txt": 7,
|
||||
"uploads/Mon fichier_.txt": 5,
|
||||
"uploads/gros.bin": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/file/delete",
|
||||
"body": "{\"path\":\"faux.png\"}",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"freed": 26,
|
||||
"ok": true
|
||||
},
|
||||
"tree": {
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"notes/sous/b.txt": 6,
|
||||
"rapport.md": 10,
|
||||
"uploads/Mon fichier_-2.txt": 7,
|
||||
"uploads/Mon fichier_.txt": 5,
|
||||
"uploads/gros.bin": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"url": "/api/chat/file/delete",
|
||||
"body": "{\"path\":\"notes/sous\"}",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"freed": 6,
|
||||
"ok": true
|
||||
},
|
||||
"tree": {
|
||||
"image.png": 124,
|
||||
"notes/a.txt": 5,
|
||||
"rapport.md": 10,
|
||||
"uploads/Mon fichier_-2.txt": 7,
|
||||
"uploads/Mon fichier_.txt": 5,
|
||||
"uploads/gros.bin": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/files/zip",
|
||||
"status": 404,
|
||||
"out": {
|
||||
"error": "archive absente — redemande la fiche (meta=1) d'abord",
|
||||
"ok": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/files/zip?meta=1",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"e2e": false,
|
||||
"files": 6,
|
||||
"name": "loki-c-fichiers-{{DATE}}.zip",
|
||||
"ok": true,
|
||||
"size": "…"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/files/zip?b64=1&offset=0&len=99999999",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"data": "…",
|
||||
"eof": true,
|
||||
"name": "loki-c-fichiers-{{DATE}}.zip",
|
||||
"offset": 0,
|
||||
"ok": true,
|
||||
"size": "…"
|
||||
},
|
||||
"zip": {
|
||||
"image.png": "iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAQ0lEQVR4nAAzAMz/AxY7YIWfssTX6fwOobPG2OsAZouw1fofRGmOs9j9IkdskQC22wAlSm+Uud4DKE1yl7zhAQAA//+6OxpmJmJbrAAAAABJRU5ErkJggg==",
|
||||
"notes/a.txt": "YWxwaGE=",
|
||||
"rapport.md": "IyBSYXBwb3J0Cg==",
|
||||
"uploads/Mon fichier_-2.txt": "aGVsbG8gIQ==",
|
||||
"uploads/Mon fichier_.txt": "aGVsbG8=",
|
||||
"uploads/gros.bin": "AAECAwQ="
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/files/zip?b64=1&offset=-1",
|
||||
"status": 400,
|
||||
"out": {
|
||||
"error": "position hors du fichier",
|
||||
"ok": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/files/zip?meta=1&scope=legacy",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"e2e": false,
|
||||
"files": 2,
|
||||
"name": "loki-hors-discussion-{{DATE}}.zip",
|
||||
"ok": true,
|
||||
"size": "…"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "/api/chat/files/zip?b64=1&scope=legacy",
|
||||
"status": 200,
|
||||
"out": {
|
||||
"data": "…",
|
||||
"eof": true,
|
||||
"name": "loki-hors-discussion-{{DATE}}.zip",
|
||||
"offset": 0,
|
||||
"ok": true,
|
||||
"size": "…"
|
||||
},
|
||||
"zip": {
|
||||
"uploads/ancien.pdf": "JVBERg==",
|
||||
"vieux.txt": "b2xk"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -103,6 +103,7 @@ async fn facade(backend: SocketAddr) -> (SocketAddr, oneshot::Sender<()>) {
|
||||
models: None,
|
||||
dictate: None,
|
||||
settings: false,
|
||||
files: None,
|
||||
};
|
||||
tokio::spawn(loki_rs::proxy::serve(l, cfg, async move {
|
||||
let _ = rx.await;
|
||||
|
||||
Reference in new issue
Block a user