diff --git a/opax-web/backend/routes/consoleApi.js b/opax-web/backend/routes/consoleApi.js index ca9129a..c18ae50 100644 --- a/opax-web/backend/routes/consoleApi.js +++ b/opax-web/backend/routes/consoleApi.js @@ -101,4 +101,13 @@ router.get('/capabilities', (req, res) => { res.json(capabilities); }); +router.get('/emma/status', async (req, res) => { + try { + await callAgent('get_health', {}); + res.json({ status: 'ready', label: 'Emma Local', message: 'Emma is ready.' }); + } catch (e) { + res.status(500).json({ status: 'unavailable', label: 'Emma Local', message: 'Emma is unavailable.' }); + } +}); + export default router; diff --git a/opax-web/frontend/src/components/AgentConsoleView.tsx b/opax-web/frontend/src/components/AgentConsoleView.tsx deleted file mode 100644 index a38a591..0000000 --- a/opax-web/frontend/src/components/AgentConsoleView.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React, { useState } from 'react'; - -const AgentConsoleView = () => { - const [message, setMessage] = useState(''); - const [transcript, setTranscript] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [ticketNumber, setTicketNumber] = useState(null); - - const handleSend = async () => { - if (!message.trim()) return; - - const newMessage = { role: 'user', content: message }; - setTranscript(prev => [...prev, newMessage]); - setMessage(''); - setLoading(true); - setError(null); - - try { - const res = await fetch('/api/console/emma/chat', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ message, ticket_number: ticketNumber }), - }); - - if (!res.ok) { - throw new Error('Emma is unavailable.'); - } - - const data = await res.json(); - const emmaMessage = { role: 'assistant', content: data.reply }; - setTranscript(prev => [...prev, emmaMessage]); - - } catch (err) { - setError(err.message); - } finally { - setLoading(false); - } - }; - - return ( -
-

Emma Local

-
- Local model · server-side memory enabled - {ticketNumber && Working context: Gitea #{ticketNumber}} -
-
- {transcript.map((msg, index) => ( -
- {msg.role}: {msg.content} -
- ))} - {loading &&
...
} - {error &&
{error}
} -
-
- setMessage(e.target.value)} - onKeyPress={(e) => e.key === 'Enter' && handleSend()} - placeholder="Chat with Emma..." - disabled={loading} - /> - -
-
- ); -}; - -export default AgentConsoleView; diff --git a/opax-web/frontend/src/components/ApprovalsView.tsx b/opax-web/frontend/src/components/ApprovalsView.tsx deleted file mode 100644 index 5075d7e..0000000 --- a/opax-web/frontend/src/components/ApprovalsView.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; - -const ApprovalsView = () => { - return ( -
-

Approvals

-

No approval read model configured yet.

-
- ); -}; - -export default ApprovalsView; diff --git a/opax-web/frontend/src/components/CapabilityPanel.tsx b/opax-web/frontend/src/components/CapabilityPanel.tsx index 3d6d30a..1430178 100644 --- a/opax-web/frontend/src/components/CapabilityPanel.tsx +++ b/opax-web/frontend/src/components/CapabilityPanel.tsx @@ -1,7 +1,16 @@ import React, { useState, useEffect } from 'react'; +interface Capability { + type: string; +} + +interface Capabilities { + [key: string]: Capability; +} + const CapabilityPanel = () => { - const [capabilities, setCapabilities] = useState({}); + const [capabilities, setCapabilities] = useState({}); + const [isOpen, setIsOpen] = useState(false); useEffect(() => { const fetchCapabilities = async () => { @@ -13,35 +22,36 @@ const CapabilityPanel = () => { }, []); const capabilityGroups = { - 'Platform Status': ['get_health', 'get_build_status', 'get_state', 'get_telemetry'], - 'Git and Gitea': ['list_commits', 'get_file'], - 'Tickets and Work Queue': [], - 'Code Agents': ['run_emma'], - 'Tests and Validation': [], - 'Builds and Deployments': ['trigger_build', 'commit_and_push_files'], - 'Cloud Run and Infrastructure': [], - 'Logs and Telemetry': [], - 'Billing and CostGuard': ['get_billing_summary', 'get_billing_forecast', 'get_billing_anomalies', 'get_billing_budget', 'set_billing_budget'], - 'Customers and Workspace': ['list_customers', 'list_workspace_users', 'create_invite'], + 'Platform': ['get_health', 'get_build_status', 'get_state', 'get_telemetry'], + 'Repository': ['list_commits', 'get_file'], + 'Work and tickets': [], + 'Code agents': ['run_emma'], + 'Validation': [], + 'Deployment': ['trigger_build', 'commit_and_push_files'], + 'Costs': ['get_billing_summary', 'get_billing_forecast', 'get_billing_anomalies', 'get_billing_budget', 'set_billing_budget'], 'Notifications': ['send_email', 'send_sms', 'send_webhook'], + 'Customers and Workspace': ['list_customers', 'list_workspace_users', 'create_invite'], 'Memory and Knowledge': [], }; return ( -
-

Capabilities

- {Object.entries(capabilityGroups).map(([group, capabilityKeys]) => ( -
-

{group}

-
    - {capabilityKeys.map(key => { - const capability = capabilities[key]; - if (!capability) return null; - return
  • {key}
  • - })} -
+
+ +
+

Capabilities

+ {Object.entries(capabilityGroups).map(([group, capabilityKeys]) => ( +
+

{group}

+
    + {capabilityKeys.map(key => { + const capability = capabilities[key]; + if (!capability) return null; + return
  • {key.replace(/_/g, ' ')}
  • + })} +
+
+ ))}
- ))}
); }; diff --git a/opax-web/frontend/src/components/ChatWorkspace.tsx b/opax-web/frontend/src/components/ChatWorkspace.tsx new file mode 100644 index 0000000..670cf03 --- /dev/null +++ b/opax-web/frontend/src/components/ChatWorkspace.tsx @@ -0,0 +1,105 @@ +import React, { useState, useEffect } from 'react'; + +interface Message { + role: string; + content: string; +} + +const ChatWorkspace = () => { + const [message, setMessage] = useState(''); + const [transcript, setTranscript] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [emmaStatus, setEmmaStatus] = useState('unavailable'); + const [ticketNumber, setTicketNumber] = useState(null); + + useEffect(() => { + const fetchEmmaStatus = async () => { + try { + const res = await fetch('/api/console/emma/status', { credentials: 'include' }); + const data = await res.json(); + setEmmaStatus(data.status); + } catch (e) { + setEmmaStatus('unavailable'); + } + }; + fetchEmmaStatus(); + }, []); + + const handleSend = async () => { + if (!message.trim()) return; + + const newMessage: Message = { role: 'user', content: message }; + setTranscript(prev => [...prev, newMessage]); + setMessage(''); + setLoading(true); + setError(null); + + try { + const res = await fetch('/api/console/emma/chat', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + body: JSON.stringify({ message, ticket_number: ticketNumber }), + }); + + if (!res.ok) { + throw new Error('Emma is unavailable.'); + } + + const data = await res.json(); + const emmaMessage: Message = { role: 'assistant', content: data.reply }; + setTranscript(prev => [...prev, emmaMessage]); + + } catch (err: any) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + return ( +
+
+

Emma Local

+ {emmaStatus === 'ready' ? 'Connected' : 'Unavailable'} + {ticketNumber && Gitea #{ticketNumber}} +
+
+ {transcript.length === 0 && !loading && ( +
+

Hei Chris. Hva vil du at jeg skal få gjort?

+
+ + + + + +
+
+ )} + {transcript.map((msg, index) => ( +
+ {msg.role}: {msg.content} +
+ ))} + {loading &&
...
} + {error &&
{error}
} +
+
+