mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-26 15:34:26 +00:00
feat(web): add sidebar feedback popover
Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
This commit is contained in:
@@ -30,6 +30,50 @@ class SurveyRouterGroup(group.RouterGroup):
|
||||
return self.fail(2, 'Failed to submit response')
|
||||
return self.fail(3, 'Survey not available')
|
||||
|
||||
@self.route('/feedback', methods=['POST'], auth_type=group.AuthType.USER_TOKEN)
|
||||
async def _feedback(user_email: str) -> str:
|
||||
"""Submit on-demand user feedback from the sidebar."""
|
||||
json_data = await quart.request.get_json(silent=True) or {}
|
||||
content = str(json_data.get('content', '')).strip()
|
||||
attachments = json_data.get('attachments', [])
|
||||
page_url = str(json_data.get('page_url', ''))[:2048]
|
||||
user_agent = str(json_data.get('user_agent', ''))[:512]
|
||||
|
||||
if not content:
|
||||
return self.fail(1, 'content required')
|
||||
if len(content) > 5000:
|
||||
return self.fail(2, 'content too long')
|
||||
if not isinstance(attachments, list):
|
||||
return self.fail(3, 'attachments must be an array')
|
||||
if len(attachments) > 3:
|
||||
return self.fail(4, 'too many attachments')
|
||||
|
||||
normalized_attachments = []
|
||||
for item in attachments:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
data_url = str(item.get('data_url', ''))
|
||||
mime_type = str(item.get('mime_type', ''))[:128]
|
||||
name = str(item.get('name', ''))[:255]
|
||||
if not data_url.startswith('data:image/'):
|
||||
continue
|
||||
if len(data_url) > 2_800_000:
|
||||
return self.fail(5, 'attachment too large')
|
||||
normalized_attachments.append({'name': name, 'mime_type': mime_type, 'data_url': data_url})
|
||||
|
||||
if self.ap.survey:
|
||||
ok = await self.ap.survey.submit_feedback(
|
||||
content=content,
|
||||
attachments=normalized_attachments,
|
||||
page_url=page_url,
|
||||
user_agent=user_agent,
|
||||
user_email=user_email,
|
||||
)
|
||||
if ok:
|
||||
return self.success()
|
||||
return self.fail(6, 'Failed to submit feedback')
|
||||
return self.fail(7, 'Survey not available')
|
||||
|
||||
@self.route('/dismiss', methods=['POST'], auth_type=group.AuthType.USER_TOKEN)
|
||||
async def _dismiss() -> str:
|
||||
"""Dismiss survey."""
|
||||
|
||||
@@ -159,6 +159,21 @@ class SurveyManager:
|
||||
"""Clear the pending survey (after user responds or dismisses)."""
|
||||
self._pending_survey = None
|
||||
|
||||
async def _build_base_metadata(self, user_email: str | None = None) -> dict:
|
||||
metadata = {
|
||||
'version': constants.semantic_version,
|
||||
'instance_id': constants.instance_id,
|
||||
}
|
||||
if user_email:
|
||||
metadata['login_account'] = user_email
|
||||
try:
|
||||
user_obj = await self.ap.user_service.get_user_by_email(user_email)
|
||||
metadata['account_type'] = getattr(user_obj, 'account_type', '') or 'local'
|
||||
metadata['space_account_uuid'] = getattr(user_obj, 'space_account_uuid', '') or ''
|
||||
except Exception:
|
||||
pass
|
||||
return metadata
|
||||
|
||||
async def submit_response(self, survey_id: str, answers: dict, completed: bool = True) -> bool:
|
||||
"""Submit a survey response to Space."""
|
||||
if not self._is_space_configured():
|
||||
@@ -169,9 +184,7 @@ class SurveyManager:
|
||||
'survey_id': survey_id,
|
||||
'instance_id': constants.instance_id,
|
||||
'answers': answers,
|
||||
'metadata': {
|
||||
'version': constants.semantic_version,
|
||||
},
|
||||
'metadata': await self._build_base_metadata(),
|
||||
'completed': completed,
|
||||
}
|
||||
async with httpx.AsyncClient(timeout=httpx.Timeout(10)) as client:
|
||||
@@ -183,6 +196,41 @@ class SurveyManager:
|
||||
self.ap.logger.warning(f'Failed to submit survey response: {e}')
|
||||
return False
|
||||
|
||||
async def submit_feedback(
|
||||
self,
|
||||
content: str,
|
||||
attachments: list[dict],
|
||||
page_url: str,
|
||||
user_agent: str,
|
||||
user_email: str | None = None,
|
||||
) -> bool:
|
||||
"""Submit an on-demand user feedback item to Space."""
|
||||
if not self._is_space_configured():
|
||||
return False
|
||||
try:
|
||||
url = f'{self._space_url}/api/v1/survey/feedback'
|
||||
metadata = await self._build_base_metadata(user_email)
|
||||
metadata.update(
|
||||
{
|
||||
'page_url': page_url,
|
||||
'user_agent': user_agent,
|
||||
}
|
||||
)
|
||||
payload = {
|
||||
'instance_id': constants.instance_id,
|
||||
'content': content,
|
||||
'attachments': attachments,
|
||||
'metadata': metadata,
|
||||
}
|
||||
async with httpx.AsyncClient(timeout=httpx.Timeout(30)) as client:
|
||||
resp = await client.post(url, json=payload)
|
||||
if resp.status_code == 200:
|
||||
return True
|
||||
self.ap.logger.warning(f'Failed to submit feedback: {resp.status_code} {resp.text[:200]}')
|
||||
except Exception as e:
|
||||
self.ap.logger.warning(f'Failed to submit feedback: {e}')
|
||||
return False
|
||||
|
||||
async def dismiss_survey(self, survey_id: str) -> bool:
|
||||
"""Dismiss a survey."""
|
||||
if not self._is_space_configured():
|
||||
|
||||
Reference in New Issue
Block a user