mirror of
https://github.com/khoj-ai/khoj.git
synced 2024-11-27 17:35:07 +01:00
[Multi-User Part 8]: Make conversation processor settings server-wide (#529)
- Rather than having each individual user configure their conversation settings, allow the server admin to configure the OpenAI API key or offline model once, and let all the users re-use that code. - To configure the settings, the admin should go to the `django/admin` page and configure the relevant chat settings. To create an admin, run `python3 src/manage.py createsuperuser` and enter in the details. For simplicity, the email and username should match. - Remove deprecated/unnecessary endpoints and views for configuring per-user chat settings
This commit is contained in:
parent
0fb81189ca
commit
fe6720fa06
21 changed files with 458 additions and 509 deletions
|
@ -30,7 +30,8 @@ from database.models import (
|
||||||
Entry,
|
Entry,
|
||||||
GithubRepoConfig,
|
GithubRepoConfig,
|
||||||
Conversation,
|
Conversation,
|
||||||
ConversationProcessorConfig,
|
ChatModelOptions,
|
||||||
|
UserConversationConfig,
|
||||||
OpenAIProcessorConversationConfig,
|
OpenAIProcessorConversationConfig,
|
||||||
OfflineChatProcessorConversationConfig,
|
OfflineChatProcessorConversationConfig,
|
||||||
)
|
)
|
||||||
|
@ -184,27 +185,42 @@ class ConversationAdapters:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_any_conversation_config(user: KhojUser):
|
def has_any_conversation_config(user: KhojUser):
|
||||||
return ConversationProcessorConfig.objects.filter(user=user).exists()
|
return ChatModelOptions.objects.filter(user=user).exists()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_openai_conversation_config(user: KhojUser):
|
def get_openai_conversation_config():
|
||||||
return OpenAIProcessorConversationConfig.objects.filter(user=user).first()
|
return OpenAIProcessorConversationConfig.objects.filter().first()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_offline_chat_conversation_config(user: KhojUser):
|
def get_offline_chat_conversation_config():
|
||||||
return OfflineChatProcessorConversationConfig.objects.filter(user=user).first()
|
return OfflineChatProcessorConversationConfig.objects.filter().first()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_valid_offline_conversation_config(user: KhojUser):
|
def has_valid_offline_conversation_config():
|
||||||
return OfflineChatProcessorConversationConfig.objects.filter(user=user, enable_offline_chat=True).exists()
|
return OfflineChatProcessorConversationConfig.objects.filter(enabled=True).exists()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_valid_openai_conversation_config(user: KhojUser):
|
def has_valid_openai_conversation_config():
|
||||||
return OpenAIProcessorConversationConfig.objects.filter(user=user).exists()
|
return OpenAIProcessorConversationConfig.objects.filter().exists()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def aset_user_conversation_processor(user: KhojUser, conversation_processor_config_id: int):
|
||||||
|
config = await ChatModelOptions.objects.filter(id=conversation_processor_config_id).afirst()
|
||||||
|
if not config:
|
||||||
|
return None
|
||||||
|
new_config = await UserConversationConfig.objects.aupdate_or_create(user=user, defaults={"setting": config})
|
||||||
|
return new_config
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_conversation_config(user: KhojUser):
|
def get_conversation_config(user: KhojUser):
|
||||||
return ConversationProcessorConfig.objects.filter(user=user).first()
|
config = UserConversationConfig.objects.filter(user=user).first()
|
||||||
|
if not config:
|
||||||
|
return None
|
||||||
|
return config.setting
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_default_conversation_config():
|
||||||
|
return ChatModelOptions.objects.filter().first()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def save_conversation(user: KhojUser, conversation_log: dict):
|
def save_conversation(user: KhojUser, conversation_log: dict):
|
||||||
|
@ -215,75 +231,45 @@ class ConversationAdapters:
|
||||||
Conversation.objects.create(user=user, conversation_log=conversation_log)
|
Conversation.objects.create(user=user, conversation_log=conversation_log)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_conversation_processor_config(user: KhojUser, new_config: UserConversationProcessorConfig):
|
def get_conversation_processor_options():
|
||||||
conversation_config, _ = ConversationProcessorConfig.objects.get_or_create(user=user)
|
return ChatModelOptions.objects.all()
|
||||||
conversation_config.max_prompt_size = new_config.max_prompt_size
|
|
||||||
conversation_config.tokenizer = new_config.tokenizer
|
|
||||||
conversation_config.save()
|
|
||||||
|
|
||||||
if new_config.openai:
|
|
||||||
default_values = {
|
|
||||||
"api_key": new_config.openai.api_key,
|
|
||||||
}
|
|
||||||
if new_config.openai.chat_model:
|
|
||||||
default_values["chat_model"] = new_config.openai.chat_model
|
|
||||||
|
|
||||||
OpenAIProcessorConversationConfig.objects.update_or_create(user=user, defaults=default_values)
|
|
||||||
|
|
||||||
if new_config.offline_chat:
|
|
||||||
default_values = {
|
|
||||||
"enable_offline_chat": str(new_config.offline_chat.enable_offline_chat),
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_config.offline_chat.chat_model:
|
|
||||||
default_values["chat_model"] = new_config.offline_chat.chat_model
|
|
||||||
|
|
||||||
OfflineChatProcessorConversationConfig.objects.update_or_create(user=user, defaults=default_values)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_enabled_conversation_settings(user: KhojUser):
|
def set_conversation_processor_config(user: KhojUser, new_config: ChatModelOptions):
|
||||||
openai_config = ConversationAdapters.get_openai_conversation_config(user)
|
user_conversation_config, _ = UserConversationConfig.objects.get_or_create(user=user)
|
||||||
|
user_conversation_config.setting = new_config
|
||||||
return {
|
user_conversation_config.save()
|
||||||
"openai": True if openai_config is not None else False,
|
|
||||||
"offline_chat": ConversationAdapters.has_offline_chat(user),
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def clear_conversation_config(user: KhojUser):
|
def has_offline_chat():
|
||||||
ConversationProcessorConfig.objects.filter(user=user).delete()
|
return OfflineChatProcessorConversationConfig.objects.filter(enabled=True).exists()
|
||||||
ConversationAdapters.clear_openai_conversation_config(user)
|
|
||||||
ConversationAdapters.clear_offline_chat_conversation_config(user)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def clear_openai_conversation_config(user: KhojUser):
|
async def ahas_offline_chat():
|
||||||
OpenAIProcessorConversationConfig.objects.filter(user=user).delete()
|
return await OfflineChatProcessorConversationConfig.objects.filter(enabled=True).aexists()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def clear_offline_chat_conversation_config(user: KhojUser):
|
async def get_offline_chat():
|
||||||
OfflineChatProcessorConversationConfig.objects.filter(user=user).delete()
|
return await ChatModelOptions.objects.filter(model_type="offline").afirst()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_offline_chat(user: KhojUser):
|
async def aget_user_conversation_config(user: KhojUser):
|
||||||
return OfflineChatProcessorConversationConfig.objects.filter(user=user, enable_offline_chat=True).exists()
|
config = await UserConversationConfig.objects.filter(user=user).prefetch_related("setting").afirst()
|
||||||
|
if not config:
|
||||||
|
return None
|
||||||
|
return config.setting
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def ahas_offline_chat(user: KhojUser):
|
async def has_openai_chat():
|
||||||
return await OfflineChatProcessorConversationConfig.objects.filter(
|
return await OpenAIProcessorConversationConfig.objects.filter().aexists()
|
||||||
user=user, enable_offline_chat=True
|
|
||||||
).aexists()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_offline_chat(user: KhojUser):
|
async def get_openai_chat():
|
||||||
return await OfflineChatProcessorConversationConfig.objects.filter(user=user).afirst()
|
return await OpenAIProcessorConversationConfig.objects.filter().afirst()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def has_openai_chat(user: KhojUser):
|
async def aget_default_conversation_config():
|
||||||
return await OpenAIProcessorConversationConfig.objects.filter(user=user).aexists()
|
return await ChatModelOptions.objects.filter().afirst()
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def get_openai_chat(user: KhojUser):
|
|
||||||
return await OpenAIProcessorConversationConfig.objects.filter(user=user).afirst()
|
|
||||||
|
|
||||||
|
|
||||||
class EntryAdapters:
|
class EntryAdapters:
|
||||||
|
|
|
@ -3,6 +3,15 @@ from django.contrib.auth.admin import UserAdmin
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
|
|
||||||
from database.models import KhojUser
|
from database.models import (
|
||||||
|
KhojUser,
|
||||||
|
ChatModelOptions,
|
||||||
|
OpenAIProcessorConversationConfig,
|
||||||
|
OfflineChatProcessorConversationConfig,
|
||||||
|
)
|
||||||
|
|
||||||
admin.site.register(KhojUser, UserAdmin)
|
admin.site.register(KhojUser, UserAdmin)
|
||||||
|
|
||||||
|
admin.site.register(ChatModelOptions)
|
||||||
|
admin.site.register(OpenAIProcessorConversationConfig)
|
||||||
|
admin.site.register(OfflineChatProcessorConversationConfig)
|
||||||
|
|
|
@ -13,19 +13,6 @@ class Migration(migrations.Migration):
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
|
||||||
name="ConversationProcessorConfig",
|
|
||||||
fields=[
|
|
||||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
|
||||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
||||||
("updated_at", models.DateTimeField(auto_now=True)),
|
|
||||||
("conversation", models.JSONField()),
|
|
||||||
("enable_offline_chat", models.BooleanField(default=False)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"abstract": False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="GithubConfig",
|
name="GithubConfig",
|
||||||
fields=[
|
fields=[
|
|
@ -6,7 +6,7 @@ import uuid
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("database", "0004_conversationprocessorconfig_githubconfig_and_more"),
|
("database", "0004_content_types_and_more"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
|
27
src/database/migrations/0007_add_conversation.py
Normal file
27
src/database/migrations/0007_add_conversation.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 4.2.5 on 2023-10-18 05:31
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("database", "0006_embeddingsdates"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Conversation",
|
||||||
|
fields=[
|
||||||
|
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
("conversation_log", models.JSONField()),
|
||||||
|
("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,81 +0,0 @@
|
||||||
# Generated by Django 4.2.5 on 2023-10-18 05:31
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("database", "0006_embeddingsdates"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="conversationprocessorconfig",
|
|
||||||
name="conversation",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="conversationprocessorconfig",
|
|
||||||
name="enable_offline_chat",
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="conversationprocessorconfig",
|
|
||||||
name="max_prompt_size",
|
|
||||||
field=models.IntegerField(blank=True, default=None, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="conversationprocessorconfig",
|
|
||||||
name="tokenizer",
|
|
||||||
field=models.CharField(blank=True, default=None, max_length=200, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="conversationprocessorconfig",
|
|
||||||
name="user",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
|
||||||
),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="OpenAIProcessorConversationConfig",
|
|
||||||
fields=[
|
|
||||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
|
||||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
||||||
("updated_at", models.DateTimeField(auto_now=True)),
|
|
||||||
("api_key", models.CharField(max_length=200)),
|
|
||||||
("chat_model", models.CharField(max_length=200)),
|
|
||||||
("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"abstract": False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="OfflineChatProcessorConversationConfig",
|
|
||||||
fields=[
|
|
||||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
|
||||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
||||||
("updated_at", models.DateTimeField(auto_now=True)),
|
|
||||||
("enable_offline_chat", models.BooleanField(default=False)),
|
|
||||||
("chat_model", models.CharField(default="llama-2-7b-chat.ggmlv3.q4_0.bin", max_length=200)),
|
|
||||||
("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"abstract": False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Conversation",
|
|
||||||
fields=[
|
|
||||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
|
||||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
||||||
("updated_at", models.DateTimeField(auto_now=True)),
|
|
||||||
("conversation_log", models.JSONField()),
|
|
||||||
("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"abstract": False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -5,7 +5,7 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("database", "0007_remove_conversationprocessorconfig_conversation_and_more"),
|
("database", "0007_add_conversation"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
|
83
src/database/migrations/0010_chatmodeloptions_and_more.py
Normal file
83
src/database/migrations/0010_chatmodeloptions_and_more.py
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
# Generated by Django 4.2.4 on 2023-11-01 17:41
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("database", "0009_khojapiuser"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="ChatModelOptions",
|
||||||
|
fields=[
|
||||||
|
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
("max_prompt_size", models.IntegerField(blank=True, default=None, null=True)),
|
||||||
|
("tokenizer", models.CharField(blank=True, default=None, max_length=200, null=True)),
|
||||||
|
("chat_model", models.CharField(blank=True, default=None, max_length=200, null=True)),
|
||||||
|
(
|
||||||
|
"model_type",
|
||||||
|
models.CharField(
|
||||||
|
choices=[("openai", "Openai"), ("offline", "Offline")], default="openai", max_length=200
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="OfflineChatProcessorConversationConfig",
|
||||||
|
fields=[
|
||||||
|
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
("enabled", models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="OpenAIProcessorConversationConfig",
|
||||||
|
fields=[
|
||||||
|
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
("api_key", models.CharField(max_length=200)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="UserConversationConfig",
|
||||||
|
fields=[
|
||||||
|
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"setting",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="database.chatmodeloptions",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
12
src/database/migrations/0011_merge_20231102_0138.py
Normal file
12
src/database/migrations/0011_merge_20231102_0138.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# Generated by Django 4.2.5 on 2023-11-02 01:38
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("database", "0010_chatmodeloptions_and_more"),
|
||||||
|
("database", "0010_rename_embeddings_entry_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = []
|
|
@ -93,20 +93,26 @@ class LocalPlaintextConfig(BaseModel):
|
||||||
|
|
||||||
class OpenAIProcessorConversationConfig(BaseModel):
|
class OpenAIProcessorConversationConfig(BaseModel):
|
||||||
api_key = models.CharField(max_length=200)
|
api_key = models.CharField(max_length=200)
|
||||||
chat_model = models.CharField(max_length=200)
|
|
||||||
user = models.ForeignKey(KhojUser, on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
|
|
||||||
class OfflineChatProcessorConversationConfig(BaseModel):
|
class OfflineChatProcessorConversationConfig(BaseModel):
|
||||||
enable_offline_chat = models.BooleanField(default=False)
|
enabled = models.BooleanField(default=False)
|
||||||
chat_model = models.CharField(max_length=200, default="llama-2-7b-chat.ggmlv3.q4_0.bin")
|
|
||||||
user = models.ForeignKey(KhojUser, on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
|
|
||||||
class ConversationProcessorConfig(BaseModel):
|
class ChatModelOptions(BaseModel):
|
||||||
|
class ModelType(models.TextChoices):
|
||||||
|
OPENAI = "openai"
|
||||||
|
OFFLINE = "offline"
|
||||||
|
|
||||||
max_prompt_size = models.IntegerField(default=None, null=True, blank=True)
|
max_prompt_size = models.IntegerField(default=None, null=True, blank=True)
|
||||||
tokenizer = models.CharField(max_length=200, default=None, null=True, blank=True)
|
tokenizer = models.CharField(max_length=200, default=None, null=True, blank=True)
|
||||||
user = models.ForeignKey(KhojUser, on_delete=models.CASCADE)
|
chat_model = models.CharField(max_length=200, default=None, null=True, blank=True)
|
||||||
|
model_type = models.CharField(max_length=200, choices=ModelType.choices, default=ModelType.OPENAI)
|
||||||
|
|
||||||
|
|
||||||
|
class UserConversationConfig(BaseModel):
|
||||||
|
user = models.OneToOneField(KhojUser, on_delete=models.CASCADE)
|
||||||
|
setting = models.ForeignKey(ChatModelOptions, on_delete=models.CASCADE, default=None, null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
class Conversation(BaseModel):
|
class Conversation(BaseModel):
|
||||||
|
|
|
@ -12,6 +12,7 @@ import os
|
||||||
import schedule
|
import schedule
|
||||||
from starlette.middleware.sessions import SessionMiddleware
|
from starlette.middleware.sessions import SessionMiddleware
|
||||||
from starlette.middleware.authentication import AuthenticationMiddleware
|
from starlette.middleware.authentication import AuthenticationMiddleware
|
||||||
|
from starlette.requests import HTTPConnection
|
||||||
|
|
||||||
from starlette.authentication import (
|
from starlette.authentication import (
|
||||||
AuthCredentials,
|
AuthCredentials,
|
||||||
|
@ -60,7 +61,7 @@ class UserAuthenticationBackend(AuthenticationBackend):
|
||||||
password="default",
|
password="default",
|
||||||
)
|
)
|
||||||
|
|
||||||
async def authenticate(self, request: Request):
|
async def authenticate(self, request: HTTPConnection):
|
||||||
current_user = request.session.get("user")
|
current_user = request.session.get("user")
|
||||||
if current_user and current_user.get("email"):
|
if current_user and current_user.get("email"):
|
||||||
user = await self.khojuser_manager.filter(email=current_user.get("email")).afirst()
|
user = await self.khojuser_manager.filter(email=current_user.get("email")).afirst()
|
||||||
|
|
|
@ -234,6 +234,19 @@
|
||||||
height: 32px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select#chat-models {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
div.api-settings {
|
||||||
|
width: 640px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.api-key-action:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 700px) {
|
@media screen and (max-width: 700px) {
|
||||||
.section-cards {
|
.section-cards {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
@ -268,6 +281,10 @@
|
||||||
div.khoj-header-wrapper {
|
div.khoj-header-wrapper {
|
||||||
grid-template-columns: auto;
|
grid-template-columns: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.api-settings {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -10,9 +10,7 @@
|
||||||
<img class="card-icon" src="/static/assets/icons/github.svg" alt="Github">
|
<img class="card-icon" src="/static/assets/icons/github.svg" alt="Github">
|
||||||
<h3 class="card-title">
|
<h3 class="card-title">
|
||||||
Github
|
Github
|
||||||
{% if current_model_state.github == False %}
|
{% if current_model_state.github == True %}
|
||||||
<img id="misconfigured-icon-github" class="configured-icon" src="/static/assets/icons/question-mark-icon.svg" alt="Not Configured" title="Embeddings have not been generated yet for this content type. Either the configuration is invalid, or you just need to click Configure.">
|
|
||||||
{% else %}
|
|
||||||
<img id="configured-icon-github" class="configured-icon" src="/static/assets/icons/confirm-icon.svg" alt="Configured">
|
<img id="configured-icon-github" class="configured-icon" src="/static/assets/icons/confirm-icon.svg" alt="Configured">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
|
@ -43,9 +41,7 @@
|
||||||
<img class="card-icon" src="/static/assets/icons/notion.svg" alt="Notion">
|
<img class="card-icon" src="/static/assets/icons/notion.svg" alt="Notion">
|
||||||
<h3 class="card-title">
|
<h3 class="card-title">
|
||||||
Notion
|
Notion
|
||||||
{% if current_model_state.notion == False %}
|
{% if current_model_state.notion == True %}
|
||||||
<img id="misconfigured-icon-notion" class="configured-icon" src="/static/assets/icons/question-mark-icon.svg" alt="Not Configured" title="Embeddings have not been generated yet for this content type. Either the configuration is invalid, or you just need to click Configure.">
|
|
||||||
{% else %}
|
|
||||||
<img id="configured-icon-notion" class="configured-icon" src="/static/assets/icons/confirm-icon.svg" alt="Configured">
|
<img id="configured-icon-notion" class="configured-icon" src="/static/assets/icons/confirm-icon.svg" alt="Configured">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
|
@ -76,12 +72,8 @@
|
||||||
<img class="card-icon" src="/static/assets/icons/markdown.svg" alt="markdown">
|
<img class="card-icon" src="/static/assets/icons/markdown.svg" alt="markdown">
|
||||||
<h3 class="card-title">
|
<h3 class="card-title">
|
||||||
Markdown
|
Markdown
|
||||||
{% if current_model_state.markdown %}
|
{% if current_model_state.markdown == True%}
|
||||||
{% if current_model_state.markdown == False%}
|
<img id="configured-icon-markdown" class="configured-icon" src="/static/assets/icons/confirm-icon.svg" alt="Configured">
|
||||||
<img id="misconfigured-icon-markdown" class="configured-icon" src="/static/assets/icons/question-mark-icon.svg" alt="Not Configured" title="Embeddings have not been generated yet for this content type. Either the configuration is invalid, or you just need to click Configure.">
|
|
||||||
{% else %}
|
|
||||||
<img id="configured-icon-markdown" class="configured-icon" src="/static/assets/icons/confirm-icon.svg" alt="Configured">
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
@ -111,12 +103,8 @@
|
||||||
<img class="card-icon" src="/static/assets/icons/org.svg" alt="org">
|
<img class="card-icon" src="/static/assets/icons/org.svg" alt="org">
|
||||||
<h3 class="card-title">
|
<h3 class="card-title">
|
||||||
Org
|
Org
|
||||||
{% if current_model_state.org %}
|
{% if current_model_state.org == True %}
|
||||||
{% if current_model_state.org == False %}
|
<img id="configured-icon-org" class="configured-icon" src="/static/assets/icons/confirm-icon.svg" alt="Configured">
|
||||||
<img id="misconfigured-icon-org" class="configured-icon" src="/static/assets/icons/question-mark-icon.svg" alt="Not Configured" title="Embeddings have not been generated yet for this content type. Either the configuration is invalid, or you just need to click Configure.">
|
|
||||||
{% else %}
|
|
||||||
<img id="configured-icon-org" class="configured-icon" src="/static/assets/icons/confirm-icon.svg" alt="Configured">
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
@ -146,12 +134,8 @@
|
||||||
<img class="card-icon" src="/static/assets/icons/pdf.svg" alt="PDF">
|
<img class="card-icon" src="/static/assets/icons/pdf.svg" alt="PDF">
|
||||||
<h3 class="card-title">
|
<h3 class="card-title">
|
||||||
PDF
|
PDF
|
||||||
{% if current_model_state.pdf %}
|
{% if current_model_state.pdf == True %}
|
||||||
{% if current_model_state.pdf == False %}
|
<img id="configured-icon-pdf" class="configured-icon" src="/static/assets/icons/confirm-icon.svg" alt="Configured">
|
||||||
<img id="misconfigured-icon-pdf" class="configured-icon" src="/static/assets/icons/question-mark-icon.svg" alt="Not Configured" title="Embeddings have not been generated yet for this content type. Either the configuration is invalid, or you need to click Configure.">
|
|
||||||
{% else %}
|
|
||||||
<img id="configured-icon-pdf" class="configured-icon" src="/static/assets/icons/confirm-icon.svg" alt="Configured">
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
@ -181,12 +165,8 @@
|
||||||
<img class="card-icon" src="/static/assets/icons/plaintext.svg" alt="Plaintext">
|
<img class="card-icon" src="/static/assets/icons/plaintext.svg" alt="Plaintext">
|
||||||
<h3 class="card-title">
|
<h3 class="card-title">
|
||||||
Plaintext
|
Plaintext
|
||||||
{% if current_model_state.plaintext %}
|
{% if current_model_state.plaintext == True %}
|
||||||
{% if current_model_state.plaintext == False %}
|
<img id="configured-icon-plaintext" class="configured-icon" src="/static/assets/icons/confirm-icon.svg" alt="Configured">
|
||||||
<img id="misconfigured-icon-plaintext" class="configured-icon" src="/static/assets/icons/question-mark-icon.svg" alt="Not Configured" title="Embeddings have not been generated yet for this content type. Either the configuration is invalid, or you need to click Configure.">
|
|
||||||
{% else %}
|
|
||||||
<img id="configured-icon-plaintext" class="configured-icon" src="/static/assets/icons/confirm-icon.svg" alt="Configured">
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
@ -217,79 +197,37 @@
|
||||||
<h2 class="section-title">Features</h2>
|
<h2 class="section-title">Features</h2>
|
||||||
<div id="features-hint-text"></div>
|
<div id="features-hint-text"></div>
|
||||||
<div class="section-cards">
|
<div class="section-cards">
|
||||||
<div class="card">
|
|
||||||
<div class="card-title-row">
|
|
||||||
<img class="card-icon" src="/static/assets/icons/openai-logomark.svg" alt="Chat">
|
|
||||||
<h3 class="card-title">
|
|
||||||
Chat
|
|
||||||
{% if current_config.processor and current_config.processor.conversation.openai %}
|
|
||||||
{% if current_model_state.conversation_openai == False %}
|
|
||||||
<img id="misconfigured-icon-conversation-processor" class="configured-icon" src="/static/assets/icons/question-mark-icon.svg" alt="Not Configured" title="The OpenAI configuration did not work as expected.">
|
|
||||||
{% else %}
|
|
||||||
<img id="configured-icon-conversation-processor" class="configured-icon" src="/static/assets/icons/confirm-icon.svg" alt="Configured">
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-description-row">
|
|
||||||
<p class="card-description">Setup online chat using OpenAI</p>
|
|
||||||
</div>
|
|
||||||
<div class="card-action-row">
|
|
||||||
<a class="card-button" href="/config/processor/conversation/openai">
|
|
||||||
{% if current_config.processor and current_config.processor.conversation.openai %}
|
|
||||||
Update
|
|
||||||
{% else %}
|
|
||||||
Setup
|
|
||||||
{% endif %}
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"></path></svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% if current_config.processor and current_config.processor.conversation.openai %}
|
|
||||||
<div id="clear-conversation" class="card-action-row">
|
|
||||||
<button class="card-button" onclick="clearConversationProcessor()">
|
|
||||||
Disable
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-title-row">
|
<div class="card-title-row">
|
||||||
<img class="card-icon" src="/static/assets/icons/chat.svg" alt="Chat">
|
<img class="card-icon" src="/static/assets/icons/chat.svg" alt="Chat">
|
||||||
<h3 class="card-title">
|
<h3 class="card-title">
|
||||||
Offline Chat
|
Chat Model
|
||||||
<img id="configured-icon-conversation-enable-offline-chat" class="configured-icon {% if current_model_state.enable_offline_model and current_model_state.conversation_gpt4all %}enabled{% else %}disabled{% endif %}" src="/static/assets/icons/confirm-icon.svg" alt="Configured">
|
|
||||||
{% if current_model_state.enable_offline_model and not current_model_state.conversation_gpt4all %}
|
|
||||||
<img id="misconfigured-icon-conversation-enable-offline-chat" class="configured-icon" src="/static/assets/icons/question-mark-icon.svg" alt="Not Configured" title="The model was not downloaded as expected.">
|
|
||||||
{% endif %}
|
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-description-row">
|
<div class="card-description-row">
|
||||||
<p class="card-description">Setup offline chat</p>
|
<select id="chat-models">
|
||||||
|
{% for option in conversation_options %}
|
||||||
|
<option value="{{ option.id }}" {% if option.id == selected_conversation_config %}selected{% endif %}>{{ option.chat_model }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div id="clear-enable-offline-chat" class="card-action-row {% if current_model_state.enable_offline_model %}enabled{% else %}disabled{% endif %}">
|
<div class="card-action-row">
|
||||||
<button class="card-button" onclick="toggleEnableLocalLLLM(false)">
|
<button id="save-model" class="card-button happy" onclick="updateChatModel()">
|
||||||
Disable
|
Save
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="set-enable-offline-chat" class="card-action-row {% if current_model_state.enable_offline_model %}disabled{% else %}enabled{% endif %}">
|
|
||||||
<button class="card-button happy" onclick="toggleEnableLocalLLLM(true)">
|
|
||||||
Enable
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div id="toggle-enable-offline-chat" class="card-action-row disabled">
|
|
||||||
<div class="loader"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
|
<h2 class="section-title">Clients</h2>
|
||||||
<div class="api-settings">
|
<div class="api-settings">
|
||||||
<div class="card-title-row">
|
<div class="card-title-row">
|
||||||
<img class="card-icon" src="/static/assets/icons/key.svg" alt="API Key">
|
<img class="card-icon" src="/static/assets/icons/key.svg" alt="API Key">
|
||||||
<h3 class="card-title">API Keys</h3>
|
<h3 class="card-title">API Keys</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-description-row">
|
<div class="card-description-row">
|
||||||
<p id="api-settings-card-description" class="card-description">Manage access to your Khoj from client apps</p>
|
<p id="api-settings-card-description" class="card-description">Manage access from your client apps to Khoj</p>
|
||||||
</div>
|
</div>
|
||||||
<table id="api-settings-keys-table">
|
<table id="api-settings-keys-table">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -328,13 +266,35 @@
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
function updateChatModel() {
|
||||||
|
const chatModel = document.getElementById("chat-models").value;
|
||||||
|
const saveModelButton = document.getElementById("save-model");
|
||||||
|
saveModelButton.disabled = true;
|
||||||
|
saveModelButton.innerHTML = "Saving...";
|
||||||
|
|
||||||
|
fetch('/api/config/data/conversation/model?id=' + chatModel, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status == "ok") {
|
||||||
|
saveModelButton.innerHTML = "Save";
|
||||||
|
saveModelButton.disabled = false;
|
||||||
|
} else {
|
||||||
|
saveModelButton.innerHTML = "Error";
|
||||||
|
saveModelButton.disabled = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
function clearContentType(content_type) {
|
function clearContentType(content_type) {
|
||||||
const csrfToken = document.cookie.split('; ').find(row => row.startsWith('csrftoken'))?.split('=')[1];
|
|
||||||
fetch('/api/delete/config/data/content_type/' + content_type, {
|
fetch('/api/delete/config/data/content_type/' + content_type, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRFToken': csrfToken
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
|
@ -356,96 +316,6 @@
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
function toggleEnableLocalLLLM(enable) {
|
|
||||||
const csrfToken = document.cookie.split('; ').find(row => row.startsWith('csrftoken'))?.split('=')[1];
|
|
||||||
var toggleEnableLocalLLLMButton = document.getElementById("toggle-enable-offline-chat");
|
|
||||||
var featuresHintText = document.getElementById("features-hint-text");
|
|
||||||
toggleEnableLocalLLLMButton.classList.remove("disabled");
|
|
||||||
toggleEnableLocalLLLMButton.classList.add("enabled");
|
|
||||||
|
|
||||||
if (enable) {
|
|
||||||
featuresHintText.style.display = "block";
|
|
||||||
featuresHintText.innerHTML = "An open source model is being downloaded in the background. Hang tight, this may take a few minutes ⏳.";
|
|
||||||
featuresHintText.classList.add("show");
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch('/api/config/data/processor/conversation/offline_chat' + '?enable_offline_chat=' + enable, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRFToken': csrfToken
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.status != "ok") {
|
|
||||||
featuresHintText.innerHTML = `🚨 Failed to ${enable ? "enable": "disable"} offline chat model! Inform server admins.`;
|
|
||||||
enable = !enable;
|
|
||||||
} else {
|
|
||||||
featuresHintText.classList.remove("show");
|
|
||||||
featuresHintText.innerHTML = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle the Enabled/Disabled UI based on the action/response.
|
|
||||||
var enableLocalLLLMButton = document.getElementById("set-enable-offline-chat");
|
|
||||||
var disableLocalLLLMButton = document.getElementById("clear-enable-offline-chat");
|
|
||||||
var configuredIcon = document.getElementById("configured-icon-conversation-enable-offline-chat");
|
|
||||||
var toggleEnableLocalLLLMButton = document.getElementById("toggle-enable-offline-chat");
|
|
||||||
|
|
||||||
toggleEnableLocalLLLMButton.classList.remove("enabled");
|
|
||||||
toggleEnableLocalLLLMButton.classList.add("disabled");
|
|
||||||
|
|
||||||
if (enable) {
|
|
||||||
enableLocalLLLMButton.classList.add("disabled");
|
|
||||||
enableLocalLLLMButton.classList.remove("enabled");
|
|
||||||
|
|
||||||
configuredIcon.classList.add("enabled");
|
|
||||||
configuredIcon.classList.remove("disabled");
|
|
||||||
|
|
||||||
disableLocalLLLMButton.classList.remove("disabled");
|
|
||||||
disableLocalLLLMButton.classList.add("enabled");
|
|
||||||
} else {
|
|
||||||
enableLocalLLLMButton.classList.remove("disabled");
|
|
||||||
enableLocalLLLMButton.classList.add("enabled");
|
|
||||||
|
|
||||||
configuredIcon.classList.remove("enabled");
|
|
||||||
configuredIcon.classList.add("disabled");
|
|
||||||
|
|
||||||
disableLocalLLLMButton.classList.add("disabled");
|
|
||||||
disableLocalLLLMButton.classList.remove("enabled");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearConversationProcessor() {
|
|
||||||
const csrfToken = document.cookie.split('; ').find(row => row.startsWith('csrftoken'))?.split('=')[1];
|
|
||||||
fetch('/api/delete/config/data/processor/conversation/openai', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRFToken': csrfToken
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.status == "ok") {
|
|
||||||
var conversationClearButton = document.getElementById("clear-conversation");
|
|
||||||
conversationClearButton.style.display = "none";
|
|
||||||
|
|
||||||
var configuredIcon = document.getElementById("configured-icon-conversation-processor");
|
|
||||||
if (configuredIcon) {
|
|
||||||
configuredIcon.style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
var misconfiguredIcon = document.getElementById("misconfigured-icon-conversation-processor");
|
|
||||||
|
|
||||||
if (misconfiguredIcon) {
|
|
||||||
misconfiguredIcon.style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
var configure = document.getElementById("configure");
|
var configure = document.getElementById("configure");
|
||||||
configure.addEventListener("click", function(event) {
|
configure.addEventListener("click", function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -572,8 +442,8 @@
|
||||||
<td><b>${tokenName}</b></td>
|
<td><b>${tokenName}</b></td>
|
||||||
<td id="api-key-${token}">${truncatedToken}</td>
|
<td id="api-key-${token}">${truncatedToken}</td>
|
||||||
<td>
|
<td>
|
||||||
<img onclick="copyAPIKey('${token}')" class="configured-icon enabled" src="/static/assets/icons/copy-solid.svg" alt="Copy API Key" title="Copy API Key">
|
<img onclick="copyAPIKey('${token}')" class="configured-icon api-key-action enabled" src="/static/assets/icons/copy-solid.svg" alt="Copy API Key" title="Copy API Key">
|
||||||
<img onclick="deleteAPIKey('${token}')" class="configured-icon enabled" src="/static/assets/icons/trash-solid.svg" alt="Delete API Key" title="Delete API Key">
|
<img onclick="deleteAPIKey('${token}')" class="configured-icon api-key-action enabled" src="/static/assets/icons/trash-solid.svg" alt="Delete API Key" title="Delete API Key">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
|
|
117
src/khoj/migrations/migrate_server_pg.py
Normal file
117
src/khoj/migrations/migrate_server_pg.py
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
"""
|
||||||
|
The application config currently looks like this:
|
||||||
|
app:
|
||||||
|
should-log-telemetry: true
|
||||||
|
content-type:
|
||||||
|
...
|
||||||
|
processor:
|
||||||
|
conversation:
|
||||||
|
conversation-logfile: ~/.khoj/processor/conversation/conversation_logs.json
|
||||||
|
max-prompt-size: null
|
||||||
|
offline-chat:
|
||||||
|
chat-model: llama-2-7b-chat.ggmlv3.q4_0.bin
|
||||||
|
enable-offline-chat: false
|
||||||
|
openai:
|
||||||
|
api-key: sk-blah
|
||||||
|
chat-model: gpt-3.5-turbo
|
||||||
|
tokenizer: null
|
||||||
|
search-type:
|
||||||
|
asymmetric:
|
||||||
|
cross-encoder: cross-encoder/ms-marco-MiniLM-L-6-v2
|
||||||
|
encoder: sentence-transformers/multi-qa-MiniLM-L6-cos-v1
|
||||||
|
encoder-type: null
|
||||||
|
model-directory: /Users/si/.khoj/search/asymmetric
|
||||||
|
image:
|
||||||
|
encoder: sentence-transformers/clip-ViT-B-32
|
||||||
|
encoder-type: null
|
||||||
|
model-directory: /Users/si/.khoj/search/image
|
||||||
|
symmetric:
|
||||||
|
cross-encoder: cross-encoder/ms-marco-MiniLM-L-6-v2
|
||||||
|
encoder: sentence-transformers/all-MiniLM-L6-v2
|
||||||
|
encoder-type: null
|
||||||
|
model-directory: ~/.khoj/search/symmetric
|
||||||
|
version: 0.12.4
|
||||||
|
|
||||||
|
|
||||||
|
The new version will looks like this:
|
||||||
|
app:
|
||||||
|
should-log-telemetry: true
|
||||||
|
processor:
|
||||||
|
conversation:
|
||||||
|
offline-chat:
|
||||||
|
enabled: false
|
||||||
|
openai:
|
||||||
|
api-key: sk-blah
|
||||||
|
chat-model-options:
|
||||||
|
- chat-model: gpt-3.5-turbo
|
||||||
|
tokenizer: null
|
||||||
|
type: openai
|
||||||
|
- chat-model: llama-2-7b-chat.ggmlv3.q4_0.bin
|
||||||
|
tokenizer: null
|
||||||
|
type: offline
|
||||||
|
search-type:
|
||||||
|
asymmetric:
|
||||||
|
cross-encoder: cross-encoder/ms-marco-MiniLM-L-6-v2
|
||||||
|
encoder: sentence-transformers/multi-qa-MiniLM-L6-cos-v1
|
||||||
|
image:
|
||||||
|
encoder: sentence-transformers/clip-ViT-B-32
|
||||||
|
encoder-type: null
|
||||||
|
model-directory: /Users/si/.khoj/search/image
|
||||||
|
version: 0.12.4
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
|
from khoj.utils.yaml import load_config_from_file, save_config_to_file
|
||||||
|
from database.models import (
|
||||||
|
OpenAIProcessorConversationConfig,
|
||||||
|
OfflineChatProcessorConversationConfig,
|
||||||
|
ChatModelOptions,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_server_pg(args):
|
||||||
|
schema_version = "0.14.0"
|
||||||
|
raw_config = load_config_from_file(args.config_file)
|
||||||
|
previous_version = raw_config.get("version")
|
||||||
|
|
||||||
|
if previous_version is None or version.parse(previous_version) < version.parse(schema_version):
|
||||||
|
logger.info(
|
||||||
|
f"Migrating configuration used for version {previous_version} to latest version for server with postgres in {args.version_no}"
|
||||||
|
)
|
||||||
|
raw_config["version"] = schema_version
|
||||||
|
|
||||||
|
if "processor" in raw_config and "conversation" in raw_config["processor"]:
|
||||||
|
processor_conversation = raw_config["processor"]["conversation"]
|
||||||
|
|
||||||
|
if "offline-chat" in raw_config["processor"]["conversation"]:
|
||||||
|
offline_chat = raw_config["processor"]["conversation"]["offline-chat"]
|
||||||
|
OfflineChatProcessorConversationConfig.objects.create(
|
||||||
|
enabled=offline_chat.get("enable-offline-chat"),
|
||||||
|
)
|
||||||
|
ChatModelOptions.objects.create(
|
||||||
|
chat_model=offline_chat.get("chat-model"),
|
||||||
|
tokenizer=processor_conversation.get("tokenizer"),
|
||||||
|
max_prompt_size=processor_conversation.get("max-prompt-size"),
|
||||||
|
model_type=ChatModelOptions.ModelType.OFFLINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
if "openai" in raw_config["processor"]["conversation"]:
|
||||||
|
openai = raw_config["processor"]["conversation"]["openai"]
|
||||||
|
|
||||||
|
OpenAIProcessorConversationConfig.objects.create(
|
||||||
|
api_key=openai.get("api-key"),
|
||||||
|
)
|
||||||
|
ChatModelOptions.objects.create(
|
||||||
|
chat_model=openai.get("chat-model"),
|
||||||
|
tokenizer=processor_conversation.get("tokenizer"),
|
||||||
|
max_prompt_size=processor_conversation.get("max-prompt-size"),
|
||||||
|
model_type=ChatModelOptions.ModelType.OPENAI,
|
||||||
|
)
|
||||||
|
|
||||||
|
save_config_to_file(raw_config, args.config_file)
|
||||||
|
|
||||||
|
return args
|
|
@ -255,7 +255,7 @@ help_message = PromptTemplate.from_template(
|
||||||
**/default**: Chat using your knowledge base and Khoj's general knowledge for context.
|
**/default**: Chat using your knowledge base and Khoj's general knowledge for context.
|
||||||
**/help**: Show this help message.
|
**/help**: Show this help message.
|
||||||
|
|
||||||
You are using the **{model}** model.
|
You are using the **{model}** model on the **{device}**.
|
||||||
**version**: {version}
|
**version**: {version}
|
||||||
""".strip()
|
""".strip()
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import time
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from typing import List, Optional, Union, Any
|
from typing import List, Optional, Union, Any
|
||||||
import asyncio
|
|
||||||
|
|
||||||
# External Packages
|
# External Packages
|
||||||
from fastapi import APIRouter, HTTPException, Header, Request
|
from fastapi import APIRouter, HTTPException, Header, Request
|
||||||
|
@ -25,19 +24,16 @@ from khoj.utils.rawconfig import (
|
||||||
SearchConfig,
|
SearchConfig,
|
||||||
SearchResponse,
|
SearchResponse,
|
||||||
TextContentConfig,
|
TextContentConfig,
|
||||||
OpenAIProcessorConfig,
|
|
||||||
GithubContentConfig,
|
GithubContentConfig,
|
||||||
NotionContentConfig,
|
NotionContentConfig,
|
||||||
ConversationProcessorConfig,
|
|
||||||
OfflineChatProcessorConfig,
|
|
||||||
)
|
)
|
||||||
from khoj.utils.state import SearchType
|
from khoj.utils.state import SearchType
|
||||||
from khoj.utils import state, constants
|
from khoj.utils import state, constants
|
||||||
from khoj.utils.helpers import AsyncIteratorWrapper
|
from khoj.utils.helpers import AsyncIteratorWrapper, get_device
|
||||||
from fastapi.responses import StreamingResponse, Response
|
from fastapi.responses import StreamingResponse, Response
|
||||||
from khoj.routers.helpers import (
|
from khoj.routers.helpers import (
|
||||||
get_conversation_command,
|
get_conversation_command,
|
||||||
perform_chat_checks,
|
validate_conversation_config,
|
||||||
agenerate_chat_response,
|
agenerate_chat_response,
|
||||||
update_telemetry_state,
|
update_telemetry_state,
|
||||||
is_ready_to_chat,
|
is_ready_to_chat,
|
||||||
|
@ -113,8 +109,6 @@ async def map_config_to_db(config: FullConfig, user: KhojUser):
|
||||||
user=user,
|
user=user,
|
||||||
token=config.content_type.notion.token,
|
token=config.content_type.notion.token,
|
||||||
)
|
)
|
||||||
if config.processor and config.processor.conversation:
|
|
||||||
ConversationAdapters.set_conversation_processor_config(user, config.processor.conversation)
|
|
||||||
|
|
||||||
|
|
||||||
# If it's a demo instance, prevent updating any of the configuration.
|
# If it's a demo instance, prevent updating any of the configuration.
|
||||||
|
@ -246,26 +240,6 @@ if not state.demo:
|
||||||
enabled_content = await sync_to_async(EntryAdapters.get_unique_file_types)(user)
|
enabled_content = await sync_to_async(EntryAdapters.get_unique_file_types)(user)
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
@api.post("/delete/config/data/processor/conversation/openai", status_code=200)
|
|
||||||
@requires(["authenticated"])
|
|
||||||
async def remove_processor_conversation_config_data(
|
|
||||||
request: Request,
|
|
||||||
client: Optional[str] = None,
|
|
||||||
):
|
|
||||||
user = request.user.object
|
|
||||||
|
|
||||||
await sync_to_async(ConversationAdapters.clear_openai_conversation_config)(user)
|
|
||||||
|
|
||||||
update_telemetry_state(
|
|
||||||
request=request,
|
|
||||||
telemetry_type="api",
|
|
||||||
api="delete_processor_openai_config",
|
|
||||||
client=client,
|
|
||||||
metadata={"processor_conversation_type": "openai"},
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"status": "ok"}
|
|
||||||
|
|
||||||
@api.post("/config/data/content_type/{content_type}", status_code=200)
|
@api.post("/config/data/content_type/{content_type}", status_code=200)
|
||||||
@requires(["authenticated"])
|
@requires(["authenticated"])
|
||||||
async def set_content_config_data(
|
async def set_content_config_data(
|
||||||
|
@ -291,70 +265,27 @@ if not state.demo:
|
||||||
|
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
@api.post("/config/data/processor/conversation/openai", status_code=200)
|
@api.post("/config/data/conversation/model", status_code=200)
|
||||||
@requires(["authenticated"])
|
@requires(["authenticated"])
|
||||||
async def set_processor_openai_config_data(
|
async def update_chat_model(
|
||||||
request: Request,
|
request: Request,
|
||||||
updated_config: Union[OpenAIProcessorConfig, None],
|
id: str,
|
||||||
client: Optional[str] = None,
|
client: Optional[str] = None,
|
||||||
):
|
):
|
||||||
user = request.user.object
|
user = request.user.object
|
||||||
|
|
||||||
conversation_config = ConversationProcessorConfig(openai=updated_config)
|
new_config = await ConversationAdapters.aset_user_conversation_processor(user, int(id))
|
||||||
|
|
||||||
await sync_to_async(ConversationAdapters.set_conversation_processor_config)(user, conversation_config)
|
|
||||||
|
|
||||||
update_telemetry_state(
|
update_telemetry_state(
|
||||||
request=request,
|
request=request,
|
||||||
telemetry_type="api",
|
telemetry_type="api",
|
||||||
api="set_processor_config",
|
api="set_conversation_chat_model",
|
||||||
client=client,
|
client=client,
|
||||||
metadata={"processor_conversation_type": "conversation"},
|
metadata={"processor_conversation_type": "conversation"},
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"status": "ok"}
|
if new_config is None:
|
||||||
|
return {"status": "error", "message": "Model not found"}
|
||||||
@api.post("/config/data/processor/conversation/offline_chat", status_code=200)
|
|
||||||
@requires(["authenticated"])
|
|
||||||
async def set_processor_enable_offline_chat_config_data(
|
|
||||||
request: Request,
|
|
||||||
enable_offline_chat: bool,
|
|
||||||
offline_chat_model: Optional[str] = None,
|
|
||||||
client: Optional[str] = None,
|
|
||||||
):
|
|
||||||
user = request.user.object
|
|
||||||
|
|
||||||
try:
|
|
||||||
if enable_offline_chat:
|
|
||||||
conversation_config = ConversationProcessorConfig(
|
|
||||||
offline_chat=OfflineChatProcessorConfig(
|
|
||||||
enable_offline_chat=enable_offline_chat,
|
|
||||||
chat_model=offline_chat_model,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
await sync_to_async(ConversationAdapters.set_conversation_processor_config)(user, conversation_config)
|
|
||||||
|
|
||||||
offline_chat = await ConversationAdapters.get_offline_chat(user)
|
|
||||||
chat_model = offline_chat.chat_model
|
|
||||||
if state.gpt4all_processor_config is None:
|
|
||||||
state.gpt4all_processor_config = GPT4AllProcessorModel(chat_model=chat_model)
|
|
||||||
|
|
||||||
else:
|
|
||||||
await sync_to_async(ConversationAdapters.clear_offline_chat_conversation_config)(user)
|
|
||||||
state.gpt4all_processor_config = None
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error updating offline chat config: {e}", exc_info=True)
|
|
||||||
return {"status": "error", "message": str(e)}
|
|
||||||
|
|
||||||
update_telemetry_state(
|
|
||||||
request=request,
|
|
||||||
telemetry_type="api",
|
|
||||||
api="set_processor_config",
|
|
||||||
client=client,
|
|
||||||
metadata={"processor_conversation_type": f"{'enable' if enable_offline_chat else 'disable'}_local_llm"},
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
@ -572,7 +503,7 @@ def chat_history(
|
||||||
host: Optional[str] = Header(None),
|
host: Optional[str] = Header(None),
|
||||||
):
|
):
|
||||||
user = request.user.object
|
user = request.user.object
|
||||||
perform_chat_checks(user)
|
validate_conversation_config()
|
||||||
|
|
||||||
# Load Conversation History
|
# Load Conversation History
|
||||||
meta_log = ConversationAdapters.get_conversation_by_user(user=user).conversation_log
|
meta_log = ConversationAdapters.get_conversation_by_user(user=user).conversation_log
|
||||||
|
@ -644,8 +575,11 @@ async def chat(
|
||||||
conversation_command = ConversationCommand.General
|
conversation_command = ConversationCommand.General
|
||||||
|
|
||||||
if conversation_command == ConversationCommand.Help:
|
if conversation_command == ConversationCommand.Help:
|
||||||
model_type = "offline" if await ConversationAdapters.has_offline_chat(user) else "openai"
|
conversation_config = await ConversationAdapters.aget_user_conversation_config(user)
|
||||||
formatted_help = help_message.format(model=model_type, version=state.khoj_version)
|
if conversation_config == None:
|
||||||
|
conversation_config = await ConversationAdapters.aget_default_conversation_config()
|
||||||
|
model_type = conversation_config.model_type
|
||||||
|
formatted_help = help_message.format(model=model_type, version=state.khoj_version, device=get_device())
|
||||||
return StreamingResponse(iter([formatted_help]), media_type="text/event-stream", status_code=200)
|
return StreamingResponse(iter([formatted_help]), media_type="text/event-stream", status_code=200)
|
||||||
|
|
||||||
# Get the (streamed) chat response from the LLM of choice.
|
# Get the (streamed) chat response from the LLM of choice.
|
||||||
|
@ -723,9 +657,9 @@ async def extract_references_and_questions(
|
||||||
# Infer search queries from user message
|
# Infer search queries from user message
|
||||||
with timer("Extracting search queries took", logger):
|
with timer("Extracting search queries took", logger):
|
||||||
# If we've reached here, either the user has enabled offline chat or the openai model is enabled.
|
# If we've reached here, either the user has enabled offline chat or the openai model is enabled.
|
||||||
if await ConversationAdapters.ahas_offline_chat(user):
|
if await ConversationAdapters.ahas_offline_chat():
|
||||||
using_offline_chat = True
|
using_offline_chat = True
|
||||||
offline_chat = await ConversationAdapters.get_offline_chat(user)
|
offline_chat = await ConversationAdapters.get_offline_chat()
|
||||||
chat_model = offline_chat.chat_model
|
chat_model = offline_chat.chat_model
|
||||||
if state.gpt4all_processor_config is None:
|
if state.gpt4all_processor_config is None:
|
||||||
state.gpt4all_processor_config = GPT4AllProcessorModel(chat_model=chat_model)
|
state.gpt4all_processor_config = GPT4AllProcessorModel(chat_model=chat_model)
|
||||||
|
@ -735,8 +669,8 @@ async def extract_references_and_questions(
|
||||||
inferred_queries = extract_questions_offline(
|
inferred_queries = extract_questions_offline(
|
||||||
defiltered_query, loaded_model=loaded_model, conversation_log=meta_log, should_extract_questions=False
|
defiltered_query, loaded_model=loaded_model, conversation_log=meta_log, should_extract_questions=False
|
||||||
)
|
)
|
||||||
elif await ConversationAdapters.has_openai_chat(user):
|
elif await ConversationAdapters.has_openai_chat():
|
||||||
openai_chat = await ConversationAdapters.get_openai_chat(user)
|
openai_chat = await ConversationAdapters.get_openai_chat()
|
||||||
api_key = openai_chat.api_key
|
api_key = openai_chat.api_key
|
||||||
chat_model = openai_chat.chat_model
|
chat_model = openai_chat.chat_model
|
||||||
inferred_queries = extract_questions(
|
inferred_queries = extract_questions(
|
||||||
|
|
|
@ -21,22 +21,25 @@ logger = logging.getLogger(__name__)
|
||||||
executor = ThreadPoolExecutor(max_workers=1)
|
executor = ThreadPoolExecutor(max_workers=1)
|
||||||
|
|
||||||
|
|
||||||
def perform_chat_checks(user: KhojUser):
|
def validate_conversation_config():
|
||||||
if ConversationAdapters.has_valid_offline_conversation_config(
|
if (
|
||||||
user
|
ConversationAdapters.has_valid_offline_conversation_config()
|
||||||
) or ConversationAdapters.has_valid_openai_conversation_config(user):
|
or ConversationAdapters.has_valid_openai_conversation_config()
|
||||||
|
):
|
||||||
|
if ConversationAdapters.get_default_conversation_config() is None:
|
||||||
|
raise HTTPException(status_code=500, detail="Contact the server administrator to set a default chat model.")
|
||||||
return
|
return
|
||||||
|
|
||||||
raise HTTPException(status_code=500, detail="Set your OpenAI API key or enable Local LLM via Khoj settings.")
|
raise HTTPException(status_code=500, detail="Set your OpenAI API key or enable Local LLM via Khoj settings.")
|
||||||
|
|
||||||
|
|
||||||
async def is_ready_to_chat(user: KhojUser):
|
async def is_ready_to_chat(user: KhojUser):
|
||||||
has_offline_config = await ConversationAdapters.ahas_offline_chat(user=user)
|
has_offline_config = await ConversationAdapters.ahas_offline_chat()
|
||||||
has_openai_config = await ConversationAdapters.has_openai_chat(user=user)
|
has_openai_config = await ConversationAdapters.has_openai_chat()
|
||||||
|
user_conversation_config = await ConversationAdapters.aget_user_conversation_config(user)
|
||||||
|
|
||||||
if has_offline_config:
|
if has_offline_config and user_conversation_config and user_conversation_config.model_type == "offline":
|
||||||
offline_chat = await ConversationAdapters.get_offline_chat(user)
|
chat_model = user_conversation_config.chat_model
|
||||||
chat_model = offline_chat.chat_model
|
|
||||||
if state.gpt4all_processor_config is None:
|
if state.gpt4all_processor_config is None:
|
||||||
state.gpt4all_processor_config = GPT4AllProcessorModel(chat_model=chat_model)
|
state.gpt4all_processor_config = GPT4AllProcessorModel(chat_model=chat_model)
|
||||||
return True
|
return True
|
||||||
|
@ -139,10 +142,12 @@ def generate_chat_response(
|
||||||
meta_log=meta_log,
|
meta_log=meta_log,
|
||||||
)
|
)
|
||||||
|
|
||||||
offline_chat_config = ConversationAdapters.get_offline_chat_conversation_config(user=user)
|
offline_chat_config = ConversationAdapters.get_offline_chat_conversation_config()
|
||||||
conversation_config = ConversationAdapters.get_conversation_config(user)
|
conversation_config = ConversationAdapters.get_conversation_config(user)
|
||||||
openai_chat_config = ConversationAdapters.get_openai_conversation_config(user)
|
if conversation_config is None:
|
||||||
if offline_chat_config:
|
conversation_config = ConversationAdapters.get_default_conversation_config()
|
||||||
|
openai_chat_config = ConversationAdapters.get_openai_conversation_config()
|
||||||
|
if offline_chat_config and offline_chat_config.enabled and conversation_config.model_type == "offline":
|
||||||
if state.gpt4all_processor_config.loaded_model is None:
|
if state.gpt4all_processor_config.loaded_model is None:
|
||||||
state.gpt4all_processor_config = GPT4AllProcessorModel(offline_chat_config.chat_model)
|
state.gpt4all_processor_config = GPT4AllProcessorModel(offline_chat_config.chat_model)
|
||||||
|
|
||||||
|
@ -154,14 +159,14 @@ def generate_chat_response(
|
||||||
conversation_log=meta_log,
|
conversation_log=meta_log,
|
||||||
completion_func=partial_completion,
|
completion_func=partial_completion,
|
||||||
conversation_command=conversation_command,
|
conversation_command=conversation_command,
|
||||||
model=offline_chat_config.chat_model,
|
model=conversation_config.chat_model,
|
||||||
max_prompt_size=conversation_config.max_prompt_size,
|
max_prompt_size=conversation_config.max_prompt_size,
|
||||||
tokenizer_name=conversation_config.tokenizer,
|
tokenizer_name=conversation_config.tokenizer,
|
||||||
)
|
)
|
||||||
|
|
||||||
elif openai_chat_config:
|
elif openai_chat_config and conversation_config.model_type == "openai":
|
||||||
api_key = openai_chat_config.api_key
|
api_key = openai_chat_config.api_key
|
||||||
chat_model = openai_chat_config.chat_model
|
chat_model = conversation_config.chat_model
|
||||||
chat_response = converse(
|
chat_response = converse(
|
||||||
compiled_references,
|
compiled_references,
|
||||||
q,
|
q,
|
||||||
|
@ -170,8 +175,8 @@ def generate_chat_response(
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
completion_func=partial_completion,
|
completion_func=partial_completion,
|
||||||
conversation_command=conversation_command,
|
conversation_command=conversation_command,
|
||||||
max_prompt_size=conversation_config.max_prompt_size if conversation_config else None,
|
max_prompt_size=conversation_config.max_prompt_size,
|
||||||
tokenizer_name=conversation_config.tokenizer if conversation_config else None,
|
tokenizer_name=conversation_config.tokenizer,
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
@ -10,7 +10,6 @@ from fastapi.templating import Jinja2Templates
|
||||||
from starlette.authentication import requires
|
from starlette.authentication import requires
|
||||||
from khoj.utils.rawconfig import (
|
from khoj.utils.rawconfig import (
|
||||||
TextContentConfig,
|
TextContentConfig,
|
||||||
OpenAIProcessorConfig,
|
|
||||||
FullConfig,
|
FullConfig,
|
||||||
GithubContentConfig,
|
GithubContentConfig,
|
||||||
GithubRepoConfig,
|
GithubRepoConfig,
|
||||||
|
@ -119,12 +118,6 @@ if not state.demo:
|
||||||
user = request.user.object
|
user = request.user.object
|
||||||
user_picture = request.session.get("user", {}).get("picture")
|
user_picture = request.session.get("user", {}).get("picture")
|
||||||
enabled_content = set(EntryAdapters.get_unique_file_types(user).all())
|
enabled_content = set(EntryAdapters.get_unique_file_types(user).all())
|
||||||
default_full_config = FullConfig(
|
|
||||||
content_type=None,
|
|
||||||
search_type=None,
|
|
||||||
processor=None,
|
|
||||||
)
|
|
||||||
current_config = state.config or json.loads(default_full_config.json())
|
|
||||||
|
|
||||||
successfully_configured = {
|
successfully_configured = {
|
||||||
"pdf": ("pdf" in enabled_content),
|
"pdf": ("pdf" in enabled_content),
|
||||||
|
@ -143,26 +136,26 @@ if not state.demo:
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
enabled_chat_config = ConversationAdapters.get_enabled_conversation_settings(user)
|
conversation_options = ConversationAdapters.get_conversation_processor_options().all()
|
||||||
|
all_conversation_options = list()
|
||||||
|
for conversation_option in conversation_options:
|
||||||
|
all_conversation_options.append(
|
||||||
|
{"chat_model": conversation_option.chat_model, "id": conversation_option.id}
|
||||||
|
)
|
||||||
|
|
||||||
successfully_configured.update(
|
selected_conversation_config = ConversationAdapters.get_conversation_config(user)
|
||||||
{
|
|
||||||
"conversation_openai": enabled_chat_config["openai"],
|
|
||||||
"enable_offline_model": enabled_chat_config["offline_chat"],
|
|
||||||
"conversation_gpt4all": state.gpt4all_processor_config.loaded_model is not None
|
|
||||||
if state.gpt4all_processor_config
|
|
||||||
else False,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"config.html",
|
"config.html",
|
||||||
context={
|
context={
|
||||||
"request": request,
|
"request": request,
|
||||||
"current_config": current_config,
|
|
||||||
"current_model_state": successfully_configured,
|
"current_model_state": successfully_configured,
|
||||||
"anonymous_mode": state.anonymous_mode,
|
"anonymous_mode": state.anonymous_mode,
|
||||||
"username": user.username,
|
"username": user.username if user else None,
|
||||||
|
"conversation_options": all_conversation_options,
|
||||||
|
"selected_conversation_config": selected_conversation_config.id
|
||||||
|
if selected_conversation_config
|
||||||
|
else None,
|
||||||
"user_photo": user_picture,
|
"user_photo": user_picture,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -256,33 +249,3 @@ if not state.demo:
|
||||||
"user_photo": user_picture,
|
"user_photo": user_picture,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@web_client.get("/config/processor/conversation/openai", response_class=HTMLResponse)
|
|
||||||
@requires(["authenticated"], redirect="login_page")
|
|
||||||
def conversation_processor_config_page(request: Request):
|
|
||||||
user = request.user.object
|
|
||||||
user_picture = request.session.get("user", {}).get("picture")
|
|
||||||
openai_config = ConversationAdapters.get_openai_conversation_config(user)
|
|
||||||
|
|
||||||
if openai_config:
|
|
||||||
current_processor_openai_config = OpenAIProcessorConfig(
|
|
||||||
api_key=openai_config.api_key,
|
|
||||||
chat_model=openai_config.chat_model,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
current_processor_openai_config = OpenAIProcessorConfig(
|
|
||||||
api_key="",
|
|
||||||
chat_model="gpt-3.5-turbo",
|
|
||||||
)
|
|
||||||
|
|
||||||
current_processor_openai_config = json.loads(current_processor_openai_config.json())
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
"processor_conversation_input.html",
|
|
||||||
context={
|
|
||||||
"request": request,
|
|
||||||
"current_config": current_processor_openai_config,
|
|
||||||
"username": user.username,
|
|
||||||
"user_photo": user_picture,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ from khoj.migrations.migrate_version import migrate_config_to_version
|
||||||
from khoj.migrations.migrate_processor_config_openai import migrate_processor_conversation_schema
|
from khoj.migrations.migrate_processor_config_openai import migrate_processor_conversation_schema
|
||||||
from khoj.migrations.migrate_offline_model import migrate_offline_model
|
from khoj.migrations.migrate_offline_model import migrate_offline_model
|
||||||
from khoj.migrations.migrate_offline_chat_schema import migrate_offline_chat_schema
|
from khoj.migrations.migrate_offline_chat_schema import migrate_offline_chat_schema
|
||||||
|
from khoj.migrations.migrate_server_pg import migrate_server_pg
|
||||||
|
|
||||||
|
|
||||||
def cli(args=None):
|
def cli(args=None):
|
||||||
|
@ -75,6 +76,7 @@ def run_migrations(args):
|
||||||
migrate_processor_conversation_schema,
|
migrate_processor_conversation_schema,
|
||||||
migrate_offline_model,
|
migrate_offline_model,
|
||||||
migrate_offline_chat_schema,
|
migrate_offline_chat_schema,
|
||||||
|
migrate_server_pg,
|
||||||
]
|
]
|
||||||
for migration in migrations:
|
for migration in migrations:
|
||||||
args = migration(args)
|
args = migration(args)
|
||||||
|
|
|
@ -39,9 +39,10 @@ from database.models import (
|
||||||
|
|
||||||
from tests.helpers import (
|
from tests.helpers import (
|
||||||
UserFactory,
|
UserFactory,
|
||||||
ConversationProcessorConfigFactory,
|
ChatModelOptionsFactory,
|
||||||
OpenAIProcessorConversationConfigFactory,
|
OpenAIProcessorConversationConfigFactory,
|
||||||
OfflineChatProcessorConversationConfigFactory,
|
OfflineChatProcessorConversationConfigFactory,
|
||||||
|
UserConversationProcessorConfigFactory,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -188,7 +189,9 @@ def chat_client(search_config: SearchConfig, default_user2: KhojUser):
|
||||||
|
|
||||||
# Initialize Processor from Config
|
# Initialize Processor from Config
|
||||||
if os.getenv("OPENAI_API_KEY"):
|
if os.getenv("OPENAI_API_KEY"):
|
||||||
OpenAIProcessorConversationConfigFactory(user=default_user2)
|
chat_model = ChatModelOptionsFactory(chat_model="gpt-3.5-turbo", model_type="openai")
|
||||||
|
OpenAIProcessorConversationConfigFactory()
|
||||||
|
UserConversationProcessorConfigFactory(user=default_user2, setting=chat_model)
|
||||||
|
|
||||||
state.anonymous_mode = False
|
state.anonymous_mode = False
|
||||||
|
|
||||||
|
@ -257,7 +260,6 @@ def client(
|
||||||
user=api_user.user,
|
user=api_user.user,
|
||||||
)
|
)
|
||||||
|
|
||||||
ConversationProcessorConfigFactory(user=api_user.user)
|
|
||||||
state.anonymous_mode = False
|
state.anonymous_mode = False
|
||||||
|
|
||||||
configure_routes(app)
|
configure_routes(app)
|
||||||
|
@ -284,8 +286,8 @@ def client_offline_chat(search_config: SearchConfig, default_user2: KhojUser):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize Processor from Config
|
# Initialize Processor from Config
|
||||||
ConversationProcessorConfigFactory(user=default_user2)
|
OfflineChatProcessorConversationConfigFactory(enabled=True)
|
||||||
OfflineChatProcessorConversationConfigFactory(user=default_user2)
|
UserConversationProcessorConfigFactory(user=default_user2)
|
||||||
|
|
||||||
state.anonymous_mode = True
|
state.anonymous_mode = True
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,10 @@ import os
|
||||||
from database.models import (
|
from database.models import (
|
||||||
KhojUser,
|
KhojUser,
|
||||||
KhojApiUser,
|
KhojApiUser,
|
||||||
ConversationProcessorConfig,
|
ChatModelOptions,
|
||||||
OfflineChatProcessorConversationConfig,
|
OfflineChatProcessorConversationConfig,
|
||||||
OpenAIProcessorConversationConfig,
|
OpenAIProcessorConversationConfig,
|
||||||
|
UserConversationConfig,
|
||||||
Conversation,
|
Conversation,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,20 +31,29 @@ class ApiUserFactory(factory.django.DjangoModelFactory):
|
||||||
token = factory.Faker("password")
|
token = factory.Faker("password")
|
||||||
|
|
||||||
|
|
||||||
class ConversationProcessorConfigFactory(factory.django.DjangoModelFactory):
|
class ChatModelOptionsFactory(factory.django.DjangoModelFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConversationProcessorConfig
|
model = ChatModelOptions
|
||||||
|
|
||||||
max_prompt_size = 2000
|
max_prompt_size = 2000
|
||||||
tokenizer = None
|
tokenizer = None
|
||||||
|
chat_model = "llama-2-7b-chat.ggmlv3.q4_0.bin"
|
||||||
|
model_type = "offline"
|
||||||
|
|
||||||
|
|
||||||
|
class UserConversationProcessorConfigFactory(factory.django.DjangoModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = UserConversationConfig
|
||||||
|
|
||||||
|
user = factory.SubFactory(UserFactory)
|
||||||
|
setting = factory.SubFactory(ChatModelOptionsFactory)
|
||||||
|
|
||||||
|
|
||||||
class OfflineChatProcessorConversationConfigFactory(factory.django.DjangoModelFactory):
|
class OfflineChatProcessorConversationConfigFactory(factory.django.DjangoModelFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = OfflineChatProcessorConversationConfig
|
model = OfflineChatProcessorConversationConfig
|
||||||
|
|
||||||
enable_offline_chat = True
|
enabled = True
|
||||||
chat_model = "llama-2-7b-chat.ggmlv3.q4_0.bin"
|
|
||||||
|
|
||||||
|
|
||||||
class OpenAIProcessorConversationConfigFactory(factory.django.DjangoModelFactory):
|
class OpenAIProcessorConversationConfigFactory(factory.django.DjangoModelFactory):
|
||||||
|
@ -51,7 +61,6 @@ class OpenAIProcessorConversationConfigFactory(factory.django.DjangoModelFactory
|
||||||
model = OpenAIProcessorConversationConfig
|
model = OpenAIProcessorConversationConfig
|
||||||
|
|
||||||
api_key = os.getenv("OPENAI_API_KEY")
|
api_key = os.getenv("OPENAI_API_KEY")
|
||||||
chat_model = "gpt-3.5-turbo"
|
|
||||||
|
|
||||||
|
|
||||||
class ConversationFactory(factory.django.DjangoModelFactory):
|
class ConversationFactory(factory.django.DjangoModelFactory):
|
||||||
|
|
Loading…
Reference in a new issue