basic AI agent
This commit is contained in:
parent
b700e16dec
commit
3b7559b157
Binary file not shown.
@ -382,17 +382,11 @@ def _http_request(url: str, method: str, body: Optional[bytes], headers: Dict[st
|
|||||||
"data": decoded if decoded is not None else response_body,
|
"data": decoded if decoded is not None else response_body,
|
||||||
}
|
}
|
||||||
|
|
||||||
error_message = "AI proxy request failed"
|
|
||||||
if isinstance(decoded, dict):
|
|
||||||
error_message = decoded.get("error") or decoded.get("message") or error_message
|
|
||||||
elif response_body:
|
|
||||||
error_message = response_body
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"status": status,
|
"status": status,
|
||||||
"error": error_message,
|
"error": "AI proxy request failed",
|
||||||
"response": decoded if decoded is not None else response_body,
|
"data": decoded if decoded is not None else response_body,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
BIN
assets/pasted-20251120-002324-c898244a.png
Normal file
BIN
assets/pasted-20251120-002324-c898244a.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 179 KiB |
Binary file not shown.
@ -152,3 +152,17 @@ STATICFILES_DIRS = [
|
|||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
X_FRAME_OPTIONS = 'ALLOWALL'
|
X_FRAME_OPTIONS = 'ALLOWALL'
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
'version': 1,
|
||||||
|
'disable_existing_loggers': False,
|
||||||
|
'handlers': {
|
||||||
|
'console': {
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'root': {
|
||||||
|
'handlers': ['console'],
|
||||||
|
'level': 'INFO',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-19 23:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0004_conversation_message'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='message',
|
||||||
|
options={'ordering': ['created_at']},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='message',
|
||||||
|
name='is_from_user',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='message',
|
||||||
|
name='sender',
|
||||||
|
field=models.CharField(choices=[('user', 'User'), ('ai', 'AI'), ('system', 'System'), ('ai_command', 'AI Command')], default='user', max_length=20),
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
@ -35,8 +35,11 @@ class Conversation(models.Model):
|
|||||||
class Message(models.Model):
|
class Message(models.Model):
|
||||||
conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE, related_name='messages')
|
conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE, related_name='messages')
|
||||||
content = models.TextField()
|
content = models.TextField()
|
||||||
is_from_user = models.BooleanField(default=True)
|
sender = models.CharField(max_length=20, choices=[('user', 'User'), ('ai', 'AI'), ('system', 'System'), ('ai_command', 'AI Command')], default='user')
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['created_at']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Message from {'User' if self.is_from_user else 'AI'} at {self.created_at}"
|
return f"Message from {self.get_sender_display()} at {self.created_at}"
|
||||||
@ -33,10 +33,24 @@
|
|||||||
|
|
||||||
<div class="chat-messages" id="chat-messages">
|
<div class="chat-messages" id="chat-messages">
|
||||||
{% for message in selected_conversation.messages.all %}
|
{% for message in selected_conversation.messages.all %}
|
||||||
<div class="message {% if message.is_from_user %}user{% else %}ai{% endif %}">
|
<div class="message {{ message.sender }}">
|
||||||
<div class="message-content">
|
<div class="message-content">
|
||||||
<div class="message-author">{% if message.is_from_user %}You{% else %}AI{% endif %}</div>
|
<div class="message-author">
|
||||||
<p>{{ message.content|linebreaksbr }}</p>
|
{% if message.sender == 'user' %}
|
||||||
|
You
|
||||||
|
{% elif message.sender == 'ai' %}
|
||||||
|
AI
|
||||||
|
{% elif message.sender == 'system' %}
|
||||||
|
System
|
||||||
|
{% elif message.sender == 'ai_command' %}
|
||||||
|
AI Command
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if message.sender == 'ai_command' %}
|
||||||
|
<pre><code>{{ message.content|linebreaksbr }}</code></pre>
|
||||||
|
{% else %}
|
||||||
|
<p>{{ message.content|linebreaksbr }}</p>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -38,8 +38,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
<div class="card-header bg-white">
|
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||||
<h2 class="h5 mb-0">Your To-Do List</h2>
|
<h2 class="h5 mb-0">Your To-Do List</h2>
|
||||||
|
<form method="post" action="{% url 'core:cleanup_tasks' %}" style="display: inline;">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-warning btn-sm">Cleanup All Tasks</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover mb-0">
|
<table class="table table-hover mb-0">
|
||||||
@ -50,6 +54,7 @@
|
|||||||
<th scope="col">Tags</th>
|
<th scope="col">Tags</th>
|
||||||
<th scope="col">Status</th>
|
<th scope="col">Status</th>
|
||||||
<th scope="col">Created</th>
|
<th scope="col">Created</th>
|
||||||
|
<th scope="col">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -66,6 +71,12 @@
|
|||||||
</td>
|
</td>
|
||||||
<td><span class="badge status-{{ item.status }}">{{ item.get_status_display }}</span></td>
|
<td><span class="badge status-{{ item.status }}">{{ item.get_status_display }}</span></td>
|
||||||
<td>{{ item.created_at|date:"M d, Y" }}</td>
|
<td>{{ item.created_at|date:"M d, Y" }}</td>
|
||||||
|
<td>
|
||||||
|
<form method="post" action="{% url 'core:delete_task' item.id %}" style="display: inline;">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@ -25,6 +25,10 @@
|
|||||||
{% for item in tasks %}
|
{% for item in tasks %}
|
||||||
<div class="card kanban-card mb-3 shadow-sm" data-task-id="{{ item.id }}">
|
<div class="card kanban-card mb-3 shadow-sm" data-task-id="{{ item.id }}">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<form action="{% url 'core:delete_task' item.id %}" method="post" class="delete-task-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn-close" aria-label="Close"></button>
|
||||||
|
</form>
|
||||||
<h5 class="card-title h6">{{ item.title }}</h5>
|
<h5 class="card-title h6">{{ item.title }}</h5>
|
||||||
<p class="card-text small">{{ item.description|default:""|truncatewords:15 }}</p>
|
<p class="card-text small">{{ item.description|default:""|truncatewords:15 }}</p>
|
||||||
{% if item.tags %}
|
{% if item.tags %}
|
||||||
|
|||||||
@ -8,6 +8,8 @@ urlpatterns = [
|
|||||||
path('kanban/', views.kanban_board, name='kanban'),
|
path('kanban/', views.kanban_board, name='kanban'),
|
||||||
path('article/<int:article_id>/', views.article_detail, name='article_detail'),
|
path('article/<int:article_id>/', views.article_detail, name='article_detail'),
|
||||||
path('update_task_status/', views.update_task_status, name='update_task_status'),
|
path('update_task_status/', views.update_task_status, name='update_task_status'),
|
||||||
|
path('delete_task/<int:task_id>/', views.delete_task, name='delete_task'),
|
||||||
path('chat/', views.chat_view, name='chat'),
|
path('chat/', views.chat_view, name='chat'),
|
||||||
path('chat/<int:conversation_id>/', views.chat_view, name='chat_detail'),
|
path('chat/<int:conversation_id>/', views.chat_view, name='chat_detail'),
|
||||||
|
path('cleanup_tasks/', views.cleanup_tasks, name='cleanup_tasks'),
|
||||||
]
|
]
|
||||||
231
core/views.py
231
core/views.py
@ -2,17 +2,21 @@ from django.shortcuts import render, redirect, get_object_or_404
|
|||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
from .models import Article, TodoItem, Conversation, Message
|
from .models import Article, TodoItem, Conversation, Message
|
||||||
from .forms import TodoItemForm
|
from .forms import TodoItemForm
|
||||||
import time
|
import time
|
||||||
from ai.local_ai_api import LocalAIApi
|
from ai.local_ai_api import LocalAIApi
|
||||||
|
|
||||||
|
# Get an instance of a logger
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = TodoItemForm(request.POST)
|
form = TodoItemForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
return redirect('index')
|
return redirect('core:index')
|
||||||
else:
|
else:
|
||||||
form = TodoItemForm()
|
form = TodoItemForm()
|
||||||
|
|
||||||
@ -60,6 +64,87 @@ def update_task_status(request):
|
|||||||
except (json.JSONDecodeError, TypeError, ValueError) as e:
|
except (json.JSONDecodeError, TypeError, ValueError) as e:
|
||||||
return JsonResponse({'success': False, 'error': str(e)}, status=400)
|
return JsonResponse({'success': False, 'error': str(e)}, status=400)
|
||||||
|
|
||||||
|
|
||||||
|
@require_POST
|
||||||
|
def delete_task(request, task_id):
|
||||||
|
task = get_object_or_404(TodoItem, id=task_id)
|
||||||
|
task.delete()
|
||||||
|
|
||||||
|
# Redirect to the previous page or a default URL
|
||||||
|
referer = request.META.get('HTTP_REFERER')
|
||||||
|
if referer:
|
||||||
|
return redirect(referer)
|
||||||
|
return redirect('core:index')
|
||||||
|
|
||||||
|
@require_POST
|
||||||
|
def cleanup_tasks(request):
|
||||||
|
TodoItem.objects.all().delete()
|
||||||
|
return redirect('core:index')
|
||||||
|
|
||||||
|
def execute_command(command_data):
|
||||||
|
command_name = command_data.get('name')
|
||||||
|
args = command_data.get('args', {})
|
||||||
|
logger.info(f"Executing command: {command_name} with args: {args}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if command_name == 'send_message':
|
||||||
|
message = args.get('message')
|
||||||
|
if not message:
|
||||||
|
logger.error("Command 'send_message' failed: 'message' is a required argument.")
|
||||||
|
return "[SYSTEM] Error: 'message' is a required argument for send_message."
|
||||||
|
return message
|
||||||
|
|
||||||
|
elif command_name == 'add_task':
|
||||||
|
title = args.get('title')
|
||||||
|
if not title:
|
||||||
|
logger.error("Command 'add_task' failed: 'title' is a required argument.")
|
||||||
|
return "[SYSTEM] Error: 'title' is a required argument for add_task."
|
||||||
|
|
||||||
|
new_task = TodoItem.objects.create(
|
||||||
|
title=title,
|
||||||
|
description=args.get('description', ''),
|
||||||
|
status=args.get('status', 'todo')
|
||||||
|
)
|
||||||
|
logger.info(f"Command 'add_task' executed successfully. New task ID: {new_task.id}")
|
||||||
|
return f"[SYSTEM] Command 'add_task' executed successfully. New task ID: {new_task.id}"
|
||||||
|
|
||||||
|
elif command_name == 'edit_task':
|
||||||
|
task_id = args.get('task_id')
|
||||||
|
if not task_id:
|
||||||
|
logger.error("Command 'edit_task' failed: 'task_id' is a required argument.")
|
||||||
|
return "[SYSTEM] Error: 'task_id' is a required argument for edit_task."
|
||||||
|
|
||||||
|
task = get_object_or_404(TodoItem, id=task_id)
|
||||||
|
|
||||||
|
if 'title' in args:
|
||||||
|
task.title = args['title']
|
||||||
|
if 'description' in args:
|
||||||
|
task.description = args['description']
|
||||||
|
if 'status' in args:
|
||||||
|
task.status = args['status']
|
||||||
|
|
||||||
|
task.save()
|
||||||
|
logger.info(f"Command 'edit_task' for task ID {task_id} executed successfully.")
|
||||||
|
return f"[SYSTEM] Command 'edit_task' for task ID {task_id} executed successfully."
|
||||||
|
|
||||||
|
elif command_name == 'delete_task':
|
||||||
|
task_id = args.get('task_id')
|
||||||
|
if not task_id:
|
||||||
|
logger.error("Command 'delete_task' failed: 'task_id' is a required argument.")
|
||||||
|
return "[SYSTEM] Error: 'task_id' is a required argument for delete_task."
|
||||||
|
|
||||||
|
task = get_object_or_404(TodoItem, id=task_id)
|
||||||
|
task.delete()
|
||||||
|
logger.info(f"Command 'delete_task' for task ID {task_id} executed successfully.")
|
||||||
|
return f"[SYSTEM] Command 'delete_task' for task ID {task_id} executed successfully."
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.warning(f"Unknown command received: '{command_name}'")
|
||||||
|
return f"[SYSTEM] Error: Unknown command '{command_name}'."
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error executing command '{command_name}': {e}", exc_info=True)
|
||||||
|
return f"[SYSTEM] Error executing command '{command_name}': {e}"
|
||||||
|
|
||||||
def chat_view(request, conversation_id=None):
|
def chat_view(request, conversation_id=None):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if 'title' in request.POST:
|
if 'title' in request.POST:
|
||||||
@ -70,25 +155,103 @@ def chat_view(request, conversation_id=None):
|
|||||||
elif 'text' in request.POST and conversation_id:
|
elif 'text' in request.POST and conversation_id:
|
||||||
text = request.POST.get('text')
|
text = request.POST.get('text')
|
||||||
selected_conversation = get_object_or_404(Conversation, id=conversation_id)
|
selected_conversation = get_object_or_404(Conversation, id=conversation_id)
|
||||||
|
|
||||||
if text:
|
if text:
|
||||||
Message.objects.create(conversation=selected_conversation, content=text, is_from_user=True)
|
command_name = None # Initialize command_name
|
||||||
|
Message.objects.create(conversation=selected_conversation, content=text, sender='user')
|
||||||
|
|
||||||
# Construct the conversation history for the AI
|
|
||||||
history = []
|
history = []
|
||||||
for msg in selected_conversation.messages.order_by('created_at'):
|
for msg in selected_conversation.messages.order_by('created_at'):
|
||||||
role = "user" if msg.is_from_user else "assistant"
|
role = msg.sender
|
||||||
|
if role == 'ai':
|
||||||
|
role = 'assistant'
|
||||||
|
elif role == 'system':
|
||||||
|
role = 'user'
|
||||||
history.append({"role": role, "content": msg.content})
|
history.append({"role": role, "content": msg.content})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
system_message = {
|
system_message = {
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": "You are a helpful assistant for a project management application. Your purpose is to assist users with their tasks and provide information about the application. The application manages articles and a to-do list."
|
"content": '''You are a project management assistant. To communicate with the user, you MUST use the `send_message` command.
|
||||||
|
|
||||||
|
**Commands must be in a specific JSON format.** Your response must be a JSON object with the following structure:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command": {
|
||||||
|
"name": "command_name",
|
||||||
|
"args": {
|
||||||
|
"arg1": "value1",
|
||||||
|
"arg2": "value2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Available Commands:**
|
||||||
|
|
||||||
|
* `send_message`: Sends a message to the user. **USE THIS FOR ALL CONVERSATIONAL RESPONSES.**
|
||||||
|
* `args`:
|
||||||
|
* `message` (string, required): The message to send to the user.
|
||||||
|
* `add_task`: Adds a new task.
|
||||||
|
* `args`:
|
||||||
|
* `title` (string, required): The title of the task.
|
||||||
|
* `description` (string, optional): The description of the task.
|
||||||
|
* `status` (string, optional, default: 'todo'): The status of the task. Can be 'todo', 'inprogress', 'done', 'blocked'.
|
||||||
|
* `edit_task`: Edits an existing task.
|
||||||
|
* `args`:
|
||||||
|
* `task_id` (integer, required): The ID of the task to edit.
|
||||||
|
* `title` (string, optional): The new title.
|
||||||
|
* `description` (string, optional): The new description.
|
||||||
|
* `status` (string, optional): The new status.
|
||||||
|
* `delete_task`: Deletes a task.
|
||||||
|
* `args`:
|
||||||
|
* `task_id` (integer, required): The ID of the task to delete.
|
||||||
|
|
||||||
|
**Execution Loop:**
|
||||||
|
|
||||||
|
1. You issue a command.
|
||||||
|
2. The system executes it.
|
||||||
|
3. The system returns a result message to you, like `[SYSTEM] Command 'add_task' executed successfully. New task ID: 5`.
|
||||||
|
4. You can then issue another command.
|
||||||
|
|
||||||
|
**VERY IMPORTANT:**
|
||||||
|
- To talk to the user, you MUST use the `send_message` command.
|
||||||
|
- ONLY use other commands if the user explicitly asks you to `add`, `edit`, or `delete` tasks.
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
* **User:** "Hi, how are you?"
|
||||||
|
* **Correct AI Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command": {
|
||||||
|
"name": "send_message",
|
||||||
|
"args": {
|
||||||
|
"message": "I'm doing great, thanks for asking! How can I help you with your tasks today?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* **User:** "add a new task to buy milk"
|
||||||
|
* **Correct AI Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command": {
|
||||||
|
"name": "add_task",
|
||||||
|
"args": {
|
||||||
|
"title": "buy milk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**IMPORTANT:** Do not wrap the JSON command in markdown backticks or any other text. The entire response must be the JSON object.'''
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks = TodoItem.objects.all().order_by('created_at')
|
tasks = TodoItem.objects.all().order_by('created_at')
|
||||||
task_list_str = "\n".join([
|
task_list_str = "\n".join([
|
||||||
f"- {task.title} (Status: {task.get_status_display()}, Tags: {task.tags or 'None'})"
|
f"- ID {task.id}: {task.title} (Status: {task.get_status_display()}, Tags: {task.tags or 'None'})" for task in tasks
|
||||||
for task in tasks
|
|
||||||
])
|
])
|
||||||
|
|
||||||
tasks_context = {
|
tasks_context = {
|
||||||
@ -96,29 +259,55 @@ def chat_view(request, conversation_id=None):
|
|||||||
"content": f"Here is the current list of tasks:\n{task_list_str}"
|
"content": f"Here is the current list of tasks:\n{task_list_str}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info("Sending request to AI...")
|
||||||
response = LocalAIApi.create_response({
|
response = LocalAIApi.create_response({
|
||||||
"input": [
|
"input": [
|
||||||
system_message,
|
system_message,
|
||||||
tasks_context,
|
tasks_context,
|
||||||
*history
|
] + history,
|
||||||
]
|
"text": {"format": {"type": "json_object"}},
|
||||||
})
|
})
|
||||||
ai_text = LocalAIApi.extract_text(response)
|
|
||||||
if not ai_text:
|
if not response.get("success"):
|
||||||
|
logger.error(f"AI API request failed with status {response.get('status')}. Full error: {response.get('response')}")
|
||||||
ai_text = "I couldn't process that. Please try again."
|
ai_text = "I couldn't process that. Please try again."
|
||||||
except Exception as e:
|
Message.objects.create(conversation=selected_conversation, content=ai_text, sender='ai')
|
||||||
ai_text = f"An error occurred: {str(e)}"
|
else:
|
||||||
|
logger.info(f"AI raw response: {response}")
|
||||||
|
ai_text = LocalAIApi.extract_text(response)
|
||||||
|
logger.info(f"Extracted AI text: {ai_text}")
|
||||||
|
|
||||||
Message.objects.create(conversation=selected_conversation, content=ai_text, is_from_user=False)
|
if not ai_text:
|
||||||
|
logger.warning("AI response was empty.")
|
||||||
# Re-fetch the conversation to include the new messages for rendering
|
ai_text = "I couldn't process that. Please try again."
|
||||||
selected_conversation = get_object_or_404(Conversation, id=conversation_id)
|
Message.objects.create(conversation=selected_conversation, content=ai_text, sender='ai')
|
||||||
|
else:
|
||||||
|
ai_message_content = ai_text
|
||||||
|
sender = 'ai'
|
||||||
|
|
||||||
|
try:
|
||||||
|
command_json = json.loads(ai_text)
|
||||||
|
if 'command' in command_json:
|
||||||
|
command_name = command_json.get('command', {}).get('name')
|
||||||
|
command_result = execute_command(command_json['command'])
|
||||||
|
|
||||||
|
ai_message_content = command_result
|
||||||
|
sender = 'ai' if command_name == 'send_message' else 'system'
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
# Not a JSON command, treat as a raw message.
|
||||||
|
pass
|
||||||
|
|
||||||
|
Message.objects.create(conversation=selected_conversation, content=ai_message_content, sender=sender)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An unexpected error occurred: {e}", exc_info=True)
|
||||||
|
ai_text = f"An error occurred: {str(e)}"
|
||||||
|
Message.objects.create(conversation=selected_conversation, content=ai_text, sender='ai')
|
||||||
|
|
||||||
|
return redirect('core:chat_detail', conversation_id=conversation_id)
|
||||||
|
|
||||||
conversations = Conversation.objects.order_by('-created_at')
|
conversations = Conversation.objects.order_by('-created_at')
|
||||||
if conversation_id:
|
selected_conversation = get_object_or_404(Conversation, id=conversation_id) if conversation_id else None
|
||||||
selected_conversation = get_object_or_404(Conversation, id=conversation_id)
|
|
||||||
else:
|
|
||||||
selected_conversation = None
|
|
||||||
|
|
||||||
return render(request, 'core/chat.html', {
|
return render(request, 'core/chat.html', {
|
||||||
'conversation_list': conversations,
|
'conversation_list': conversations,
|
||||||
|
|||||||
@ -198,4 +198,33 @@ body {
|
|||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
0% { transform: rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
100% { transform: rotate(360deg); }
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* System and AI Command Messages */
|
||||||
|
.message.system .message-content {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.ai_command .message-content {
|
||||||
|
background-color: #d1ecf1;
|
||||||
|
color: #0c5460;
|
||||||
|
border: 1px solid #bee5eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.ai_command .message-content pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-card .card-body {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-task-form {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
}
|
}
|
||||||
@ -198,4 +198,33 @@ body {
|
|||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
0% { transform: rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
100% { transform: rotate(360deg); }
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* System and AI Command Messages */
|
||||||
|
.message.system .message-content {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.ai_command .message-content {
|
||||||
|
background-color: #d1ecf1;
|
||||||
|
color: #0c5460;
|
||||||
|
border: 1px solid #bee5eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.ai_command .message-content pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-card .card-body {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-task-form {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user