Skip to content

Commit c4286e1

Browse files
authored
switch to the multi-file iteration endpoint (#1219)
* updates Signed-off-by: Jess Frazelle <[email protected]> updates Signed-off-by: Jess Frazelle <[email protected]> updates Signed-off-by: Jess Frazelle <[email protected]> updates Signed-off-by: Jess Frazelle <[email protected]> updates Signed-off-by: Jess Frazelle <[email protected]> fixes Signed-off-by: Jess Frazelle <[email protected]> updates Signed-off-by: Jess Frazelle <[email protected]> updates Signed-off-by: Jess Frazelle <[email protected]> updates Signed-off-by: Jess Frazelle <[email protected]> * updates Signed-off-by: Jess Frazelle <[email protected]> * updates Signed-off-by: Jess Frazelle <[email protected]> --------- Signed-off-by: Jess Frazelle <[email protected]>
1 parent ba5f6f4 commit c4286e1

26 files changed

+48750
-48519
lines changed

Cargo.lock

Lines changed: 180 additions & 126 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ colored_json = "4.1"
1717
data-encoding = "2.6.0"
1818
dialoguer = "0.11.0"
1919
dirs = "6"
20+
futures = "0.3"
2021
git_rev = "0.1.0"
2122
heck = "0.5.0"
2223
http = "1"
2324
itertools = "0.12.1"
24-
kcl-lib = { version = "0.2.62", features = ["disable-println"] }
25+
kcl-lib = { version = "0.2.64", features = ["disable-println"] }
2526
#kcl-lib = { path = "../modeling-app/src/wasm-lib/kcl" }
26-
kcl-test-server = "0.1.62"
27-
kittycad = { version = "0.3.33", features = ["clap", "tabled", "requests", "retry"] }
27+
kcl-test-server = "0.1.64"
28+
kittycad = { version = "0.3.34", features = ["clap", "tabled", "requests", "retry"] }
2829
kittycad-modeling-cmds = { version = "0.2.107", features = ["websocket", "convert_client_crate", "tabled"] }
2930
log = "0.4.27"
3031
miette = { version = "7.5.0", features = ["fancy"] }

flake.nix

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222
rustToolchain = super.rust-bin.stable.latest.default.override {
2323
extensions = ["rustfmt" "clippy" "rust-src"];
2424
};
25+
26+
# stand-alone nightly formatter so we get the fancy unstable flags
27+
nightlyRustfmt = super.rust-bin.selectLatestNightlyWith (toolchain:
28+
toolchain.default.override {
29+
extensions = ["rustfmt"]; # just the formatter
30+
});
2531
})
2632
];
2733
pkgs = import nixpkgs {
@@ -33,6 +39,8 @@
3339
nativeBuildInputs =
3440
(with pkgs; [
3541
rustToolchain
42+
nightlyRustfmt
43+
cargo-sort
3644
toml-cli
3745
openssl
3846
postgresql
@@ -41,7 +49,7 @@
4149
++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [
4250
]);
4351

44-
RUSTFMT = "${pkgs.rust-bin.nightly.latest.rustfmt}/bin/rustfmt";
52+
RUSTFMT = "${pkgs.nightlyRustfmt}/bin/rustfmt";
4553
};
4654
}
4755
);

src/cmd_config.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use crate::config::{ConfigOption, CONFIG_OPTIONS};
21
use anyhow::{bail, Result};
32
use clap::Parser;
43

4+
use crate::config::{ConfigOption, CONFIG_OPTIONS};
5+
56
// TODO: make this doc a function that parses from the config the options so it's not hardcoded
67
/// Manage configuration for `zoo`.
78
///
@@ -170,9 +171,7 @@ impl crate::cmd::Command for CmdConfigList {
170171
mod test {
171172
use pretty_assertions::assert_eq;
172173

173-
use crate::cmd::Command;
174-
use crate::config;
175-
use crate::config::Config;
174+
use crate::{cmd::Command, config, config::Config};
176175

177176
pub struct TestItem {
178177
name: String,

src/cmd_kcl.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
use std::{net::SocketAddr, str::FromStr};
22

3-
use crate::kcl_error_fmt;
43
use anyhow::Result;
54
use clap::Parser;
65
use kcmc::format::OutputFormat3d as OutputFormat;
76
use kittycad::types as kt;
87
use kittycad_modeling_cmds as kcmc;
98
use url::Url;
109

11-
use crate::iostreams::IoStreams;
10+
use crate::{iostreams::IoStreams, kcl_error_fmt};
1211

1312
/// Perform actions on `kcl` files.
1413
#[derive(Parser, Debug, Clone)]
@@ -134,6 +133,7 @@ impl crate::cmd::Command for CmdKclExport {
134133
for file in files {
135134
let path = self.output_dir.join(file.name);
136135
std::fs::write(&path, file.contents)?;
136+
137137
writeln!(ctx.io.out, "Wrote file: {}", path.display())?;
138138
}
139139

src/cmd_ml/cmd_kcl.rs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,18 @@ impl crate::cmd::Command for CmdKcl {
2323
}
2424
}
2525

26-
/// Edit a `kcl` file with a prompt.
26+
/// Edit `kcl` file(s) with a prompt.
2727
///
2828
/// $ zoo ml kcl edit --prompt "Make it blue"
2929
///
30-
/// This command outputs the edited `kcl` file to stdout.
30+
/// This command outputs the edited `kcl` files back to the same location.
31+
/// We do not output to stdout, because for projects with multiple files,
32+
/// it would be difficult to know which file the output corresponds to.
3133
#[derive(Parser, Debug, Clone)]
3234
#[clap(verbatim_doc_comment)]
3335
pub struct CmdKclEdit {
34-
/// The path to the input file.
36+
/// The path to the input file or directory containing a main.kcl file.
37+
/// We will read in the contents of all the project's `kcl` files.
3538
/// If you pass `-` as the path, the file will be read from stdin.
3639
#[clap(name = "input", required = true)]
3740
pub input: std::path::PathBuf,
@@ -50,9 +53,7 @@ pub struct CmdKclEdit {
5053
impl crate::cmd::Command for CmdKclEdit {
5154
async fn run(&self, ctx: &mut crate::context::Context) -> Result<()> {
5255
// Get the contents of the input file.
53-
let input = ctx.read_file(self.input.to_str().unwrap_or(""))?;
54-
// Parse the input as a string.
55-
let input = std::str::from_utf8(&input)?;
56+
let (files, filepath) = ctx.collect_kcl_files(&self.input).await?;
5657

5758
let prompt = self.prompt.join(" ");
5859

@@ -61,27 +62,34 @@ impl crate::cmd::Command for CmdKclEdit {
6162
}
6263

6364
let source_ranges = if let Some(source_range) = &self.source_range {
64-
vec![kittycad::types::SourceRangePrompt {
65+
Some(vec![kittycad::types::SourceRangePrompt {
6566
range: convert_to_source_range(source_range)?,
6667
prompt: prompt.clone(),
67-
file: None,
68-
}]
68+
file: Some(filepath.to_string_lossy().to_string()),
69+
}])
6970
} else {
7071
Default::default()
7172
};
7273

73-
let body = kittycad::types::TextToCadIterationBody {
74-
original_source_code: input.to_string(),
75-
prompt: if source_ranges.is_empty() { Some(prompt) } else { None },
74+
let body = kittycad::types::TextToCadMultiFileIterationBody {
75+
prompt: if source_ranges.is_none() { Some(prompt) } else { None },
7676
source_ranges,
7777
project_name: None,
7878
kcl_version: Some(kcl_lib::version().to_owned()),
7979
};
8080

81-
let model = ctx.get_edit_for_prompt("", &body).await?;
81+
let model = ctx.get_edit_for_prompt("", &body, files).await?;
8282

83-
// Print the output of the conversion.
84-
writeln!(ctx.io.out, "{}", model.code)?;
83+
let Some(outputs) = model.outputs else {
84+
anyhow::bail!("model did not return any outputs");
85+
};
86+
87+
// Write the output to each file locally.
88+
for (file, output) in outputs {
89+
// We could do these in parallel...
90+
tokio::fs::write(&file, output).await?;
91+
writeln!(ctx.io.out, "Wrote to {}", file)?;
92+
}
8593

8694
Ok(())
8795
}

src/cmd_ml/cmd_text_to_cad.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
use anyhow::Result;
22
use clap::Parser;
33
use kcl_lib::EngineManager;
4-
use kcmc::each_cmd as mcmd;
5-
use kcmc::format::InputFormat3d;
6-
use kcmc::ok_response::OkModelingCmdResponse;
7-
use kcmc::websocket::OkWebSocketResponseData;
8-
use kcmc::{ImageFormat, ModelingCmd};
4+
use kcmc::{
5+
each_cmd as mcmd, format::InputFormat3d, ok_response::OkModelingCmdResponse, websocket::OkWebSocketResponseData,
6+
ImageFormat, ModelingCmd,
7+
};
98
use kittycad_modeling_cmds::{self as kcmc, ImportFile};
109

1110
/// Perform Text-to-CAD commands.

src/context.rs

Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
use std::str::FromStr;
22

33
use anyhow::{anyhow, Result};
4-
use kcl_lib::native_engine::EngineConnection;
5-
use kcl_lib::EngineManager;
6-
use kcmc::each_cmd as mcmd;
7-
use kcmc::websocket::OkWebSocketResponseData;
8-
use kittycad::types::{ApiCallStatus, AsyncApiCallOutput, TextToCad, TextToCadCreateBody, TextToCadIteration};
4+
use kcl_lib::{native_engine::EngineConnection, EngineManager};
5+
use kcmc::{each_cmd as mcmd, websocket::OkWebSocketResponseData};
6+
use kittycad::types::{ApiCallStatus, AsyncApiCallOutput, TextToCad, TextToCadCreateBody, TextToCadMultiFileIteration};
97
use kittycad_modeling_cmds::{self as kcmc, shared::FileExportFormat, websocket::ModelingSessionData, ModelingCmd};
108

119
use crate::{config::Config, config_file::get_env_var, kcl_error_fmt, types::FormatOutput};
@@ -288,12 +286,13 @@ impl Context<'_> {
288286
pub async fn get_edit_for_prompt(
289287
&self,
290288
hostname: &str,
291-
body: &kittycad::types::TextToCadIterationBody,
292-
) -> Result<TextToCadIteration> {
289+
body: &kittycad::types::TextToCadMultiFileIterationBody,
290+
files: Vec<kittycad::types::multipart::Attachment>,
291+
) -> Result<TextToCadMultiFileIteration> {
293292
let client = self.api_client(hostname)?;
294293

295294
// Create the text-to-cad request.
296-
let mut gen_model = client.ml().create_text_to_cad_iteration(body).await?;
295+
let mut gen_model = client.ml().create_text_to_cad_multi_file_iteration(files, body).await?;
297296

298297
// Poll until the model is ready.
299298
let mut status = gen_model.status.clone();
@@ -308,7 +307,7 @@ impl Context<'_> {
308307
// Poll for the status.
309308
let result = client.api_calls().get_async_operation(gen_model.id).await?;
310309

311-
if let AsyncApiCallOutput::TextToCadIteration {
310+
if let AsyncApiCallOutput::TextToCadMultiFileIteration {
312311
completed_at,
313312
created_at,
314313
error,
@@ -320,13 +319,12 @@ impl Context<'_> {
320319
status,
321320
updated_at,
322321
user_id,
323-
code,
324322
model,
325-
original_source_code,
326323
source_ranges,
324+
outputs,
327325
} = result
328326
{
329-
gen_model = TextToCadIteration {
327+
gen_model = TextToCadMultiFileIteration {
330328
completed_at,
331329
created_at,
332330
error,
@@ -338,10 +336,9 @@ impl Context<'_> {
338336
status,
339337
updated_at,
340338
user_id,
341-
code,
342339
model,
343-
original_source_code,
344340
source_ranges,
341+
outputs,
345342
};
346343
} else {
347344
anyhow::bail!("Unexpected response type: {:?}", result);
@@ -475,6 +472,75 @@ impl Context<'_> {
475472
let code = std::str::from_utf8(&b)?;
476473
Ok((code.to_string(), path))
477474
}
475+
476+
/// Collect all the kcl files in the given directory or parent directory to the given path.
477+
pub async fn collect_kcl_files(
478+
&mut self,
479+
path: &std::path::Path,
480+
) -> Result<(Vec<kittycad::types::multipart::Attachment>, std::path::PathBuf)> {
481+
let mut files = Vec::new();
482+
483+
let (code, filepath) = self.get_code_and_file_path(path).await?;
484+
files.push(kittycad::types::multipart::Attachment {
485+
name: filepath.to_string_lossy().to_string(),
486+
filepath: Some(filepath.clone()),
487+
content_type: Some("text/plain".parse()?),
488+
data: code.as_bytes().to_vec(),
489+
});
490+
491+
// Walk the directory and collect all the kcl files.
492+
let parent = filepath
493+
.parent()
494+
.ok_or_else(|| anyhow!("Could not get parent directory to: `{}`", filepath.display()))?;
495+
let walked_kcl = kcl_lib::walk_dir(&parent.to_path_buf()).await?;
496+
497+
// Get all the attachements async.
498+
let futures = walked_kcl
499+
.into_iter()
500+
.filter(|file| *file != filepath)
501+
.map(|file| {
502+
tokio::spawn(async move {
503+
let contents = tokio::fs::read(&file)
504+
.await
505+
.map_err(|err| anyhow::anyhow!("Failed to read file `{}`: {:?}", file.display(), err))?;
506+
507+
Ok::<kittycad::types::multipart::Attachment, anyhow::Error>(
508+
kittycad::types::multipart::Attachment {
509+
name: file.to_string_lossy().to_string(),
510+
filepath: Some(file),
511+
content_type: Some("text/plain".parse()?),
512+
data: contents,
513+
},
514+
)
515+
})
516+
})
517+
.collect::<Vec<_>>();
518+
519+
// Join all futures and await their completion
520+
let results = futures::future::join_all(futures).await;
521+
522+
// Check if any of the futures failed.
523+
let mut errors = Vec::new();
524+
for result in results {
525+
match result {
526+
Ok(Ok(attachment)) => {
527+
files.push(attachment);
528+
}
529+
Ok(Err(err)) => {
530+
errors.push(err);
531+
}
532+
Err(err) => {
533+
errors.push(anyhow::anyhow!("Failed to join future: {:?}", err));
534+
}
535+
}
536+
}
537+
538+
if !errors.is_empty() {
539+
anyhow::bail!("Failed to walk some kcl files: {:?}", errors);
540+
}
541+
542+
Ok((files, filepath))
543+
}
478544
}
479545

480546
#[cfg(test)]

src/iostreams.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
use std::io::IsTerminal;
2-
use std::{collections::HashMap, env, process::Command};
1+
use std::{collections::HashMap, env, io::IsTerminal, process::Command};
32

43
use anyhow::{anyhow, Result};
54
use terminal_size::{terminal_size, Height, Width};

src/tests.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -767,13 +767,13 @@ async fn test_main(ctx: &mut MainContext) {
767767
"ml".to_string(),
768768
"kcl".to_string(),
769769
"edit".to_string(),
770-
"tests/gear.kcl".to_string(),
770+
"tests/assembly-edit".to_string(),
771771
"make".to_string(),
772-
"the".to_string(),
773-
"teeth".to_string(),
772+
"it".to_string(),
774773
"blue".to_string(),
775774
],
776-
want_out: r#"appearance("#.to_string(),
775+
want_out: r#"Wrote to tests/assembly-edit/main.kcl"#.to_string(), // Make sure it keeps
776+
// the path.
777777
want_err: "".to_string(),
778778
want_code: 0,
779779
..Default::default()

0 commit comments

Comments
 (0)