mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-20 05:28:15 +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
@@ -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