Compare commits

..

No commits in common. "3477fc639ba98bdd2b9c2b9d54ecd894ae52e9fe" and "63cf731d1f2931da8c844cf48be315f076fdef24" have entirely different histories.

25 changed files with 90 additions and 187 deletions

View File

@ -314,7 +314,7 @@ def _config() -> Dict[str, Any]:
"project_id": project_id, "project_id": project_id,
"project_uuid": os.getenv("PROJECT_UUID"), "project_uuid": os.getenv("PROJECT_UUID"),
"project_header": os.getenv("AI_PROJECT_HEADER", "project-uuid"), "project_header": os.getenv("AI_PROJECT_HEADER", "project-uuid"),
"default_model": os.getenv("AI_DEFAULT_MODEL", "gpt-5"), "default_model": os.getenv("AI_DEFAULT_MODEL", "gpt-5-mini"),
"timeout": int(os.getenv("AI_TIMEOUT", "30")), "timeout": int(os.getenv("AI_TIMEOUT", "30")),
"verify_tls": os.getenv("AI_VERIFY_TLS", "true").lower() not in {"0", "false", "no"}, "verify_tls": os.getenv("AI_VERIFY_TLS", "true").lower() not in {"0", "false", "no"},
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

View File

@ -24,12 +24,6 @@ DEBUG = os.getenv("DJANGO_DEBUG", "true").lower() == "true"
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
USE_X_FORWARDED_HOST = True USE_X_FORWARDED_HOST = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = "None"
CSRF_COOKIE_SAMESITE = "None"
ALLOWED_HOSTS = [ ALLOWED_HOSTS = [
"127.0.0.1", "127.0.0.1",
"localhost", "localhost",

View File

@ -4,7 +4,7 @@ from .models import TodoItem
class TodoItemForm(forms.ModelForm): class TodoItemForm(forms.ModelForm):
class Meta: class Meta:
model = TodoItem model = TodoItem
fields = ['title', 'description', 'tags', 'status', 'deadline'] fields = ['title', 'description', 'tags', 'status']
widgets = { widgets = {
'title': forms.TextInput(attrs={ 'title': forms.TextInput(attrs={
'class': 'form-control', 'class': 'form-control',
@ -21,9 +21,5 @@ class TodoItemForm(forms.ModelForm):
}), }),
'status': forms.Select(attrs={ 'status': forms.Select(attrs={
'class': 'form-control' 'class': 'form-control'
}),
'deadline': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}) })
} }

View File

@ -1,18 +0,0 @@
# Generated by Django 5.2.7 on 2025-11-23 15:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0007_conversation_is_generating'),
]
operations = [
migrations.AddField(
model_name='todoitem',
name='deadline',
field=models.DateField(blank=True, null=True),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.2.7 on 2025-11-23 15:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0008_todoitem_deadline'),
]
operations = [
migrations.AlterField(
model_name='todoitem',
name='status',
field=models.CharField(choices=[('todo', 'To Do'), ('inprogress', 'In Progress'), ('blocked', 'Blocked'), ('delegated', 'Delegated'), ('done', 'Done')], default='todo', max_length=20),
),
]

View File

@ -13,13 +13,11 @@ class TodoItem(models.Model):
('todo', 'To Do'), ('todo', 'To Do'),
('inprogress', 'In Progress'), ('inprogress', 'In Progress'),
('blocked', 'Blocked'), ('blocked', 'Blocked'),
('delegated', 'Delegated'),
('done', 'Done'), ('done', 'Done'),
] ]
title = models.CharField(max_length=200) title = models.CharField(max_length=200)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
tags = models.CharField(max_length=255, blank=True, null=True) tags = models.CharField(max_length=255, blank=True, null=True)
deadline = models.DateField(blank=True, null=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='todo') status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='todo')
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)

View File

@ -38,7 +38,7 @@
</div> </div>
</nav> </nav>
<main class="{% block container_class %}container{% endblock %} mt-5"> <main class="container mt-5">
{% block content %} {% block content %}
{% endblock %} {% endblock %}
</main> </main>

View File

@ -3,8 +3,6 @@
{% block title %}Chat{% endblock %} {% block title %}Chat{% endblock %}
{% block container_class %}container-fluid{% endblock %}
{% block content %} {% block content %}
<div class="chat-container"> <div class="chat-container">
<!-- Sidebar --> <!-- Sidebar -->

View File

@ -4,7 +4,10 @@
{% block title %}AI Task Manager - Home{% endblock %} {% block title %}AI Task Manager - Home{% endblock %}
{% block content %} {% block content %}
<div class="hero-section text-center py-5">
<h1 class="display-4">AI Task Manager</h1>
<p class="lead">Your intelligent assistant for managing tasks and conversations.</p>
</div>
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-lg-8"> <div class="col-lg-8">
@ -25,10 +28,6 @@
{{ form.tags.label_tag }} {{ form.tags.label_tag }}
{{ form.tags }} {{ form.tags }}
</div> </div>
<div class="mb-3">
{{ form.deadline.label_tag }}
{{ form.deadline }}
</div>
<div class="mb-3"> <div class="mb-3">
{{ form.status.label_tag }} {{ form.status.label_tag }}
{{ form.status }} {{ form.status }}
@ -55,28 +54,23 @@
<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">Deadline</th>
<th scope="col">Actions</th> <th scope="col">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for item in todo_list %} {% for item in todo_list %}
<tr class="{% if item.status == 'inprogress' %}table-info{% elif item.status == 'done' %}table-success{% elif item.status == 'blocked' %}table-danger{% elif item.status == 'delegated' %}table-warning{% endif %}"> <tr>
<td> <td>{{ item.title }}</td>
{{ item.title }}
<div><span class="task-id text-muted small">#{{ item.id }}</span></div>
</td>
<td>{{ item.description|default:"" }}</td> <td>{{ item.description|default:"" }}</td>
<td> <td>
{% if item.tags %} {% if item.tags %}
{% for tag in item.tags.split:','|slice:":3" %} {% for tag in item.tags.split|slice:":3" %}
<span class="badge bg-secondary">{{ tag }}</span> <span class="badge bg-secondary">{{ tag }}</span>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</td> </td>
<td><span class="badge {% if item.status == 'inprogress' %}bg-info text-dark{% elif item.status == 'done' %}bg-success{% elif item.status == 'blocked' %}bg-danger{% elif item.status == 'delegated' %}bg-warning text-dark{% else %}bg-light text-dark{% endif %}">{{ 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>{{ item.deadline|date:"M d, Y"|default:"" }}</td>
<td> <td>
<form method="post" action="{% url 'core:delete_task' item.id %}" style="display: inline;"> <form method="post" action="{% url 'core:delete_task' item.id %}" style="display: inline;">
{% csrf_token %} {% csrf_token %}
@ -86,7 +80,7 @@
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="7" class="text-center text-muted py-4">No tasks yet. Add one above!</td> <td colspan="5" class="text-center text-muted py-4">No tasks yet. Add one above!</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -4,10 +4,11 @@
{% block title %}AI Task Manager - Kanban Board{% endblock %} {% block title %}AI Task Manager - Kanban Board{% endblock %}
{% block container_class %}container-fluid{% endblock %}
{% block content %} {% block content %}
<div class="hero-section text-center py-5">
<h1 class="display-4">Kanban Board</h1>
<p class="lead">Visualize your tasks and track progress.</p>
</div>
<div class="kanban-board-container"> <div class="kanban-board-container">
<div class="loader-overlay" style="display: none;"> <div class="loader-overlay" style="display: none;">
@ -28,21 +29,15 @@
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn-close" aria-label="Close"></button> <button type="submit" class="btn-close" aria-label="Close"></button>
</form> </form>
<div class="d-flex justify-content-between align-items-start"> <h5 class="card-title h6">{{ item.title }}</h5>
<h5 class="card-title h6 mb-0">{{ item.title }}</h5>
<span class="task-id text-muted small">#{{ item.id }}</span>
</div>
<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 %}
<div class="tags"> <div class="tags">
{% for tag in item.tags.split:','|slice:":3" %} {% for tag in item.tags.split|slice:":3" %}
<span class="badge bg-secondary">{{ tag }}</span> <span class="badge bg-secondary">{{ tag }}</span>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
{% if item.deadline %}
<p class="card-text small text-muted mt-2">Deadline: {{ item.deadline|date:"Y-m-d" }}</p>
{% endif %}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
@ -64,6 +59,9 @@ document.addEventListener('DOMContentLoaded', function () {
new Sortable(column, { new Sortable(column, {
group: 'kanban', group: 'kanban',
animation: 150, animation: 150,
onStart: function (evt) {
document.querySelector('.loader-overlay').style.display = 'flex';
},
onEnd: function (evt) { onEnd: function (evt) {
const itemEl = evt.item; const itemEl = evt.item;
const toContainer = evt.to; const toContainer = evt.to;
@ -104,6 +102,9 @@ document.addEventListener('DOMContentLoaded', function () {
// Revert the move in the UI // Revert the move in the UI
fromContainer.insertBefore(itemEl, fromContainer.children[oldIndex]); fromContainer.insertBefore(itemEl, fromContainer.children[oldIndex]);
alert('An error occurred while updating the task. Please try again.'); alert('An error occurred while updating the task. Please try again.');
})
.finally(() => {
document.querySelector('.loader-overlay').style.display = 'none';
}); });
} }
}); });

View File

@ -1,5 +1,3 @@
# core/views.py
from django.shortcuts import render, redirect, get_object_or_404 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
@ -112,12 +110,10 @@ def execute_command(command_data):
new_task = TodoItem.objects.create( new_task = TodoItem.objects.create(
title=title, title=title,
description=args.get('description', ''), description=args.get('description', ''),
status=args.get('status', 'todo'), status=args.get('status', 'todo')
tags=args.get('tags', ''),
deadline=args.get('deadline')
) )
logger.info(f"Command 'add_task' executed successfully. New task ID: {new_task.id}") logger.info(f"Command 'add_task' executed successfully. New task ID: {new_task.id}")
return f"[SYSTEM] Command 'add_task' executed successfully. A new task with ID {new_task.id}, title '{new_task.title}', description '{new_task.description}', status '{new_task.status}', tags '{new_task.tags}', and deadline '{new_task.deadline}' has been created." return f"[SYSTEM] Command 'add_task' executed successfully. New task ID: {new_task.id}"
elif command_name == 'edit_task': elif command_name == 'edit_task':
task_id = args.get('task_id') task_id = args.get('task_id')
@ -133,14 +129,10 @@ def execute_command(command_data):
task.description = args['description'] task.description = args['description']
if 'status' in args: if 'status' in args:
task.status = args['status'] task.status = args['status']
if 'tags' in args:
task.tags = args['tags']
if 'deadline' in args:
task.deadline = args['deadline']
task.save() task.save()
logger.info(f"Command 'edit_task' for task ID {task_id} executed successfully.") 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. The task now has title '{task.title}', description '{task.description}', status '{task.status}', tags '{task.tags}', and deadline '{task.deadline}'." return f"[SYSTEM] Command 'edit_task' for task ID {task_id} executed successfully."
elif command_name == 'delete_task': elif command_name == 'delete_task':
task_id = args.get('task_id') task_id = args.get('task_id')
@ -182,7 +174,7 @@ def run_ai_process_in_background(conversation_id):
key='custom_instructions', key='custom_instructions',
defaults={'value': ''} defaults={'value': ''}
) )
custom_instructions_text = custom_instructions.value + '''\n\n''' if custom_instructions.value else '' custom_instructions_text = custom_instructions.value + '\n\n' if custom_instructions.value else ''
system_message = { system_message = {
"role": "system", "role": "system",
@ -210,18 +202,14 @@ def run_ai_process_in_background(conversation_id):
* `add_task`: Adds a new task. * `add_task`: Adds a new task.
* `args`: * `args`:
* `title` (string, required): The title of the task. * `title` (string, required): The title of the task.
* `description` (string, optional): A detailed description of the task. * `description` (string, optional): The description of the task.
* `status` (string, optional, default: 'todo'): The status of the task. Valid options are 'todo', 'inprogress', 'done', 'blocked'. * `status` (string, optional, default: 'todo'): The status of the task. Can be 'todo', 'inprogress', 'done', 'blocked'.
* `tags` (string, optional): A comma-separated string of tags (e.g., "work,urgent").
* `deadline` (string, optional): The deadline for the task in YYYY-MM-DD format.
* `edit_task`: Edits an existing task. * `edit_task`: Edits an existing task.
* `args`: * `args`:
* `task_id` (integer, required): The ID of the task to edit. * `task_id` (integer, required): The ID of the task to edit.
* `title` (string, optional): The new title. * `title` (string, optional): The new title.
* `description` (string, optional): The new description. * `description` (string, optional): The new description.
* `status` (string, optional): The new status. Valid options are 'todo', 'inprogress', 'done', 'blocked'. * `status` (string, optional): The new status.
* `tags` (string, optional): The new comma-separated string of tags.
* `deadline` (string, optional): The new deadline for the task in YYYY-MM-DD format.
* `delete_task`: Deletes a task. * `delete_task`: Deletes a task.
* `args`: * `args`:
* `task_id` (integer, required): The ID of the task to delete. * `task_id` (integer, required): The ID of the task to delete.
@ -230,7 +218,7 @@ def run_ai_process_in_background(conversation_id):
1. You can issue a series of commands to be executed sequentially. 1. You can issue a series of commands to be executed sequentially.
2. The system executes each command and provides a result. 2. The system executes each command and provides a result.
3. The loop will stop if you call `send_message` or after a maximum of 31 iterations. 3. The loop will stop if you call `send_message` or after a maximum of 7 iterations.
**VERY IMPORTANT:** **VERY IMPORTANT:**
- To talk to the user, you MUST use the `send_message` command. This command will STOP the execution loop. - To talk to the user, you MUST use the `send_message` command. This command will STOP the execution loop.
@ -251,15 +239,14 @@ def run_ai_process_in_background(conversation_id):
} }
} }
``` ```
* **User:** "add a new task to buy milk with tag 'personal'" * **User:** "add a new task to buy milk"
* **Correct AI Response:** * **Correct AI Response:**
```json ```json
{ {
"command": { "command": {
"name": "add_task", "name": "add_task",
"args": { "args": {
"title": "buy milk", "title": "buy milk"
"tags": "personal"
} }
} }
} }
@ -269,11 +256,9 @@ def run_ai_process_in_background(conversation_id):
} }
tasks = TodoItem.objects.all().order_by('created_at') tasks = TodoItem.objects.all().order_by('created_at')
task_list = [] task_list_str = "\n".join([
for task in tasks: f"- ID {task.id}: {task.title} (Status: {task.get_status_display()}, Tags: {task.tags or 'None'})" for task in tasks
deadline_str = f", Deadline: {task.deadline}" if task.deadline else "" ])
task_list.append(f"- ID {task.id}: {task.title} (Status: {task.get_status_display()}, Tags: {task.tags or 'None'}{deadline_str})")
task_list_str = "\n".join(task_list)
tasks_context = { tasks_context = {
"role": "system", "role": "system",
@ -282,7 +267,7 @@ def run_ai_process_in_background(conversation_id):
logger.info("Starting AI processing loop...") logger.info("Starting AI processing loop...")
for i in range(31): # Loop up to 31 times for i in range(7): # Loop up to 7 times
logger.info(f"AI loop iteration {i+1}") logger.info(f"AI loop iteration {i+1}")
response = LocalAIApi.create_response({ response = LocalAIApi.create_response({
@ -326,7 +311,7 @@ def run_ai_process_in_background(conversation_id):
Message.objects.create(conversation=conversation, content=ai_text, sender='ai') Message.objects.create(conversation=conversation, content=ai_text, sender='ai')
break break
else: else:
logger.warning("AI loop finished after 31 iterations without sending a message.") logger.warning("AI loop finished after 7 iterations without sending a message.")
final_message = "I seem to be stuck in a loop. Could you clarify what you'd like me to do?" final_message = "I seem to be stuck in a loop. Could you clarify what you'd like me to do?"
Message.objects.create(conversation=conversation, content=final_message, sender='ai') Message.objects.create(conversation=conversation, content=final_message, sender='ai')
@ -419,10 +404,3 @@ def settings_view(request):
return render(request, 'core/settings.html', { return render(request, 'core/settings.html', {
'custom_instructions': custom_instructions 'custom_instructions': custom_instructions
}) })
@require_POST
def delete_conversation(request, conversation_id):
conversation = get_object_or_404(Conversation, id=conversation_id)
conversation.delete()
return redirect('core:chat')

View File

@ -202,11 +202,9 @@ body {
/* System and AI Command Messages */ /* System and AI Command Messages */
.message.system .message-content { .message.system .message-content {
background-color: #f1f3f5; background-color: #f8d7da;
color: #495057; color: #721c24;
border: 1px solid #dee2e6; border: 1px solid #f5c6cb;
font-size: 0.8rem;
padding: 0.5rem 0.75rem;
} }
.message.ai_command .message-content { .message.ai_command .message-content {
@ -245,13 +243,14 @@ body {
} }
.kanban-column { .kanban-column {
flex: 1 1 18rem; flex: 1 1 300px; /* Flex-grow, flex-shrink, and basis */
min-width: 16rem; min-width: 300px;
max-width: 18rem; max-width: 320px;
background-color: #f0f2f5; background-color: #f0f2f5;
border-radius: 0.5rem; border-radius: 0.5rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-height: calc(100vh - 250px); /* Adjust based on your layout */
} }
.kanban-column h2 { .kanban-column h2 {
@ -296,12 +295,3 @@ body {
box-shadow: 0 8px 16px rgba(0,0,0,0.2); box-shadow: 0 8px 16px rgba(0,0,0,0.2);
transform: rotate(3deg); transform: rotate(3deg);
} }
.task-id {
font-weight: 600;
font-size: 0.8rem;
}
.kanban-card .task-id {
margin-top: 0.2rem;
}

View File

@ -202,11 +202,9 @@ body {
/* System and AI Command Messages */ /* System and AI Command Messages */
.message.system .message-content { .message.system .message-content {
background-color: #f1f3f5; background-color: #f8d7da;
color: #495057; color: #721c24;
border: 1px solid #dee2e6; border: 1px solid #f5c6cb;
font-size: 0.8rem;
padding: 0.5rem 0.75rem;
} }
.message.ai_command .message-content { .message.ai_command .message-content {
@ -245,13 +243,14 @@ body {
} }
.kanban-column { .kanban-column {
flex: 1 1 18rem; flex: 1 1 300px; /* Flex-grow, flex-shrink, and basis */
min-width: 16rem; min-width: 300px;
max-width: 18rem; max-width: 320px;
background-color: #f0f2f5; background-color: #f0f2f5;
border-radius: 0.5rem; border-radius: 0.5rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-height: calc(100vh - 250px); /* Adjust based on your layout */
} }
.kanban-column h2 { .kanban-column h2 {
@ -296,12 +295,3 @@ body {
box-shadow: 0 8px 16px rgba(0,0,0,0.2); box-shadow: 0 8px 16px rgba(0,0,0,0.2);
transform: rotate(3deg); transform: rotate(3deg);
} }
.task-id {
font-weight: 600;
font-size: 0.8rem;
}
.kanban-card .task-id {
margin-top: 0.2rem;
}