diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index cba48c1..aad8ca3 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/ai_service.cpython-311.pyc b/core/__pycache__/ai_service.cpython-311.pyc new file mode 100644 index 0000000..4f6c1b0 Binary files /dev/null and b/core/__pycache__/ai_service.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 6f02a63..df8d42e 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 f364545..8f39e45 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/utils.cpython-311.pyc b/core/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000..f163af5 Binary files /dev/null and b/core/__pycache__/utils.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index f9e1f12..b2a89da 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 9966056..9f578d7 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,5 +1,6 @@ from django.contrib import admin -from .models import Company, Profile, AIChatHistory +from .models import Company, Profile, AIChatHistory, AIConfiguration, SyncHistoryLog +from .utils import encrypt_value, decrypt_value @admin.register(Company) class CompanyAdmin(admin.ModelAdmin): @@ -11,8 +12,25 @@ class ProfileAdmin(admin.ModelAdmin): list_display = ('user', 'company', 'role') list_filter = ('company', 'role') +@admin.register(AIConfiguration) +class AIConfigurationAdmin(admin.ModelAdmin): + list_display = ('company', 'provider', 'is_active', 'last_sync') + list_filter = ('provider', 'is_active') + + def save_model(self, request, obj, form, change): + # Encrypt API key before saving if it's changed or new + if 'api_key' in form.changed_data or not change: + obj.api_key = encrypt_value(obj.api_key) + super().save_model(request, obj, form, change) + @admin.register(AIChatHistory) class AIChatHistoryAdmin(admin.ModelAdmin): - list_display = ('chat_title', 'ai_chat_engine', 'company', 'chat_last_date') - list_filter = ('ai_chat_engine', 'company') - search_fields = ('chat_title', 'chat_content') \ No newline at end of file + list_display = ('chat_title', 'company', 'ai_chat_engine', 'chat_last_date') + list_filter = ('company', 'ai_chat_engine') + search_fields = ('chat_title', 'chat_content') + +@admin.register(SyncHistoryLog) +class SyncHistoryLogAdmin(admin.ModelAdmin): + list_display = ('timestamp', 'company', 'configuration', 'status', 'records_synced') + list_filter = ('status', 'company') + readonly_fields = ('timestamp', 'company', 'configuration', 'status', 'records_synced', 'error_message') diff --git a/core/migrations/0002_aiconfiguration_aichathistory_ai_configuration_and_more.py b/core/migrations/0002_aiconfiguration_aichathistory_ai_configuration_and_more.py new file mode 100644 index 0000000..89d1f3e --- /dev/null +++ b/core/migrations/0002_aiconfiguration_aichathistory_ai_configuration_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 5.2.7 on 2026-02-08 16:27 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='AIConfiguration', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('provider', models.CharField(choices=[('openai', 'OpenAI'), ('perplexity', 'Perplexity')], max_length=50)), + ('api_key', models.CharField(max_length=512)), + ('is_active', models.BooleanField(default=True)), + ('last_sync', models.DateTimeField(blank=True, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ai_configs', to='core.company')), + ], + ), + migrations.AddField( + model_name='aichathistory', + name='ai_configuration', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='chat_histories', to='core.aiconfiguration'), + ), + migrations.CreateModel( + name='SyncHistoryLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.CharField(choices=[('success', 'Success'), ('error', 'Error')], max_length=50)), + ('records_synced', models.IntegerField(default=0)), + ('error_message', models.TextField(blank=True, null=True)), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.company')), + ('configuration', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.aiconfiguration')), + ], + ), + ] diff --git a/core/migrations/0003_alter_aiconfiguration_provider.py b/core/migrations/0003_alter_aiconfiguration_provider.py new file mode 100644 index 0000000..917ac7c --- /dev/null +++ b/core/migrations/0003_alter_aiconfiguration_provider.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-02-08 18:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_aiconfiguration_aichathistory_ai_configuration_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='aiconfiguration', + name='provider', + field=models.CharField(choices=[('openai', 'OpenAI'), ('perplexity', 'Perplexity'), ('merlin', 'Merlin AI'), ('poe', 'POE')], max_length=50), + ), + ] diff --git a/core/migrations/0004_alter_aiconfiguration_provider.py b/core/migrations/0004_alter_aiconfiguration_provider.py new file mode 100644 index 0000000..8394804 --- /dev/null +++ b/core/migrations/0004_alter_aiconfiguration_provider.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-02-08 18:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0003_alter_aiconfiguration_provider'), + ] + + operations = [ + migrations.AlterField( + model_name='aiconfiguration', + name='provider', + field=models.CharField(choices=[('openai', 'OpenAI'), ('perplexity', 'Perplexity'), ('merlin', 'Merlin AI'), ('poe', 'POE'), ('openrouter', 'OpenRouter (Free Tiers)')], max_length=50), + ), + ] diff --git a/core/migrations/0005_alter_aichathistory_options.py b/core/migrations/0005_alter_aichathistory_options.py new file mode 100644 index 0000000..4ef67ef --- /dev/null +++ b/core/migrations/0005_alter_aichathistory_options.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.7 on 2026-02-08 19:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0004_alter_aiconfiguration_provider'), + ] + + operations = [ + migrations.AlterModelOptions( + name='aichathistory', + options={'ordering': ['-chat_last_date'], 'verbose_name_plural': 'AI Chat Histories'}, + ), + ] diff --git a/core/migrations/0006_alter_aichathistory_unique_together.py b/core/migrations/0006_alter_aichathistory_unique_together.py new file mode 100644 index 0000000..f0f74e3 --- /dev/null +++ b/core/migrations/0006_alter_aichathistory_unique_together.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.7 on 2026-02-08 19:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_alter_aichathistory_options'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='aichathistory', + unique_together={('company', 'ai_chat_id')}, + ), + ] diff --git a/core/migrations/__pycache__/0002_aiconfiguration_aichathistory_ai_configuration_and_more.cpython-311.pyc b/core/migrations/__pycache__/0002_aiconfiguration_aichathistory_ai_configuration_and_more.cpython-311.pyc new file mode 100644 index 0000000..d993974 Binary files /dev/null and b/core/migrations/__pycache__/0002_aiconfiguration_aichathistory_ai_configuration_and_more.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0003_alter_aiconfiguration_provider.cpython-311.pyc b/core/migrations/__pycache__/0003_alter_aiconfiguration_provider.cpython-311.pyc new file mode 100644 index 0000000..d72a5dc Binary files /dev/null and b/core/migrations/__pycache__/0003_alter_aiconfiguration_provider.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0004_alter_aiconfiguration_provider.cpython-311.pyc b/core/migrations/__pycache__/0004_alter_aiconfiguration_provider.cpython-311.pyc new file mode 100644 index 0000000..f725916 Binary files /dev/null and b/core/migrations/__pycache__/0004_alter_aiconfiguration_provider.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0005_alter_aichathistory_options.cpython-311.pyc b/core/migrations/__pycache__/0005_alter_aichathistory_options.cpython-311.pyc new file mode 100644 index 0000000..f069cce Binary files /dev/null and b/core/migrations/__pycache__/0005_alter_aichathistory_options.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0006_alter_aichathistory_unique_together.cpython-311.pyc b/core/migrations/__pycache__/0006_alter_aichathistory_unique_together.cpython-311.pyc new file mode 100644 index 0000000..fefd943 Binary files /dev/null and b/core/migrations/__pycache__/0006_alter_aichathistory_unique_together.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 84a4182..fbfe82b 100644 --- a/core/models.py +++ b/core/models.py @@ -17,8 +17,27 @@ class Profile(models.Model): def __str__(self): return f"{self.user.username} ({self.company.name})" +class AIConfiguration(models.Model): + PROVIDER_CHOICES = [ + ('openai', 'OpenAI'), + ('perplexity', 'Perplexity'), + ('merlin', 'Merlin AI'), + ('poe', 'POE'), + ('openrouter', 'OpenRouter (Free Tiers)'), + ] + company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='ai_configs') + provider = models.CharField(max_length=50, choices=PROVIDER_CHOICES) + api_key = models.CharField(max_length=512) # Stored encrypted + is_active = models.BooleanField(default=True) + last_sync = models.DateTimeField(null=True, blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.company.name} - {self.get_provider_display()}" + class AIChatHistory(models.Model): company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='chat_histories') + ai_configuration = models.ForeignKey(AIConfiguration, on_delete=models.SET_NULL, null=True, blank=True, related_name='chat_histories') ai_chat_engine = models.CharField(max_length=100) # ChatGPT, Bing, etc. ai_chat_id = models.CharField(max_length=255) chat_title = models.CharField(max_length=512) @@ -28,10 +47,22 @@ class AIChatHistory(models.Model): class Meta: verbose_name_plural = "AI Chat Histories" + ordering = ['-chat_last_date'] + unique_together = ('company', 'ai_chat_id') indexes = [ models.Index(fields=['chat_title']), - # We'll use database-level full-text search or icontains for now ] def __str__(self): - return f"[{self.ai_chat_engine}] {self.chat_title}" \ No newline at end of file + return f"[{self.ai_chat_engine}] {self.chat_title}" + +class SyncHistoryLog(models.Model): + company = models.ForeignKey(Company, on_delete=models.CASCADE) + configuration = models.ForeignKey(AIConfiguration, on_delete=models.CASCADE) + status = models.CharField(max_length=50, choices=[('success', 'Success'), ('error', 'Error')]) + records_synced = models.IntegerField(default=0) + error_message = models.TextField(null=True, blank=True) + timestamp = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.timestamp} - {self.company.name} ({self.status})" diff --git a/core/templates/admin/base_site.html b/core/templates/admin/base_site.html new file mode 100644 index 0000000..017da64 --- /dev/null +++ b/core/templates/admin/base_site.html @@ -0,0 +1,15 @@ +{% extends "admin/base.html" %} +{% load static %} + +{% block title %}{{ title }} | AI Chat Archive Admin{% endblock %} + +{% block extrastyle %} +{{ block.super }} + +{% endblock %} + +{% block branding %} +

AI Chat Archive Admin

+{% endblock %} + +{% block nav-global %}{% endblock %} \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index b721411..dde5314 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -12,6 +12,8 @@ + + @@ -80,6 +82,10 @@ padding: 2px 4px; border-radius: 4px; } + + .x-small { + font-size: 0.75rem; + } {% block head %}{% endblock %} @@ -122,7 +128,7 @@
{% if messages %} {% for message in messages %} -
+
{{ message }}
{% endfor %} @@ -144,4 +150,4 @@ {% block scripts %}{% endblock %} - + \ No newline at end of file diff --git a/core/templates/core/chat_detail.html b/core/templates/core/chat_detail.html index 8cea243..cc3063e 100644 --- a/core/templates/core/chat_detail.html +++ b/core/templates/core/chat_detail.html @@ -4,40 +4,45 @@ {% block content %}
-