feat: Created component for ai signposting (#25187)
## Description feat: Created component for ai signposting #### PR fixes following issue(s) Fixes # (issue number) > if no issue exists, please create an issue and ask the maintainers about this first > > #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change > Please delete options that are not relevant. - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds functionality) - Breaking change (fix or feature that would cause existing functionality to not work as expected) - Chore (housekeeping or task changes that don't impact user perception) - This change requires a documentation update > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [ ] Manual - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
This commit is contained in:
parent
0bc1213795
commit
6dcb996bbb
|
|
@ -0,0 +1,26 @@
|
|||
import BindingPrompt from "components/editorComponents/CodeEditor/BindingPrompt";
|
||||
import type {
|
||||
EditorTheme,
|
||||
TEditorModes,
|
||||
} from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
import React from "react";
|
||||
|
||||
export function CodeEditorSignPosting(props: {
|
||||
promptMessage?: React.ReactNode | string;
|
||||
isOpen?: boolean;
|
||||
editorTheme?: EditorTheme;
|
||||
showLightningMenu?: boolean;
|
||||
isAIEnabled?: boolean;
|
||||
mode: TEditorModes;
|
||||
forComp?: string;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<BindingPrompt
|
||||
editorTheme={props.editorTheme}
|
||||
isAIEnabled={props.isAIEnabled}
|
||||
isOpen={props.isOpen || false}
|
||||
promptMessage={props.promptMessage}
|
||||
showLightningMenu={props.showLightningMenu}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import type { TEditorModes } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
|
||||
export type Props = {
|
||||
isAIEnabled: boolean;
|
||||
mode: TEditorModes;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export function EditorFormSignPosting(props: Props) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
import type { CodeEditorExpected } from "components/editorComponents/CodeEditor";
|
||||
import type { TEditorModes } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
import type {
|
||||
FieldEntityInformation,
|
||||
TEditorModes,
|
||||
} from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
import React from "react";
|
||||
|
||||
export type TAIWrapperProps = {
|
||||
|
|
@ -12,6 +15,7 @@ export type TAIWrapperProps = {
|
|||
enableAIAssistance: boolean;
|
||||
dataTreePath?: string;
|
||||
mode: TEditorModes;
|
||||
entity: FieldEntityInformation;
|
||||
};
|
||||
|
||||
export function AIWindow(props: TAIWrapperProps) {
|
||||
|
|
|
|||
|
|
@ -834,6 +834,7 @@ const ActionTypes = {
|
|||
BIND_WIDGET_TO_DATASOURCE_ERROR: "BIND_WIDGET_TO_DATASOURCE_ERROR",
|
||||
LOAD_FILE_PICKER_ACTION: "LOAD_FILE_PICKER_ACTION",
|
||||
TOGGLE_AI_WINDOW: "TOGGLE_AI_WINDOW",
|
||||
UPDATE_AI_TRIGGERED: "UPDATE_AI_TRIGGERED",
|
||||
UPDATE_DATASOURCE_AUTH_STATE: "UPDATE_DATASOURCE_AUTH_STATE",
|
||||
UPDATE_POSITIONS_ON_TAB_CHANGE: "UPDATE_POSITIONS_ON_TAB_CHANGE",
|
||||
RESET_DATA_TREE: "RESET_DATA_TREE",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
import React from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import styled from "styled-components";
|
||||
import { Icon } from "design-system";
|
||||
|
||||
export type SignPostingBannerProps = {
|
||||
iconName: string;
|
||||
content: ReactNode;
|
||||
};
|
||||
|
||||
export const Container = styled.div`
|
||||
background-color: var(--ads-v2-color-blue-100);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
function SignPostingBanner(props: SignPostingBannerProps) {
|
||||
return (
|
||||
<Container className="py-2 px-3 rounded">
|
||||
<div className="flex items-start">
|
||||
<Icon
|
||||
className="font-semibold mr-2 flex items-start"
|
||||
color="var(--ads-v2-color-fg-information)"
|
||||
name={props.iconName}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
{props.content}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignPostingBanner;
|
||||
|
|
@ -6,6 +6,7 @@ import type { EntityNavigationData } from "selectors/navigationSelectors";
|
|||
import type { ExpectedValueExample } from "utils/validation/common";
|
||||
|
||||
import { editorSQLModes } from "./sql/config";
|
||||
import type { WidgetType } from "constants/WidgetConstants";
|
||||
|
||||
export const EditorModes = {
|
||||
TEXT: "text/plain",
|
||||
|
|
@ -61,6 +62,7 @@ export type FieldEntityInformation = {
|
|||
example?: ExpectedValueExample;
|
||||
mode?: TEditorModes;
|
||||
token?: CodeMirror.Token;
|
||||
widgetType?: WidgetType;
|
||||
};
|
||||
|
||||
export type HintHelper = (
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ import {
|
|||
bindingHint,
|
||||
sqlHint,
|
||||
} from "components/editorComponents/CodeEditor/hintHelpers";
|
||||
import BindingPrompt from "./BindingPrompt";
|
||||
|
||||
import { showBindingPrompt } from "./BindingPromptHelper";
|
||||
import { Button } from "design-system";
|
||||
import "codemirror/addon/fold/brace-fold";
|
||||
|
|
@ -103,6 +103,7 @@ import { getLintAnnotations, getLintTooltipDirection } from "./lintHelpers";
|
|||
import { executeCommandAction } from "actions/apiPaneActions";
|
||||
import { startingEntityUpdate } from "actions/editorActions";
|
||||
import type { SlashCommandPayload } from "entities/Action";
|
||||
import { SlashCommand } from "entities/Action";
|
||||
import type { Indices } from "constants/Layers";
|
||||
import { replayHighlightClass } from "globalStyles/portals";
|
||||
import {
|
||||
|
|
@ -158,6 +159,7 @@ import { PeekOverlayExpressionIdentifier, SourceType } from "@shared/ast";
|
|||
import type { MultiplexingModeConfig } from "components/editorComponents/CodeEditor/modes";
|
||||
import { MULTIPLEXING_MODE_CONFIGS } from "components/editorComponents/CodeEditor/modes";
|
||||
import { getDeleteLineShortcut } from "./utils/deleteLine";
|
||||
import { CodeEditorSignPosting } from "@appsmith/components/editorComponents/CodeEditorSignPosting";
|
||||
|
||||
type ReduxStateProps = ReturnType<typeof mapStateToProps>;
|
||||
type ReduxDispatchProps = ReturnType<typeof mapDispatchToProps>;
|
||||
|
|
@ -489,6 +491,10 @@ class CodeEditor extends Component<Props, State> {
|
|||
handleSlashCommandSelection = (...args: any) => {
|
||||
const [command] = args;
|
||||
if (command === APPSMITH_AI) {
|
||||
this.props.executeCommand({
|
||||
actionType: SlashCommand.ASK_AI,
|
||||
args: {},
|
||||
});
|
||||
this.setState({ showAIWindow: true });
|
||||
}
|
||||
this.handleAutocompleteVisibility(this.editor);
|
||||
|
|
@ -1244,6 +1250,8 @@ class CodeEditor extends Component<Props, State> {
|
|||
entityInformation.entityId = entity.widgetId;
|
||||
if (isTriggerPath)
|
||||
entityInformation.expectedType = AutocompleteDataType.FUNCTION;
|
||||
|
||||
entityInformation.widgetType = entity.type;
|
||||
}
|
||||
}
|
||||
entityInformation.propertyPath = propertyPath;
|
||||
|
|
@ -1552,12 +1560,14 @@ class CodeEditor extends Component<Props, State> {
|
|||
currentValue={this.props.input.value}
|
||||
dataTreePath={dataTreePath}
|
||||
enableAIAssistance={this.AIEnabled}
|
||||
entity={entityInformation}
|
||||
isOpen={this.state.showAIWindow}
|
||||
mode={this.props.mode}
|
||||
triggerContext={this.props.expected}
|
||||
update={this.updateValueWithAIResponse}
|
||||
>
|
||||
<EditorWrapper
|
||||
AIEnabled
|
||||
border={border}
|
||||
borderLess={borderLess}
|
||||
className={`${className} ${replayHighlightClass} ${
|
||||
|
|
@ -1575,6 +1585,7 @@ class CodeEditor extends Component<Props, State> {
|
|||
isNotHover={this.state.isFocused || this.state.isOpened}
|
||||
isRawView={this.props.isRawView}
|
||||
isReadOnly={this.props.isReadOnly}
|
||||
mode={this.props.mode}
|
||||
onMouseMove={this.handleLintTooltip}
|
||||
onMouseOver={this.handleMouseMove}
|
||||
ref={this.editorWrapperRef}
|
||||
|
|
@ -1604,10 +1615,12 @@ class CodeEditor extends Component<Props, State> {
|
|||
ref={this.codeEditorTarget}
|
||||
tabIndex={0}
|
||||
>
|
||||
<BindingPrompt
|
||||
<CodeEditorSignPosting
|
||||
editorTheme={this.props.theme}
|
||||
forComp="editor"
|
||||
isAIEnabled={this.AIEnabled}
|
||||
isOpen={this.isBindingPromptOpen()}
|
||||
mode={this.props.mode}
|
||||
promptMessage={this.props.promptMessage}
|
||||
showLightningMenu={this.props.showLightningMenu}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import styled from "styled-components";
|
||||
import type { CodeEditorBorder } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
|
||||
import {
|
||||
EditorSize,
|
||||
EditorTheme,
|
||||
|
|
@ -56,6 +57,8 @@ export const EditorWrapper = styled.div<{
|
|||
codeEditorVisibleOverflow?: boolean;
|
||||
ctrlPressed: boolean;
|
||||
removeHoverAndFocusStyle?: boolean;
|
||||
AIEnabled?: boolean;
|
||||
mode: string;
|
||||
}>`
|
||||
// Bottom border was getting clipped
|
||||
.CodeMirror.cm-s-duotone-light.CodeMirror-wrap {
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export * from "ce/components/editorComponents/CodeEditorSignPosting";
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "ce/components/editorComponents/EditorFormSignPosting";
|
||||
|
|
@ -58,6 +58,7 @@ import {
|
|||
} from "@appsmith/sagas/userSagas";
|
||||
import { getFirstTimeUserOnboardingComplete } from "selectors/onboardingSelectors";
|
||||
import { isAirgapped } from "@appsmith/utils/airgapHelpers";
|
||||
import { getAIPromptTriggered } from "utils/storage";
|
||||
|
||||
export default class AppEditorEngine extends AppEngine {
|
||||
constructor(mode: APP_MODE) {
|
||||
|
|
@ -214,6 +215,16 @@ export default class AppEditorEngine extends AppEngine {
|
|||
payload: [],
|
||||
});
|
||||
}
|
||||
|
||||
const noOfTimesAIPromptTriggered: number = yield getAIPromptTriggered();
|
||||
|
||||
yield put({
|
||||
type: ReduxActionTypes.UPDATE_AI_TRIGGERED,
|
||||
payload: {
|
||||
value: noOfTimesAIPromptTriggered,
|
||||
},
|
||||
});
|
||||
|
||||
yield call(waitForWidgetConfigBuild);
|
||||
yield put({
|
||||
type: ReduxActionTypes.INITIALIZE_EDITOR_SUCCESS,
|
||||
|
|
|
|||
|
|
@ -131,8 +131,14 @@ import { ENTITY_TYPE as SOURCE_ENTITY_TYPE } from "entities/AppsmithConsole";
|
|||
import { DocsLink, openDoc } from "../../../constants/DocumentationLinks";
|
||||
import ActionExecutionInProgressView from "components/editorComponents/ActionExecutionInProgressView";
|
||||
import { CloseDebugger } from "components/editorComponents/Debugger/DebuggerTabs";
|
||||
import { isAIEnabled } from "@appsmith/components/editorComponents/GPT/trigger";
|
||||
import { editorSQLModes } from "components/editorComponents/CodeEditor/sql/config";
|
||||
import { EditorFormSignPosting } from "@appsmith/components/editorComponents/EditorFormSignPosting";
|
||||
import { DatasourceStructureContext } from "../Explorer/Datasources/DatasourceStructureContainer";
|
||||
import { selectFeatureFlagCheck } from "@appsmith/selectors/featureFlagsSelectors";
|
||||
import {
|
||||
selectFeatureFlagCheck,
|
||||
selectFeatureFlags,
|
||||
} from "@appsmith/selectors/featureFlagsSelectors";
|
||||
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
|
||||
|
||||
const QueryFormContainer = styled.form`
|
||||
|
|
@ -875,6 +881,14 @@ export function EditorJSONtoForm(props: Props) {
|
|||
dispatch(setDebuggerSelectedTab(tabKey));
|
||||
}, []);
|
||||
|
||||
const featureFlags = useSelector(selectFeatureFlags);
|
||||
const editorMode =
|
||||
currentActionPluginName === PluginName.POSTGRES
|
||||
? editorSQLModes.POSTGRESQL_WITH_BINDING
|
||||
: editorSQLModes.MYSQL_WITH_BINDING;
|
||||
|
||||
const isAIEnabledForPosting = isAIEnabled(featureFlags, editorMode);
|
||||
|
||||
// close the debugger
|
||||
//TODO: move this to a common place
|
||||
const onClose = () => dispatch(showDebugger(false));
|
||||
|
|
@ -980,6 +994,11 @@ export function EditorJSONtoForm(props: Props) {
|
|||
className="tab-panel"
|
||||
value={EDITOR_TABS.QUERY}
|
||||
>
|
||||
<EditorFormSignPosting
|
||||
isAIEnabled={isAIEnabledForPosting}
|
||||
mode={editorMode}
|
||||
/>
|
||||
|
||||
<SettingsWrapper>
|
||||
{editorConfig && editorConfig.length > 0 ? (
|
||||
renderConfig(editorConfig)
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ import { DEFAULT_GRAPHQL_ACTION_CONFIG } from "constants/ApiEditorConstants/Grap
|
|||
import { DEFAULT_API_ACTION_CONFIG } from "constants/ApiEditorConstants/ApiEditorConstants";
|
||||
import { createNewApiName, createNewQueryName } from "utils/AppsmithUtils";
|
||||
import { fetchDatasourceStructure } from "actions/datasourceActions";
|
||||
import { setAIPromptTriggered } from "utils/storage";
|
||||
|
||||
export function* createDefaultActionPayload(
|
||||
pageId: string,
|
||||
|
|
@ -942,6 +943,21 @@ function* executeCommandSaga(actionPayload: ReduxAction<SlashCommandPayload>) {
|
|||
break;
|
||||
case SlashCommand.ASK_AI: {
|
||||
const context = get(actionPayload, "payload.args", {});
|
||||
|
||||
const noOfTimesAIPromptTriggered: number = yield select(
|
||||
(state) => state.ai.noOfTimesAITriggered,
|
||||
);
|
||||
|
||||
if (noOfTimesAIPromptTriggered < 5) {
|
||||
const currentValue: number = yield setAIPromptTriggered();
|
||||
yield put({
|
||||
type: ReduxActionTypes.UPDATE_AI_TRIGGERED,
|
||||
payload: {
|
||||
value: currentValue,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
yield put({
|
||||
type: ReduxActionTypes.TOGGLE_AI_WINDOW,
|
||||
payload: {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export const STORAGE_KEYS: {
|
|||
FIRST_TIME_USER_ONBOARDING_TELEMETRY_CALLOUT_VISIBILITY:
|
||||
"FIRST_TIME_USER_ONBOARDING_TELEMETRY_CALLOUT_VISIBILITY",
|
||||
SIGNPOSTING_APP_STATE: "SIGNPOSTING_APP_STATE",
|
||||
AI_TRIGGERED: "AI_TRIGGERED",
|
||||
FEATURE_WALKTHROUGH: "FEATURE_WALKTHROUGH",
|
||||
USER_SIGN_UP: "USER_SIGN_UP",
|
||||
};
|
||||
|
|
@ -421,6 +422,39 @@ export const setFirstTimeUserOnboardingTelemetryCalloutVisibility = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const setAIPromptTriggered = async () => {
|
||||
try {
|
||||
let noOfTimesAITriggered: number = await getAIPromptTriggered();
|
||||
|
||||
if (noOfTimesAITriggered >= 5) {
|
||||
return noOfTimesAITriggered;
|
||||
}
|
||||
|
||||
noOfTimesAITriggered += 1;
|
||||
await store.setItem(STORAGE_KEYS.AI_TRIGGERED, noOfTimesAITriggered);
|
||||
|
||||
return noOfTimesAITriggered;
|
||||
} catch (error) {
|
||||
log.error("An error occurred while setting AI_TRIGGERED");
|
||||
log.error(error);
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
export const getAIPromptTriggered = async () => {
|
||||
try {
|
||||
const flag: number | null = await store.getItem(STORAGE_KEYS.AI_TRIGGERED);
|
||||
|
||||
if (flag === null) return 0;
|
||||
|
||||
return flag;
|
||||
} catch (error) {
|
||||
log.error("An error occurred while fetching AI_TRIGGERED");
|
||||
log.error(error);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
export const setFeatureFlagShownStatus = async (key: string, value: any) => {
|
||||
try {
|
||||
let flagsJSON: Record<string, any> | null = await store.getItem(
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user