mirror of
https://github.com/langgenius/dify.git
synced 2026-11-10 21:35:45 +08:00
141 lines
4.7 KiB
Python
141 lines
4.7 KiB
Python
"""Message feedback shared by user-facing and Console management endpoints."""
|
|
|
|
import logging
|
|
from dataclasses import dataclass
|
|
from typing import Protocol
|
|
|
|
from models.enums import FeedbackRating
|
|
from services.entities.message_entities import MessageAccount, MessageActor
|
|
from services.installed_app_access_service import InstalledAppRef
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class MessageFeedbackRecord:
|
|
id: str
|
|
app_id: str
|
|
conversation_id: str
|
|
message_id: str
|
|
rating: str
|
|
content: str | None
|
|
from_source: str
|
|
from_end_user_id: str | None
|
|
from_account_id: str | None
|
|
created_at: str
|
|
updated_at: str
|
|
|
|
|
|
class MessageFeedbackStore(Protocol):
|
|
def set_feedback(
|
|
self,
|
|
*,
|
|
app_id: str,
|
|
app_owner_tenant_id: str,
|
|
actor: MessageActor,
|
|
message_id: str,
|
|
rating: FeedbackRating | None,
|
|
content: str | None,
|
|
app_wide: bool,
|
|
installed_app: InstalledAppRef | None,
|
|
) -> MessageFeedbackRecord | None:
|
|
"""Commit a rating and return detached data, or revoke it and return None."""
|
|
...
|
|
|
|
def get_feedbacks(
|
|
self, *, app_id: str, app_owner_tenant_id: str, page: int, limit: int
|
|
) -> list[MessageFeedbackRecord]: ...
|
|
|
|
|
|
class MessageFeedbackService:
|
|
def __init__(self, *, repository: MessageFeedbackStore) -> None:
|
|
self._repository: MessageFeedbackStore = repository
|
|
|
|
def set_feedback(
|
|
self,
|
|
*,
|
|
app_id: str,
|
|
app_owner_tenant_id: str,
|
|
actor: MessageActor,
|
|
message_id: str,
|
|
rating: FeedbackRating | None,
|
|
content: str | None,
|
|
installed_app: InstalledAppRef | None = None,
|
|
) -> None:
|
|
"""Rate the actor's own message; None revokes existing feedback.
|
|
|
|
Explore supplies its installation to recheck admission in the write
|
|
transaction. Other entrypoints have no installation (None).
|
|
Content may be absent (None) or explicitly empty, and is saved as given.
|
|
"""
|
|
feedback = self._repository.set_feedback(
|
|
app_id=app_id,
|
|
app_owner_tenant_id=app_owner_tenant_id,
|
|
actor=actor,
|
|
message_id=message_id,
|
|
rating=rating,
|
|
content=content,
|
|
app_wide=False,
|
|
installed_app=installed_app,
|
|
)
|
|
if feedback is None:
|
|
return
|
|
|
|
# The repository has committed and closed its session. Telemetry failure
|
|
# must not turn a successful feedback write into a failed HTTP request.
|
|
try:
|
|
from core.telemetry import FeedbackCreatedEvent, TelemetryContext, emit
|
|
|
|
emit(
|
|
FeedbackCreatedEvent(
|
|
context=TelemetryContext(tenant_id=app_owner_tenant_id),
|
|
payload={
|
|
"message_id": feedback.message_id,
|
|
"app_id": feedback.app_id,
|
|
"conversation_id": feedback.conversation_id,
|
|
"from_end_user_id": None if isinstance(actor, MessageAccount) else actor.end_user_id,
|
|
"from_account_id": actor.account_id if isinstance(actor, MessageAccount) else None,
|
|
"rating": feedback.rating,
|
|
"from_source": feedback.from_source,
|
|
"content": feedback.content,
|
|
},
|
|
)
|
|
)
|
|
except Exception:
|
|
logger.warning("Failed to emit feedback telemetry for message %s", message_id, exc_info=True)
|
|
|
|
def set_admin_feedback(
|
|
self,
|
|
*,
|
|
app_id: str,
|
|
app_owner_tenant_id: str,
|
|
account_id: str,
|
|
message_id: str,
|
|
rating: FeedbackRating | None,
|
|
content: str | None,
|
|
) -> None:
|
|
"""Rate any message in an admitted Console app, without user telemetry.
|
|
|
|
None rating revokes existing admin feedback. None content means no comment.
|
|
Console admission owns workspace/RBAC checks; being a MessageAccount alone
|
|
does not grant this broader message access.
|
|
"""
|
|
self._repository.set_feedback(
|
|
app_id=app_id,
|
|
app_owner_tenant_id=app_owner_tenant_id,
|
|
actor=MessageAccount(account_id=account_id),
|
|
message_id=message_id,
|
|
rating=rating,
|
|
content=content,
|
|
app_wide=True,
|
|
installed_app=None,
|
|
)
|
|
|
|
def get_feedbacks(
|
|
self, *, app_id: str, app_owner_tenant_id: str, page: int, limit: int
|
|
) -> list[MessageFeedbackRecord]:
|
|
"""List app feedback newest first, using one-based pages."""
|
|
return self._repository.get_feedbacks(
|
|
app_id=app_id, app_owner_tenant_id=app_owner_tenant_id, page=page, limit=limit
|
|
)
|