GUI: add Ctrl+B2 'Send to' menu for cross-agent messaging
- Ctrl+middle-click on selection opens cascading menu - Session list → Agent list navigation - ollie.submitTo(sessionId, agentId, prompt) via 9P - Fixed nested Instantiator scope with explicit property capture - Theme colors for ProcManager/PeerManager windows - Kate plugin uses 'default' session instead of 'kate'
This commit is contained in:
parent
9449bd2ba4
commit
dd6755befa
|
|
@ -13,6 +13,9 @@ Column {
|
|||
required property string blockContent
|
||||
required property int index
|
||||
|
||||
// For "Send to" menu
|
||||
property string textToSend: ""
|
||||
|
||||
// Expand state based on block type
|
||||
property bool blockExpanded: {
|
||||
switch (blockType) {
|
||||
|
|
@ -142,7 +145,7 @@ Column {
|
|||
topPadding: 4
|
||||
bottomPadding: 4
|
||||
|
||||
// Acme-style mouse handling: B2=execute, B3=plumb
|
||||
// Acme-style mouse handling: B2=execute, B3=plumb, Ctrl+B2=send to
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.MiddleButton | Qt.RightButton
|
||||
|
|
@ -163,7 +166,13 @@ Column {
|
|||
if (mouse.button === Qt.MiddleButton) {
|
||||
var sel = contentArea.selectedText
|
||||
if (sel && sel.trim() !== "") {
|
||||
ollie.submit(sel.trim())
|
||||
// Ctrl+B2 = Send to another agent
|
||||
if (mouse.modifiers & Qt.ControlModifier) {
|
||||
root.textToSend = sel.trim()
|
||||
sendToMenu.popup()
|
||||
} else {
|
||||
ollie.submit(sel.trim())
|
||||
}
|
||||
}
|
||||
mouse.accepted = true
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
|
|
@ -209,4 +218,36 @@ Column {
|
|||
if (start === end) return ""
|
||||
return text.substring(start, end)
|
||||
}
|
||||
|
||||
// "Send to" cascading menu — session → agent
|
||||
Menu {
|
||||
id: sendToMenu
|
||||
title: "Send to..."
|
||||
|
||||
Instantiator {
|
||||
model: ollie.sessions
|
||||
delegate: Menu {
|
||||
id: sessionMenu
|
||||
required property var modelData
|
||||
title: modelData.name || modelData.id
|
||||
property string sessionId: modelData.id
|
||||
property var agentList: modelData.agents || []
|
||||
|
||||
Instantiator {
|
||||
model: sessionMenu.agentList
|
||||
delegate: MenuItem {
|
||||
required property var modelData
|
||||
text: modelData.name || modelData.id
|
||||
onTriggered: {
|
||||
ollie.submitTo(sessionMenu.sessionId, modelData.id, root.textToSend)
|
||||
}
|
||||
}
|
||||
onObjectAdded: function(index, object) { sessionMenu.insertItem(index, object) }
|
||||
onObjectRemoved: function(index, object) { sessionMenu.removeItem(object) }
|
||||
}
|
||||
}
|
||||
onObjectAdded: function(index, object) { sendToMenu.insertMenu(index, object) }
|
||||
onObjectRemoved: function(index, object) { sendToMenu.removeMenu(object) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ Window {
|
|||
height: 500
|
||||
minimumWidth: 500
|
||||
minimumHeight: 400
|
||||
color: theme.baseColor
|
||||
|
||||
property string currentSessionId: ollie.activeSessionId
|
||||
property string currentAgentId: ollie.activeAgentId
|
||||
|
|
@ -138,8 +139,8 @@ Window {
|
|||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: palette.base
|
||||
border.color: palette.mid
|
||||
color: theme.baseColor
|
||||
border.color: theme.midColor
|
||||
border.width: 1
|
||||
clip: true
|
||||
|
||||
|
|
@ -158,7 +159,7 @@ Window {
|
|||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
ctx.fillStyle = palette.base
|
||||
ctx.fillStyle = theme.baseColor
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
|
||||
ctx.save()
|
||||
|
|
@ -168,7 +169,7 @@ Window {
|
|||
var nodeRadius = 30
|
||||
|
||||
// Draw edges first
|
||||
ctx.strokeStyle = palette.highlight
|
||||
ctx.strokeStyle = theme.highlightColor
|
||||
ctx.lineWidth = 2
|
||||
for (var i = 0; i < agents.length; i++) {
|
||||
var a = agents[i]
|
||||
|
|
@ -221,19 +222,19 @@ Window {
|
|||
|
||||
// Border
|
||||
if (isSelected || isConnecting) {
|
||||
ctx.strokeStyle = palette.highlightedText
|
||||
ctx.strokeStyle = theme.highlightedTextColor
|
||||
ctx.lineWidth = 3
|
||||
} else if (isActive) {
|
||||
ctx.strokeStyle = palette.highlight
|
||||
ctx.strokeStyle = theme.highlightColor
|
||||
ctx.lineWidth = 2
|
||||
} else {
|
||||
ctx.strokeStyle = palette.text
|
||||
ctx.strokeStyle = theme.textColor
|
||||
ctx.lineWidth = 1
|
||||
}
|
||||
ctx.stroke()
|
||||
|
||||
// Label
|
||||
ctx.fillStyle = palette.text
|
||||
ctx.fillStyle = theme.textColor
|
||||
ctx.font = "12px sans-serif"
|
||||
ctx.textAlign = "center"
|
||||
ctx.textBaseline = "middle"
|
||||
|
|
@ -458,7 +459,7 @@ Window {
|
|||
Label {
|
||||
visible: connectingFrom !== null
|
||||
text: "Click another node to connect, or empty space to cancel"
|
||||
color: palette.highlight
|
||||
color: theme.highlightColor
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ Window {
|
|||
height: 450
|
||||
minimumWidth: 400
|
||||
minimumHeight: 300
|
||||
color: theme.baseColor
|
||||
|
||||
property var procs: []
|
||||
property string outputText: ""
|
||||
|
|
@ -127,7 +128,7 @@ Window {
|
|||
delegate: Rectangle {
|
||||
width: procList.width
|
||||
height: 44
|
||||
color: selectedPid === modelData.pid ? palette.highlight : (index % 2 ? palette.alternateBase : palette.base)
|
||||
color: selectedPid === modelData.pid ? theme.highlightColor : (index % 2 ? Qt.darker(theme.baseColor, 1.05) : theme.baseColor)
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
|
|
|||
|
|
@ -480,6 +480,15 @@ bool Ollie9pClient::submit(const QString &prompt)
|
|||
return m_9p->write(agentPath() + "/prompt", prompt.toUtf8());
|
||||
}
|
||||
|
||||
bool Ollie9pClient::submitTo(const QString &sessionId, const QString &agentId, const QString &prompt)
|
||||
{
|
||||
if (sessionId.isEmpty() || agentId.isEmpty() || prompt.trimmed().isEmpty()) return false;
|
||||
if (!m_9p || !m_9p->isConnected()) return false;
|
||||
|
||||
QString path = "session/" + sessionId + "/agent/" + agentId + "/prompt";
|
||||
return m_9p->write(path, prompt.toUtf8());
|
||||
}
|
||||
|
||||
bool Ollie9pClient::interrupt()
|
||||
{
|
||||
if (m_activeSessionId.isEmpty() || m_agentId.isEmpty()) return false;
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ public:
|
|||
Q_INVOKABLE QString readLog();
|
||||
Q_INVOKABLE QString readLogForSession(const QString &sessionId, const QString &agentId);
|
||||
Q_INVOKABLE bool submit(const QString &prompt);
|
||||
Q_INVOKABLE bool submitTo(const QString &sessionId, const QString &agentId, const QString &prompt);
|
||||
Q_INVOKABLE bool interrupt();
|
||||
Q_INVOKABLE bool kill();
|
||||
Q_INVOKABLE bool killSession(const QString &sessionId);
|
||||
|
|
|
|||
|
|
@ -225,26 +225,26 @@ void OllieKateView::ensureAgent()
|
|||
|
||||
QString agentName = agentNameFromCwd(cwd);
|
||||
|
||||
// Ensure kate session exists
|
||||
// Ensure default session exists
|
||||
if (m_sessionId.isEmpty()) {
|
||||
QByteArray resp = m_9p->rdwr("session/new", ("name=kate\ncwd=" + cwd + "\n").toUtf8());
|
||||
QByteArray resp = m_9p->rdwr("session/new", ("name=default\ncwd=" + cwd + "\n").toUtf8());
|
||||
if (resp.isEmpty()) {
|
||||
qWarning() << "Ollie Kate: failed to create session";
|
||||
return;
|
||||
}
|
||||
m_sessionId = "kate";
|
||||
m_sessionId = "default";
|
||||
}
|
||||
|
||||
// Check if agent with this name already exists
|
||||
QByteArray check = m_9p->read("session/kate/agent/" + agentName + "/cfg");
|
||||
QByteArray check = m_9p->read("session/default/agent/" + agentName + "/cfg");
|
||||
if (!check.isEmpty()) {
|
||||
m_agentId = agentName;
|
||||
// Ensure cwd is up to date
|
||||
m_9p->rdwr("session/kate/agent/" + agentName + "/ctl", ("cwd " + cwd).toUtf8());
|
||||
m_9p->rdwr("session/default/agent/" + agentName + "/ctl", ("cwd " + cwd).toUtf8());
|
||||
} else {
|
||||
// Create new agent with computed name
|
||||
QString agentCfg = "name=" + agentName + "\ncwd=" + cwd + "\n";
|
||||
QByteArray resp = m_9p->rdwr("session/kate/agent/new", agentCfg.toUtf8());
|
||||
QByteArray resp = m_9p->rdwr("session/default/agent/new", agentCfg.toUtf8());
|
||||
if (!resp.isEmpty()) {
|
||||
m_agentId = agentName;
|
||||
}
|
||||
|
|
@ -254,10 +254,10 @@ void OllieKateView::ensureAgent()
|
|||
|
||||
bool OllieKateView::selectAgentForProject()
|
||||
{
|
||||
// Check if kate session exists
|
||||
QByteArray sessCheck = m_9p->read("session/kate/id");
|
||||
// Check if default session exists
|
||||
QByteArray sessCheck = m_9p->read("session/default/id");
|
||||
if (sessCheck.isEmpty()) return false;
|
||||
m_sessionId = "kate";
|
||||
m_sessionId = "default";
|
||||
|
||||
QString cwd = projectBaseDir();
|
||||
if (cwd.isEmpty()) return false;
|
||||
|
|
@ -265,11 +265,11 @@ bool OllieKateView::selectAgentForProject()
|
|||
QString agentName = agentNameFromCwd(cwd);
|
||||
|
||||
// Check if agent exists
|
||||
QByteArray check = m_9p->read("session/kate/agent/" + agentName + "/cfg");
|
||||
QByteArray check = m_9p->read("session/default/agent/" + agentName + "/cfg");
|
||||
if (!check.isEmpty()) {
|
||||
m_agentId = agentName;
|
||||
// Ensure cwd is up to date
|
||||
m_9p->rdwr("session/kate/agent/" + agentName + "/ctl", ("cwd " + cwd).toUtf8());
|
||||
m_9p->rdwr("session/default/agent/" + agentName + "/ctl", ("cwd " + cwd).toUtf8());
|
||||
updateStatusLabel();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -282,7 +282,7 @@ bool OllieKateView::selectAgentForProject()
|
|||
|
||||
void OllieKateView::switchToAgent(const QString &agentId)
|
||||
{
|
||||
m_sessionId = "kate";
|
||||
m_sessionId = "default";
|
||||
m_agentId = agentId;
|
||||
updateStatusLabel();
|
||||
}
|
||||
|
|
@ -338,7 +338,7 @@ void OllieKateView::updateStatusLabel()
|
|||
m_statusLabel->setText(QStringLiteral("○ ollie: no agent"));
|
||||
m_statusLabel->setStyleSheet(QStringLiteral("color: gray; padding: 0px 4px;"));
|
||||
} else {
|
||||
m_statusLabel->setText(QStringLiteral("● ollie: kate/") + m_agentId);
|
||||
m_statusLabel->setText(QStringLiteral("● ollie: default/") + m_agentId);
|
||||
m_statusLabel->setStyleSheet(QStringLiteral("color: green; padding: 0px 4px;"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue