pax_compiler/design_server/
websocket.rs1use crate::design_server::{AppState, FileContent, WatcherFileChanged};
2
3use pax_manifest::{
4 code_serialization::serialize_component_to_file, parsing::TemplateNodeParseContext,
5};
6
7use actix::{Actor, AsyncContext, Handler, Running, StreamHandler};
8use actix_web::web::Data;
9use actix_web_actors::ws::{self};
10use pax_designtime::messages::{
11 AgentMessage, ComponentSerializationRequest, FileChangedNotification,
12 LoadFileToStaticDirRequest, LoadManifestResponse, ManifestSerializationRequest,
13 UpdateTemplateRequest,
14};
15use pax_manifest::{ComponentDefinition, ComponentTemplate, PaxManifest, TypeId};
16use std::collections::HashMap;
17
18pub mod socket_message_accumulator;
19
20pub use socket_message_accumulator::SocketMessageAccumulator;
21
22pub struct PrivilegedAgentWebSocket {
23 state: Data<AppState>,
24 socket_msg_accum: SocketMessageAccumulator,
25}
26
27impl PrivilegedAgentWebSocket {
28 pub fn new(state: Data<AppState>) -> Self {
29 Self {
30 state,
31 socket_msg_accum: SocketMessageAccumulator::new(),
32 }
33 }
34}
35
36impl Actor for PrivilegedAgentWebSocket {
37 type Context = ws::WebsocketContext<Self>;
38
39 fn started(&mut self, ctx: &mut Self::Context) {
40 let mut active_client = self.state.active_websocket_client.lock().unwrap();
41 *active_client = Some(ctx.address());
42 }
43
44 fn stopping(&mut self, _ctx: &mut Self::Context) -> Running {
45 let mut active_client = self.state.active_websocket_client.lock().unwrap();
46 *active_client = None;
47 Running::Stop
48 }
49}
50
51impl Handler<WatcherFileChanged> for PrivilegedAgentWebSocket {
52 type Result = ();
53
54 fn handle(&mut self, msg: WatcherFileChanged, ctx: &mut Self::Context) -> Self::Result {
55 println!("File changed: {:?}", msg.path);
56 if self.state.active_websocket_client.lock().unwrap().is_some() {
57 if let FileContent::Pax(content) = msg.contents {
58 if let Some(manifest) = self.state.manifest.lock().unwrap().as_mut() {
59 let mut template_map: HashMap<String, TypeId> = HashMap::new();
60 let mut matched_component: Option<TypeId> = None;
61 let mut original_template: Option<ComponentTemplate> = None;
62
63 for (type_id, component) in manifest.components.iter() {
65 template_map
66 .insert(type_id.get_pascal_identifier().unwrap(), type_id.clone());
67 if let Some(template) = &component.template {
68 if let Some(file_path) = template.get_file_path() {
69 if file_path == msg.path {
70 matched_component = Some(type_id.clone());
71 original_template = Some(template.clone());
72 }
73 }
74 }
75 }
76
77 if let Some(self_type_id) = matched_component {
78 let original_template = original_template.unwrap();
79 let mut tpc = TemplateNodeParseContext {
80 pascal_identifier_to_type_id_map: template_map,
81 template: ComponentTemplate::new(
82 self_type_id.clone(),
83 original_template.get_file_path(),
84 ),
85 };
86
87 let ast = pax_lang::parse_pax_str(
88 pax_lang::Rule::pax_component_definition,
89 &content,
90 )
91 .expect("Unsuccessful parse");
92 let settings =
93 pax_manifest::parsing::parse_settings_from_component_definition_string(
94 ast.clone(),
95 );
96 pax_manifest::parsing::parse_template_from_component_definition_string(
97 &mut tpc,
98 &content,
99 ast.clone(),
100 );
101
102 let new_template = tpc.template;
103
104 let comp = manifest.components.get_mut(&self_type_id).unwrap();
106 comp.template = Some(new_template.clone());
107 let msg =
108 AgentMessage::UpdateTemplateRequest(Box::new(UpdateTemplateRequest {
109 type_id: self_type_id,
110 new_template,
111 settings_block: settings,
112 }));
113 let serialized_msg = rmp_serde::to_vec(&msg).unwrap();
114 ctx.binary(serialized_msg);
115 }
116 }
117 }
118 }
119 let serialized_notification = rmp_serde::to_vec(
120 &AgentMessage::ProjectFileChangedNotification(FileChangedNotification {}),
121 )
122 .unwrap();
123 ctx.binary(serialized_notification);
124 }
125}
126
127impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for PrivilegedAgentWebSocket {
128 fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
129 let Ok(msg) = msg else {
130 eprintln!("failed to receive on socket");
131 return;
132 };
133
134 let processed_message = self.socket_msg_accum.process(msg);
135 if let Ok(Some(bin_data)) = processed_message {
136 match rmp_serde::from_slice::<AgentMessage>(&bin_data) {
137 Ok(AgentMessage::LoadManifestRequest) => {
138 let manifest =
139 rmp_serde::to_vec(&*self.state.manifest.lock().unwrap()).unwrap();
140
141 let message =
142 AgentMessage::LoadManifestResponse(LoadManifestResponse { manifest });
143 ctx.binary(rmp_serde::to_vec(&message).unwrap());
144 }
145 Ok(AgentMessage::ComponentSerializationRequest(request)) => {
146 handle_component_serialization_request(
147 request,
148 self.state.manifest.lock().unwrap().as_mut(),
149 );
150 self.state.update_last_written_timestamp();
151 }
152 Ok(AgentMessage::ManifestSerializationRequest(request)) => {
153 handle_manifest_serialization_request(
154 request,
155 &mut self.state.manifest.lock().unwrap(),
156 self.state.generate_request_id(),
157 ctx,
158 );
159 self.state.update_last_written_timestamp();
160 }
161 Ok(AgentMessage::LoadFileToStaticDirRequest(load_info)) => {
162 let LoadFileToStaticDirRequest { name, data } = load_info;
163 println!(
164 "received a file {} (size: {})! root dir to write to: {:?}",
165 name,
166 data.len(),
167 self.state.userland_project_root.lock().unwrap(),
168 );
169
170 let mut path = self.state.userland_project_root.lock().unwrap().clone();
171 path.push("assets");
172 path.push(&name);
173
174 if let Some(parent) = path.parent() {
175 std::fs::create_dir_all(parent)
176 .unwrap_or_else(|e| eprintln!("Failed to create directory: {}", e));
177 }
178 if std::fs::write(&path, data.clone()).is_err() {
179 eprintln!("server couldn't write to assets folder: {:?}", path);
180 };
181 let path = self
182 .state
183 .serve_dir
184 .lock()
185 .unwrap()
186 .clone()
187 .join("assets")
188 .join(name);
189 if std::fs::write(&path, data).is_err() {
190 eprintln!("server couldn't write to served folder: {:?}", path);
191 };
192 }
193 Ok(_) => {}
194 Err(e) => {
195 eprintln!("Deserialization error: {:?}", e);
196 }
197 }
198 } else if let Ok(None) = processed_message {
199 } else {
201 eprintln!("unhandled socket message");
202 }
203 }
204}
205
206fn handle_component_serialization_request(
207 request: ComponentSerializationRequest,
208 manifest: Option<&mut PaxManifest>,
209) {
210 let component: ComponentDefinition = rmp_serde::from_slice(&request.component_bytes).unwrap();
211 let file_path = component
212 .template
213 .as_ref()
214 .unwrap()
215 .get_file_path()
216 .unwrap()
217 .to_owned();
218 serialize_component_to_file(&component, file_path.clone());
219 if let Some(manifest) = manifest {
221 for comp in manifest.components.values_mut() {
222 if comp
223 .template
224 .as_ref()
225 .is_some_and(|t| t.get_file_path().is_some_and(|p| p == file_path))
226 {
227 *comp = component;
228 break;
229 }
230 }
231 }
232}
233
234fn handle_manifest_serialization_request(
235 request: ManifestSerializationRequest,
236 manifest: &mut Option<PaxManifest>,
237 _id: usize,
238 _ctx: &mut ws::WebsocketContext<PrivilegedAgentWebSocket>,
239) {
240 *manifest = Some(rmp_serde::from_slice(&request.manifest).unwrap());
241 if let Some(manifest) = manifest {
242 for component in manifest.components.values() {
243 let file_path = component.template.as_ref().unwrap().get_file_path();
244 if let Some(file_path) = &file_path {
245 serialize_component_to_file(component, file_path.clone());
246 }
247 }
248 }
249}
250
251struct LLMRequestMessage {
252 pub request: String,
253 pub simple_world_info: String,
254 pub file_content: String,
255}
256
257impl actix::Message for LLMRequestMessage {
258 type Result = ();
259}
260
261#[allow(unused)]
262fn build_llm_request(request: LLMRequestMessage) -> String {
263 let mut req = format!("User Request:\n {}\n\n", request.request);
264 req.push_str(&format!(
265 "Simple World Information:\n {} \n\n",
266 request.simple_world_info
267 ));
268 req.push_str(&format!(
269 "Full Pax Template:\n {} \n\n",
270 request.file_content
271 ));
272 req
273}