pax_compiler/design_server/
websocket.rs

1use 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                    // Search for component that was changed, while building a template map for the parse context
64                    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                        // update the manifest with this new template
105                        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            // Do nothing, wait until entire message has been received
200        } 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    // update in memory manifest
220    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}