diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 20f0cc2..42269e6 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index cd02fcf..8072a30 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 0580716..18d5687 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 53fe095..8f27c3c 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 19afb57..fab9d55 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,5 +1,8 @@ from django.contrib import admin -from .models import Article, TodoItem +from .models import Article, TodoItem, Setting, Conversation, Message admin.site.register(Article) -admin.site.register(TodoItem) \ No newline at end of file +admin.site.register(TodoItem) +admin.site.register(Setting) +admin.site.register(Conversation) +admin.site.register(Message) diff --git a/core/migrations/0006_setting.py b/core/migrations/0006_setting.py new file mode 100644 index 0000000..cc29edb --- /dev/null +++ b/core/migrations/0006_setting.py @@ -0,0 +1,21 @@ +# Generated by Django 5.2.7 on 2025-11-20 10:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_alter_message_options_remove_message_is_from_user_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='Setting', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.CharField(max_length=255, unique=True)), + ('value', models.TextField()), + ], + ), + ] diff --git a/core/migrations/__pycache__/0006_setting.cpython-311.pyc b/core/migrations/__pycache__/0006_setting.cpython-311.pyc new file mode 100644 index 0000000..49bfd5f Binary files /dev/null and b/core/migrations/__pycache__/0006_setting.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index b9cb4ae..01752e5 100644 --- a/core/models.py +++ b/core/models.py @@ -42,4 +42,11 @@ class Message(models.Model): ordering = ['created_at'] def __str__(self): - return f"Message from {self.get_sender_display()} at {self.created_at}" \ No newline at end of file + return f"Message from {self.get_sender_display()} at {self.created_at}" + +class Setting(models.Model): + key = models.CharField(max_length=255, unique=True) + value = models.TextField() + + def __str__(self): + return self.key diff --git a/core/templates/base.html b/core/templates/base.html index 905e789..bdd75c5 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -30,6 +30,9 @@ + diff --git a/core/templates/core/settings.html b/core/templates/core/settings.html new file mode 100644 index 0000000..f4a5f3d --- /dev/null +++ b/core/templates/core/settings.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block content %} +
+

Settings

+
+ {% csrf_token %} +
+ + +
+ These instructions will be added to the AI's system prompt. +
+
+ +
+
+{% endblock %} diff --git a/core/urls.py b/core/urls.py index 1a7b1a1..b1dc985 100644 --- a/core/urls.py +++ b/core/urls.py @@ -12,4 +12,5 @@ urlpatterns = [ path('chat/', views.chat_view, name='chat'), path('chat//', views.chat_view, name='chat_detail'), path('cleanup_tasks/', views.cleanup_tasks, name='cleanup_tasks'), + path('settings/', views.settings_view, name='settings'), ] \ No newline at end of file diff --git a/core/views.py b/core/views.py index 73e0364..29c919f 100644 --- a/core/views.py +++ b/core/views.py @@ -3,7 +3,7 @@ from django.http import JsonResponse from django.views.decorators.http import require_POST import json import logging -from .models import Article, TodoItem, Conversation, Message +from .models import Article, TodoItem, Conversation, Message, Setting from .forms import TodoItemForm import time from ai.local_ai_api import LocalAIApi @@ -170,9 +170,15 @@ def chat_view(request, conversation_id=None): history.append({"role": role, "content": msg.content}) try: + custom_instructions, created = Setting.objects.get_or_create( + key='custom_instructions', + defaults={'value': ''} + ) + custom_instructions_text = custom_instructions.value + '\n\n' if custom_instructions.value else '' + system_message = { "role": "system", - "content": '''You are a project management assistant. To communicate with the user, you MUST use the `send_message` command. + "content": custom_instructions_text + '''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: @@ -333,3 +339,23 @@ def chat_view(request, conversation_id=None): 'selected_conversation': selected_conversation, 'timestamp': int(time.time()), }) + +def conversation_list(request): + conversations = Conversation.objects.order_by('-created_at') + return render(request, 'core/conversation_list.html', {'conversation_list': conversations}) + +def settings_view(request): + # Get or create the custom_instructions setting + custom_instructions, created = Setting.objects.get_or_create( + key='custom_instructions', + defaults={'value': ''} + ) + + if request.method == 'POST': + custom_instructions.value = request.POST.get('custom_instructions', '') + custom_instructions.save() + return redirect('core:settings') + + return render(request, 'core/settings.html', { + 'custom_instructions': custom_instructions + }) \ No newline at end of file diff --git a/static/css/custom.css b/static/css/custom.css index 551c68f..293163a 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -227,4 +227,71 @@ body { position: absolute; top: 10px; right: 10px; -} \ No newline at end of file +} + +/* Kanban Board Styles */ +.kanban-board-container { + width: 100%; + overflow-x: auto; + padding: 1rem; +} + +.kanban-board { + display: flex; + gap: 1rem; + min-width: max-content; /* Ensure board expands horizontally */ +} + +.kanban-column { + flex: 1 1 300px; /* Flex-grow, flex-shrink, and basis */ + min-width: 300px; + max-width: 320px; + background-color: #f0f2f5; + border-radius: 0.5rem; + display: flex; + flex-direction: column; + max-height: calc(100vh - 250px); /* Adjust based on your layout */ +} + +.kanban-column h2 { + position: sticky; + top: 0; + z-index: 2; +} + +.kanban-cards { + overflow-y: auto; + flex-grow: 1; + min-height: 150px; /* Ensure drop zone is available even when empty */ +} + +.kanban-card { + cursor: grab; + transition: background-color 0.2s, box-shadow 0.2s; +} + +.kanban-card:hover { + background-color: #f8f9fa; + box-shadow: 0 4px 8px rgba(0,0,0,0.1); +} + +.kanban-card .btn-close { + transition: opacity 0.2s; + opacity: 0; +} + +.kanban-card:hover .btn-close { + opacity: 1; +} + +/* For the drag-and-drop placeholder */ +.sortable-ghost { + background-color: #e9ecef; + border: 2px dashed #ced4da; +} + +.sortable-drag { + opacity: 1 !important; /* Override Sortable.js default opacity */ + box-shadow: 0 8px 16px rgba(0,0,0,0.2); + transform: rotate(3deg); +} diff --git a/static/css/custom.css.bak b/static/css/custom.css.bak new file mode 100644 index 0000000..551c68f --- /dev/null +++ b/static/css/custom.css.bak @@ -0,0 +1,230 @@ +/* General App Body & Layout */ +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + background-color: #f8f9fa; + color: #212529; +} + +/* Main Chat Layout */ +.chat-container { + display: flex; + height: calc(100vh - 120px); /* Adjusted for header/footer */ + width: 100%; + background-color: #fff; + border: 1px solid #dee2e6; + border-radius: 0.5rem; +} + +/* Sidebar Styles */ +.chat-sidebar { + width: 280px; + background-color: #f8f9fa; + border-right: 1px solid #dee2e6; + display: flex; + flex-direction: column; + padding: 1rem; + flex-shrink: 0; +} + +.new-chat-form { + display: flex; + margin-bottom: 1rem; +} + +.new-chat-form input { + flex: 1; + padding: 0.75rem; + border: 1px solid #ced4da; + border-radius: 0.375rem 0 0 0.375rem; + font-size: 0.9rem; +} + +.new-chat-form button { + padding: 0.75rem 1rem; + background-color: #0d6efd; + color: #fff; + border: 1px solid #0d6efd; + border-radius: 0 0.375rem 0.375rem 0; + cursor: pointer; +} + +.conversation-list { + list-style: none; + padding: 0; + margin: 0; + overflow-y: auto; +} + +.conversation-list a { + display: block; + padding: 0.75rem 1rem; + color: #495057; + text-decoration: none; + border-radius: 0.375rem; + margin-bottom: 0.25rem; +} + +.conversation-list a:hover { + background-color: #e9ecef; +} + +.conversation-list a.active { + background-color: #0d6efd; + color: #fff; +} + +/* Main Content Area */ +.chat-main { + flex: 1; + display: flex; + flex-direction: column; + background-color: #ffffff; + position: relative; /* Needed for loader overlay */ +} + +/* Chat Header */ +.chat-header { + padding: 1rem 1.5rem; + border-bottom: 1px solid #dee2e6; + flex-shrink: 0; +} + +.chat-header h3 { + margin: 0; + font-size: 1.1rem; +} + +/* Message Area */ +.chat-messages { + flex: 1; + padding: 1.5rem; + overflow-y: auto; +} + +.message { + margin-bottom: 1rem; + display: flex; +} + +.message-content { + max-width: 80%; + padding: 0.75rem 1rem; + border-radius: 0.5rem; + line-height: 1.5; +} + +.message.user { + justify-content: flex-end; +} + +.message.user .message-content { + background-color: #0d6efd; + color: #fff; +} + +.message.ai .message-content { + background-color: #e9ecef; + color: #343a40; +} + +.message-author { + font-weight: bold; + font-size: 0.8rem; + margin-bottom: 0.25rem; +} + +/* Message Input Form */ +.chat-form-container { + padding: 1rem 1.5rem; + border-top: 1px solid #dee2e6; + background-color: #f8f9fa; +} + +.chat-form { + display: flex; +} + +.chat-form textarea { + flex: 1; + padding: 0.75rem; + border: 1px solid #ced4da; + border-radius: 0.375rem; + font-size: 1rem; + resize: none; +} + +.chat-form button { + margin-left: 1rem; + padding: 0.75rem 1.5rem; + background-color: #0d6efd; + color: #fff; + border: none; + border-radius: 0.375rem; + cursor: pointer; +} + +/* Empty State for Chat */ +.no-conversation-selected { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + color: #6c757d; +} + +/* Loader Styles */ +.loader-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(255, 255, 255, 0.8); + display: flex; + justify-content: center; + align-items: center; + z-index: 10; +} + +.loader { + border: 5px solid #f3f3f3; /* Light grey */ + border-top: 5px solid #0d6efd; /* Blue */ + border-radius: 50%; + width: 50px; + height: 50px; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 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; +} \ No newline at end of file diff --git a/staticfiles/css/custom.css b/staticfiles/css/custom.css index 551c68f..293163a 100644 --- a/staticfiles/css/custom.css +++ b/staticfiles/css/custom.css @@ -227,4 +227,71 @@ body { position: absolute; top: 10px; right: 10px; -} \ No newline at end of file +} + +/* Kanban Board Styles */ +.kanban-board-container { + width: 100%; + overflow-x: auto; + padding: 1rem; +} + +.kanban-board { + display: flex; + gap: 1rem; + min-width: max-content; /* Ensure board expands horizontally */ +} + +.kanban-column { + flex: 1 1 300px; /* Flex-grow, flex-shrink, and basis */ + min-width: 300px; + max-width: 320px; + background-color: #f0f2f5; + border-radius: 0.5rem; + display: flex; + flex-direction: column; + max-height: calc(100vh - 250px); /* Adjust based on your layout */ +} + +.kanban-column h2 { + position: sticky; + top: 0; + z-index: 2; +} + +.kanban-cards { + overflow-y: auto; + flex-grow: 1; + min-height: 150px; /* Ensure drop zone is available even when empty */ +} + +.kanban-card { + cursor: grab; + transition: background-color 0.2s, box-shadow 0.2s; +} + +.kanban-card:hover { + background-color: #f8f9fa; + box-shadow: 0 4px 8px rgba(0,0,0,0.1); +} + +.kanban-card .btn-close { + transition: opacity 0.2s; + opacity: 0; +} + +.kanban-card:hover .btn-close { + opacity: 1; +} + +/* For the drag-and-drop placeholder */ +.sortable-ghost { + background-color: #e9ecef; + border: 2px dashed #ced4da; +} + +.sortable-drag { + opacity: 1 !important; /* Override Sortable.js default opacity */ + box-shadow: 0 8px 16px rgba(0,0,0,0.2); + transform: rotate(3deg); +} diff --git a/staticfiles/css/custom.css.bak b/staticfiles/css/custom.css.bak new file mode 100644 index 0000000..551c68f --- /dev/null +++ b/staticfiles/css/custom.css.bak @@ -0,0 +1,230 @@ +/* General App Body & Layout */ +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + background-color: #f8f9fa; + color: #212529; +} + +/* Main Chat Layout */ +.chat-container { + display: flex; + height: calc(100vh - 120px); /* Adjusted for header/footer */ + width: 100%; + background-color: #fff; + border: 1px solid #dee2e6; + border-radius: 0.5rem; +} + +/* Sidebar Styles */ +.chat-sidebar { + width: 280px; + background-color: #f8f9fa; + border-right: 1px solid #dee2e6; + display: flex; + flex-direction: column; + padding: 1rem; + flex-shrink: 0; +} + +.new-chat-form { + display: flex; + margin-bottom: 1rem; +} + +.new-chat-form input { + flex: 1; + padding: 0.75rem; + border: 1px solid #ced4da; + border-radius: 0.375rem 0 0 0.375rem; + font-size: 0.9rem; +} + +.new-chat-form button { + padding: 0.75rem 1rem; + background-color: #0d6efd; + color: #fff; + border: 1px solid #0d6efd; + border-radius: 0 0.375rem 0.375rem 0; + cursor: pointer; +} + +.conversation-list { + list-style: none; + padding: 0; + margin: 0; + overflow-y: auto; +} + +.conversation-list a { + display: block; + padding: 0.75rem 1rem; + color: #495057; + text-decoration: none; + border-radius: 0.375rem; + margin-bottom: 0.25rem; +} + +.conversation-list a:hover { + background-color: #e9ecef; +} + +.conversation-list a.active { + background-color: #0d6efd; + color: #fff; +} + +/* Main Content Area */ +.chat-main { + flex: 1; + display: flex; + flex-direction: column; + background-color: #ffffff; + position: relative; /* Needed for loader overlay */ +} + +/* Chat Header */ +.chat-header { + padding: 1rem 1.5rem; + border-bottom: 1px solid #dee2e6; + flex-shrink: 0; +} + +.chat-header h3 { + margin: 0; + font-size: 1.1rem; +} + +/* Message Area */ +.chat-messages { + flex: 1; + padding: 1.5rem; + overflow-y: auto; +} + +.message { + margin-bottom: 1rem; + display: flex; +} + +.message-content { + max-width: 80%; + padding: 0.75rem 1rem; + border-radius: 0.5rem; + line-height: 1.5; +} + +.message.user { + justify-content: flex-end; +} + +.message.user .message-content { + background-color: #0d6efd; + color: #fff; +} + +.message.ai .message-content { + background-color: #e9ecef; + color: #343a40; +} + +.message-author { + font-weight: bold; + font-size: 0.8rem; + margin-bottom: 0.25rem; +} + +/* Message Input Form */ +.chat-form-container { + padding: 1rem 1.5rem; + border-top: 1px solid #dee2e6; + background-color: #f8f9fa; +} + +.chat-form { + display: flex; +} + +.chat-form textarea { + flex: 1; + padding: 0.75rem; + border: 1px solid #ced4da; + border-radius: 0.375rem; + font-size: 1rem; + resize: none; +} + +.chat-form button { + margin-left: 1rem; + padding: 0.75rem 1.5rem; + background-color: #0d6efd; + color: #fff; + border: none; + border-radius: 0.375rem; + cursor: pointer; +} + +/* Empty State for Chat */ +.no-conversation-selected { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + color: #6c757d; +} + +/* Loader Styles */ +.loader-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(255, 255, 255, 0.8); + display: flex; + justify-content: center; + align-items: center; + z-index: 10; +} + +.loader { + border: 5px solid #f3f3f3; /* Light grey */ + border-top: 5px solid #0d6efd; /* Blue */ + border-radius: 50%; + width: 50px; + height: 50px; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 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; +} \ No newline at end of file