バックエンド
models.py — MailSender.rule に always_notify 追加、MailEmail.feedback にも追加、マイグレーション適用済み
views.py — FeedbackView.post が always_notify を受け取ったら MailSender ルールを作成(never_notify と同じ仕組み)
フロントエンド
feedback/[token]/page.tsx — 4択目「🔔 常に通知してほしい」を追加。スコープ選択(アドレス/ドメイン)もあり。色はteal系で区別
mail/rules/page.tsx — 追加フォームにルール種別セレクタを追加、一覧に「常に通知」バッジ(teal)を表示
Windmill側の使い方(メモ)
GET /api/mail/sender-rule/ のレスポンスに "rule": "always_notify" が返ってきたら、LLMをスキップして llm_verdict: "important" で直接 POST /api/mail/emails/ を呼べばOKです。
107 lines
3.6 KiB
Python
107 lines
3.6 KiB
Python
import uuid
|
||
from django.db import models
|
||
|
||
|
||
SENDER_RULE_CHOICES = [
|
||
('always_notify', '常に通知'),
|
||
('never_notify', '通知しない'),
|
||
]
|
||
|
||
|
||
class MailSender(models.Model):
|
||
"""送信者ルール"""
|
||
email = models.EmailField(null=True, blank=True, verbose_name="メールアドレス")
|
||
domain = models.CharField(max_length=255, null=True, blank=True, verbose_name="ドメイン")
|
||
rule = models.CharField(
|
||
max_length=20,
|
||
choices=SENDER_RULE_CHOICES,
|
||
default='never_notify',
|
||
verbose_name="ルール"
|
||
)
|
||
note = models.TextField(blank=True, verbose_name="メモ")
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
class Meta:
|
||
verbose_name = "送信者ルール"
|
||
verbose_name_plural = "送信者ルール"
|
||
constraints = [
|
||
models.CheckConstraint(
|
||
check=(
|
||
models.Q(email__isnull=False, domain__isnull=True) |
|
||
models.Q(email__isnull=True, domain__isnull=False)
|
||
),
|
||
name='mail_sender_email_or_domain'
|
||
)
|
||
]
|
||
|
||
def __str__(self):
|
||
value = self.email or self.domain
|
||
kind = "アドレス" if self.email else "ドメイン"
|
||
return f"[{kind}] {value}"
|
||
|
||
|
||
ACCOUNT_CHOICES = [
|
||
('xserver', 'Xserver'),
|
||
('gmail', 'Gmail'),
|
||
('hotmail', 'Hotmail'),
|
||
]
|
||
|
||
FEEDBACK_CHOICES = [
|
||
('important', '重要だった'),
|
||
('not_important', '普通のメール'),
|
||
('never_notify', '今後通知しない'),
|
||
('always_notify', '常に通知してほしい'),
|
||
]
|
||
|
||
|
||
class MailEmail(models.Model):
|
||
"""受信メール記録(LLMに渡したメール)"""
|
||
account = models.CharField(max_length=20, choices=ACCOUNT_CHOICES, verbose_name="アカウント")
|
||
message_id = models.CharField(max_length=500, unique=True, verbose_name="Message-ID")
|
||
sender_email = models.EmailField(verbose_name="送信者アドレス")
|
||
sender_domain = models.CharField(max_length=255, verbose_name="送信者ドメイン")
|
||
subject = models.CharField(max_length=500, verbose_name="件名")
|
||
body_preview = models.TextField(verbose_name="本文冒頭")
|
||
received_at = models.DateTimeField(verbose_name="受信日時")
|
||
llm_verdict = models.CharField(
|
||
max_length=20,
|
||
choices=[('important', '重要'), ('not_important', '重要でない')],
|
||
verbose_name="LLM判定"
|
||
)
|
||
notified_at = models.DateTimeField(null=True, blank=True, verbose_name="LINE通知日時")
|
||
feedback = models.CharField(
|
||
max_length=20,
|
||
choices=FEEDBACK_CHOICES,
|
||
null=True, blank=True,
|
||
verbose_name="フィードバック"
|
||
)
|
||
feedback_at = models.DateTimeField(null=True, blank=True, verbose_name="フィードバック日時")
|
||
|
||
class Meta:
|
||
verbose_name = "受信メール"
|
||
verbose_name_plural = "受信メール"
|
||
ordering = ['-received_at']
|
||
|
||
def __str__(self):
|
||
return f"{self.subject} ({self.sender_email})"
|
||
|
||
|
||
class MailNotificationToken(models.Model):
|
||
"""LINEフィードバックURL用トークン(有効期限なし)"""
|
||
email = models.OneToOneField(
|
||
MailEmail,
|
||
on_delete=models.CASCADE,
|
||
related_name='notification_token',
|
||
verbose_name="メール"
|
||
)
|
||
token = models.UUIDField(default=uuid.uuid4, unique=True, verbose_name="トークン")
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
|
||
class Meta:
|
||
verbose_name = "通知トークン"
|
||
verbose_name_plural = "通知トークン"
|
||
|
||
def __str__(self):
|
||
return str(self.token)
|