custom instructions

This commit is contained in:
Flatlogic Bot 2025-11-20 17:15:00 +00:00
parent e2769f2d2d
commit 1566d6c207
16 changed files with 680 additions and 7 deletions

View File

@ -1,5 +1,8 @@
from django.contrib import admin 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(Article)
admin.site.register(TodoItem) admin.site.register(TodoItem)
admin.site.register(Setting)
admin.site.register(Conversation)
admin.site.register(Message)

View File

@ -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()),
],
),
]

View File

@ -42,4 +42,11 @@ class Message(models.Model):
ordering = ['created_at'] ordering = ['created_at']
def __str__(self): def __str__(self):
return f"Message from {self.get_sender_display()} at {self.created_at}" 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

View File

@ -30,6 +30,9 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'core:chat' %}">Chat</a> <a class="nav-link" href="{% url 'core:chat' %}">Chat</a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="{% url 'core:settings' %}">Settings</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-4">
<h2>Settings</h2>
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="custom_instructions" class="form-label">Custom AI Instructions</label>
<textarea class="form-control" id="custom_instructions" name="custom_instructions" rows="10">{{ custom_instructions.value }}</textarea>
<div class="form-text">
These instructions will be added to the AI's system prompt.
</div>
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>
{% endblock %}

View File

@ -12,4 +12,5 @@ urlpatterns = [
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'), path('cleanup_tasks/', views.cleanup_tasks, name='cleanup_tasks'),
path('settings/', views.settings_view, name='settings'),
] ]

View File

@ -3,7 +3,7 @@ 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 import logging
from .models import Article, TodoItem, Conversation, Message from .models import Article, TodoItem, Conversation, Message, Setting
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
@ -170,9 +170,15 @@ def chat_view(request, conversation_id=None):
history.append({"role": role, "content": msg.content}) history.append({"role": role, "content": msg.content})
try: 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 = { system_message = {
"role": "system", "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: **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, 'selected_conversation': selected_conversation,
'timestamp': int(time.time()), '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
})

View File

@ -227,4 +227,71 @@ body {
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 10px; right: 10px;
} }
/* 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);
}

230
static/css/custom.css.bak Normal file
View File

@ -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;
}

View File

@ -227,4 +227,71 @@ body {
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 10px; right: 10px;
} }
/* 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);
}

View File

@ -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;
}