1use super::*;
8
9pub(super) async fn handle_window_name(
16 State(state): State<AppState>,
17 Json(req): Json<WindowNameRequest>,
18) -> impl IntoResponse {
19 let name: String = req.name.trim().chars().take(64).collect();
21 if name.is_empty() {
22 return (StatusCode::BAD_REQUEST, Json(json!({ "error": "name must not be empty" }))).into_response();
23 }
24
25 let window_id = match req.window_id.filter(|w| !w.is_empty()) {
26 Some(w) => w,
27 None => {
28 let block_id = req.block_id.unwrap_or_default();
29 if block_id.is_empty() {
30 return (StatusCode::BAD_REQUEST, Json(json!({ "error": "provide window_id or block_id" }))).into_response();
31 }
32 match service::resolve_agent_context(&state.mstore, &block_id) {
33 Ok(ctx) => match ctx.window_id {
34 Some(w) => w,
35 None => {
36 return (
37 StatusCode::NOT_FOUND,
38 Json(json!({ "error": "no live window for this agent (tab not attached to a window)" })),
39 )
40 .into_response()
41 }
42 },
43 Err(e) => return (StatusCode::NOT_FOUND, Json(json!({ "error": e }))).into_response(),
44 }
45 }
46 };
47
48 let call = crate::backend::service::WebCallType {
49 service: "object".to_string(),
50 method: "UpdateObjectMeta".to_string(),
51 uicontext: None,
52 args: vec![
53 json!(format!("window:{window_id}")),
54 json!({ "window:displayname": name }),
55 ],
56 };
57 finish_name_call(
58 &state,
59 call,
60 json!({ "success": true, "window_id": window_id, "name": name }),
61 )
62 .await
63}
64
65pub(super) fn clean_name(raw: &str, max: usize) -> Option<String> {
67 let n: String = raw.trim().chars().take(max).collect();
68 if n.is_empty() {
69 None
70 } else {
71 Some(n)
72 }
73}
74
75pub(super) async fn handle_tab_name(
78 State(state): State<AppState>,
79 Json(req): Json<TabNameRequest>,
80) -> impl IntoResponse {
81 let name = match clean_name(&req.name, 128) {
82 Some(n) => n,
83 None => return (StatusCode::BAD_REQUEST, Json(json!({ "error": "name must not be empty" }))).into_response(),
84 };
85 let tab_id = match req.tab_id.filter(|t| !t.is_empty()) {
86 Some(t) => t,
87 None => match resolve_own(&state, req.block_id, |c| Some(c.tab_id.clone())) {
88 Ok(t) => t,
89 Err(resp) => return resp,
90 },
91 };
92 let call = crate::backend::service::WebCallType {
93 service: "object".to_string(),
94 method: "UpdateTabName".to_string(),
95 uicontext: None,
96 args: vec![json!(tab_id), json!(name)],
97 };
98 finish_name_call(&state, call, json!({ "success": true, "tab_id": tab_id, "name": name })).await
99}
100
101pub(super) async fn handle_pane_title(
105 State(state): State<AppState>,
106 Json(req): Json<PaneTitleRequest>,
107) -> impl IntoResponse {
108 let title = match clean_name(&req.title, 128) {
109 Some(t) => t,
110 None => return (StatusCode::BAD_REQUEST, Json(json!({ "error": "title must not be empty" }))).into_response(),
111 };
112 let block_id = match req.block_id.filter(|b| !b.is_empty()) {
113 Some(b) => b,
114 None => return (StatusCode::BAD_REQUEST, Json(json!({ "error": "missing block_id" }))).into_response(),
115 };
116 let call = crate::backend::service::WebCallType {
117 service: "object".to_string(),
118 method: "UpdateObjectMeta".to_string(),
119 uicontext: None,
120 args: vec![
121 json!(format!("block:{block_id}")),
122 json!({ "frame:title": title }),
123 ],
124 };
125 finish_name_call(&state, call, json!({ "success": true, "block_id": block_id, "title": title })).await
126}
127
128pub(super) async fn handle_workspace_name(
132 State(state): State<AppState>,
133 Json(req): Json<WorkspaceNameRequest>,
134) -> impl IntoResponse {
135 let name = match clean_name(&req.name, 128) {
136 Some(n) => n,
137 None => return (StatusCode::BAD_REQUEST, Json(json!({ "error": "name must not be empty" }))).into_response(),
138 };
139 let workspace_id = match req.workspace_id.filter(|w| !w.is_empty()) {
140 Some(w) => w,
141 None => match resolve_own(&state, req.block_id, |c| c.workspace_id.clone()) {
142 Ok(w) => w,
143 Err(resp) => return resp,
144 },
145 };
146 let call = crate::backend::service::WebCallType {
147 service: "workspace".to_string(),
148 method: "UpdateWorkspace".to_string(),
149 uicontext: None,
150 args: vec![json!(workspace_id), json!(name)],
151 };
152 finish_name_call(&state, call, json!({ "success": true, "workspace_id": workspace_id, "name": name })).await
153}
154
155pub(super) fn resolve_own(
159 state: &AppState,
160 block_id: Option<String>,
161 pick: impl Fn(&service::AgentContext) -> Option<String>,
162) -> Result<String, Response> {
163 let block_id = block_id.unwrap_or_default();
164 if block_id.is_empty() {
165 return Err((StatusCode::BAD_REQUEST, Json(json!({ "error": "provide an explicit target id or block_id" }))).into_response());
166 }
167 let ctx = service::resolve_agent_context(&state.mstore, &block_id)
168 .map_err(|e| (StatusCode::NOT_FOUND, Json(json!({ "error": e }))).into_response())?;
169 pick(&ctx).filter(|s| !s.is_empty()).ok_or_else(|| {
170 (
171 StatusCode::NOT_FOUND,
172 Json(json!({ "error": "no such target resolved for this agent" })),
173 )
174 .into_response()
175 })
176}
177
178pub(super) async fn finish_name_call(
180 state: &AppState,
181 call: crate::backend::service::WebCallType,
182 ok_body: serde_json::Value,
183) -> Response {
184 let result = service::run_service_call(state, &call).await;
185 if let Some(err) = result.error {
186 return (app_api_error_status(&err), Json(json!({ "error": err }))).into_response();
187 }
188 Json(ok_body).into_response()
189}