Skip to content

Commit

Permalink
feat(answer): knowledge controller preparation (#4129)
Browse files Browse the repository at this point in the history
## Summary

This change is just moving logic around; this increment is not modifying
any functionalities.

The change intents to prepare the addition of a new answer controller
that will leverage the new answerApi client. The end goal is to separate
the legacy logic of using the streamId from the searchApi, from the new
logic of the answerApi stream.

## Why is this needed

Since both logics (search-api VS answer-api) share a strong common root,
the current change will permit us to reuse the current
headless-core-generated-answer controller in both cases.

## Testing

Those change must be tested to ensure that the current functionalities
stay unchanged

Co-authored-by: Danny Gauthier <dgauthier@WKS-002169.corp.coveo.com>
  • Loading branch information
dmgauthier and Danny Gauthier authored Jul 9, 2024
1 parent 7d9a5e9 commit 3e1b7d1
Show file tree
Hide file tree
Showing 10 changed files with 508 additions and 158 deletions.
3 changes: 2 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -264,13 +264,14 @@
"spworkflowprocesslist",
"spworkspacepagelist",
"stenciljs",
"SVCC",
"TDIDF",
"Techzample",
"testid",
"thisisanotherurl",
"thisisaurl",
"thisisnotadate",
"tryitnow",
"testid",
"tsdoc",
"ttfb",
"uikit",
Expand Down
1 change: 0 additions & 1 deletion packages/atomic/cypress/e2e/generated-answer.cypress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,6 @@ describe('Generated Answer Test Suites', () => {
GeneratedAnswerSelectors.container().should('not.exist');
});
});

describe('Retryable error', () => {
[500, 429].forEach((errorCode) => {
describe(`${errorCode} error`, () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,6 @@ describe('generated answer', () => {
});
});

it('should subscribe to state updates', () => {
expect(engine.subscribe).toHaveBeenCalledTimes(1);
});

it('should return the state', () => {
expect(generatedAnswer.state).toEqual(getGeneratedAnswerInitialState());
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import {Unsubscribe} from '@reduxjs/toolkit';
import {GeneratedAnswerAPIClient} from '../../../api/generated-answer/generated-answer-client';
import {GeneratedAnswerCitation} from '../../../api/generated-answer/generated-answer-event-payload';
import {CoreEngine} from '../../../app/engine';
import {ClientThunkExtraArguments} from '../../../app/thunk-extra-arguments';
import {
CustomAction,
LegacySearchAction,
} from '../../../features/analytics/analytics-utils';
import {
streamAnswer,
resetAnswer,
likeGeneratedAnswer,
dislikeGeneratedAnswer,
updateResponseFormat,
Expand All @@ -18,7 +13,6 @@ import {
setIsVisible,
sendGeneratedAnswerFeedback,
registerFieldsToIncludeInCitations,
setId,
expandGeneratedAnswer,
collapseGeneratedAnswer,
} from '../../../features/generated-answer/generated-answer-actions';
Expand All @@ -35,7 +29,6 @@ import {
SearchSection,
} from '../../../state/state-sections';
import {loadReducerError} from '../../../utils/errors';
import {randomID} from '../../../utils/utils';
import {
Controller,
buildController,
Expand Down Expand Up @@ -123,101 +116,6 @@ export interface GeneratedAnswer extends Controller {
logCitationHover(citationId: string, citationHoverTimeMs: number): void;
}

export interface GeneratedAnswerProps {
initialState?: {
/**
* Sets the component visibility state on load.
*/
isVisible?: boolean;
/**
* The initial formatting options applied to generated answers when the controller first loads.
*/
responseFormat?: GeneratedResponseFormat;
/**
* The initial expanded state of the generated answer.
*/
expanded?: boolean;
};
/**
* A list of indexed fields to include in the citations returned with the generated answer.
*/
fieldsToIncludeInCitations?: string[];
}

interface SubscribeStateManager {
engines: Record<
string,
{
abortController: AbortController | undefined;
lastRequestId: string;
lastStreamId: string;
}
>;
getIsStreamInProgress: (genQaEngineId: string) => boolean;
setAbortControllerRef: (ref: AbortController, genQaEngineId: string) => void;
subscribeToSearchRequests: (
engine: CoreEngine<
GeneratedAnswerSection & SearchSection & DebugSection,
ClientThunkExtraArguments<GeneratedAnswerAPIClient>
>
) => Unsubscribe;
}

const subscribeStateManager: SubscribeStateManager = {
engines: {},

setAbortControllerRef: (ref: AbortController, genQaEngineId: string) => {
subscribeStateManager.engines[genQaEngineId].abortController = ref;
},

getIsStreamInProgress: (genQaEngineId: string) => {
if (
!subscribeStateManager.engines[genQaEngineId].abortController ||
subscribeStateManager.engines[genQaEngineId].abortController?.signal
.aborted
) {
subscribeStateManager.engines[genQaEngineId].abortController = undefined;
return false;
}
return true;
},

subscribeToSearchRequests: (engine) => {
const strictListener = () => {
const state = engine.state;
const requestId = state.search.requestId;
const streamId =
state.search.extendedResults.generativeQuestionAnsweringId;
const genQaEngineId = state.generatedAnswer.id;

if (
subscribeStateManager.engines[genQaEngineId].lastRequestId !== requestId
) {
subscribeStateManager.engines[genQaEngineId].lastRequestId = requestId;
subscribeStateManager.engines[genQaEngineId].abortController?.abort();
engine.dispatch(resetAnswer());
}

const isStreamInProgress =
subscribeStateManager.getIsStreamInProgress(genQaEngineId);
if (
!isStreamInProgress &&
streamId &&
streamId !== subscribeStateManager.engines[genQaEngineId].lastStreamId
) {
subscribeStateManager.engines[genQaEngineId].lastStreamId = streamId;
engine.dispatch(
streamAnswer({
setAbortControllerRef: (ref: AbortController) =>
subscribeStateManager.setAbortControllerRef(ref, genQaEngineId),
})
);
}
};
return engine.subscribe(strictListener);
},
};

export interface GeneratedAnswerAnalyticsClient {
logLikeGeneratedAnswer: () => CustomAction;
logDislikeGeneratedAnswer: () => CustomAction;
Expand All @@ -241,8 +139,32 @@ export interface GeneratedAnswerAnalyticsClient {
logGeneratedAnswerCollapse: () => CustomAction;
}

export interface GeneratedAnswerPropsInitialState {
initialState?: {
/**
* Sets the component visibility state on load.
*/
isVisible?: boolean;
/**
* The initial formatting options applied to generated answers when the controller first loads.
*/
responseFormat?: GeneratedResponseFormat;
/**
* The initial expanded state of the generated answer.
*/
expanded?: boolean;
};
}

export interface GeneratedAnswerProps extends GeneratedAnswerPropsInitialState {
/**
* A list of indexed fields to include in the citations returned with the generated answer.
*/
fieldsToIncludeInCitations?: string[];
}

/**
* Creates a core `GeneratedAnswer` controller instance.
* Can be used as a basis for controllers aiming to return a `GeneratedAnswer` controller instance.
*
* @param engine - The headless engine.
* @param props - The configurable `GeneratedAnswer` properties.
Expand All @@ -261,16 +183,6 @@ export function buildCoreGeneratedAnswer(
const controller = buildController(engine);
const getState = () => engine.state;

if (!engine.state.generatedAnswer.id) {
const genQaEngineId = randomID('genQA-', 12);
dispatch(setId({id: genQaEngineId}));
subscribeStateManager.engines[genQaEngineId] = {
abortController: undefined,
lastRequestId: '',
lastStreamId: '',
};
}

const isVisible = props.initialState?.isVisible;
if (isVisible !== undefined) {
dispatch(setIsVisible(isVisible));
Expand All @@ -290,8 +202,6 @@ export function buildCoreGeneratedAnswer(
dispatch(expandGeneratedAnswer());
}

subscribeStateManager.subscribeToSearchRequests(engine);

return {
...controller,

Expand Down
Loading

0 comments on commit 3e1b7d1

Please sign in to comment.