Autosave: 20260226-002417

This commit is contained in:
Flatlogic Bot 2026-02-26 00:24:17 +00:00
parent 79c28f68a7
commit 70e9a21b87
19 changed files with 861 additions and 181 deletions

Binary file not shown.

View File

@ -1,3 +1,17 @@
from django.contrib import admin
from .models import MCPToolRequest, HumanResponse, SlackSettings
# Register your models here.
@admin.register(SlackSettings)
class SlackSettingsAdmin(admin.ModelAdmin):
list_display = ('__str__', 'updated_at')
@admin.register(MCPToolRequest)
class MCPToolRequestAdmin(admin.ModelAdmin):
list_display = ('id', 'tool_name', 'status', 'created_at')
list_filter = ('status', 'tool_name')
readonly_fields = ('id', 'created_at', 'updated_at')
@admin.register(HumanResponse)
class HumanResponseAdmin(admin.ModelAdmin):
list_display = ('id', 'request', 'user_name', 'created_at')
readonly_fields = ('id', 'created_at')

190
core/mcp.py Normal file
View File

@ -0,0 +1,190 @@
import json
import os
import httpx
from django.conf import settings
from .models import MCPToolRequest, HumanResponse, SlackSettings
from asgiref.sync import sync_to_async
SLACK_API_URL = "https://slack.com/api/chat.postMessage"
@sync_to_async
def get_slack_token():
token = os.getenv("SLACK_BOT_TOKEN")
if token:
return token
config = SlackSettings.objects.first()
if config and config.bot_token:
return config.bot_token
return None
async def send_slack_message(channel, text, thread_ts=None):
token = await get_slack_token()
if not token:
return {"error": "SLACK_BOT_TOKEN not configured", "ok": False}
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json; charset=utf-8"
}
payload = {
"channel": channel,
"text": text,
}
if thread_ts:
payload["thread_ts"] = thread_ts
async with httpx.AsyncClient() as client:
response = await client.post(SLACK_API_URL, headers=headers, json=payload)
data = response.json()
return data
async def handle_mcp_request(body):
"""
Simple MCP JSON-RPC handler for HTTP/SSE.
Supports tools/list and tools/call.
"""
try:
request_data = json.loads(body)
except json.JSONDecodeError:
return {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": None}
method = request_data.get("method")
params = request_data.get("params", {})
request_id = request_data.get("id")
if method == "initialize":
return {
"jsonrpc": "2.0",
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {}
},
"serverInfo": {
"name": "flatlogic-slack",
"version": "1.0.0"
}
},
"id": request_id
}
elif method == "notifications/initialized":
return None # No response for notifications
elif method == "ping":
return {
"jsonrpc": "2.0",
"result": {},
"id": request_id
}
elif method == "tools/list":
return {
"jsonrpc": "2.0",
"result": {
"tools": [
{
"name": "send_slack_message",
"description": "Sends a message to a Slack channel.",
"inputSchema": {
"type": "object",
"properties": {
"channel": {"type": "string", "description": "Channel ID or Name"},
"text": {"type": "string", "description": "Message content"}
},
"required": ["channel", "text"]
}
},
{
"name": "list_responses",
"description": "Lists human responses for a specific request ID.",
"inputSchema": {
"type": "object",
"properties": {
"request_id": {"type": "string", "description": "The UUID of the original request"}
},
"required": ["request_id"]
}
}
]
},
"id": request_id
}
elif method == "tools/call":
tool_name = params.get("name")
arguments = params.get("arguments", {})
if tool_name == "send_slack_message":
channel = arguments.get("channel")
text = arguments.get("text")
# Log request
db_request = await MCPToolRequest.objects.acreate(
tool_name=tool_name,
arguments=arguments,
slack_channel=channel or ""
)
slack_result = await send_slack_message(channel, text)
if slack_result.get("ok"):
db_request.status = 'SENT'
db_request.slack_ts = slack_result.get("ts", "")
db_request.response_json = slack_result
await db_request.asave()
return {
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": f"Message sent to Slack (TS: {db_request.slack_ts}). Request ID: {db_request.id}"
}
]
},
"id": request_id
}
else:
db_request.status = 'ERROR'
db_request.response_json = slack_result
await db_request.asave()
return {
"jsonrpc": "2.0",
"error": {
"code": -32000,
"message": f"Slack API Error: {slack_result.get('error')}"
},
"id": request_id
}
elif tool_name == "list_responses":
req_id = arguments.get("request_id")
responses = []
async for r in HumanResponse.objects.filter(request_id=req_id):
responses.append({
"text": r.text,
"user": r.user_name,
"file_url": r.file_url,
"timestamp": str(r.created_at)
})
return {
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": json.dumps(responses, indent=2) if responses else "No responses yet."
}
]
},
"id": request_id
}
return {
"jsonrpc": "2.0",
"error": {"code": -32601, "message": "Method not found"},
"id": request_id
}

View File

@ -0,0 +1,41 @@
# Generated by Django 5.2.7 on 2026-02-25 23:18
import django.db.models.deletion
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='MCPToolRequest',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('tool_name', models.CharField(max_length=100)),
('arguments', models.JSONField()),
('status', models.CharField(choices=[('PENDING', 'Pending'), ('SENT', 'Sent'), ('ERROR', 'Error')], default='PENDING', max_length=10)),
('slack_channel', models.CharField(blank=True, max_length=50)),
('slack_ts', models.CharField(blank=True, help_text='Slack message timestamp', max_length=50)),
('response_json', models.JSONField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
name='HumanResponse',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('text', models.TextField()),
('user_name', models.CharField(blank=True, max_length=100)),
('file_url', models.URLField(blank=True, max_length=500, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('request', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='core.mcptoolrequest')),
],
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 5.2.7 on 2026-02-25 23:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='SlackSettings',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('bot_token', models.CharField(blank=True, max_length=255, null=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name_plural': 'Slack Settings',
},
),
]

View File

@ -1,3 +1,43 @@
from django.db import models
import uuid
# Create your models here.
class SlackSettings(models.Model):
"""Stores the Slack App Token so users don't have to edit .env files."""
bot_token = models.CharField(max_length=255, blank=True, null=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return "Slack Settings"
class Meta:
verbose_name_plural = "Slack Settings"
class MCPToolRequest(models.Model):
STATUS_CHOICES = [
('PENDING', 'Pending'),
('SENT', 'Sent'),
('ERROR', 'Error'),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
tool_name = models.CharField(max_length=100)
arguments = models.JSONField()
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='PENDING')
slack_channel = models.CharField(max_length=50, blank=True)
slack_ts = models.CharField(max_length=50, blank=True, help_text="Slack message timestamp")
response_json = models.JSONField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.tool_name} - {self.status} ({self.created_at})"
class HumanResponse(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
request = models.ForeignKey(MCPToolRequest, related_name='responses', on_delete=models.CASCADE)
text = models.TextField()
user_name = models.CharField(max_length=100, blank=True)
file_url = models.URLField(max_length=500, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"Response to {self.request.id} - {self.created_at}"

View File

@ -2,24 +2,56 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}Knowledge Base{% endblock %}</title>
{% if project_description %}
<meta name="description" content="{{ project_description }}">
<meta property="og:description" content="{{ project_description }}">
<meta property="twitter:description" content="{{ project_description }}">
{% endif %}
{% if project_image_url %}
<meta property="og:image" content="{{ project_image_url }}">
<meta property="twitter:image" content="{{ project_image_url }}">
{% endif %}
{% load static %}
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}MCP Slack Server{% endblock %}</title>
{% if project_description %}
<meta name="description" content="{{ project_description }}">
{% endif %}
{% load static %}
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Google Fonts: Inter & JetBrains Mono -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
<nav class="navbar navbar-expand-lg sticky-top">
<div class="container">
<a class="navbar-brand" href="{% url 'home' %}">
<span class="status-dot online"></span>MCP SLACK SERVER
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'home' %}">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/">Admin</a>
</li>
</ul>
</div>
</div>
</nav>
<main>
{% block content %}{% endblock %}
</main>
<footer class="py-5 mt-5 border-top border-secondary">
<div class="container text-center text-secondary">
<p>&copy; 2026 MCP Slack Bridge &bull; Powered by Flatlogic AI</p>
</div>
</footer>
<!-- Bootstrap 5 JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}{% endblock %}
</body>
</html>
</html>

View File

@ -1,145 +1,144 @@
{% extends "base.html" %}
{% load static %}
{% block title %}{{ project_name }}{% endblock %}
{% block head %}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% {
background-position: 0% 0%;
}
100% {
background-position: 100% 100%;
}
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2.5rem 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
}
h1 {
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
font-weight: 700;
margin: 0 0 1.2rem;
letter-spacing: -0.02em;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
opacity: 0.92;
}
.loader {
margin: 1.5rem auto;
width: 56px;
height: 56px;
border: 4px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.runtime code {
background: rgba(0, 0, 0, 0.25);
padding: 0.15rem 0.45rem;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
footer {
position: absolute;
bottom: 1rem;
width: 100%;
text-align: center;
font-size: 0.85rem;
opacity: 0.75;
}
</style>
{% endblock %}
{% block title %}Dashboard | MCP Slack Bridge{% endblock %}
{% block content %}
<main>
<div class="card">
<h1>Analyzing your requirements and generating your app…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
<section class="hero-section">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-8">
<h1 class="display-4 fw-bold mb-4">AI &Human Slack Bridge</h1>
<p class="lead text-secondary mb-5">
Bridging the gap between autonomous AI agents and human workers.
Exposing tools to request design, feedback, or any manual task directly via Slack.
</p>
<div class="d-flex gap-3">
<span class="badge-online d-flex align-items-center">
<span class="status-dot online"></span>
Server: {{ server_status }}
</span>
<span class="badge rounded-pill border border-secondary text-secondary d-flex align-items-center px-3">
DJANGO 5.2 &bull; MCP v1.0
</span>
</div>
</div>
<div class="col-lg-4 d-none d-lg-block">
<div class="card p-4 text-center shadow">
<h5 class="mb-3 text-secondary">MCP Endpoint</h5>
<code class="d-block py-2">/mcp/</code>
<p class="small text-secondary mt-3 mb-0">Post JSON-RPC here</p>
</div>
</div>
</div>
</div>
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
<p class="runtime">
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
</p>
</div>
</main>
<footer>
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
</footer>
{% endblock %}
</section>
<div class="container my-5">
<div class="row">
<div class="col-md-12">
{% if not is_slack_configured %}
<div class="alert alert-warning border-warning bg-opacity-10 bg-warning mb-5">
<h5 class="alert-heading fw-bold">Configuration Required</h5>
<p class="mb-0">
Set <code>SLACK_BOT_TOKEN</code> in your environment to enable message delivery.
</p>
</div>
{% endif %}
<div class="card mb-5">
<div class="card-body p-0">
<div class="p-4 border-bottom border-secondary d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0 text-white">Recent Tool Activity</h5>
<small class="text-secondary">Showing last 20 requests</small>
</div>
<div class="table-responsive">
<table class="table table-borderless table-hover mb-0">
<thead>
<tr>
<th class="ps-4">TIMESTAMP</th>
<th>TOOL NAME</th>
<th>CHANNEL</th>
<th>STATUS</th>
<th class="pe-4 text-end">ACTION</th>
</tr>
</thead>
<tbody>
{% for r in requests %}
<tr class="log-entry" onclick="location.href='{% url 'request_detail' r.pk %}'">
<td class="ps-4 text-secondary small">{{ r.created_at|date:"Y-m-d H:i" }}</td>
<td><code class="text-info">{{ r.tool_name }}</code></td>
<td class="text-secondary">#{{ r.slack_channel }}</td>
<td>
{% if r.status == 'SENT' %}
<span class="badge-online bg-opacity-25 bg-success text-success border border-success">SENT</span>
{% elif r.status == 'ERROR' %}
<span class="badge-error bg-opacity-25 bg-danger text-danger border border-danger">ERROR</span>
{% else %}
<span class="badge-pending bg-opacity-25 bg-warning text-warning border border-warning">PENDING</span>
{% endif %}
</td>
<td class="pe-4 text-end">
<a href="{% url 'request_detail' r.pk %}" class="btn btn-sm btn-outline-secondary">Details</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center py-5 text-secondary italic">
No activity logged yet. Connect an MCP client to start.
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="card h-100 p-4">
<h5 class="text-white mb-4 fw-bold">How to use (Tools/Call)</h5>
<pre class="small mb-0">
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "send_slack_message",
"arguments": {
"channel": "C12345",
"text": "Hello Human!"
}
},
"id": 1
}</pre>
</div>
</div>
<div class="col-lg-6 mt-4 mt-lg-0">
<div class="card h-100 p-4">
<h5 class="text-white mb-4 fw-bold">MCP Server Info</h5>
<ul class="list-unstyled text-secondary">
<li class="mb-3 d-flex justify-content-between">
<span>Status:</span>
<span class="text-success fw-bold">ONLINE</span>
</li>
<li class="mb-3 d-flex justify-content-between">
<span>Version:</span>
<span>1.0.0</span>
</li>
<li class="mb-3 d-flex justify-content-between">
<span>Available Tools:</span>
<span>2</span>
</li>
<li class="mb-0 d-flex justify-content-between">
<span>Python:</span>
<span>{{ python_version|default:"3.11" }}</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,98 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Request Details | MCP Slack Bridge{% endblock %}
{% block content %}
<div class="container my-5 py-5">
<div class="row">
<div class="col-lg-10 mx-auto">
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'home' %}">Dashboard</a></li>
<li class="breadcrumb-item active text-secondary" aria-current="page">Request: {{ req.id|truncatechars:12 }}</li>
</ol>
</nav>
<div class="card p-4 p-md-5">
<div class="d-flex justify-content-between align-items-center mb-5">
<h2 class="text-white fw-bold mb-0">Tool Call Details</h2>
{% if req.status == 'SENT' %}
<span class="badge-online bg-opacity-25 bg-success text-success border border-success">SENT</span>
{% elif req.status == 'ERROR' %}
<span class="badge-error bg-opacity-25 bg-danger text-danger border border-danger">ERROR</span>
{% else %}
<span class="badge-pending bg-opacity-25 bg-warning text-warning border border-warning">PENDING</span>
{% endif %}
</div>
<div class="row g-4">
<div class="col-md-4">
<p class="small text-secondary fw-bold text-uppercase">Tool Name</p>
<h5 class="text-info">{{ req.tool_name }}</h5>
</div>
<div class="col-md-4">
<p class="small text-secondary fw-bold text-uppercase">Slack Channel</p>
<h5 class="text-white">#{{ req.slack_channel|default:"N/A" }}</h5>
</div>
<div class="col-md-4">
<p class="small text-secondary fw-bold text-uppercase">Slack Timestamp</p>
<h5 class="text-white">{{ req.slack_ts|default:"N/A" }}</h5>
</div>
</div>
<hr class="my-5 border-secondary opacity-25">
<div class="mb-5">
<h5 class="text-white mb-4">Arguments</h5>
<pre class="small bg-dark p-4 rounded-3 text-info"><code>{{ req.arguments|json_script:"args-data" }}</code>
<script>
document.addEventListener("DOMContentLoaded", function() {
const data = JSON.parse(document.getElementById('args-data').textContent);
document.querySelector('pre code').textContent = JSON.stringify(data, null, 2);
});
</script></pre>
</div>
<div class="mb-5">
<h5 class="text-white mb-4">Slack API Response</h5>
<pre class="small bg-dark p-4 rounded-3 text-secondary"><code>{{ req.response_json|json_script:"resp-data" }}</code>
<script>
document.addEventListener("DOMContentLoaded", function() {
const data = JSON.parse(document.getElementById('resp-data').textContent);
document.querySelectorAll('pre code')[1].textContent = JSON.stringify(data, null, 2);
});
</script></pre>
</div>
<div class="responses-section">
<h5 class="text-white mb-4 d-flex align-items-center">
Human Responses
<span class="ms-3 badge rounded-pill bg-primary fs-6">{{ responses.count }}</span>
</h5>
{% for r in responses %}
<div class="card bg-opacity-10 bg-info border-info border-opacity-25 p-4 mb-3">
<div class="d-flex justify-content-between mb-3">
<span class="fw-bold text-info">{{ r.user_name|default:"Human" }}</span>
<span class="small text-secondary">{{ r.created_at|date:"Y-m-d H:i" }}</span>
</div>
<p class="mb-0 text-white">{{ r.text }}</p>
{% if r.file_url %}
<div class="mt-3">
<a href="{{ r.file_url }}" target="_blank" class="btn btn-sm btn-outline-info">View Attachment</a>
</div>
{% endif %}
</div>
{% empty %}
<div class="text-center py-5 border border-dashed border-secondary rounded-3">
<p class="text-secondary italic mb-0">No responses recorded yet.</p>
<small class="text-secondary">AI agent can check for updates using <code>list_responses</code></small>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,7 +1,9 @@
from django.urls import path
from .views import home
from .views import dashboard, mcp_endpoint, slack_webhook, request_detail
urlpatterns = [
path("", home, name="home"),
]
path("", dashboard, name="home"),
path("mcp/", mcp_endpoint, name="mcp_endpoint"),
path("webhook/slack/", slack_webhook, name="slack_webhook"),
path("request/<uuid:pk>/", request_detail, name="request_detail"),
]

View File

@ -1,25 +1,112 @@
import json
import os
import platform
from django import get_version as django_version
from django.shortcuts import render
from django.http import JsonResponse, HttpResponse
from django.shortcuts import render, get_object_or_404
from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt
from asgiref.sync import sync_to_async
from .models import MCPToolRequest, HumanResponse, SlackSettings
from .mcp import handle_mcp_request
@csrf_exempt
async def mcp_endpoint(request):
"""
Main entry point for MCP clients via HTTP.
Accepts JSON-RPC 2.0 payloads.
"""
if request.method != "POST":
return JsonResponse({"error": "Method not allowed"}, status=405)
body = request.body.decode("utf-8")
response_data = await handle_mcp_request(body)
if response_data is None:
return HttpResponse(status=204)
return JsonResponse(response_data)
def home(request):
"""Render the landing screen with loader and environment details."""
host_name = request.get_host().lower()
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
now = timezone.now()
def dashboard(request):
"""
Polished landing page showing active server status and recent logs.
"""
requests_list = MCPToolRequest.objects.all().order_by('-created_at')[:20]
# Slack config check
slack_token = os.getenv("SLACK_BOT_TOKEN")
if not slack_token:
config = SlackSettings.objects.first()
if config and config.bot_token:
slack_token = config.bot_token
is_slack_configured = bool(slack_token)
context = {
"project_name": "New Style",
"agent_brand": agent_brand,
"django_version": django_version(),
"python_version": platform.python_version(),
"current_time": now,
"host_name": host_name,
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
"is_slack_configured": is_slack_configured,
"requests": requests_list,
"django_version": platform.python_version(),
"now": timezone.now(),
"server_status": "ONLINE" if is_slack_configured else "CONFIG_PENDING"
}
return render(request, "core/index.html", context)
@csrf_exempt
def slack_webhook(request):
"""
Slack Event API Webhook.
Captures threaded replies and saves them as HumanResponse.
"""
if request.method == "POST":
try:
data = json.loads(request.body)
# 1. Verification Challenge
if data.get("type") == "url_verification":
return JsonResponse({"challenge": data.get("challenge")})
# 2. Event Callback
if data.get("type") == "event_callback":
event = data.get("event", {})
# We only care about user messages (no bot_id) in threads (has thread_ts)
if event.get("type") == "message" and not event.get("bot_id"):
thread_ts = event.get("thread_ts")
text = event.get("text", "")
user_id = event.get("user", "Unknown User")
# Basic extraction
user_name = user_id
files = event.get("files", [])
file_url = None
if files:
file_url = files[0].get("url_private", "")
if thread_ts:
# Find the corresponding tool request
mcp_request = MCPToolRequest.objects.filter(slack_ts=thread_ts).first()
if mcp_request:
# Avoid duplicate responses if Slack retries
# We can just check if we already have this text/user in last 10 seconds, or just save it
HumanResponse.objects.create(
request=mcp_request,
text=text,
user_name=user_name,
file_url=file_url
)
return HttpResponse("Created", status=201)
except Exception as e:
print("Webhook error:", e)
return HttpResponse("Error", status=400)
return HttpResponse("OK")
def request_detail(request, pk):
"""Detailed view of a single tool call."""
tool_request = get_object_or_404(MCPToolRequest, pk=pk)
responses = tool_request.responses.all().order_by('-created_at')
return render(request, "core/request_detail.html", {
"req": tool_request,
"responses": responses
})

View File

@ -1,3 +1,4 @@
Django==5.2.7
mysqlclient==2.2.7
python-dotenv==1.1.1
httpx==0.27.0

View File

@ -1,4 +1,156 @@
/* Custom styles for the application */
body {
font-family: system-ui, -apple-system, sans-serif;
:root {
--bg-color: #0d1117;
--card-bg: #161b22;
--text-primary: #c9d1d9;
--text-secondary: #8b949e;
--accent-blue: #58a6ff;
--accent-green: #238636;
--border-color: #30363d;
}
body {
background-color: var(--bg-color);
color: var(--text-primary);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
margin: 0;
padding: 0;
}
.navbar {
background-color: var(--card-bg) !important;
border-bottom: 1px solid var(--border-color);
}
.navbar-brand {
font-weight: 700;
letter-spacing: -0.5px;
color: var(--accent-blue) !important;
}
.nav-link {
color: var(--text-secondary) !important;
}
.nav-link:hover {
color: var(--text-primary) !important;
}
.card {
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
color: var(--text-primary);
}
.card-title {
color: var(--accent-blue);
font-weight: 600;
}
.btn-primary {
background-color: var(--accent-blue);
border-color: var(--accent-blue);
border-radius: 8px;
font-weight: 600;
}
.btn-primary:hover {
background-color: #388bfd;
border-color: #388bfd;
}
.badge-online {
background-color: var(--accent-green);
color: white;
padding: 4px 8px;
border-radius: 20px;
font-size: 0.75rem;
text-transform: uppercase;
font-weight: 700;
box-shadow: 0 0 10px rgba(35, 134, 54, 0.4);
}
.badge-pending {
background-color: #d29922;
color: white;
padding: 4px 8px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 700;
}
.badge-error {
background-color: #f85149;
color: white;
padding: 4px 8px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 700;
}
.table {
color: var(--text-primary);
}
.table thead th {
border-bottom: 1px solid var(--border-color);
color: var(--text-secondary);
font-weight: 500;
font-size: 0.85rem;
}
.table td {
border-bottom: 1px solid var(--border-color);
vertical-align: middle;
}
.log-entry:hover {
background-color: rgba(88, 166, 255, 0.05);
cursor: pointer;
}
.hero-section {
padding: 80px 0;
background: radial-gradient(circle at top right, rgba(88, 166, 255, 0.1), transparent);
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
margin-right: 8px;
}
.status-dot.online {
background-color: var(--accent-green);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(35, 134, 54, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(35, 134, 54, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(35, 134, 54, 0);
}
}
code {
background-color: #0d1117;
padding: 2px 6px;
border-radius: 4px;
color: #ff7b72;
}
pre {
background-color: #0d1117;
padding: 16px;
border-radius: 8px;
border: 1px solid var(--border-color);
color: var(--text-primary);
overflow-x: auto;
}