-
Notifications
You must be signed in to change notification settings - Fork 32
Add AskAI message feedback endpoint #2297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
ba8a7ef
4ce3f48
1e52f45
264ac88
2d08faa
25eb256
7926de7
4df1e5c
d0883cf
3dadebf
b48be34
c2b7c3c
79e4117
5de5770
a112b98
6eea0cb
b6b2cbf
5f6a852
07ff6e6
01cd487
ce47e59
e562c5c
d313731
ce6d0ea
7dca09e
65ad494
f622191
717d3d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import { logWarn } from '../../../telemetry/logging' | ||
| import { traceSpan } from '../../../telemetry/tracing' | ||
| import { useMutation } from '@tanstack/react-query' | ||
| import { useState, useCallback } from 'react' | ||
|
|
||
| export type Reaction = 'thumbsUp' | 'thumbsDown' | ||
|
|
||
| interface MessageFeedbackRequest { | ||
| messageId: string | ||
| conversationId: string | ||
| reaction: Reaction | ||
| } | ||
|
|
||
| interface UseMessageFeedbackReturn { | ||
| selectedReaction: Reaction | null | ||
| submitFeedback: (reaction: Reaction) => void | ||
| isPending: boolean | ||
| } | ||
|
|
||
| const submitFeedbackToApi = async ( | ||
| payload: MessageFeedbackRequest | ||
| ): Promise<void> => { | ||
| await traceSpan('submit message-feedback', async (span) => { | ||
| span.setAttribute('gen_ai.conversation.id', payload.conversationId) // correlation with backend | ||
| span.setAttribute('ask_ai.message.id', payload.messageId) | ||
| span.setAttribute('ask_ai.feedback.reaction', payload.reaction) | ||
|
|
||
| const response = await fetch('/docs/_api/v1/ask-ai/message-feedback', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| body: JSON.stringify(payload), | ||
| }) | ||
|
|
||
| if (!response.ok) { | ||
| logWarn('Failed to submit feedback', { | ||
| 'http.status_code': response.status, | ||
| 'ask_ai.message.id': payload.messageId, | ||
| 'ask_ai.feedback.reaction': payload.reaction, | ||
| }) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| export const useMessageFeedback = ( | ||
| messageId: string, | ||
| conversationId: string | null | ||
| ): UseMessageFeedbackReturn => { | ||
| const [selectedReaction, setSelectedReaction] = useState<Reaction | null>( | ||
| null | ||
| ) | ||
|
|
||
| const mutation = useMutation({ | ||
|
Check failure on line 54 in src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/AskAi/useMessageFeedback.ts
|
||
| mutationFn: submitFeedbackToApi, | ||
| onError: (error) => { | ||
| logWarn('Error submitting feedback', { | ||
| 'error.message': | ||
| error instanceof Error ? error.message : String(error), | ||
| }) | ||
| // Don't reset selection on error - user intent was clear | ||
| }, | ||
| }) | ||
|
|
||
| const submitFeedback = useCallback( | ||
| (reaction: Reaction) => { | ||
| if (!conversationId) { | ||
| console.warn('Cannot submit feedback without conversationId') | ||
| return | ||
| } | ||
|
|
||
| // Ignore if same reaction already selected | ||
| if (selectedReaction === reaction) { | ||
| return | ||
| } | ||
|
|
||
| // Ignore if already submitting | ||
| if (mutation.isPending) { | ||
| return | ||
| } | ||
|
|
||
| // Optimistic update | ||
| setSelectedReaction(reaction) | ||
|
|
||
| // Submit to API | ||
| mutation.mutate({ | ||
| messageId, | ||
| conversationId, | ||
| reaction, | ||
| }) | ||
| }, | ||
| [messageId, conversationId, selectedReaction, mutation] | ||
| ) | ||
|
|
||
| return { | ||
| selectedReaction, | ||
| submitFeedback, | ||
| isPending: mutation.isPending, | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // Licensed to Elasticsearch B.V under one or more agreements. | ||
| // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. | ||
| // See the LICENSE file in the project root for more information | ||
|
|
||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace Elastic.Documentation.Api.Core.AskAi; | ||
|
|
||
| /// <summary> | ||
| /// Request model for submitting feedback on a specific Ask AI message. | ||
| /// </summary> | ||
| public record AskAiMessageFeedbackRequest( | ||
| string MessageId, | ||
| string ConversationId, | ||
| Reaction Reaction | ||
| ); | ||
|
|
||
| /// <summary> | ||
| /// The user's reaction to an Ask AI message. | ||
| /// </summary> | ||
| [JsonConverter(typeof(JsonStringEnumConverter<Reaction>))] | ||
| public enum Reaction | ||
| { | ||
| [JsonStringEnumMemberName("thumbsUp")] | ||
| ThumbsUp, | ||
|
|
||
| [JsonStringEnumMemberName("thumbsDown")] | ||
| ThumbsDown | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| // Licensed to Elasticsearch B.V under one or more agreements. | ||
| // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. | ||
| // See the LICENSE file in the project root for more information | ||
|
|
||
| using System.Diagnostics; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace Elastic.Documentation.Api.Core.AskAi; | ||
|
|
||
| /// <summary> | ||
| /// Use case for handling Ask AI message feedback submissions. | ||
| /// </summary> | ||
| public class AskAiMessageFeedbackUsecase( | ||
| IAskAiMessageFeedbackGateway feedbackGateway, | ||
| ILogger<AskAiMessageFeedbackUsecase> logger) | ||
| { | ||
| private static readonly ActivitySource FeedbackActivitySource = new(TelemetryConstants.AskAiFeedbackSourceName); | ||
|
|
||
| public async Task SubmitFeedback(AskAiMessageFeedbackRequest request, string? euid, CancellationToken ctx) | ||
| { | ||
| using var activity = FeedbackActivitySource.StartActivity("record message-feedback", ActivityKind.Internal); | ||
| _ = activity?.SetTag("gen_ai.conversation.id", request.ConversationId); // correlation with chat traces | ||
| _ = activity?.SetTag("ask_ai.message.id", request.MessageId); | ||
| _ = activity?.SetTag("ask_ai.feedback.reaction", request.Reaction.ToString().ToLowerInvariant()); | ||
| // Note: user.euid is automatically added to spans by EuidSpanProcessor | ||
|
|
||
| logger.LogInformation( | ||
| "Recording message feedback for message {MessageId} in conversation {ConversationId}: {Reaction}", | ||
| LogSanitizer.Sanitize(request.MessageId), | ||
| LogSanitizer.Sanitize(request.ConversationId), | ||
|
||
| request.Reaction); | ||
|
|
||
| var record = new AskAiMessageFeedbackRecord( | ||
| request.MessageId, | ||
| request.ConversationId, | ||
| request.Reaction, | ||
| euid | ||
| ); | ||
|
|
||
| await feedbackGateway.RecordFeedbackAsync(record, ctx); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // Licensed to Elasticsearch B.V under one or more agreements. | ||
| // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. | ||
| // See the LICENSE file in the project root for more information | ||
|
|
||
| namespace Elastic.Documentation.Api.Core.AskAi; | ||
|
|
||
| /// <summary> | ||
| /// Gateway interface for recording Ask AI message feedback. | ||
| /// Infrastructure implementations may use different storage backends (Elasticsearch, database, etc.) | ||
| /// </summary> | ||
| public interface IAskAiMessageFeedbackGateway | ||
| { | ||
| /// <summary> | ||
| /// Records feedback for a specific Ask AI message. | ||
| /// </summary> | ||
| /// <param name="record">The feedback record to store.</param> | ||
| /// <param name="ctx">Cancellation token.</param> | ||
| Task RecordFeedbackAsync(AskAiMessageFeedbackRecord record, CancellationToken ctx); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Internal record used to pass message feedback data to the gateway. | ||
| /// </summary> | ||
| public record AskAiMessageFeedbackRecord( | ||
| string MessageId, | ||
| string ConversationId, | ||
| Reaction Reaction, | ||
| string? Euid = null | ||
| ); |
Uh oh!
There was an error while loading. Please reload this page.