# Copyright (c) 2023-present Plane Software, Inc. and contributors # SPDX-License-Identifier: AGPL-3.0-only # See the LICENSE file for details. from django.conf import settings from django.db import models from .base import BaseModel class WorkspaceAISettings(BaseModel): class Provider(models.TextChoices): OPENAI = "openai", "OpenAI" class AccessMode(models.TextChoices): ALL_WORKSPACE_MEMBERS = "all_workspace_members", "All workspace members" ADMINS_ONLY = "admins_only", "Admins only" SELECTED_PROJECTS = "selected_projects", "Selected projects" SELECTED_MEMBERS = "selected_members", "Selected members" workspace = models.OneToOneField( "db.Workspace", on_delete=models.CASCADE, related_name="ai_settings", ) voice_tasker_enabled = models.BooleanField(default=False) provider = models.CharField(max_length=32, choices=Provider.choices, default=Provider.OPENAI) transcription_model = models.CharField(max_length=80, default="gpt-4o-mini-transcribe") structuring_model = models.CharField(max_length=80, default="gpt-4o-mini") default_project = models.ForeignKey( "db.Project", on_delete=models.SET_NULL, null=True, blank=True, related_name="workspace_ai_default_project", ) access_mode = models.CharField( max_length=40, choices=AccessMode.choices, default=AccessMode.ALL_WORKSPACE_MEMBERS, ) enabled_projects = models.ManyToManyField( "db.Project", blank=True, related_name="workspace_ai_feature_settings", ) enabled_members = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=True, related_name="workspace_ai_feature_settings", ) max_audio_duration_seconds = models.PositiveIntegerField(default=120) per_user_hourly_limit = models.PositiveIntegerField(default=30) workspace_hourly_limit = models.PositiveIntegerField(default=300) per_user_daily_limit = models.PositiveIntegerField(default=100) workspace_daily_limit = models.PositiveIntegerField(default=1000) project_daily_limit = models.PositiveIntegerField(default=300) workspace_concurrency_limit = models.PositiveIntegerField(default=3) sensitive_data_retention_days = models.PositiveIntegerField(default=30) class Meta: verbose_name = "Workspace AI Settings" verbose_name_plural = "Workspace AI Settings" db_table = "workspace_ai_settings" ordering = ("-created_at",) def __str__(self): return f"{self.workspace.slug} AI settings" class WorkspaceAICredential(BaseModel): class Provider(models.TextChoices): OPENAI = "openai", "OpenAI" workspace = models.OneToOneField( "db.Workspace", on_delete=models.CASCADE, related_name="ai_credential", ) provider = models.CharField(max_length=32, choices=Provider.choices, default=Provider.OPENAI) encrypted_api_key = models.TextField(blank=True) key_last4 = models.CharField(max_length=4, blank=True) is_active = models.BooleanField(default=True) class Meta: verbose_name = "Workspace AI Credential" verbose_name_plural = "Workspace AI Credentials" db_table = "workspace_ai_credentials" ordering = ("-created_at",) def __str__(self): return f"{self.workspace.slug} {self.provider} credential" class VoiceTaskSession(BaseModel): class Status(models.TextChoices): QUEUED = "queued", "Queued" PROCESSING = "processing", "Processing" UPLOADED = "uploaded", "Uploaded" TRANSCRIBING = "transcribing", "Transcribing" TRANSCRIBED = "transcribed", "Transcribed" PARSING = "parsing", "Parsing" PARSED = "parsed", "Parsed" FAILED = "failed", "Failed" workspace = models.ForeignKey( "db.Workspace", on_delete=models.CASCADE, related_name="voice_task_sessions", ) user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="voice_task_sessions", ) project = models.ForeignKey( "db.Project", on_delete=models.SET_NULL, null=True, blank=True, related_name="voice_task_sessions", ) status = models.CharField(max_length=32, choices=Status.choices, default=Status.UPLOADED) audio_file = models.FileField(upload_to="voice-task-sessions/%Y/%m/%d/", null=True, blank=True) audio_duration_seconds = models.FloatField(null=True, blank=True) audio_content_type = models.CharField(max_length=100, blank=True) audio_size = models.PositiveIntegerField(null=True, blank=True) transcript = models.TextField(blank=True) intent = models.CharField(max_length=40, blank=True) parsed_json = models.JSONField(blank=True, default=dict) client_context = models.JSONField(blank=True, default=dict) queued_at = models.DateTimeField(null=True, blank=True) processing_started_at = models.DateTimeField(null=True, blank=True) transcribed_at = models.DateTimeField(null=True, blank=True) completed_at = models.DateTimeField(null=True, blank=True) failed_at = models.DateTimeField(null=True, blank=True) transcription_duration_ms = models.PositiveIntegerField(null=True, blank=True) parsing_duration_ms = models.PositiveIntegerField(null=True, blank=True) processing_duration_ms = models.PositiveIntegerField(null=True, blank=True) parser_prompt_tokens = models.PositiveIntegerField(null=True, blank=True) parser_completion_tokens = models.PositiveIntegerField(null=True, blank=True) parser_total_tokens = models.PositiveIntegerField(null=True, blank=True) sensitive_data_redacted_at = models.DateTimeField(null=True, blank=True) created_task = models.ForeignKey( "db.Issue", on_delete=models.SET_NULL, null=True, blank=True, related_name="created_by_voice_sessions", ) updated_task = models.ForeignKey( "db.Issue", on_delete=models.SET_NULL, null=True, blank=True, related_name="updated_by_voice_sessions", ) error_code = models.CharField(max_length=80, blank=True) error_message = models.TextField(blank=True) class Meta: verbose_name = "Voice Task Session" verbose_name_plural = "Voice Task Sessions" db_table = "voice_task_sessions" ordering = ("-created_at",) indexes = [ models.Index(fields=["workspace", "user", "-created_at"], name="voice_task_session_user_idx"), models.Index(fields=["workspace", "project", "-created_at"], name="voice_task_session_project_idx"), models.Index(fields=["workspace", "status", "-created_at"], name="voice_task_session_status_idx"), ] def __str__(self): return f"{self.workspace_id} {self.user_id} {self.status}"