Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- .gitignore +100 -6
- App.tsx +128 -0
- README.md +20 -12
- app.py +15 -11
- components/ChatInterface.tsx +101 -0
- components/ContextManager.tsx +65 -0
- components/LogMonitor.tsx +141 -0
- components/Sidebar.tsx +89 -0
- constants.ts +66 -0
- deployment/idea.md +0 -0
- deployment/jules_prompt_template.md +12 -0
- deployment/tasks.md +0 -0
- index.html +39 -0
- index.tsx +15 -0
- metadata.json +5 -0
- multi_agent_system/jules_agent_client/agent.py +1 -1
- multi_agent_system/llm_client.py +6 -6
- multi_agent_system/monitor_agent/agent.py +74 -78
- multi_agent_system/orchestrator/agent.py +6 -6
- package.json +22 -0
- requirements.txt +1 -1
- services/julesService.ts +59 -0
- tsconfig.json +29 -0
- types.ts +38 -0
- vite.config.ts +23 -0
.gitignore
CHANGED
|
@@ -1,10 +1,104 @@
|
|
| 1 |
-
#
|
| 2 |
__pycache__/
|
| 3 |
-
*.
|
| 4 |
-
*.
|
| 5 |
-
*.pyd
|
| 6 |
|
| 7 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
.env
|
| 9 |
-
venv
|
| 10 |
env/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
|
|
|
| 5 |
|
| 6 |
+
# C extensions
|
| 7 |
+
*.so
|
| 8 |
+
|
| 9 |
+
# Distribution / packaging
|
| 10 |
+
.Python
|
| 11 |
+
build/
|
| 12 |
+
develop-eggs/
|
| 13 |
+
dist/
|
| 14 |
+
downloads/
|
| 15 |
+
eggs/
|
| 16 |
+
.eggs/
|
| 17 |
+
lib/
|
| 18 |
+
lib64/
|
| 19 |
+
parts/
|
| 20 |
+
sdist/
|
| 21 |
+
var/
|
| 22 |
+
wheels/
|
| 23 |
+
*.egg-info/
|
| 24 |
+
.installed.cfg
|
| 25 |
+
*.egg
|
| 26 |
+
MANIFEST
|
| 27 |
+
|
| 28 |
+
# PyInstaller
|
| 29 |
+
# Usually these files are written by a python script from a template
|
| 30 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 31 |
+
*.manifest
|
| 32 |
+
*.spec
|
| 33 |
+
|
| 34 |
+
# Installer logs
|
| 35 |
+
pip-log.txt
|
| 36 |
+
pip-delete-this-directory.txt
|
| 37 |
+
|
| 38 |
+
# Unit test / coverage reports
|
| 39 |
+
htmlcov/
|
| 40 |
+
.tox/
|
| 41 |
+
.coverage
|
| 42 |
+
.coverage.*
|
| 43 |
+
.cache
|
| 44 |
+
nosetests.xml
|
| 45 |
+
coverage.xml
|
| 46 |
+
*.cover
|
| 47 |
+
.hypothesis/
|
| 48 |
+
.pytest_cache/
|
| 49 |
+
|
| 50 |
+
# Translations
|
| 51 |
+
*.mo
|
| 52 |
+
*.pot
|
| 53 |
+
|
| 54 |
+
# Django stuff:
|
| 55 |
+
*.log
|
| 56 |
+
local_settings.py
|
| 57 |
+
db.sqlite3
|
| 58 |
+
|
| 59 |
+
# Flask stuff:
|
| 60 |
+
instance/
|
| 61 |
+
.webassets-cache
|
| 62 |
+
|
| 63 |
+
# Scrapy stuff:
|
| 64 |
+
.scrapy
|
| 65 |
+
|
| 66 |
+
# Sphinx documentation
|
| 67 |
+
docs/_build/
|
| 68 |
+
|
| 69 |
+
# PyBuilder
|
| 70 |
+
target/
|
| 71 |
+
|
| 72 |
+
# Jupyter Notebook
|
| 73 |
+
.ipynb_checkpoints
|
| 74 |
+
|
| 75 |
+
# pyenv
|
| 76 |
+
.python-version
|
| 77 |
+
|
| 78 |
+
# celery beat schedule file
|
| 79 |
+
celerybeat-schedule
|
| 80 |
+
|
| 81 |
+
# SageMath parsed files
|
| 82 |
+
*.sage.py
|
| 83 |
+
|
| 84 |
+
# Environments
|
| 85 |
.env
|
| 86 |
+
.venv
|
| 87 |
env/
|
| 88 |
+
venv/
|
| 89 |
+
ENV/
|
| 90 |
+
env.bak/
|
| 91 |
+
venv.bak/
|
| 92 |
+
|
| 93 |
+
# Spyder project settings
|
| 94 |
+
.spyderproject
|
| 95 |
+
.spyderworkspace
|
| 96 |
+
|
| 97 |
+
# Rope project settings
|
| 98 |
+
.ropeproject
|
| 99 |
+
|
| 100 |
+
# mkdocs documentation
|
| 101 |
+
/site
|
| 102 |
+
|
| 103 |
+
# mypy
|
| 104 |
+
.mypy_cache/
|
App.tsx
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import Sidebar from './components/Sidebar';
|
| 3 |
+
import ChatInterface from './components/ChatInterface';
|
| 4 |
+
import LogMonitor from './components/LogMonitor';
|
| 5 |
+
import ContextManager from './components/ContextManager';
|
| 6 |
+
import { AGENT_CONFIGS } from './constants';
|
| 7 |
+
import { Message, AgentType } from './types';
|
| 8 |
+
import { createJulesSession } from './services/julesService';
|
| 9 |
+
|
| 10 |
+
const App: React.FC = () => {
|
| 11 |
+
const [activeView, setActiveView] = useState('Orchestrator');
|
| 12 |
+
const [messages, setMessages] = useState<Message[]>([
|
| 13 |
+
{
|
| 14 |
+
id: 'welcome',
|
| 15 |
+
sender: AgentType.ORCHESTRATOR,
|
| 16 |
+
content: "Hello! I am your Orchestrator. I can delegate tasks to Jules (Coding), the Developer Agent (Planning), or handle general research. How can I help you today?",
|
| 17 |
+
timestamp: new Date()
|
| 18 |
+
}
|
| 19 |
+
]);
|
| 20 |
+
const [isProcessing, setIsProcessing] = useState(false);
|
| 21 |
+
|
| 22 |
+
const handleSendMessage = async (content: string) => {
|
| 23 |
+
// 1. Add User Message
|
| 24 |
+
const userMsg: Message = {
|
| 25 |
+
id: Date.now().toString(),
|
| 26 |
+
sender: 'User',
|
| 27 |
+
content,
|
| 28 |
+
timestamp: new Date()
|
| 29 |
+
};
|
| 30 |
+
setMessages(prev => [...prev, userMsg]);
|
| 31 |
+
setIsProcessing(true);
|
| 32 |
+
|
| 33 |
+
// 2. Simulate Orchestrator Logic
|
| 34 |
+
// In a real app, this would be an LLM call to decide delegation
|
| 35 |
+
setTimeout(async () => {
|
| 36 |
+
let responseContent = "";
|
| 37 |
+
|
| 38 |
+
const lowerContent = content.toLowerCase();
|
| 39 |
+
|
| 40 |
+
if (lowerContent.includes("plan") || lowerContent.includes("idea")) {
|
| 41 |
+
// Delegate to Developer Agent
|
| 42 |
+
responseContent = "I'll ask the **Developer Agent** to start planning this architecture for you. I'm initializing the A2A connection to `agent-zero-http`...";
|
| 43 |
+
|
| 44 |
+
// Simulate Dev Agent thinking
|
| 45 |
+
setTimeout(() => {
|
| 46 |
+
const devMsg: Message = {
|
| 47 |
+
id: Date.now().toString() + 'dev',
|
| 48 |
+
sender: AgentType.DEVELOPER,
|
| 49 |
+
content: `I've analyzed the request. Based on the "Example MCP Server Configuration", I will outline a plan using the git-agent.\n\n**Plan:**\n1. Clone GitHub Repo\n2. Analyze structure\n3. Create /deployment file\n\nWaiting for confirmation to execute on HuggingFace Space...`,
|
| 50 |
+
timestamp: new Date()
|
| 51 |
+
};
|
| 52 |
+
setMessages(prev => [...prev, devMsg]);
|
| 53 |
+
}, 1500);
|
| 54 |
+
|
| 55 |
+
} else if (lowerContent.includes("code") || lowerContent.includes("jules") || lowerContent.includes("fix")) {
|
| 56 |
+
// Delegate to Jules
|
| 57 |
+
responseContent = "I'm delegating this coding task to **Jules**. I'm initiating a new Jules API session using key `AQ.Ab8RN...`.";
|
| 58 |
+
|
| 59 |
+
// Simulate Jules Session Creation
|
| 60 |
+
try {
|
| 61 |
+
await createJulesSession("Auto-Generated Task", content, "owner/repo");
|
| 62 |
+
setTimeout(() => {
|
| 63 |
+
const julesMsg: Message = {
|
| 64 |
+
id: Date.now().toString() + 'jules',
|
| 65 |
+
sender: AgentType.JULES,
|
| 66 |
+
content: `Session Created.\nSource: GitHub\nContext: /deployment detected.\n\nI am now analyzing the code. I will report back when the PR is ready.`,
|
| 67 |
+
timestamp: new Date()
|
| 68 |
+
};
|
| 69 |
+
setMessages(prev => [...prev, julesMsg]);
|
| 70 |
+
}, 2000);
|
| 71 |
+
} catch (e) {
|
| 72 |
+
console.error(e);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
} else if (lowerContent.includes("monitor") || lowerContent.includes("log")) {
|
| 76 |
+
responseContent = "I will alert the **Monitoring Agent** to track the Jules session and HuggingFace build logs. Switching view to monitoring...";
|
| 77 |
+
setTimeout(() => setActiveView('monitoring'), 1500);
|
| 78 |
+
} else {
|
| 79 |
+
responseContent = "I've noted that. Is this a coding task for Jules or a planning task for the Developer agent?";
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
const orchestratorMsg: Message = {
|
| 83 |
+
id: (Date.now() + 1).toString(),
|
| 84 |
+
sender: AgentType.ORCHESTRATOR,
|
| 85 |
+
content: responseContent,
|
| 86 |
+
timestamp: new Date()
|
| 87 |
+
};
|
| 88 |
+
|
| 89 |
+
setMessages(prev => [...prev, orchestratorMsg]);
|
| 90 |
+
setIsProcessing(false);
|
| 91 |
+
}, 1000);
|
| 92 |
+
};
|
| 93 |
+
|
| 94 |
+
const renderContent = () => {
|
| 95 |
+
switch (activeView) {
|
| 96 |
+
case 'monitoring':
|
| 97 |
+
return <LogMonitor />;
|
| 98 |
+
case 'context':
|
| 99 |
+
return <ContextManager />;
|
| 100 |
+
case 'Orchestrator':
|
| 101 |
+
default:
|
| 102 |
+
// For the demo, other agents also redirect to the main chat but we could filter messages
|
| 103 |
+
return (
|
| 104 |
+
<ChatInterface
|
| 105 |
+
messages={messages}
|
| 106 |
+
onSendMessage={handleSendMessage}
|
| 107 |
+
isProcessing={isProcessing}
|
| 108 |
+
/>
|
| 109 |
+
);
|
| 110 |
+
}
|
| 111 |
+
};
|
| 112 |
+
|
| 113 |
+
return (
|
| 114 |
+
<div className="flex h-screen bg-black text-gray-100 font-sans">
|
| 115 |
+
<Sidebar
|
| 116 |
+
agents={AGENT_CONFIGS}
|
| 117 |
+
activeView={activeView}
|
| 118 |
+
setActiveView={setActiveView}
|
| 119 |
+
/>
|
| 120 |
+
<main className="flex-1 p-4 bg-gray-950 overflow-hidden relative">
|
| 121 |
+
<div className="absolute inset-0 bg-grid-pattern opacity-5 pointer-events-none"></div>
|
| 122 |
+
{renderContent()}
|
| 123 |
+
</main>
|
| 124 |
+
</div>
|
| 125 |
+
);
|
| 126 |
+
};
|
| 127 |
+
|
| 128 |
+
export default App;
|
README.md
CHANGED
|
@@ -1,12 +1,20 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div align="center">
|
| 2 |
+
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
|
| 3 |
+
</div>
|
| 4 |
+
|
| 5 |
+
# Run and deploy your AI Studio app
|
| 6 |
+
|
| 7 |
+
This contains everything you need to run your app locally.
|
| 8 |
+
|
| 9 |
+
View your app in AI Studio: https://ai.studio/apps/drive/1cOgxcY9XRXwQoIScAsa5h9uFcPwztMM1
|
| 10 |
+
|
| 11 |
+
## Run Locally
|
| 12 |
+
|
| 13 |
+
**Prerequisites:** Node.js
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
1. Install dependencies:
|
| 17 |
+
`npm install`
|
| 18 |
+
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
| 19 |
+
3. Run the app:
|
| 20 |
+
`npm run dev`
|
app.py
CHANGED
|
@@ -21,8 +21,10 @@ def stream_orchestrator(msg, history, jules_key, hf_token, blablador_key):
|
|
| 21 |
A "pure" generator that only handles streaming UI updates.
|
| 22 |
It returns the data needed for the background task.
|
| 23 |
"""
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
| 26 |
|
| 27 |
api_keys = {
|
| 28 |
"JULES_API_KEY": os.environ.get("JULES_API_KEY", jules_key),
|
|
@@ -31,7 +33,8 @@ def stream_orchestrator(msg, history, jules_key, hf_token, blablador_key):
|
|
| 31 |
}
|
| 32 |
for key, value in api_keys.items():
|
| 33 |
if not value:
|
| 34 |
-
|
|
|
|
| 35 |
yield history, None
|
| 36 |
return
|
| 37 |
os.environ[key] = value
|
|
@@ -39,12 +42,12 @@ def stream_orchestrator(msg, history, jules_key, hf_token, blablador_key):
|
|
| 39 |
workflow_generator = main_orchestration_workflow()
|
| 40 |
monitor_data = None
|
| 41 |
final_bot_message = ""
|
| 42 |
-
|
| 43 |
try:
|
| 44 |
-
|
| 45 |
-
|
| 46 |
final_bot_message += update
|
| 47 |
-
history[-1][
|
| 48 |
yield history, None
|
| 49 |
except StopIteration as e:
|
| 50 |
monitor_data = e.value
|
|
@@ -53,10 +56,11 @@ def stream_orchestrator(msg, history, jules_key, hf_token, blablador_key):
|
|
| 53 |
if key in os.environ:
|
| 54 |
del os.environ[key]
|
| 55 |
|
|
|
|
| 56 |
if monitor_data:
|
| 57 |
-
history[-1][
|
| 58 |
else:
|
| 59 |
-
history[-1][
|
| 60 |
|
| 61 |
yield history, monitor_data
|
| 62 |
|
|
@@ -96,13 +100,13 @@ with gr.Blocks(title="Desk Agent") as demo:
|
|
| 96 |
|
| 97 |
chatbot = gr.Chatbot(label="Agent Conversation", height=500)
|
| 98 |
msg_input = gr.Textbox(label="Your Message", placeholder="Press 'Start Workflow' to begin...")
|
| 99 |
-
|
| 100 |
with gr.Accordion("API Key Overrides (Optional)", open=False):
|
| 101 |
gr.Markdown("API keys will be automatically loaded from Space secrets if available.")
|
| 102 |
jules_key_input = gr.Textbox(label="Jules API Key", type="password")
|
| 103 |
hf_token_input = gr.Textbox(label="Hugging Face Token", type="password")
|
| 104 |
blablador_key_input = gr.Textbox(label="Blablador API Key", type="password")
|
| 105 |
-
|
| 106 |
start_button = gr.Button("Start Workflow", variant="primary")
|
| 107 |
clear_button = gr.ClearButton([msg_input, chatbot])
|
| 108 |
|
|
|
|
| 21 |
A "pure" generator that only handles streaming UI updates.
|
| 22 |
It returns the data needed for the background task.
|
| 23 |
"""
|
| 24 |
+
# Append user message and initial empty bot message
|
| 25 |
+
history.append({"role": "user", "content": msg})
|
| 26 |
+
history.append({"role": "assistant", "content": ""})
|
| 27 |
+
yield history, None # Yield initial history
|
| 28 |
|
| 29 |
api_keys = {
|
| 30 |
"JULES_API_KEY": os.environ.get("JULES_API_KEY", jules_key),
|
|
|
|
| 33 |
}
|
| 34 |
for key, value in api_keys.items():
|
| 35 |
if not value:
|
| 36 |
+
error_msg = f"Error: {key} is not set. Please provide it as a Space secret or in the UI."
|
| 37 |
+
history[-1]["content"] = error_msg # Update bot message with error
|
| 38 |
yield history, None
|
| 39 |
return
|
| 40 |
os.environ[key] = value
|
|
|
|
| 42 |
workflow_generator = main_orchestration_workflow()
|
| 43 |
monitor_data = None
|
| 44 |
final_bot_message = ""
|
| 45 |
+
|
| 46 |
try:
|
| 47 |
+
# Stream updates to the bot's message (the last item in history)
|
| 48 |
+
for update in workflow_generator:
|
| 49 |
final_bot_message += update
|
| 50 |
+
history[-1]["content"] = final_bot_message
|
| 51 |
yield history, None
|
| 52 |
except StopIteration as e:
|
| 53 |
monitor_data = e.value
|
|
|
|
| 56 |
if key in os.environ:
|
| 57 |
del os.environ[key]
|
| 58 |
|
| 59 |
+
# Append final status to the bot's message
|
| 60 |
if monitor_data:
|
| 61 |
+
history[-1]["content"] = final_bot_message + "\n\n**Orchestration complete. Starting background monitoring...**"
|
| 62 |
else:
|
| 63 |
+
history[-1]["content"] = final_bot_message + "\n\n**Workflow finished with an error. Could not start monitor.**"
|
| 64 |
|
| 65 |
yield history, monitor_data
|
| 66 |
|
|
|
|
| 100 |
|
| 101 |
chatbot = gr.Chatbot(label="Agent Conversation", height=500)
|
| 102 |
msg_input = gr.Textbox(label="Your Message", placeholder="Press 'Start Workflow' to begin...")
|
| 103 |
+
|
| 104 |
with gr.Accordion("API Key Overrides (Optional)", open=False):
|
| 105 |
gr.Markdown("API keys will be automatically loaded from Space secrets if available.")
|
| 106 |
jules_key_input = gr.Textbox(label="Jules API Key", type="password")
|
| 107 |
hf_token_input = gr.Textbox(label="Hugging Face Token", type="password")
|
| 108 |
blablador_key_input = gr.Textbox(label="Blablador API Key", type="password")
|
| 109 |
+
|
| 110 |
start_button = gr.Button("Start Workflow", variant="primary")
|
| 111 |
clear_button = gr.ClearButton([msg_input, chatbot])
|
| 112 |
|
components/ChatInterface.tsx
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useRef, useEffect } from 'react';
|
| 2 |
+
import { Message, AgentType } from '../types';
|
| 3 |
+
import { Send, User, Bot, Loader2 } from 'lucide-react';
|
| 4 |
+
|
| 5 |
+
interface ChatInterfaceProps {
|
| 6 |
+
messages: Message[];
|
| 7 |
+
onSendMessage: (content: string) => void;
|
| 8 |
+
isProcessing: boolean;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
const ChatInterface: React.FC<ChatInterfaceProps> = ({ messages, onSendMessage, isProcessing }) => {
|
| 12 |
+
const [input, setInput] = useState('');
|
| 13 |
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
| 14 |
+
|
| 15 |
+
const scrollToBottom = () => {
|
| 16 |
+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
useEffect(() => {
|
| 20 |
+
scrollToBottom();
|
| 21 |
+
}, [messages]);
|
| 22 |
+
|
| 23 |
+
const handleSubmit = (e: React.FormEvent) => {
|
| 24 |
+
e.preventDefault();
|
| 25 |
+
if (!input.trim() || isProcessing) return;
|
| 26 |
+
onSendMessage(input);
|
| 27 |
+
setInput('');
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
const renderMessageIcon = (sender: string) => {
|
| 31 |
+
if (sender === 'User') return <User className="w-5 h-5 text-gray-300" />;
|
| 32 |
+
return <Bot className="w-5 h-5 text-blue-400" />;
|
| 33 |
+
};
|
| 34 |
+
|
| 35 |
+
return (
|
| 36 |
+
<div className="flex flex-col h-full bg-gray-950 rounded-xl overflow-hidden shadow-2xl border border-gray-800">
|
| 37 |
+
{/* Header */}
|
| 38 |
+
<div className="p-4 border-b border-gray-800 bg-gray-900/50 backdrop-blur">
|
| 39 |
+
<h2 className="text-lg font-semibold text-white">Orchestration Chat</h2>
|
| 40 |
+
<p className="text-xs text-gray-400">Interacting with Orchestrator Agent (Memory Active)</p>
|
| 41 |
+
</div>
|
| 42 |
+
|
| 43 |
+
{/* Messages */}
|
| 44 |
+
<div className="flex-1 overflow-y-auto p-4 space-y-6">
|
| 45 |
+
{messages.map((msg) => (
|
| 46 |
+
<div
|
| 47 |
+
key={msg.id}
|
| 48 |
+
className={`flex gap-3 ${msg.sender === 'User' ? 'flex-row-reverse' : 'flex-row'}`}
|
| 49 |
+
>
|
| 50 |
+
<div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
|
| 51 |
+
msg.sender === 'User' ? 'bg-gray-700' : 'bg-blue-900/30'
|
| 52 |
+
}`}>
|
| 53 |
+
{renderMessageIcon(msg.sender)}
|
| 54 |
+
</div>
|
| 55 |
+
|
| 56 |
+
<div className={`max-w-[80%] rounded-lg p-3 text-sm leading-relaxed ${
|
| 57 |
+
msg.sender === 'User'
|
| 58 |
+
? 'bg-gray-800 text-white'
|
| 59 |
+
: 'bg-blue-900/10 text-gray-200 border border-blue-900/30'
|
| 60 |
+
}`}>
|
| 61 |
+
<div className="flex items-center gap-2 mb-1">
|
| 62 |
+
<span className="font-bold text-xs opacity-70">{msg.sender}</span>
|
| 63 |
+
<span className="text-[10px] opacity-40">{msg.timestamp.toLocaleTimeString()}</span>
|
| 64 |
+
</div>
|
| 65 |
+
<div className="whitespace-pre-wrap">{msg.content}</div>
|
| 66 |
+
</div>
|
| 67 |
+
</div>
|
| 68 |
+
))}
|
| 69 |
+
{isProcessing && (
|
| 70 |
+
<div className="flex items-center gap-2 text-gray-500 text-sm pl-12">
|
| 71 |
+
<Loader2 className="w-4 h-4 animate-spin" />
|
| 72 |
+
<span>Orchestrator is delegating...</span>
|
| 73 |
+
</div>
|
| 74 |
+
)}
|
| 75 |
+
<div ref={messagesEndRef} />
|
| 76 |
+
</div>
|
| 77 |
+
|
| 78 |
+
{/* Input */}
|
| 79 |
+
<div className="p-4 bg-gray-900 border-t border-gray-800">
|
| 80 |
+
<form onSubmit={handleSubmit} className="relative">
|
| 81 |
+
<input
|
| 82 |
+
type="text"
|
| 83 |
+
value={input}
|
| 84 |
+
onChange={(e) => setInput(e.target.value)}
|
| 85 |
+
placeholder="Describe your task (e.g., 'Plan a new health app' or 'Ask Jules to fix the bug')..."
|
| 86 |
+
className="w-full bg-gray-800 border-gray-700 text-white rounded-lg pl-4 pr-12 py-3 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:border-transparent placeholder-gray-500"
|
| 87 |
+
/>
|
| 88 |
+
<button
|
| 89 |
+
type="submit"
|
| 90 |
+
disabled={!input.trim() || isProcessing}
|
| 91 |
+
className="absolute right-2 top-2 p-1.5 bg-blue-600 hover:bg-blue-500 text-white rounded-md disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
| 92 |
+
>
|
| 93 |
+
<Send className="w-5 h-5" />
|
| 94 |
+
</button>
|
| 95 |
+
</form>
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
);
|
| 99 |
+
};
|
| 100 |
+
|
| 101 |
+
export default ChatInterface;
|
components/ContextManager.tsx
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import { INITIAL_CONTEXT_PLACEHOLDERS } from '../constants';
|
| 3 |
+
import { Save, FileText } from 'lucide-react';
|
| 4 |
+
|
| 5 |
+
const ContextManager: React.FC = () => {
|
| 6 |
+
const [contexts, setContexts] = useState(INITIAL_CONTEXT_PLACEHOLDERS);
|
| 7 |
+
const [activeTab, setActiveTab] = useState<keyof typeof INITIAL_CONTEXT_PLACEHOLDERS>('julesContext');
|
| 8 |
+
|
| 9 |
+
const handleChange = (key: keyof typeof INITIAL_CONTEXT_PLACEHOLDERS, value: string) => {
|
| 10 |
+
setContexts(prev => ({ ...prev, [key]: value }));
|
| 11 |
+
};
|
| 12 |
+
|
| 13 |
+
const tabs = [
|
| 14 |
+
{ key: 'julesContext', label: 'Jules Context' },
|
| 15 |
+
{ key: 'devPlanContext', label: 'Dev Plan Context' },
|
| 16 |
+
{ key: 'deploymentContext', label: 'Deployment Context' },
|
| 17 |
+
{ key: 'monitoringContext', label: 'Monitoring Context' },
|
| 18 |
+
];
|
| 19 |
+
|
| 20 |
+
return (
|
| 21 |
+
<div className="bg-gray-900 rounded-xl border border-gray-800 h-full flex flex-col overflow-hidden">
|
| 22 |
+
<div className="p-4 border-b border-gray-800 flex justify-between items-center">
|
| 23 |
+
<div>
|
| 24 |
+
<h2 className="text-lg font-bold text-white">Context Management</h2>
|
| 25 |
+
<p className="text-sm text-gray-400">Manage context files injected into agents.</p>
|
| 26 |
+
</div>
|
| 27 |
+
<button className="flex items-center gap-2 px-3 py-1.5 bg-green-600/20 text-green-400 border border-green-600/30 rounded-md hover:bg-green-600/30 transition-colors text-sm">
|
| 28 |
+
<Save className="w-4 h-4" />
|
| 29 |
+
<span>Save All</span>
|
| 30 |
+
</button>
|
| 31 |
+
</div>
|
| 32 |
+
|
| 33 |
+
<div className="flex border-b border-gray-800 overflow-x-auto">
|
| 34 |
+
{tabs.map(tab => (
|
| 35 |
+
<button
|
| 36 |
+
key={tab.key}
|
| 37 |
+
onClick={() => setActiveTab(tab.key as any)}
|
| 38 |
+
className={`px-4 py-3 text-sm font-medium flex items-center gap-2 transition-colors whitespace-nowrap ${
|
| 39 |
+
activeTab === tab.key
|
| 40 |
+
? 'text-white border-b-2 border-blue-500 bg-gray-800'
|
| 41 |
+
: 'text-gray-400 hover:text-white hover:bg-gray-800/50'
|
| 42 |
+
}`}
|
| 43 |
+
>
|
| 44 |
+
<FileText className="w-4 h-4" />
|
| 45 |
+
{tab.label}
|
| 46 |
+
</button>
|
| 47 |
+
))}
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
+
<div className="flex-1 p-0 relative">
|
| 51 |
+
<textarea
|
| 52 |
+
value={contexts[activeTab]}
|
| 53 |
+
onChange={(e) => handleChange(activeTab, e.target.value)}
|
| 54 |
+
className="w-full h-full bg-gray-950 text-gray-300 p-4 font-mono text-sm resize-none focus:outline-none"
|
| 55 |
+
spellCheck={false}
|
| 56 |
+
/>
|
| 57 |
+
<div className="absolute bottom-4 right-4 text-xs text-gray-600 pointer-events-none">
|
| 58 |
+
Markdown Supported
|
| 59 |
+
</div>
|
| 60 |
+
</div>
|
| 61 |
+
</div>
|
| 62 |
+
);
|
| 63 |
+
};
|
| 64 |
+
|
| 65 |
+
export default ContextManager;
|
components/LogMonitor.tsx
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useEffect, useState } from 'react';
|
| 2 |
+
import { MonitoringLog } from '../types';
|
| 3 |
+
import { Terminal, AlertTriangle, CheckCircle, Clock, FileText } from 'lucide-react';
|
| 4 |
+
|
| 5 |
+
const LogMonitor: React.FC = () => {
|
| 6 |
+
const [logs, setLogs] = useState<MonitoringLog[]>([]);
|
| 7 |
+
const [summary, setSummary] = useState<string>("Waiting for logs to generate summary...");
|
| 8 |
+
const [isMonitoring, setIsMonitoring] = useState(false);
|
| 9 |
+
|
| 10 |
+
// Simulate incoming logs
|
| 11 |
+
useEffect(() => {
|
| 12 |
+
let interval: ReturnType<typeof setInterval>;
|
| 13 |
+
|
| 14 |
+
if (isMonitoring) {
|
| 15 |
+
interval = setInterval(() => {
|
| 16 |
+
const sources: ('JulesAPI' | 'HuggingFace' | 'System')[] = ['JulesAPI', 'HuggingFace', 'System'];
|
| 17 |
+
const levels: ('INFO' | 'WARN' | 'ERROR')[] = ['INFO', 'INFO', 'INFO', 'WARN', 'INFO']; // Mostly info
|
| 18 |
+
|
| 19 |
+
const randomSource = sources[Math.floor(Math.random() * sources.length)];
|
| 20 |
+
const randomLevel = levels[Math.floor(Math.random() * levels.length)];
|
| 21 |
+
|
| 22 |
+
const newLog: MonitoringLog = {
|
| 23 |
+
id: Math.random().toString(36),
|
| 24 |
+
timestamp: new Date().toISOString(),
|
| 25 |
+
source: randomSource,
|
| 26 |
+
level: randomLevel,
|
| 27 |
+
message: generateMockLogMessage(randomSource, randomLevel)
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
setLogs(prev => {
|
| 31 |
+
const newLogs = [newLog, ...prev].slice(0, 100);
|
| 32 |
+
updateSummary(newLogs); // Trigger summary update
|
| 33 |
+
return newLogs;
|
| 34 |
+
});
|
| 35 |
+
}, 3000);
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
return () => clearInterval(interval);
|
| 39 |
+
}, [isMonitoring]);
|
| 40 |
+
|
| 41 |
+
const generateMockLogMessage = (source: string, level: string) => {
|
| 42 |
+
if (source === 'JulesAPI') {
|
| 43 |
+
return level === 'ERROR'
|
| 44 |
+
? 'Session ID #8492 connection timeout.'
|
| 45 |
+
: 'Received activity update: Code generation in progress.';
|
| 46 |
+
}
|
| 47 |
+
if (source === 'HuggingFace') {
|
| 48 |
+
return level === 'WARN'
|
| 49 |
+
? 'Build container utilizing 85% memory.'
|
| 50 |
+
: 'Successfully pulled layer sha256:e7c96db...';
|
| 51 |
+
}
|
| 52 |
+
return 'Health check ping passed.';
|
| 53 |
+
};
|
| 54 |
+
|
| 55 |
+
const updateSummary = (currentLogs: MonitoringLog[]) => {
|
| 56 |
+
const errors = currentLogs.filter(l => l.level === 'ERROR').length;
|
| 57 |
+
const warns = currentLogs.filter(l => l.level === 'WARN').length;
|
| 58 |
+
const latest = currentLogs[0];
|
| 59 |
+
|
| 60 |
+
setSummary(`Status: ${errors > 0 ? 'Issues Detected' : 'Healthy'}.
|
| 61 |
+
Found ${errors} errors and ${warns} warnings in the last batch.
|
| 62 |
+
Latest activity from ${latest?.source || 'System'}: ${latest?.message || 'None'}.
|
| 63 |
+
Sending condensed report to Jules...`);
|
| 64 |
+
};
|
| 65 |
+
|
| 66 |
+
return (
|
| 67 |
+
<div className="bg-gray-900 rounded-xl border border-gray-800 h-full flex flex-col">
|
| 68 |
+
<div className="p-4 border-b border-gray-800 flex justify-between items-center bg-gray-900/50">
|
| 69 |
+
<div className="flex items-center gap-3">
|
| 70 |
+
<div className="p-2 bg-gray-800 rounded-lg">
|
| 71 |
+
<Terminal className="w-5 h-5 text-emerald-400" />
|
| 72 |
+
</div>
|
| 73 |
+
<div>
|
| 74 |
+
<h2 className="text-lg font-bold text-white">Live Monitor</h2>
|
| 75 |
+
<div className="flex items-center gap-2 text-xs text-gray-400">
|
| 76 |
+
<span>Monitoring Agent Active</span>
|
| 77 |
+
<span className="w-1 h-1 bg-gray-500 rounded-full"></span>
|
| 78 |
+
<span>Polling Interval: 10s</span>
|
| 79 |
+
</div>
|
| 80 |
+
</div>
|
| 81 |
+
</div>
|
| 82 |
+
<button
|
| 83 |
+
onClick={() => setIsMonitoring(!isMonitoring)}
|
| 84 |
+
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
| 85 |
+
isMonitoring
|
| 86 |
+
? 'bg-red-500/10 text-red-400 border border-red-500/20 hover:bg-red-500/20'
|
| 87 |
+
: 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 hover:bg-emerald-500/20'
|
| 88 |
+
}`}
|
| 89 |
+
>
|
| 90 |
+
{isMonitoring ? 'Stop Monitoring' : 'Start Monitoring'}
|
| 91 |
+
</button>
|
| 92 |
+
</div>
|
| 93 |
+
|
| 94 |
+
{/* Summary Panel */}
|
| 95 |
+
<div className="bg-blue-900/10 border-b border-blue-900/20 p-4">
|
| 96 |
+
<div className="flex items-center gap-2 mb-2">
|
| 97 |
+
<FileText className="w-4 h-4 text-blue-400" />
|
| 98 |
+
<h3 className="text-sm font-semibold text-blue-300">Agent Summary (For Jules)</h3>
|
| 99 |
+
</div>
|
| 100 |
+
<p className="text-sm text-gray-300 font-mono leading-relaxed whitespace-pre-line">
|
| 101 |
+
{isMonitoring ? summary : "Start monitoring to generate summaries..."}
|
| 102 |
+
</p>
|
| 103 |
+
</div>
|
| 104 |
+
|
| 105 |
+
<div className="flex-1 overflow-y-auto p-2 font-mono text-sm bg-black/50 space-y-1">
|
| 106 |
+
{logs.length === 0 ? (
|
| 107 |
+
<div className="h-full flex flex-col items-center justify-center text-gray-500 gap-2">
|
| 108 |
+
<Clock className="w-8 h-8 opacity-50" />
|
| 109 |
+
<p>Waiting for logs stream...</p>
|
| 110 |
+
</div>
|
| 111 |
+
) : (
|
| 112 |
+
logs.map((log) => (
|
| 113 |
+
<div key={log.id} className="flex gap-3 hover:bg-white/5 p-1.5 rounded transition-colors group">
|
| 114 |
+
<span className="text-gray-500 text-xs w-32 shrink-0">{log.timestamp.split('T')[1].slice(0, 12)}</span>
|
| 115 |
+
<span className={`text-xs font-bold w-24 shrink-0 uppercase ${
|
| 116 |
+
log.source === 'JulesAPI' ? 'text-blue-400' :
|
| 117 |
+
log.source === 'HuggingFace' ? 'text-yellow-400' : 'text-purple-400'
|
| 118 |
+
}`}>
|
| 119 |
+
{log.source}
|
| 120 |
+
</span>
|
| 121 |
+
<span className={`text-xs font-bold w-12 shrink-0 ${
|
| 122 |
+
log.level === 'ERROR' ? 'text-red-500' :
|
| 123 |
+
log.level === 'WARN' ? 'text-orange-400' : 'text-gray-400'
|
| 124 |
+
}`}>
|
| 125 |
+
[{log.level}]
|
| 126 |
+
</span>
|
| 127 |
+
<span className="text-gray-300 break-all">{log.message}</span>
|
| 128 |
+
</div>
|
| 129 |
+
))
|
| 130 |
+
)}
|
| 131 |
+
</div>
|
| 132 |
+
|
| 133 |
+
<div className="p-3 border-t border-gray-800 bg-gray-900 text-xs text-gray-500 flex justify-between">
|
| 134 |
+
<span>Connected to gradio_logsview</span>
|
| 135 |
+
<span>Mode: Summarization</span>
|
| 136 |
+
</div>
|
| 137 |
+
</div>
|
| 138 |
+
);
|
| 139 |
+
};
|
| 140 |
+
|
| 141 |
+
export default LogMonitor;
|
components/Sidebar.tsx
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { AgentConfig, AgentType } from '../types';
|
| 3 |
+
import { Bot, Code2, BrainCircuit, Activity, Settings, FileText } from 'lucide-react';
|
| 4 |
+
|
| 5 |
+
interface SidebarProps {
|
| 6 |
+
agents: AgentConfig[];
|
| 7 |
+
activeView: string;
|
| 8 |
+
setActiveView: (view: string) => void;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
const Sidebar: React.FC<SidebarProps> = ({ agents, activeView, setActiveView }) => {
|
| 12 |
+
|
| 13 |
+
const getIcon = (type: AgentType) => {
|
| 14 |
+
switch (type) {
|
| 15 |
+
case AgentType.ORCHESTRATOR: return <BrainCircuit className="w-5 h-5" />;
|
| 16 |
+
case AgentType.JULES: return <Code2 className="w-5 h-5" />;
|
| 17 |
+
case AgentType.DEVELOPER: return <Settings className="w-5 h-5" />;
|
| 18 |
+
case AgentType.GENERAL: return <Bot className="w-5 h-5" />;
|
| 19 |
+
default: return <Bot className="w-5 h-5" />;
|
| 20 |
+
}
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
return (
|
| 24 |
+
<div className="w-64 bg-gray-900 border-r border-gray-800 h-screen flex flex-col p-4">
|
| 25 |
+
<div className="flex items-center gap-2 mb-8 px-2">
|
| 26 |
+
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
|
| 27 |
+
<span className="font-bold text-white">O4</span>
|
| 28 |
+
</div>
|
| 29 |
+
<h1 className="text-xl font-bold tracking-tight text-white">Orchestrator</h1>
|
| 30 |
+
</div>
|
| 31 |
+
|
| 32 |
+
<div className="space-y-1 mb-6">
|
| 33 |
+
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2 px-2">Agents</h3>
|
| 34 |
+
{agents.map((agent) => (
|
| 35 |
+
<button
|
| 36 |
+
key={agent.name}
|
| 37 |
+
onClick={() => setActiveView(agent.name)}
|
| 38 |
+
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors text-sm ${
|
| 39 |
+
activeView === agent.name
|
| 40 |
+
? 'bg-blue-600/10 text-blue-400 border border-blue-600/20'
|
| 41 |
+
: 'text-gray-400 hover:bg-gray-800 hover:text-gray-200'
|
| 42 |
+
}`}
|
| 43 |
+
>
|
| 44 |
+
{getIcon(agent.type)}
|
| 45 |
+
<div className="flex flex-col items-start">
|
| 46 |
+
<span>{agent.name}</span>
|
| 47 |
+
<span className="text-[10px] text-gray-600">{agent.status}</span>
|
| 48 |
+
</div>
|
| 49 |
+
</button>
|
| 50 |
+
))}
|
| 51 |
+
</div>
|
| 52 |
+
|
| 53 |
+
<div className="space-y-1">
|
| 54 |
+
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2 px-2">System</h3>
|
| 55 |
+
<button
|
| 56 |
+
onClick={() => setActiveView('monitoring')}
|
| 57 |
+
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors text-sm ${
|
| 58 |
+
activeView === 'monitoring'
|
| 59 |
+
? 'bg-emerald-600/10 text-emerald-400 border border-emerald-600/20'
|
| 60 |
+
: 'text-gray-400 hover:bg-gray-800 hover:text-gray-200'
|
| 61 |
+
}`}
|
| 62 |
+
>
|
| 63 |
+
<Activity className="w-5 h-5" />
|
| 64 |
+
<span>Monitoring</span>
|
| 65 |
+
</button>
|
| 66 |
+
<button
|
| 67 |
+
onClick={() => setActiveView('context')}
|
| 68 |
+
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors text-sm ${
|
| 69 |
+
activeView === 'context'
|
| 70 |
+
? 'bg-purple-600/10 text-purple-400 border border-purple-600/20'
|
| 71 |
+
: 'text-gray-400 hover:bg-gray-800 hover:text-gray-200'
|
| 72 |
+
}`}
|
| 73 |
+
>
|
| 74 |
+
<FileText className="w-5 h-5" />
|
| 75 |
+
<span>Context Data</span>
|
| 76 |
+
</button>
|
| 77 |
+
</div>
|
| 78 |
+
|
| 79 |
+
<div className="mt-auto pt-4 border-t border-gray-800">
|
| 80 |
+
<div className="flex items-center gap-2 px-2">
|
| 81 |
+
<div className="w-2 h-2 rounded-full bg-green-500 animate-pulse"></div>
|
| 82 |
+
<span className="text-xs text-gray-500">System Online</span>
|
| 83 |
+
</div>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
);
|
| 87 |
+
};
|
| 88 |
+
|
| 89 |
+
export default Sidebar;
|
constants.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { AgentType, AgentConfig } from './types';
|
| 2 |
+
|
| 3 |
+
// NOTE: In a production environment, keys should be in env variables.
|
| 4 |
+
// Using provided keys for the generated artifact as requested.
|
| 5 |
+
export const JULES_API_KEY = "AQ.Ab8RN6JVE9dY_3MhEkAuV3XqZUPXP0jVxG8MjNEJSFDdg6QmsQ";
|
| 6 |
+
export const JULES_API_BASE = "https://jules.googleapis.com/v1alpha";
|
| 7 |
+
|
| 8 |
+
export const AGENT_CONFIGS: AgentConfig[] = [
|
| 9 |
+
{
|
| 10 |
+
name: "Orchestrator",
|
| 11 |
+
type: AgentType.ORCHESTRATOR,
|
| 12 |
+
description: "Secretary-like coordinator with memory. Delegates tasks.",
|
| 13 |
+
status: 'idle'
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
name: "Jules",
|
| 17 |
+
type: AgentType.JULES,
|
| 18 |
+
endpoint: JULES_API_BASE,
|
| 19 |
+
description: "Specialized coding agent. Works on GitHub context.",
|
| 20 |
+
status: 'idle'
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
name: "Agent-Zero (Dev)",
|
| 24 |
+
type: AgentType.DEVELOPER,
|
| 25 |
+
endpoint: "https://harvesthealth-agent-with-cluade-skills.hf.space/mcp/t-zbg1GXJPryStqwCM/sse",
|
| 26 |
+
description: "Plans coding projects and architecture.",
|
| 27 |
+
status: 'idle'
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
name: "General Agent",
|
| 31 |
+
type: AgentType.GENERAL,
|
| 32 |
+
endpoint: "https://leon4gr45-agent-mcp-new-standard.hf.space/a2a/t-VeVubvblNRxNLxSp",
|
| 33 |
+
description: "Non-coding tasks (Research, Notion, DB).",
|
| 34 |
+
status: 'idle'
|
| 35 |
+
}
|
| 36 |
+
];
|
| 37 |
+
|
| 38 |
+
export const INITIAL_CONTEXT_PLACEHOLDERS = {
|
| 39 |
+
julesContext: `## Jules Agent Context
|
| 40 |
+
- **Role**: Coding Specialist
|
| 41 |
+
- **Integration**: GitHub API (Requires Token)
|
| 42 |
+
- **Constraint**: Must use specific prompt format for remote repo access.
|
| 43 |
+
- **Workflow**: Receive plan -> Implement in Branch -> Auto Create PR.`,
|
| 44 |
+
|
| 45 |
+
devPlanContext: `## Developer Agent Context (Planning)
|
| 46 |
+
- **Role**: Architect & Planner
|
| 47 |
+
- **Output**: JSON/Markdown deployment plans.
|
| 48 |
+
- **Resources**: HuggingFace Deployment Sheets, Log Retrieval Sheets.
|
| 49 |
+
- **Task**: Analyze cloned repo, create /deployment file.`,
|
| 50 |
+
|
| 51 |
+
deploymentContext: `## Deployment & Infrastructure Context
|
| 52 |
+
- **Target**: HuggingFace Spaces.
|
| 53 |
+
- **Tools**: git-agent, hf_transfer.
|
| 54 |
+
- **Secrets**: HF_TOKEN, GITHUB_TOKEN.
|
| 55 |
+
- **Process**:
|
| 56 |
+
1. Clone Repo
|
| 57 |
+
2. Add Dockerfile/Requirements
|
| 58 |
+
3. Push to Space`,
|
| 59 |
+
|
| 60 |
+
monitoringContext: `## Monitoring Agent Context
|
| 61 |
+
- **Target**: Jules Sessions & HF Runtime Logs.
|
| 62 |
+
- **Frequency**: Every 10 minutes.
|
| 63 |
+
- **Tool**: gradio_logsview for Python/Command logs.
|
| 64 |
+
- **Action**: Analyze raw logs and generate a **concise summary** of errors and status.
|
| 65 |
+
- **Constraint**: Do NOT copy-paste raw logs. Summarize findings for Jules API to optimize token usage.`
|
| 66 |
+
};
|
deployment/idea.md
ADDED
|
File without changes
|
deployment/jules_prompt_template.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Based on the plan from the developer agent, please implement the following in the `{repository_name}` repository.
|
| 2 |
+
|
| 3 |
+
**High-Level Idea:**
|
| 4 |
+
{high_level_idea}
|
| 5 |
+
|
| 6 |
+
**Detailed Tasks:**
|
| 7 |
+
{detailed_tasks}
|
| 8 |
+
|
| 9 |
+
**Relevant Context Files:**
|
| 10 |
+
{context_files}
|
| 11 |
+
|
| 12 |
+
Please proceed with the implementation. A monitoring agent will be observing the deployment logs for any errors.
|
deployment/tasks.md
ADDED
|
File without changes
|
index.html
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>Orchestrator-4 Agent System</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<style>
|
| 9 |
+
/* Custom scrollbar for a cleaner UI */
|
| 10 |
+
::-webkit-scrollbar {
|
| 11 |
+
width: 8px;
|
| 12 |
+
height: 8px;
|
| 13 |
+
}
|
| 14 |
+
::-webkit-scrollbar-track {
|
| 15 |
+
background: #1f2937;
|
| 16 |
+
}
|
| 17 |
+
::-webkit-scrollbar-thumb {
|
| 18 |
+
background: #4b5563;
|
| 19 |
+
border-radius: 4px;
|
| 20 |
+
}
|
| 21 |
+
::-webkit-scrollbar-thumb:hover {
|
| 22 |
+
background: #6b7280;
|
| 23 |
+
}
|
| 24 |
+
</style>
|
| 25 |
+
<script type="importmap">
|
| 26 |
+
{
|
| 27 |
+
"imports": {
|
| 28 |
+
"react": "https://esm.sh/react@^19.2.3",
|
| 29 |
+
"react-dom/": "https://esm.sh/react-dom@^19.2.3/",
|
| 30 |
+
"react/": "https://esm.sh/react@^19.2.3/",
|
| 31 |
+
"lucide-react": "https://esm.sh/lucide-react@^0.562.0"
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
</script>
|
| 35 |
+
</head>
|
| 36 |
+
<body class="bg-gray-900 text-gray-100 font-sans antialiased overflow-hidden">
|
| 37 |
+
<div id="root"></div>
|
| 38 |
+
</body>
|
| 39 |
+
</html>
|
index.tsx
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import ReactDOM from 'react-dom/client';
|
| 3 |
+
import App from './App';
|
| 4 |
+
|
| 5 |
+
const rootElement = document.getElementById('root');
|
| 6 |
+
if (!rootElement) {
|
| 7 |
+
throw new Error("Could not find root element to mount to");
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
const root = ReactDOM.createRoot(rootElement);
|
| 11 |
+
root.render(
|
| 12 |
+
<React.StrictMode>
|
| 13 |
+
<App />
|
| 14 |
+
</React.StrictMode>
|
| 15 |
+
);
|
metadata.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Orchestrator-4",
|
| 3 |
+
"description": "A four-agent orchestration interface managing Jules (Coding), Developer (Planning), and General Agents, featuring intelligent delegation and deployment monitoring.",
|
| 4 |
+
"requestFramePermissions": []
|
| 5 |
+
}
|
multi_agent_system/jules_agent_client/agent.py
CHANGED
|
@@ -124,7 +124,7 @@ if __name__ == '__main__':
|
|
| 124 |
# Use the first available source for the example
|
| 125 |
first_source = sources['sources'][0]['name']
|
| 126 |
print(f"\n--- Creating Session on source: {first_source} ---")
|
| 127 |
-
|
| 128 |
example_prompt = "Please add a new function to the main library file that calculates the factorial of a number."
|
| 129 |
session_info = create_session(jules_key, first_source, example_prompt, title="Factorial Function Task")
|
| 130 |
print(json.dumps(session_info, indent=2))
|
|
|
|
| 124 |
# Use the first available source for the example
|
| 125 |
first_source = sources['sources'][0]['name']
|
| 126 |
print(f"\n--- Creating Session on source: {first_source} ---")
|
| 127 |
+
|
| 128 |
example_prompt = "Please add a new function to the main library file that calculates the factorial of a number."
|
| 129 |
session_info = create_session(jules_key, first_source, example_prompt, title="Factorial Function Task")
|
| 130 |
print(json.dumps(session_info, indent=2))
|
multi_agent_system/llm_client.py
CHANGED
|
@@ -31,7 +31,7 @@ def get_llm_response_stream(messages, model_name):
|
|
| 31 |
messages=messages,
|
| 32 |
stream=True,
|
| 33 |
)
|
| 34 |
-
|
| 35 |
for chunk in stream:
|
| 36 |
content = chunk.choices[0].delta.content
|
| 37 |
if content:
|
|
@@ -43,21 +43,21 @@ def get_llm_response_stream(messages, model_name):
|
|
| 43 |
if __name__ == '__main__':
|
| 44 |
# Example of how to use the LLM client stream
|
| 45 |
# This requires the BLABLADOR_API_KEY to be set as an environment variable.
|
| 46 |
-
|
| 47 |
print("--- Testing LLM Client Stream ---")
|
| 48 |
-
|
| 49 |
example_messages = [
|
| 50 |
{"role": "system", "content": "You are a helpful assistant."},
|
| 51 |
{"role": "user", "content": "Tell me a short story about a robot who discovers music."}
|
| 52 |
]
|
| 53 |
-
|
| 54 |
full_story = ""
|
| 55 |
response_stream = get_llm_response_stream(messages=example_messages, model_name="alias-large")
|
| 56 |
-
|
| 57 |
print("Streaming response:")
|
| 58 |
for chunk in response_stream:
|
| 59 |
print(chunk, end="", flush=True)
|
| 60 |
full_story += chunk
|
| 61 |
-
|
| 62 |
print("\n\n--- End of Stream ---")
|
| 63 |
print(f"Final assembled story length: {len(full_story)} characters.")
|
|
|
|
| 31 |
messages=messages,
|
| 32 |
stream=True,
|
| 33 |
)
|
| 34 |
+
|
| 35 |
for chunk in stream:
|
| 36 |
content = chunk.choices[0].delta.content
|
| 37 |
if content:
|
|
|
|
| 43 |
if __name__ == '__main__':
|
| 44 |
# Example of how to use the LLM client stream
|
| 45 |
# This requires the BLABLADOR_API_KEY to be set as an environment variable.
|
| 46 |
+
|
| 47 |
print("--- Testing LLM Client Stream ---")
|
| 48 |
+
|
| 49 |
example_messages = [
|
| 50 |
{"role": "system", "content": "You are a helpful assistant."},
|
| 51 |
{"role": "user", "content": "Tell me a short story about a robot who discovers music."}
|
| 52 |
]
|
| 53 |
+
|
| 54 |
full_story = ""
|
| 55 |
response_stream = get_llm_response_stream(messages=example_messages, model_name="alias-large")
|
| 56 |
+
|
| 57 |
print("Streaming response:")
|
| 58 |
for chunk in response_stream:
|
| 59 |
print(chunk, end="", flush=True)
|
| 60 |
full_story += chunk
|
| 61 |
+
|
| 62 |
print("\n\n--- End of Stream ---")
|
| 63 |
print(f"Final assembled story length: {len(full_story)} characters.")
|
multi_agent_system/monitor_agent/agent.py
CHANGED
|
@@ -1,110 +1,106 @@
|
|
| 1 |
import requests
|
| 2 |
import os
|
| 3 |
import time
|
| 4 |
-
import sys
|
| 5 |
-
import json
|
| 6 |
-
|
| 7 |
-
# Add sibling directories to the Python path
|
| 8 |
-
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
| 9 |
|
| 10 |
-
from jules_agent_client.agent import list_session_activities, send_message_to_session
|
| 11 |
-
from llm_client import get_llm_response_stream
|
| 12 |
-
|
| 13 |
-
# Constants
|
| 14 |
HF_API_ENDPOINT = "https://huggingface.co"
|
| 15 |
-
MONITOR_MODEL = "alias-fast"
|
| 16 |
|
| 17 |
def get_space_logs(repo_id, target='container', token=None):
|
| 18 |
-
"""
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
if not token:
|
| 21 |
token = os.environ.get("HF_TOKEN")
|
|
|
|
| 22 |
url = f'{HF_API_ENDPOINT}/api/spaces/{repo_id}/logs/{target}'
|
| 23 |
headers = {'User-Agent': 'Jules-Monitor-Agent/1.0'}
|
| 24 |
if token:
|
| 25 |
headers['Authorization'] = f'Bearer {token}'
|
|
|
|
| 26 |
try:
|
| 27 |
response = requests.get(url, headers=headers, timeout=15)
|
| 28 |
-
response.
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
except requests.exceptions.RequestException as e:
|
| 31 |
-
return f'
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
""
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
if not logs or logs.strip() == "":
|
| 41 |
-
return {"error_detected": False, "summary": "Logs are empty."}
|
| 42 |
-
|
| 43 |
-
messages = [
|
| 44 |
-
{"role": "system", "content": "You are an expert log analysis agent. Your task is to determine if the provided Hugging Face space logs indicate a build or runtime error. Respond with a JSON object containing two keys: 'error_detected' (boolean) and 'summary' (a concise, one-sentence summary for another AI agent)."},
|
| 45 |
-
{"role": "user", "content": f"Please analyze the following logs:\n\n---\n{logs}\n---"}
|
| 46 |
-
]
|
| 47 |
-
|
| 48 |
-
full_response = ""
|
| 49 |
-
for chunk in get_llm_response_stream(messages, MONITOR_MODEL):
|
| 50 |
-
full_response += chunk
|
| 51 |
-
|
| 52 |
-
try:
|
| 53 |
-
# The response should be a JSON object string
|
| 54 |
-
return json.loads(full_response)
|
| 55 |
-
except json.JSONDecodeError:
|
| 56 |
-
return {"error_detected": True, "summary": f"Log analysis failed. Raw LLM response: {full_response}"}
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
def monitor_session(session_id, hf_repo_id, jules_api_key, hf_token=None):
|
| 60 |
"""
|
| 61 |
-
Main monitoring loop
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
"""
|
| 63 |
-
print(f"Starting
|
|
|
|
| 64 |
|
| 65 |
while True:
|
| 66 |
-
# 1. Check Hugging Face logs
|
| 67 |
print(f"Checking HF logs for {hf_repo_id}...")
|
| 68 |
container_logs = get_space_logs(hf_repo_id, target='container', token=hf_token)
|
| 69 |
-
|
| 70 |
-
# Simple check for "running" status to gracefully exit
|
| 71 |
-
if "running" in container_logs.lower():
|
| 72 |
-
print("Application is running successfully. Stopping monitoring.")
|
| 73 |
-
break
|
| 74 |
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
print("LLM detected an error in the logs. Reporting to Jules...")
|
| 80 |
-
error_message = f"Monitoring agent detected a potential error. AI-generated summary:\n\n> {analysis.get('summary', 'No summary available.')}"
|
| 81 |
send_message_to_session(jules_api_key, session_id, error_message)
|
|
|
|
| 82 |
print("Error reported. Stopping monitoring for this session.")
|
| 83 |
break
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
| 87 |
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
print("\n--- Analyzing Mock Success Logs ---")
|
| 105 |
-
mock_success_logs = "INFO: Uvicorn running on http://0.0.0.0:7860 (Press CTRL+C to quit)"
|
| 106 |
-
success_analysis = analyze_logs_with_llm(mock_success_logs)
|
| 107 |
-
print(f"Analysis Result: {success_analysis}")
|
| 108 |
-
assert success_analysis.get("error_detected") is False
|
| 109 |
-
|
| 110 |
-
print("\nIntelligent monitor tests passed.")
|
|
|
|
| 1 |
import requests
|
| 2 |
import os
|
| 3 |
import time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
HF_API_ENDPOINT = "https://huggingface.co"
|
|
|
|
| 6 |
|
| 7 |
def get_space_logs(repo_id, target='container', token=None):
|
| 8 |
+
"""
|
| 9 |
+
Retrieves logs from a Hugging Face Space repository.
|
| 10 |
+
|
| 11 |
+
Args:
|
| 12 |
+
repo_id (str): The identifier of the repository (e.g., 'harvesthealth/magneticui').
|
| 13 |
+
target (str): The type of logs to retrieve ('build' or 'container').
|
| 14 |
+
token (str, optional): Hugging Face API token for authentication. Defaults to None.
|
| 15 |
+
|
| 16 |
+
Returns:
|
| 17 |
+
str: The log content as a string, or an error message.
|
| 18 |
+
"""
|
| 19 |
if not token:
|
| 20 |
token = os.environ.get("HF_TOKEN")
|
| 21 |
+
|
| 22 |
url = f'{HF_API_ENDPOINT}/api/spaces/{repo_id}/logs/{target}'
|
| 23 |
headers = {'User-Agent': 'Jules-Monitor-Agent/1.0'}
|
| 24 |
if token:
|
| 25 |
headers['Authorization'] = f'Bearer {token}'
|
| 26 |
+
|
| 27 |
try:
|
| 28 |
response = requests.get(url, headers=headers, timeout=15)
|
| 29 |
+
if response.status_code == 200:
|
| 30 |
+
return response.text
|
| 31 |
+
elif response.status_code == 404:
|
| 32 |
+
return f'Error: Repository or logs not found (404). URL: {url}'
|
| 33 |
+
elif response.status_code == 401:
|
| 34 |
+
return f'Error: Unauthorized access. Token may be invalid or insufficient permissions (401).'
|
| 35 |
+
elif response.status_code == 403:
|
| 36 |
+
return f'Error: Access forbidden. Authentication may be required (403).'
|
| 37 |
+
else:
|
| 38 |
+
return f'Error: {response.status_code} - {response.text}'
|
| 39 |
except requests.exceptions.RequestException as e:
|
| 40 |
+
return f'Exception during request: {str(e)}'
|
| 41 |
|
| 42 |
+
if __name__ == '__main__':
|
| 43 |
+
# This is an example of how to use the function.
|
| 44 |
+
# Replace with a real repo_id and ensure HF_TOKEN is set in your environment.
|
| 45 |
+
repo_id_to_test = "gradio/hello_world"
|
| 46 |
+
print("--- Retrieving Build Logs ---")
|
| 47 |
+
build_logs = get_space_logs(repo_id_to_test, target='build')
|
| 48 |
+
print(build_logs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
+
print("\n--- Retrieving Container Logs ---")
|
| 51 |
+
container_logs = get_space_logs(repo_id_to_test, target='container')
|
| 52 |
+
print(container_logs)
|
| 53 |
+
|
| 54 |
+
# Add sibling directories to the Python path to allow for intra-package imports
|
| 55 |
+
import sys
|
| 56 |
+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
| 57 |
+
from jules_agent_client.agent import list_session_activities, send_message_to_session
|
| 58 |
|
| 59 |
def monitor_session(session_id, hf_repo_id, jules_api_key, hf_token=None):
|
| 60 |
"""
|
| 61 |
+
Main monitoring loop for a given Jules session and Hugging Face space.
|
| 62 |
+
|
| 63 |
+
Args:
|
| 64 |
+
session_id (str): The Jules session ID to monitor.
|
| 65 |
+
hf_repo_id (str): The Hugging Face repo ID to monitor logs for.
|
| 66 |
+
jules_api_key (str): The Jules API key.
|
| 67 |
+
hf_token (str, optional): The Hugging Face API token. Defaults to None.
|
| 68 |
"""
|
| 69 |
+
print(f"Starting monitoring for Jules session: {session_id} and HF Space: {hf_repo_id}")
|
| 70 |
+
last_activity_count = 0
|
| 71 |
|
| 72 |
while True:
|
| 73 |
+
# 1. Check Hugging Face logs for errors
|
| 74 |
print(f"Checking HF logs for {hf_repo_id}...")
|
| 75 |
container_logs = get_space_logs(hf_repo_id, target='container', token=hf_token)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
+
# Simple error check: look for "error" string in logs
|
| 78 |
+
if "error" in container_logs.lower():
|
| 79 |
+
print("Error detected in HF container logs. Reporting to Jules...")
|
| 80 |
+
error_message = f"Monitoring agent detected an error in the Hugging Face container logs:\n\n---\n{container_logs}\n---"
|
|
|
|
|
|
|
| 81 |
send_message_to_session(jules_api_key, session_id, error_message)
|
| 82 |
+
# Potentially stop monitoring after reporting an error, or wait for a fix
|
| 83 |
print("Error reported. Stopping monitoring for this session.")
|
| 84 |
break
|
| 85 |
|
| 86 |
+
# Check if the app is running
|
| 87 |
+
if "running" in container_logs.lower(): # This is a simplistic check
|
| 88 |
+
print("Application is running successfully. Stopping monitoring.")
|
| 89 |
+
break
|
| 90 |
|
| 91 |
+
# 2. Check for new Jules activities
|
| 92 |
+
print(f"Checking Jules activities for session {session_id}...")
|
| 93 |
+
activities_response = list_session_activities(jules_api_key, session_id)
|
| 94 |
+
|
| 95 |
+
if 'activities' in activities_response:
|
| 96 |
+
current_activity_count = len(activities_response['activities'])
|
| 97 |
+
if current_activity_count > last_activity_count:
|
| 98 |
+
print(f"New activities detected ({current_activity_count - last_activity_count}).")
|
| 99 |
+
# You could add logic here to parse new activities
|
| 100 |
+
last_activity_count = current_activity_count
|
| 101 |
+
else:
|
| 102 |
+
print("No new Jules activities.")
|
| 103 |
+
|
| 104 |
+
# Wait for 10 minutes before the next check
|
| 105 |
+
print("Waiting for 10 minutes...")
|
| 106 |
+
time.sleep(600)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
multi_agent_system/orchestrator/agent.py
CHANGED
|
@@ -38,7 +38,7 @@ def generate_conversational_update(action_description):
|
|
| 38 |
{"role": "system", "content": "You are an orchestrator agent. Your role is to provide clear, friendly, and slightly enthusiastic updates to the user about your progress. Keep your responses concise (1-2 sentences)."},
|
| 39 |
{"role": "user", "content": f"Please provide a conversational update for the following action: {action_description}"}
|
| 40 |
]
|
| 41 |
-
|
| 42 |
full_response = ""
|
| 43 |
for chunk in get_llm_response_stream(messages, ORCHESTRATOR_MODEL):
|
| 44 |
full_response += chunk
|
|
@@ -62,7 +62,7 @@ def main_orchestration_workflow():
|
|
| 62 |
|
| 63 |
# Step 1: Get plan from the developer agent
|
| 64 |
yield generate_conversational_update("Contacting the specialist Developer Agent for a project plan.")
|
| 65 |
-
|
| 66 |
developer_prompt = """
|
| 67 |
Your task is to act as a planner for a new software development project.
|
| 68 |
You must investigate the repository at https://github.com/JsonLord/desk_agent.git,
|
|
@@ -72,7 +72,7 @@ def main_orchestration_workflow():
|
|
| 72 |
Your deliverables are a single JSON object with four keys: "idea", "tasks", "context_files", and "hf_repo".
|
| 73 |
"""
|
| 74 |
plan_response = send_prompt_to_developer_agent(developer_prompt)
|
| 75 |
-
|
| 76 |
try:
|
| 77 |
plan_data = json.loads(plan_response)
|
| 78 |
if "error" in plan_data:
|
|
@@ -88,7 +88,7 @@ def main_orchestration_workflow():
|
|
| 88 |
if not sources or 'sources' not in sources or not sources['sources']:
|
| 89 |
yield "I couldn't find any available source repositories for Jules."
|
| 90 |
return None
|
| 91 |
-
|
| 92 |
target_source_name = sources['sources'][0]['name']
|
| 93 |
repo_name = target_source_name.split('/')[-1]
|
| 94 |
|
|
@@ -111,7 +111,7 @@ def main_orchestration_workflow():
|
|
| 111 |
if 'error' in session_info or 'name' not in session_info:
|
| 112 |
yield f"Something went wrong when I tried to start the Jules session. Details: {session_info}"
|
| 113 |
return None
|
| 114 |
-
|
| 115 |
session_id = session_info['name'].split('/')[-1]
|
| 116 |
yield generate_conversational_update(f"Success! The coding session with Jules is active (ID: {session_id}).")
|
| 117 |
|
|
@@ -122,7 +122,7 @@ def main_orchestration_workflow():
|
|
| 122 |
return None
|
| 123 |
|
| 124 |
yield generate_conversational_update(f"I'm now handing off to the Monitor Agent to watch the deployment at '{hf_repo_to_monitor}'.")
|
| 125 |
-
|
| 126 |
# Return the necessary data for the monitor to be started in a separate thread
|
| 127 |
return {
|
| 128 |
"session_id": session_id,
|
|
|
|
| 38 |
{"role": "system", "content": "You are an orchestrator agent. Your role is to provide clear, friendly, and slightly enthusiastic updates to the user about your progress. Keep your responses concise (1-2 sentences)."},
|
| 39 |
{"role": "user", "content": f"Please provide a conversational update for the following action: {action_description}"}
|
| 40 |
]
|
| 41 |
+
|
| 42 |
full_response = ""
|
| 43 |
for chunk in get_llm_response_stream(messages, ORCHESTRATOR_MODEL):
|
| 44 |
full_response += chunk
|
|
|
|
| 62 |
|
| 63 |
# Step 1: Get plan from the developer agent
|
| 64 |
yield generate_conversational_update("Contacting the specialist Developer Agent for a project plan.")
|
| 65 |
+
|
| 66 |
developer_prompt = """
|
| 67 |
Your task is to act as a planner for a new software development project.
|
| 68 |
You must investigate the repository at https://github.com/JsonLord/desk_agent.git,
|
|
|
|
| 72 |
Your deliverables are a single JSON object with four keys: "idea", "tasks", "context_files", and "hf_repo".
|
| 73 |
"""
|
| 74 |
plan_response = send_prompt_to_developer_agent(developer_prompt)
|
| 75 |
+
|
| 76 |
try:
|
| 77 |
plan_data = json.loads(plan_response)
|
| 78 |
if "error" in plan_data:
|
|
|
|
| 88 |
if not sources or 'sources' not in sources or not sources['sources']:
|
| 89 |
yield "I couldn't find any available source repositories for Jules."
|
| 90 |
return None
|
| 91 |
+
|
| 92 |
target_source_name = sources['sources'][0]['name']
|
| 93 |
repo_name = target_source_name.split('/')[-1]
|
| 94 |
|
|
|
|
| 111 |
if 'error' in session_info or 'name' not in session_info:
|
| 112 |
yield f"Something went wrong when I tried to start the Jules session. Details: {session_info}"
|
| 113 |
return None
|
| 114 |
+
|
| 115 |
session_id = session_info['name'].split('/')[-1]
|
| 116 |
yield generate_conversational_update(f"Success! The coding session with Jules is active (ID: {session_id}).")
|
| 117 |
|
|
|
|
| 122 |
return None
|
| 123 |
|
| 124 |
yield generate_conversational_update(f"I'm now handing off to the Monitor Agent to watch the deployment at '{hf_repo_to_monitor}'.")
|
| 125 |
+
|
| 126 |
# Return the necessary data for the monitor to be started in a separate thread
|
| 127 |
return {
|
| 128 |
"session_id": session_id,
|
package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "orchestrator-4",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.0.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"preview": "vite preview"
|
| 10 |
+
},
|
| 11 |
+
"dependencies": {
|
| 12 |
+
"react": "^19.2.3",
|
| 13 |
+
"react-dom": "^19.2.3",
|
| 14 |
+
"lucide-react": "^0.562.0"
|
| 15 |
+
},
|
| 16 |
+
"devDependencies": {
|
| 17 |
+
"@types/node": "^22.14.0",
|
| 18 |
+
"@vitejs/plugin-react": "^5.0.0",
|
| 19 |
+
"typescript": "~5.8.2",
|
| 20 |
+
"vite": "^6.2.0"
|
| 21 |
+
}
|
| 22 |
+
}
|
requirements.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
gradio
|
| 2 |
huggingface-hub
|
| 3 |
requests
|
| 4 |
-
openai
|
|
|
|
| 1 |
gradio
|
| 2 |
huggingface-hub
|
| 3 |
requests
|
| 4 |
+
openai
|
services/julesService.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { JULES_API_BASE, JULES_API_KEY } from '../constants';
|
| 2 |
+
import { JulesSession } from '../types';
|
| 3 |
+
|
| 4 |
+
// Helper to simulate API delay
|
| 5 |
+
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
| 6 |
+
|
| 7 |
+
export const createJulesSession = async (title: string, prompt: string, repo: string): Promise<JulesSession> => {
|
| 8 |
+
// In a real app, this would be a fetch call.
|
| 9 |
+
// Due to potential CORS on the provided endpoint from a browser, we mock the success logic here
|
| 10 |
+
// based on the curl command provided in the prompt.
|
| 11 |
+
|
| 12 |
+
/*
|
| 13 |
+
REAL IMPLEMENTATION:
|
| 14 |
+
const response = await fetch(`${JULES_API_BASE}/sessions`, {
|
| 15 |
+
method: 'POST',
|
| 16 |
+
headers: {
|
| 17 |
+
'Content-Type': 'application/json',
|
| 18 |
+
'X-Goog-Api-Key': JULES_API_KEY
|
| 19 |
+
},
|
| 20 |
+
body: JSON.stringify({
|
| 21 |
+
prompt: prompt,
|
| 22 |
+
sourceContext: {
|
| 23 |
+
source: `sources/github/${repo}`,
|
| 24 |
+
githubRepoContext: { startingBranch: "main" }
|
| 25 |
+
},
|
| 26 |
+
automationMode: "AUTO_CREATE_PR",
|
| 27 |
+
title: title
|
| 28 |
+
})
|
| 29 |
+
});
|
| 30 |
+
return response.json();
|
| 31 |
+
*/
|
| 32 |
+
|
| 33 |
+
console.log(`[JulesAPI] Creating session for repo: ${repo}`);
|
| 34 |
+
await delay(1500);
|
| 35 |
+
|
| 36 |
+
return {
|
| 37 |
+
sessionId: `sess_${Math.random().toString(36).substring(7)}`,
|
| 38 |
+
title: title,
|
| 39 |
+
status: 'active',
|
| 40 |
+
lastLog: 'Session initialized. Analyzing repository...'
|
| 41 |
+
};
|
| 42 |
+
};
|
| 43 |
+
|
| 44 |
+
export const sendMessageToJules = async (sessionId: string, message: string) => {
|
| 45 |
+
console.log(`[JulesAPI] Sending message to ${sessionId}: ${message}`);
|
| 46 |
+
await delay(1000);
|
| 47 |
+
return {
|
| 48 |
+
response: "I have received your request. I am checking the codebase context now."
|
| 49 |
+
};
|
| 50 |
+
};
|
| 51 |
+
|
| 52 |
+
export const listJulesActivities = async (sessionId: string) => {
|
| 53 |
+
console.log(`[JulesAPI] Listing activities for ${sessionId}`);
|
| 54 |
+
await delay(800);
|
| 55 |
+
return [
|
| 56 |
+
{ type: 'activity', status: 'completed', description: 'Repo clone successful' },
|
| 57 |
+
{ type: 'activity', status: 'in_progress', description: 'Analyzing /deployment directory' }
|
| 58 |
+
];
|
| 59 |
+
};
|
tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2022",
|
| 4 |
+
"experimentalDecorators": true,
|
| 5 |
+
"useDefineForClassFields": false,
|
| 6 |
+
"module": "ESNext",
|
| 7 |
+
"lib": [
|
| 8 |
+
"ES2022",
|
| 9 |
+
"DOM",
|
| 10 |
+
"DOM.Iterable"
|
| 11 |
+
],
|
| 12 |
+
"skipLibCheck": true,
|
| 13 |
+
"types": [
|
| 14 |
+
"node"
|
| 15 |
+
],
|
| 16 |
+
"moduleResolution": "bundler",
|
| 17 |
+
"isolatedModules": true,
|
| 18 |
+
"moduleDetection": "force",
|
| 19 |
+
"allowJs": true,
|
| 20 |
+
"jsx": "react-jsx",
|
| 21 |
+
"paths": {
|
| 22 |
+
"@/*": [
|
| 23 |
+
"./*"
|
| 24 |
+
]
|
| 25 |
+
},
|
| 26 |
+
"allowImportingTsExtensions": true,
|
| 27 |
+
"noEmit": true
|
| 28 |
+
}
|
| 29 |
+
}
|
types.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export enum AgentType {
|
| 2 |
+
ORCHESTRATOR = 'Orchestrator',
|
| 3 |
+
JULES = 'Jules (Coder)',
|
| 4 |
+
DEVELOPER = 'Developer (Planner)',
|
| 5 |
+
GENERAL = 'General Agent',
|
| 6 |
+
MONITOR = 'Monitoring Agent'
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
export interface Message {
|
| 10 |
+
id: string;
|
| 11 |
+
sender: AgentType | 'User';
|
| 12 |
+
content: string;
|
| 13 |
+
timestamp: Date;
|
| 14 |
+
metadata?: any; // For logs, session IDs, etc.
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
export interface AgentConfig {
|
| 18 |
+
name: string;
|
| 19 |
+
type: AgentType;
|
| 20 |
+
endpoint?: string;
|
| 21 |
+
description: string;
|
| 22 |
+
status: 'idle' | 'working' | 'error';
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
export interface JulesSession {
|
| 26 |
+
sessionId: string;
|
| 27 |
+
title: string;
|
| 28 |
+
status: 'active' | 'completed' | 'failed';
|
| 29 |
+
lastLog: string;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
export interface MonitoringLog {
|
| 33 |
+
id: string;
|
| 34 |
+
timestamp: string;
|
| 35 |
+
source: 'JulesAPI' | 'HuggingFace' | 'System';
|
| 36 |
+
level: 'INFO' | 'WARN' | 'ERROR';
|
| 37 |
+
message: string;
|
| 38 |
+
}
|
vite.config.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import path from 'path';
|
| 2 |
+
import { defineConfig, loadEnv } from 'vite';
|
| 3 |
+
import react from '@vitejs/plugin-react';
|
| 4 |
+
|
| 5 |
+
export default defineConfig(({ mode }) => {
|
| 6 |
+
const env = loadEnv(mode, '.', '');
|
| 7 |
+
return {
|
| 8 |
+
server: {
|
| 9 |
+
port: 3000,
|
| 10 |
+
host: '0.0.0.0',
|
| 11 |
+
},
|
| 12 |
+
plugins: [react()],
|
| 13 |
+
define: {
|
| 14 |
+
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
| 15 |
+
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
|
| 16 |
+
},
|
| 17 |
+
resolve: {
|
| 18 |
+
alias: {
|
| 19 |
+
'@': path.resolve(__dirname, '.'),
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
};
|
| 23 |
+
});
|