operator input: per-agent /send form (dashboard T4LK removed)
This commit is contained in:
parent
3c493934da
commit
409263f1c9
8 changed files with 142 additions and 52 deletions
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
|
@ -20,6 +21,7 @@ use axum::{
|
|||
use serde::Deserialize;
|
||||
use tokio_stream::{Stream, StreamExt, wrappers::BroadcastStream};
|
||||
|
||||
use crate::client;
|
||||
use crate::events::Bus;
|
||||
use crate::login::LoginState;
|
||||
use crate::login_session::{LoginSession, drop_if_finished};
|
||||
|
|
@ -35,18 +37,38 @@ struct AppState {
|
|||
login: LoginStateCell,
|
||||
session: Arc<Mutex<Option<Arc<LoginSession>>>>,
|
||||
bus: Bus,
|
||||
socket: PathBuf,
|
||||
flavor: Flavor,
|
||||
}
|
||||
|
||||
pub async fn serve(label: String, port: u16, login: LoginStateCell, bus: Bus) -> Result<()> {
|
||||
/// Which wire protocol the per-agent UI's `/send` handler should speak.
|
||||
/// Sub-agent → `AgentRequest::OperatorMsg`; manager → `ManagerRequest::OperatorMsg`.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Flavor {
|
||||
Agent,
|
||||
Manager,
|
||||
}
|
||||
|
||||
pub async fn serve(
|
||||
label: String,
|
||||
port: u16,
|
||||
login: LoginStateCell,
|
||||
bus: Bus,
|
||||
socket: PathBuf,
|
||||
flavor: Flavor,
|
||||
) -> Result<()> {
|
||||
let state = AppState {
|
||||
label,
|
||||
login,
|
||||
session: Arc::new(Mutex::new(None)),
|
||||
bus,
|
||||
socket,
|
||||
flavor,
|
||||
};
|
||||
let app = Router::new()
|
||||
.route("/", get(index))
|
||||
.route("/events/stream", get(events_stream))
|
||||
.route("/send", post(post_send))
|
||||
.route("/login/start", post(post_login_start))
|
||||
.route("/login/code", post(post_login_code))
|
||||
.route("/login/cancel", post(post_login_cancel))
|
||||
|
|
@ -65,7 +87,7 @@ async fn index(State(state): State<AppState>) -> Html<String> {
|
|||
let login = *state.login.lock().unwrap();
|
||||
let session_snapshot = state.session.lock().unwrap().clone();
|
||||
let body = match (login, session_snapshot) {
|
||||
(LoginState::Online, _) => render_online(),
|
||||
(LoginState::Online, _) => render_online(&state.label),
|
||||
(LoginState::NeedsLogin, None) => render_needs_login_idle(),
|
||||
(LoginState::NeedsLogin, Some(session)) => render_login_in_progress(&session),
|
||||
};
|
||||
|
|
@ -75,9 +97,15 @@ async fn index(State(state): State<AppState>) -> Html<String> {
|
|||
))
|
||||
}
|
||||
|
||||
fn render_online() -> String {
|
||||
fn render_online(label: &str) -> String {
|
||||
format!(
|
||||
"<p class=\"status-online\">▓█▓▒░ harness alive — turn loop running ▓█▓▒░</p>\n{LIVE_PANEL}",
|
||||
"<p class=\"status-online\">▓█▓▒░ harness alive — turn loop running ▓█▓▒░</p>\n\
|
||||
<form method=\"POST\" action=\"/send\" class=\"sendform\">\n \
|
||||
<input name=\"body\" placeholder=\"message {label} as operator…\" required autocomplete=\"off\">\n \
|
||||
<button type=\"submit\" class=\"btn btn-send\">◆ S3ND</button>\n\
|
||||
</form>\n\
|
||||
<p class=\"meta\">enqueued with <code>from: operator</code> on this agent's inbox; the next turn picks it up.</p>\n\
|
||||
{LIVE_PANEL}",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -200,6 +228,46 @@ fn render_login_in_progress(session: &Arc<LoginSession>) -> String {
|
|||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SendForm {
|
||||
body: String,
|
||||
}
|
||||
|
||||
async fn post_send(State(state): State<AppState>, Form(form): Form<SendForm>) -> Response {
|
||||
let body = form.body.trim().to_owned();
|
||||
if body.is_empty() {
|
||||
return error_response("send: `body` required");
|
||||
}
|
||||
let result = match state.flavor {
|
||||
Flavor::Agent => match client::request::<_, hive_sh4re::AgentResponse>(
|
||||
&state.socket,
|
||||
&hive_sh4re::AgentRequest::OperatorMsg { body },
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hive_sh4re::AgentResponse::Ok) => Ok(()),
|
||||
Ok(hive_sh4re::AgentResponse::Err { message }) => Err(message),
|
||||
Ok(other) => Err(format!("unexpected response: {other:?}")),
|
||||
Err(e) => Err(format!("transport: {e:#}")),
|
||||
},
|
||||
Flavor::Manager => match client::request::<_, hive_sh4re::ManagerResponse>(
|
||||
&state.socket,
|
||||
&hive_sh4re::ManagerRequest::OperatorMsg { body },
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hive_sh4re::ManagerResponse::Ok) => Ok(()),
|
||||
Ok(hive_sh4re::ManagerResponse::Err { message }) => Err(message),
|
||||
Ok(other) => Err(format!("unexpected response: {other:?}")),
|
||||
Err(e) => Err(format!("transport: {e:#}")),
|
||||
},
|
||||
};
|
||||
match result {
|
||||
Ok(()) => Redirect::to("/").into_response(),
|
||||
Err(e) => error_response(&format!("send failed: {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn events_stream(
|
||||
State(state): State<AppState>,
|
||||
) -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
|
||||
|
|
@ -339,6 +407,17 @@ const STYLE: &str = r#"
|
|||
.btn:hover { background: rgba(204, 102, 255, 0.1); }
|
||||
.btn-login { color: var(--amber); border-color: var(--amber); }
|
||||
.btn-cancel { color: #ff6b6b; border-color: #ff6b6b; font-size: 0.85em; padding: 0.15em 0.6em; }
|
||||
.btn-send { color: var(--green); border-color: var(--green); }
|
||||
.sendform { display: flex; gap: 0.6em; margin-top: 0.5em; }
|
||||
.sendform input {
|
||||
font-family: inherit; font-size: 1em;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: var(--fg);
|
||||
border: 1px solid var(--purple-dim);
|
||||
padding: 0.4em 0.6em;
|
||||
flex: 1;
|
||||
}
|
||||
.sendform input:focus { outline: 1px solid var(--purple); }
|
||||
.loginform { display: flex; gap: 0.6em; margin-top: 0.5em; }
|
||||
.loginform input {
|
||||
font-family: inherit; font-size: 1em;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue