1use geop_core_math::geop_error::{GeopError, GeopResult};
7use serde_json::{Value, json};
8
9use super::{Image, Session};
10
11#[derive(Clone, Debug, PartialEq)]
14pub struct AgentResource {
15 pub uri: &'static str,
16 pub name: &'static str,
17 pub description: &'static str,
18 pub media_type: &'static str,
19}
20
21pub fn resources() -> Vec<AgentResource> {
23 let resource = |uri, name, description, media_type| AgentResource {
24 uri,
25 name,
26 description,
27 media_type,
28 };
29 vec![
30 resource(
31 "geop://program",
32 "program",
33 "The program as it stands, as a .geop file holds it: what `load_program` takes as `text`.",
34 "application/json",
35 ),
36 resource(
37 "geop://status",
38 "status",
39 "Every step's status (as `run` answers, without running), the step open, the tool in hand, the tools and panels there are.",
40 "application/json",
41 ),
42 resource(
43 "geop://elements",
44 "elements",
45 "The part's elements by their stable names, as `list_elements` answers.",
46 "application/json",
47 ),
48 resource(
49 "geop://operations",
50 "operations",
51 "Every operation a step can be, as `list_operations` answers.",
52 "application/json",
53 ),
54 resource(
55 "geop://examples",
56 "examples",
57 "The names of the example programs, and of the examples of several files.",
58 "application/json",
59 ),
60 resource(
61 "geop://picture",
62 "picture",
63 "The part as `look` last saw it — isometric, or facing the plane the open step works in, if it has not looked yet — as a PNG.",
64 "image/png",
65 ),
66 ]
67}
68
69pub fn resource_templates() -> Vec<AgentResource> {
71 vec![
72 AgentResource {
73 uri: "geop://operations/{kind}",
74 name: "operation",
75 description: "One operation in full, as `list_operations` given its `kind` answers: the arguments a new step of it starts with and its form.",
76 media_type: "application/json",
77 },
78 AgentResource {
79 uri: "geop://examples/{name}",
80 name: "example",
81 description: "An example program as a .geop file holds it — or, for an example of several files, each file's program by its path: how a whole part is written.",
82 media_type: "application/json",
83 },
84 ]
85}
86
87#[derive(Clone, Debug, PartialEq)]
89pub enum Contents {
90 Text(String),
91 Image(Image),
92}
93
94#[derive(Clone, Debug, PartialEq)]
97pub struct AgentPrompt {
98 pub name: &'static str,
99 pub description: &'static str,
100 pub arguments: Vec<(&'static str, &'static str, bool)>,
101}
102
103pub fn prompts() -> Vec<AgentPrompt> {
105 vec![
106 AgentPrompt {
107 name: "model_part",
108 description: "Model a part from a description, step by step, checking each step and looking at the result.",
109 arguments: vec![(
110 "description",
111 "What to model: its shape, sizes and features.",
112 true,
113 )],
114 },
115 AgentPrompt {
116 name: "fix_failing_steps",
117 description: "Find out why the program's failing steps fail, and fix them.",
118 arguments: vec![],
119 },
120 AgentPrompt {
121 name: "review_part",
122 description: "Look the part over from every side, weigh it and check it for overlapping solids, and say what is wrong with it.",
123 arguments: vec![],
124 },
125 ]
126}
127
128const WORKFLOW: &str = "\
130Model it with the geop tools, one step at a time:
1311. `list_operations` for what there is, and `list_operations` with a `kind` for the arguments a step of it takes (`default_args`) and what each field is (`form`). The example programs (resources `geop://examples/{name}`) show how whole parts are written.
1322. `add_step` each step, and read the status it answers with: a step that does not build says why. Fix it with `edit_step` before going on.
1333. `list_elements` for the names of faces, edges and vertices a later step refers to (a sketch on a face, a fillet of edges).
1344. `look` at the part from a few sides (`iso`, `front`, `top`, ...) to see that it is what was asked for, and `inspect` it: `mass_properties`, `measure` the sizes that matter.
135Where writing a step's arguments is hard — a sketch to draw, a part placed to drag — `open_step` it and do what a user does: `step_event` clicks at points of the plane the step works in, keys pick its tools (a sketch's `l` draws lines), then `commit_step`. `undo` takes back an edit.";
136
137impl Session {
138 pub fn read_resource(&mut self, uri: &str) -> GeopResult<(Contents, &'static str)> {
140 let json = |value: Value| -> GeopResult<(Contents, &'static str)> {
141 let text = serde_json::to_string_pretty(&value)
142 .map_err(|e| GeopError::new(format!("writing {uri}: {e}")))?;
143 Ok((Contents::Text(text), "application/json"))
144 };
145 let path = (uri.strip_prefix("geop://")).ok_or_else(|| {
146 GeopError::new(format!("there is no resource {uri}: geop's are geop://…"))
147 })?;
148 match path.split_once('/') {
149 None => match path {
150 "program" => Ok((
151 Contents::Text(self.editor.program().to_json()?),
152 "application/json",
153 )),
154 "status" => json(self.status()?),
155 "elements" => json(self.list_elements()?),
156 "operations" => json(self.list_operations(None)?),
157 "examples" => {
158 let program = self.shown()?;
159 json(json!({
160 "examples": program.examples,
161 "workspace_examples": program.workspace_examples,
162 }))
163 }
164 "picture" => {
165 let data = self.picture(None)?.0;
166 let media_type = "image/png";
167 Ok((Contents::Image(Image { media_type, data }), media_type))
168 }
169 _ => Err(self.no_resource(uri)),
170 },
171 Some(("operations", kind)) => json(self.list_operations(Some(kind))?),
172 Some(("examples", name)) => {
173 let registry = self.editor.registry();
174 if self.shown()?.examples.contains(&name) {
175 let program = registry.example(name)?;
176 return Ok((Contents::Text(program.to_json()?), "application/json"));
177 }
178 let files = registry.workspace_example(name)?;
179 let files = (files.into_iter())
180 .map(|(path, program)| Ok((path.to_string(), serde_json::to_value(&program)?)))
181 .collect::<Result<serde_json::Map<_, _>, serde_json::Error>>()
182 .map_err(|e| GeopError::new(format!("writing the example {name}: {e}")))?;
183 json(Value::Object(files))
184 }
185 _ => Err(self.no_resource(uri)),
186 }
187 }
188
189 fn no_resource(&self, uri: &str) -> GeopError {
190 let uris: Vec<&str> = (resources().into_iter().chain(resource_templates()))
191 .map(|r| r.uri)
192 .collect();
193 GeopError::new(format!(
194 "there is no resource {uri}; there are {}",
195 uris.join(", ")
196 ))
197 }
198
199 pub fn prompt(&mut self, name: &str, arguments: &Value) -> GeopResult<(&'static str, String)> {
202 let prompt = (prompts().into_iter())
203 .find(|p| p.name == name)
204 .ok_or_else(|| {
205 let names: Vec<&str> = prompts().iter().map(|p| p.name).collect();
206 GeopError::new(format!(
207 "there is no prompt {name}; there are {}",
208 names.join(", ")
209 ))
210 })?;
211 let argument = |key: &str| {
212 (arguments.get(key).and_then(Value::as_str))
213 .ok_or_else(|| GeopError::new(format!("the prompt {name} needs `{key}`")))
214 };
215 let status = || -> GeopResult<String> {
216 serde_json::to_string_pretty(&self.status()?)
217 .map_err(|e| GeopError::new(format!("writing the status: {e}")))
218 };
219 let text = match name {
220 "model_part" => format!(
221 "Model this part in geop:\n\n{}\n\n{WORKFLOW}\n\nStart from `new_part`, unless the program open is the one to change.",
222 argument("description")?
223 ),
224 "fix_failing_steps" => format!(
225 "Some steps of the geop program open fail. Its status:\n\n{}\n\nFor each failing step, the first first: read its error — it names the step, the operation and the argument, with the kernel's context — look at what the steps before it built (`list_elements`, `look`), and fix the step's arguments with `edit_step`, or the step it refers to. Do not delete a step to make the status clean: the part has to stay what it was meant to be. Then `run`, and `look` at the part.\n\n{WORKFLOW}",
226 status()?
227 ),
228 "review_part" => format!(
229 "Review the geop part open. Its status:\n\n{}\n\n`look` at it from `iso`, `front`, `top` and `right` (and from below, if it has features there); `inspect` its `mass_properties` and its `interference`; `measure` the sizes that look wrong. Then say what is wrong with it, by the names of its steps and elements, and what to change. Change nothing yet.",
230 status()?
231 ),
232 _ => return Err(GeopError::new(format!("the prompt {name} has no text"))),
233 };
234 Ok((prompt.description, text))
235 }
236}