diff --git a/.github/workflows/test-on-push.yml b/.github/workflows/test-on-push.yml index 9bc6e270..88f89cc7 100644 --- a/.github/workflows/test-on-push.yml +++ b/.github/workflows/test-on-push.yml @@ -29,3 +29,25 @@ jobs: run: npm install - name: Test the app run: npm run test + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: '.node-version' + - name: Installing node_modules + run: npm install + - name: Run lint + run: npm run lint:check + typecheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: '.node-version' + - name: Installing node_modules + run: npm install + - name: Run tsc + run: npm run tsc diff --git a/package.json b/package.json index 7467c173..364694e6 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,13 @@ ], "scripts": { "build": "lerna run build", + "lint:check": "lerna run lint:check", + "lint": "lerna run lint", "postinstall": "npm run build", "publish": "rm -f package-lock.json && rm -f packages/*/package-lock.json && lerna publish --no-private", "publish-docs": "lerna run publish-docs", "test": "lerna run test", + "tsc": "lerna run tsc", "watch": "lerna watch -- lerna run build --since" }, "author": "Peter Szerzo ", diff --git a/packages/chat-core/.eslintrc.cjs b/packages/chat-core/.eslintrc.cjs new file mode 100644 index 00000000..5889268a --- /dev/null +++ b/packages/chat-core/.eslintrc.cjs @@ -0,0 +1,5 @@ +/** @type {import('eslint').Linter.Config } */ +module.exports = { + root: true, + extends: ["nlx"], +}; diff --git a/packages/chat-core/package.json b/packages/chat-core/package.json index bec69382..3a3a7182 100644 --- a/packages/chat-core/package.json +++ b/packages/chat-core/package.json @@ -10,7 +10,10 @@ "types": "lib/index.d.ts", "scripts": { "build": "rm -rf lib && rollup -c", - "prepublish": "npm run build" + "lint:check": "npx eslint src/ --ext .ts,.tsx,.js,.jsx", + "lint": "npx eslint src/ --ext .ts,.tsx,.js,.jsx --fix", + "prepublish": "npm run build", + "tsc": "tsc" }, "author": "Peter Szerzo ", "license": "MIT", @@ -25,6 +28,7 @@ "@types/node": "^11.15.11", "@types/ramda": "0.29.1", "@types/uuid": "^9.0.7", + "eslint-config-nlx": "*", "prettier": "^3.1.0", "rollup": "^4.9.3", "rollup-plugin-node-polyfills": "^0.2.1", diff --git a/packages/chat-core/src/index.ts b/packages/chat-core/src/index.ts index b72ef174..c49cb57b 100644 --- a/packages/chat-core/src/index.ts +++ b/packages/chat-core/src/index.ts @@ -101,9 +101,7 @@ export interface Config { responses?: Response[]; failureMessage?: string; environment?: Environment; - headers?: { - [key: string]: string; - }; + headers?: Record; languageCode: string; // Experimental settings experimental?: { @@ -121,7 +119,7 @@ const defaultFailureMessage = "We encountered an issue. Please try again soon."; export type State = Response[]; const normalizeSlots = (slotsWithLegacy: Slots | SlotValue[]): Slots => { - let slots: Slots = {}; + const slots: Slots = {}; if (Array.isArray(slotsWithLegacy)) { console.warn( `The legacy slot format is deprecated. Instead of '[{ slotId: "MySlot", value: "my-value" }]', use '{ MySlot: "my-value" }'`, @@ -176,6 +174,8 @@ interface InternalState { const fromInternal = (internalState: InternalState): State => internalState.responses; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type const safeJsonParse = (val: string) => { try { const json = JSON.parse(val); @@ -185,7 +185,7 @@ const safeJsonParse = (val: string) => { } }; -type Subscriber = (response: Array, newResponse?: Response) => void; +type Subscriber = (response: Response[], newResponse?: Response) => void; // Helper method to decide when a conversation needs to be re-initialized (e.g. bot URL change) export const shouldReinitialize = ( @@ -221,9 +221,13 @@ export const createConversation = (config: Config): ConversationHandler => { ); } + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing const initialConversationId = config.conversationId || uuid(); let state: InternalState = { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing responses: config.responses || [], userId: config.userId, conversationId: initialConversationId, @@ -239,15 +243,19 @@ export const createConversation = (config: Config): ConversationHandler => { ...change, }; subscribers.forEach((subscriber) => - subscriber(fromInternal(state), newResponse), + { subscriber(fromInternal(state), newResponse); }, ); }; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const failureHandler = () => { const newResponse: Response = { type: "failure", receivedAt: new Date().getTime(), payload: { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing text: config.failureMessage || defaultFailureMessage, }, }; @@ -259,8 +267,12 @@ export const createConversation = (config: Config): ConversationHandler => { ); }; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const messageResponseHandler = (response: any) => { - if (response && response.messages) { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + if (response?.messages) { const newResponse: Response = { type: "bot", receivedAt: new Date().getTime(), @@ -269,6 +281,8 @@ export const createConversation = (config: Config): ConversationHandler => { messages: response.messages.map((message: any) => ({ messageId: message.messageId, text: message.text, + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions choices: message.choices || [], })), }, @@ -279,9 +293,13 @@ export const createConversation = (config: Config): ConversationHandler => { }, newResponse, ); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (response.metadata.hasPendingDataRequest) { appendStructuredUserResponse({ poll: true }); setTimeout(() => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/no-floating-promises sendToBot({ request: { structured: { @@ -304,7 +322,9 @@ export const createConversation = (config: Config): ConversationHandler => { let socketMessageQueueCheckInterval: ReturnType | null = null; - const sendToBot = (body: BotRequest) => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + const sendToBot = async (body: BotRequest) => { const bodyWithContext = { userId: state.userId, conversationId: state.conversationId, @@ -313,20 +333,28 @@ export const createConversation = (config: Config): ConversationHandler => { channelType: config.experimental?.channelType, environment: config.environment, }; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (isUsingWebSockets()) { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (socket && socket.readyState === 1) { socket.send(JSON.stringify(bodyWithContext)); } else { socketMessageQueue = [...socketMessageQueue, bodyWithContext]; } } else { - return fetch( + await fetch( `${config.botUrl}${ + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions config.experimental?.completeBotUrl ? "" : `-${config.languageCode}` }`, { method: "POST", headers: { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing ...(config.headers || {}), Accept: "application/json", "Content-Type": "application/json", @@ -334,36 +362,52 @@ export const createConversation = (config: Config): ConversationHandler => { body: JSON.stringify(bodyWithContext), }, ) - .then((res) => { - return res.json(); + .then(async (res) => { + return await res.json(); }) .then(messageResponseHandler) .catch((err) => { console.warn(err); failureHandler(); - }); + }); } }; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const isUsingWebSockets = () => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions return config.botUrl && config.botUrl.indexOf("wss://") === 0; }; let subscribers: Subscriber[] = []; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const checkQueue = () => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (socket?.readyState === 1 && socketMessageQueue[0]) { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/no-floating-promises sendToBot(socketMessageQueue[0]); socketMessageQueue = socketMessageQueue.slice(1); } }; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const setupWebsocket = () => { const url = new URL(config.botUrl); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (!config.experimental?.completeBotUrl) { url.searchParams.set("languageCode", config.languageCode); url.searchParams.set( "channelKey", + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing `${url.searchParams.get("channelKey") || ""}-${config.languageCode}`, ); } @@ -377,10 +421,16 @@ export const createConversation = (config: Config): ConversationHandler => { }; }; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const teardownWebsocket = () => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (socketMessageQueueCheckInterval) { clearInterval(socketMessageQueueCheckInterval); } + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (socket) { socket.onmessage = null; socket.close(); @@ -388,6 +438,8 @@ export const createConversation = (config: Config): ConversationHandler => { } }; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (isUsingWebSockets()) { setupWebsocket(); } @@ -395,6 +447,8 @@ export const createConversation = (config: Config): ConversationHandler => { const appendStructuredUserResponse = ( structured: StructuredRequest, context?: Context, + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type ) => { const newResponse: Response = { type: "user", @@ -413,8 +467,12 @@ export const createConversation = (config: Config): ConversationHandler => { ); }; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const sendIntent = (intentId: string, context?: Context) => { appendStructuredUserResponse({ intentId }, context); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/no-floating-promises sendToBot({ context, request: { @@ -425,6 +483,8 @@ export const createConversation = (config: Config): ConversationHandler => { }); }; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const sendText = (text: string, context?: Context) => { const newResponse: Response = { type: "user", @@ -441,6 +501,8 @@ export const createConversation = (config: Config): ConversationHandler => { }, newResponse, ); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/no-floating-promises sendToBot({ context, request: { @@ -451,10 +513,14 @@ export const createConversation = (config: Config): ConversationHandler => { }); }; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const unsubscribe = (subscriber: Subscriber) => { subscribers = subscribers.filter((fn) => fn !== subscriber); }; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const subscribe = (subscriber: Subscriber) => { subscribers = [...subscribers, subscriber]; subscriber(fromInternal(state)); @@ -467,6 +533,8 @@ export const createConversation = (config: Config): ConversationHandler => { sendText, sendStructured: (structured: StructuredRequest, context) => { appendStructuredUserResponse(structured, context); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/no-floating-promises sendToBot({ context, request: { @@ -477,6 +545,8 @@ export const createConversation = (config: Config): ConversationHandler => { sendSlots: (slotsWithLegacy, context) => { const slots = normalizeSlots(slotsWithLegacy); appendStructuredUserResponse({ slots }, context); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/no-floating-promises sendToBot({ context, request: { @@ -491,10 +561,14 @@ export const createConversation = (config: Config): ConversationHandler => { sendIntent(welcomeIntent, context); }, sendChoice: (choiceId, context) => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const containsChoice = (botMessage: BotMessage) => + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions (botMessage.choices || []) .map((choice) => choice.choiceId) - .indexOf(choiceId) > -1; + .includes(choiceId); const lastBotResponseIndex = findLastIndex( (response: Response) => @@ -548,6 +622,8 @@ export const createConversation = (config: Config): ConversationHandler => { choiceResponse, ); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/no-floating-promises sendToBot({ context, request: { @@ -568,8 +644,12 @@ export const createConversation = (config: Config): ConversationHandler => { reset: (options) => { setState({ conversationId: uuid(), + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions responses: options?.clearResponses ? [] : state.responses, }); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (isUsingWebSockets()) { teardownWebsocket(); setupWebsocket(); @@ -577,6 +657,8 @@ export const createConversation = (config: Config): ConversationHandler => { }, destroy: () => { subscribers = []; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (isUsingWebSockets()) { teardownWebsocket(); } @@ -589,15 +671,21 @@ export function promisify( convo: ConversationHandler, timeout = 10000, ): (payload: T) => Promise { - return (payload: T) => { - return new Promise((resolve, reject) => { + return async (payload: T) => { + return await new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { + // initial eslint integration + // eslint-disable-next-line prefer-promise-reject-errors reject("The request timed out."); }, timeout); const subscription = ( _responses: Response[], newResponse: Response | undefined, + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type ) => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (newResponse && newResponse.type === "bot") { clearTimeout(timeoutId); convo.unsubscribe(subscription); diff --git a/packages/chat-core/tsconfig.json b/packages/chat-core/tsconfig.json index fb9e5158..87cc7778 100644 --- a/packages/chat-core/tsconfig.json +++ b/packages/chat-core/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "target": "es6", "module": "esnext", + "moduleResolution": "bundler", "declaration": true, "outDir": "./lib", "rootDir": "./src", diff --git a/packages/chat-preact/.eslintrc.cjs b/packages/chat-preact/.eslintrc.cjs new file mode 100644 index 00000000..4885ad56 --- /dev/null +++ b/packages/chat-preact/.eslintrc.cjs @@ -0,0 +1,6 @@ +/** @type {import('eslint').Linter.Config } */ +module.exports = { + root: true, + // appears that the preact eslint plugin isn't compatible with typescript + extends: ["nlx"], +}; diff --git a/packages/chat-preact/package.json b/packages/chat-preact/package.json index 49e5696d..b573626c 100644 --- a/packages/chat-preact/package.json +++ b/packages/chat-preact/package.json @@ -9,7 +9,10 @@ "types": "lib/index.d.ts", "scripts": { "build": "rm -rf lib && rollup -c", - "prepublish": "npm run build" + "lint:check": "npx eslint src/ --ext .ts,.tsx,.js,.jsx", + "lint": "npx eslint src/ --ext .ts,.tsx,.js,.jsx --fix", + "prepublish": "npm run build", + "tsc": "tsc" }, "author": "Peter Szerzo ", "license": "MIT", @@ -22,6 +25,7 @@ "@rollup/plugin-typescript": "^11.1.5", "@types/node": "^11.15.11", "@types/ramda": "0.29.1", + "eslint-config-nlx": "*", "prettier": "^3.0.3", "rollup": "^4.3.0", "rollup-plugin-node-polyfills": "^0.2.1", diff --git a/packages/chat-preact/src/index.ts b/packages/chat-preact/src/index.ts index d0316b40..0db8d9d4 100755 --- a/packages/chat-preact/src/index.ts +++ b/packages/chat-preact/src/index.ts @@ -3,17 +3,17 @@ import { useState, useEffect, useRef, useMemo } from "preact/hooks"; // Code from here on out is identical in the React and Preact packages import { last } from "ramda"; import createConversation, { - Config, - ConversationHandler, + type Config, + type ConversationHandler, shouldReinitialize, - Response + type Response, } from "@nlxai/chat-core"; export interface ChatHook { conversationHandler: ConversationHandler; inputValue: string; setInputValue: (val: string) => void; - responses: Array; + responses: Response[]; waiting: boolean; } @@ -28,8 +28,8 @@ export const useChat = (config: Config): ChatHook => { const conversationHandler: ConversationHandler = useMemo(() => { // Prevent re-initialization if backend-related props have not changed if ( - prevConfig.current && - prevConversationHandler.current && + prevConfig.current != null && + prevConversationHandler.current != null && !shouldReinitialize(prevConfig.current, config) ) { return prevConversationHandler.current; @@ -39,7 +39,7 @@ export const useChat = (config: Config): ChatHook => { return newHandler; }, [config]); - const [responses, setResponses] = useState>([]); + const [responses, setResponses] = useState([]); const [inputValue, setInputValue] = useState(""); @@ -52,14 +52,14 @@ export const useChat = (config: Config): ChatHook => { }, [conversationHandler]); const lastMessage = last(responses); - const isWaiting = lastMessage ? lastMessage.type === "user" : false; + const isWaiting = lastMessage?.type === "user"; return { conversationHandler, inputValue, responses, waiting: isWaiting, - setInputValue + setInputValue, }; }; diff --git a/packages/chat-react/.eslintrc.cjs b/packages/chat-react/.eslintrc.cjs new file mode 100644 index 00000000..4885ad56 --- /dev/null +++ b/packages/chat-react/.eslintrc.cjs @@ -0,0 +1,6 @@ +/** @type {import('eslint').Linter.Config } */ +module.exports = { + root: true, + // appears that the preact eslint plugin isn't compatible with typescript + extends: ["nlx"], +}; diff --git a/packages/chat-react/package.json b/packages/chat-react/package.json index 3e589db4..655b6d6b 100644 --- a/packages/chat-react/package.json +++ b/packages/chat-react/package.json @@ -9,7 +9,10 @@ "types": "lib/index.d.ts", "scripts": { "build": "rm -rf lib && rollup -c", - "prepublish": "npm run build" + "lint:check": "npx eslint src/ --ext .ts,.tsx,.js,.jsx", + "lint": "npx eslint src/ --ext .ts,.tsx,.js,.jsx --fix", + "prepublish": "npm run build", + "tsc": "tsc" }, "author": "Peter Szerzo ", "license": "MIT", @@ -24,6 +27,7 @@ "@types/ramda": "0.29.1", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", + "eslint-config-nlx": "*", "prettier": "^3.0.3", "rollup": "^4.3.0", "rollup-plugin-node-polyfills": "^0.2.1", diff --git a/packages/chat-react/src/index.ts b/packages/chat-react/src/index.ts index a799179b..ae680294 100755 --- a/packages/chat-react/src/index.ts +++ b/packages/chat-react/src/index.ts @@ -3,17 +3,17 @@ import { useState, useEffect, useRef, useMemo } from "react"; // Code from here on out is identical in the React and Preact packages import { last } from "ramda"; import createConversation, { - Config, - ConversationHandler, + type Config, + type ConversationHandler, shouldReinitialize, - Response + type Response, } from "@nlxai/chat-core"; export interface ChatHook { conversationHandler: ConversationHandler; inputValue: string; setInputValue: (val: string) => void; - responses: Array; + responses: Response[]; waiting: boolean; } @@ -28,8 +28,8 @@ export const useChat = (config: Config): ChatHook => { const conversationHandler: ConversationHandler = useMemo(() => { // Prevent re-initialization if backend-related props have not changed if ( - prevConfig.current && - prevConversationHandler.current && + prevConfig.current != null && + prevConversationHandler.current != null && !shouldReinitialize(prevConfig.current, config) ) { return prevConversationHandler.current; @@ -39,7 +39,7 @@ export const useChat = (config: Config): ChatHook => { return newHandler; }, [config]); - const [responses, setResponses] = useState>([]); + const [responses, setResponses] = useState([]); const [inputValue, setInputValue] = useState(""); @@ -52,14 +52,14 @@ export const useChat = (config: Config): ChatHook => { }, [conversationHandler]); const lastMessage = last(responses); - const isWaiting = lastMessage ? lastMessage.type === "user" : false; + const isWaiting = lastMessage?.type === "user"; return { conversationHandler, inputValue, responses, waiting: isWaiting, - setInputValue + setInputValue, }; }; diff --git a/packages/chat-react/tsconfig.json b/packages/chat-react/tsconfig.json index e875977d..7d6e65a0 100644 --- a/packages/chat-react/tsconfig.json +++ b/packages/chat-react/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "target": "es6", "module": "esnext", + "moduleResolution": "bundler", "lib": ["dom"], "jsx": "react", "declaration": true, diff --git a/packages/chat-widget/.eslintrc.cjs b/packages/chat-widget/.eslintrc.cjs new file mode 100644 index 00000000..5889268a --- /dev/null +++ b/packages/chat-widget/.eslintrc.cjs @@ -0,0 +1,5 @@ +/** @type {import('eslint').Linter.Config } */ +module.exports = { + root: true, + extends: ["nlx"], +}; diff --git a/packages/chat-widget/package.json b/packages/chat-widget/package.json index ca04b823..aeb09a24 100644 --- a/packages/chat-widget/package.json +++ b/packages/chat-widget/package.json @@ -10,7 +10,10 @@ "types": "lib/index.d.ts", "scripts": { "build": "rm -rf lib && rollup -c", - "prepublish": "npm run build" + "lint:check": "npx eslint src/ --ext .ts,.tsx,.js,.jsx", + "lint": "npx eslint src/ --ext .ts,.tsx,.js,.jsx --fix", + "prepublish": "npm run build", + "tsc": "tsc" }, "author": "Peter Szerzo ", "license": "MIT", @@ -26,6 +29,7 @@ "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", "@types/tinycolor2": "^1.4.2", + "eslint-config-nlx": "*", "prettier": "^3.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/packages/chat-widget/src/icons.tsx b/packages/chat-widget/src/icons.tsx index da80081a..4f459032 100644 --- a/packages/chat-widget/src/icons.tsx +++ b/packages/chat-widget/src/icons.tsx @@ -1,29 +1,39 @@ import React from "react"; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export const CloseIcon = () => ( ); +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export const ChatIcon = () => ( ); +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export const AirplaneIcon = () => ( ); +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export const DownloadIcon = () => ( ); +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export const ErrorOutlineIcon = () => ( diff --git a/packages/chat-widget/src/index.tsx b/packages/chat-widget/src/index.tsx index 32abd136..7de758b3 100644 --- a/packages/chat-widget/src/index.tsx +++ b/packages/chat-widget/src/index.tsx @@ -97,6 +97,8 @@ const Loader: FC<{ message?: string; showAfter?: number }> = (props) => { return ( + {/* initial eslint integration */} + {/* eslint-disable-next-line @typescript-eslint/strict-boolean-expressions */} {showMessage && props.message && ( {props.message} )} @@ -110,6 +112,8 @@ const MessageGroups: FC<{ customModalities: Record; }> = (props) => ( + {/* initial eslint integration */} + {/* eslint-disable-next-line array-callback-return */} {props.chat.responses.map((response, responseIndex) => { if (response.type === "bot") { return ( @@ -127,6 +131,8 @@ const MessageGroups: FC<{ { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions return botMessage.selectedChoiceId ? { disabled: true, @@ -145,6 +151,8 @@ const MessageGroups: FC<{ dangerouslySetInnerHTML={{ __html: marked( choice.choiceText + + // initial eslint integration + // eslint-disable-next-line no-constant-condition (false ? " asdf fadsfds fdsa fdsa fdsa " : ""), ), }} @@ -154,9 +162,13 @@ const MessageGroups: FC<{ )} ))} + {/* initial eslint integration */} + {/* eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing */} {Object.entries(response.payload.modalities || {}).map( ([key, value]) => { const Component = props.customModalities[key]; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (Component) { return ; } @@ -201,6 +213,8 @@ interface SessionData { responses: Response[]; } +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type const saveSession = (chat: ChatHook, storeIn: StorageType) => { const storage = storeIn === "sessionStorage" ? sessionStorage : localStorage; storage.setItem( @@ -212,6 +226,8 @@ const saveSession = (chat: ChatHook, storeIn: StorageType) => { ); }; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export const clearSession = (storeIn: StorageType) => { const storage = storeIn === "sessionStorage" ? sessionStorage : localStorage; storage.removeItem(storageKey); @@ -221,16 +237,24 @@ export const retrieveSession = (storeIn: StorageType): SessionData | null => { try { const storage = storeIn === "sessionStorage" ? sessionStorage : localStorage; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing const data = JSON.parse(storage.getItem(storageKey) || ""); const responses: Response[] | undefined = data?.responses; const conversationId: string | undefined = data?.conversationId; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (responses) { - let expirationTimestamp: number | undefined = undefined; + let expirationTimestamp: number | undefined; responses.forEach((response) => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (response.type === "bot" && response.payload.expirationTimestamp) { expirationTimestamp = response.payload.expirationTimestamp; } }); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (!expirationTimestamp || new Date().getTime() < expirationTimestamp) { return { responses, conversationId }; } else { @@ -247,10 +271,14 @@ const ConversationHandlerContext = createContext( null, ); +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export const useConversationHandler = () => { return useContext(ConversationHandlerContext); }; +// initial eslint integration +// eslint-disable-next-line react/display-name export const Widget = forwardRef((props, ref) => { const [windowInnerHeightValue, setWindowInnerHeightValue] = useState< number | null @@ -258,6 +286,8 @@ export const Widget = forwardRef((props, ref) => { useEffect(() => { setWindowInnerHeightValue(window.innerHeight); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const handleResize = () => { setWindowInnerHeightValue(window.innerHeight); }; @@ -268,18 +298,26 @@ export const Widget = forwardRef((props, ref) => { }, []); const savedSessionData = useMemo( + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions () => (props.storeIn ? retrieveSession(props.storeIn) : null), [props.storeIn], ); const configWithSession = useMemo(() => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (!savedSessionData) { return props.config; } return { ...props.config, conversationId: + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing savedSessionData.conversationId || props.config.conversationId, + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions responses: savedSessionData.responses || props.config.responses, }; }, [props.config, savedSessionData]); @@ -289,6 +327,8 @@ export const Widget = forwardRef((props, ref) => { const chat = useChat(configWithSession); useEffect(() => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (props.storeIn) { saveSession(chat, props.storeIn); } @@ -327,6 +367,8 @@ export const Widget = forwardRef((props, ref) => { const inputRef = useRef(null); useEffect(() => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-optional-chain if (inputRef && inputRef.current) { (inputRef as any).current.focus(); } @@ -341,6 +383,8 @@ export const Widget = forwardRef((props, ref) => { // Escape handling useEffect(() => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const handler = (ev: KeyboardEvent) => { if (ev.key === "Escape") { setExpanded(false); @@ -375,6 +419,8 @@ export const Widget = forwardRef((props, ref) => { const scrollToBottom = useCallback(() => { const node = messagesContainerRef.current; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (node) { node.scrollTop = node.scrollHeight; } @@ -393,6 +439,8 @@ export const Widget = forwardRef((props, ref) => { chat.setInputValue(""); }); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/no-unused-vars const dateTimestamp = useMemo(() => { const d = new Date(); return `${d.getFullYear()}-${toStringWithLeadingZero( @@ -405,6 +453,8 @@ export const Widget = forwardRef((props, ref) => { const mergedTheme = useMemo( () => ({ ...constants.defaultTheme, + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing ...(props.theme || {}), windowInnerHeight: windowInnerHeightValue, }), @@ -415,6 +465,8 @@ export const Widget = forwardRef((props, ref) => { <> + {/* initial eslint integration */} + {/* eslint-disable-next-line @typescript-eslint/strict-boolean-expressions */} {props.bubble ? ( ((props, ref) => { {expanded && ( + {/* initial eslint integration */} + {/* eslint-disable-next-line @typescript-eslint/strict-boolean-expressions */} {props.titleBar && ( + {/* initial eslint integration */} + {/* eslint-disable-next-line @typescript-eslint/strict-boolean-expressions */} {props.titleBar.logo && ( )} @@ -439,6 +495,8 @@ export const Widget = forwardRef((props, ref) => { )} {chat.waiting && ( @@ -457,11 +515,17 @@ export const Widget = forwardRef((props, ref) => { { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument chat.setInputValue(event.target.value); }} onKeyPress={(event: any) => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (event.key === "Enter" && submit) { submit(); } @@ -469,8 +533,12 @@ export const Widget = forwardRef((props, ref) => { /> { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (submit) { submit(); } @@ -489,6 +557,8 @@ export const Widget = forwardRef((props, ref) => { > {expanded ? ( + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions ) : props.chatIcon ? ( ) : ( diff --git a/packages/chat-widget/src/types.d.ts b/packages/chat-widget/src/types.d.ts index db95c5e4..464bb593 100644 --- a/packages/chat-widget/src/types.d.ts +++ b/packages/chat-widget/src/types.d.ts @@ -1,3 +1,5 @@ +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/no-unused-vars import { type Theme as ThemeType } from "./theme"; import "@emotion/react"; diff --git a/packages/chat-widget/src/ui/components.tsx b/packages/chat-widget/src/ui/components.tsx index 293caee8..cfb656ac 100644 --- a/packages/chat-widget/src/ui/components.tsx +++ b/packages/chat-widget/src/ui/components.tsx @@ -1,7 +1,7 @@ import React from "react"; import styled from "@emotion/styled"; import { keyframes } from "@emotion/react"; -import { Theme } from "../theme"; +import { type Theme } from "../theme"; import * as constants from "./constants"; import tinycolor from "tinycolor2"; @@ -20,6 +20,8 @@ export const hoverBg = ` } `; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export const focusShadow = (theme: Theme) => ` box-shadow: 0 0 0 3px ${tinycolor(theme.primaryColor) .setAlpha(0.15) @@ -47,6 +49,8 @@ const bounceKeyframes = keyframes` `; // The 'display: block !important' rule circumvents typical base CSS rules that set 'display: none' on empty HTML elements +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types const Dot = styled.div<{}>` display: block !important; width: 6px; @@ -70,6 +74,8 @@ const Dot = styled.div<{}>` } `; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types const DotsContainer = styled.div<{}>` display: flex; align-items: center; @@ -77,16 +83,22 @@ const DotsContainer = styled.div<{}>` padding: 4px 0; `; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const LoaderContainer = styled.div<{}>` display: flex; `; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const LoaderText = styled.span<{}>` display: inline-block; margin-left: 10px; font-size: ${constants.fontSize}px; `; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const PendingMessageDots: React.FunctionComponent<{}> = () => ( @@ -100,9 +112,13 @@ export const PendingMessageDots: React.FunctionComponent<{}> = () => ( const top = 20; const bottom = 90; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const Container = styled.div<{}>` position: fixed; top: ${(props) => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (!props.theme.windowInnerHeight) { return `${top}px`; } @@ -132,6 +148,8 @@ export const Container = styled.div<{}>` // Main +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const Main = styled.div<{}>` height: calc(100% - ${constants.bottomHeight}px); overflow: auto; @@ -139,6 +157,8 @@ export const Main = styled.div<{}>` // MessageGroups +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const MessageGroups = styled.div<{}>` padding: ${(props) => props.theme.spacing}px; box-sizing: border-box; @@ -157,6 +177,8 @@ export const MessageGroups = styled.div<{}>` // MessageGroup +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const MessageGroup = styled.div<{}>` display: flex; flex-direction: column; @@ -207,6 +229,8 @@ export const Message = styled.div<{ type: "user" | "bot" }>` } `; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const FailureMessage = styled.p<{}>` text-align: center; flex-wrap: wrap; @@ -226,6 +250,8 @@ export const FailureMessage = styled.p<{}>` // MessageBody +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const MessageBody = styled.p<{}>` margin: 0; font-size: ${constants.fontSize}px; @@ -246,6 +272,8 @@ export const MessageBody = styled.p<{}>` // Bottom +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const Bottom = styled.div<{}>` height: ${constants.bottomHeight}px; position: relative; @@ -262,6 +290,8 @@ export const IconButton = styled.button<{ disabled?: boolean }>` padding: 8px; font-size: ${constants.fontSize}px; ${(props) => + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions props.disabled ? ` opacity: 0.6; @@ -291,6 +321,8 @@ export const IconButton = styled.button<{ disabled?: boolean }>` ${hoverBg} `; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const BottomButtonsContainer = styled.div<{}>` position: absolute; top: 50%; @@ -298,6 +330,8 @@ export const BottomButtonsContainer = styled.div<{}>` transform: translate3d(0, -50%, 0); `; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const Input = styled.input<{}>` display: block; flex: 1; @@ -314,6 +348,8 @@ export const Input = styled.input<{}>` } `; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const Pin = styled.button<{}>` position: fixed; background-color: ${(props) => props.theme.primaryColor}; @@ -415,6 +451,8 @@ export const PinBubbleContainer = styled.div<{ // PinBubbleButton +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const PinBubbleButton = styled.button<{}>` width: 32px; height: 32px; @@ -446,6 +484,8 @@ export const PinBubbleButton = styled.button<{}>` // ChoicesContainer +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const ChoicesContainer = styled.div<{}>` margin-top: 10px; margin-bottom: -6px; @@ -472,6 +512,8 @@ export const ChoiceButton = styled.button<{ color: ${props.theme.white}; `} ${(props) => + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions props.selected ? ` outline: 2px solid ${props.theme.primaryColor}; @@ -483,6 +525,8 @@ export const ChoiceButton = styled.button<{ const hoverColor = tinycolor(props.theme.primaryColor) .brighten(5) .toRgbString(); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions return props.disabled ? ` opacity: 0.5; @@ -530,6 +574,8 @@ export const ChoiceButton = styled.button<{ // TitleBar +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const TitleBar = styled.div<{}>` height: ${constants.bottomHeight}px; padding: 0 ${(props) => 2 * props.theme.spacing}px; @@ -547,6 +593,8 @@ export const TitleBar = styled.div<{}>` // TitleContainer +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const TitleContainer = styled.div<{}>` display: flex; align-items: center; @@ -555,6 +603,8 @@ export const TitleContainer = styled.div<{}>` // Title +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const Title = styled.p<{}>` font-size: 16px; font-weight: bold; @@ -564,6 +614,8 @@ export const Title = styled.p<{}>` // DiscreteButton +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const DiscreteLink = styled.a<{}>` color: ${(props) => props.theme.white}; border: 0; @@ -592,6 +644,8 @@ export const DiscreteLink = styled.a<{}>` // TitleIcon +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const TitleIcon = styled.img<{}>` width: 22px; height: 22px; diff --git a/packages/chat-widget/src/ui/constants.ts b/packages/chat-widget/src/ui/constants.ts index f6b8ba42..165fa246 100644 --- a/packages/chat-widget/src/ui/constants.ts +++ b/packages/chat-widget/src/ui/constants.ts @@ -1,4 +1,4 @@ -import { Theme } from "../theme"; +import { type Theme } from "../theme"; export const bottomHeight = 60; diff --git a/packages/chat-widget/src/utils.ts b/packages/chat-widget/src/utils.ts index f80aab21..5fbe6cc9 100644 --- a/packages/chat-widget/src/utils.ts +++ b/packages/chat-widget/src/utils.ts @@ -14,6 +14,8 @@ const groupWhileHelper = ( const [head, ...tail] = list; const newGroup = (() => { const lastInGroup = currentGroup[currentGroup.length - 1]; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (!lastInGroup) { return false; } diff --git a/packages/eslint-config-nlx/documentation.js b/packages/eslint-config-nlx/documentation.js new file mode 100644 index 00000000..076ec2f6 --- /dev/null +++ b/packages/eslint-config-nlx/documentation.js @@ -0,0 +1,19 @@ +/** @type {import('eslint').Linter.Config } */ +module.exports = { + extends: ["plugin:jsdoc/recommended-typescript-error"], + plugins: ["eslint-plugin-tsdoc"], + rules: { + "jsdoc/require-jsdoc": [ + "error", + { + publicOnly: true, + // these ensure that documentation of various type script constructs is required. Will tweak as we go. + contexts: ["TSTypeAliasDeclaration","TSInterfaceDeclaration","TSMethodSignature","TSPropertySignature"] + } + ], + "jsdoc/check-tag-names": ["error", { definedTags: ["category", "hidden"] }], + "jsdoc/require-param": ["error", { checkDestructured: false }], + "jsdoc/check-param-names": ["error", { checkDestructured: false }], + "tsdoc/syntax": "error", + }, +}; diff --git a/packages/eslint-config-nlx/index.js b/packages/eslint-config-nlx/index.js new file mode 100644 index 00000000..47c5bc6c --- /dev/null +++ b/packages/eslint-config-nlx/index.js @@ -0,0 +1,36 @@ +/** @type {import('eslint').Linter.Config } */ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: [ + "standard-with-typescript", + "plugin:react/recommended", + "prettier" // note: order matters. prettier being last overrides standard-with-typescript formatting + ], + overrides: [ + { + env: { + node: true, + }, + files: [".eslintrc.{js,cjs}"], + parserOptions: { + sourceType: "script", + }, + }, + ], + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + }, + plugins: [ + "react" + ], + ignorePatterns: ["dist", "node_modules", "vite-env.d.ts"], + settings: { + react: { + version: "detect", + }, + }, +}; diff --git a/packages/eslint-config-nlx/package.json b/packages/eslint-config-nlx/package.json new file mode 100644 index 00000000..5418eef6 --- /dev/null +++ b/packages/eslint-config-nlx/package.json @@ -0,0 +1,18 @@ +{ + "name": "eslint-config-nlx", + "version": "0.0.0", + "private": true, + "peerDependencies": { + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-config-standard-with-typescript": "^43.0.1", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-react": "^7.33.2" + }, + "devDependencies": { + "eslint-plugin-jsdoc": "^48.2.1", + "eslint-plugin-tsdoc": "^0.2.17" + } +} diff --git a/packages/voice-compass/.eslintrc.cjs b/packages/voice-compass/.eslintrc.cjs new file mode 100644 index 00000000..9d09402d --- /dev/null +++ b/packages/voice-compass/.eslintrc.cjs @@ -0,0 +1,5 @@ +/** @type {import('eslint').Linter.Config } */ +module.exports = { + root: true, + extends: ["nlx", "nlx/documentation"], +}; diff --git a/packages/voice-compass/package.json b/packages/voice-compass/package.json index da0e9f10..b318b7a3 100644 --- a/packages/voice-compass/package.json +++ b/packages/voice-compass/package.json @@ -11,9 +11,12 @@ "scripts": { "build": "rm -rf lib && rollup -c", "docs": "typedoc", - "publish-docs": "npm run docs && concat-md --decrease-title-levels --dir-name-as-title docs/ > ../website/src/content/06-03-multimodal-api-reference.md", + "lint:check": "npx eslint src/ --ext .ts,.tsx,.js,.jsx", + "lint": "npx eslint src/ --ext .ts,.tsx,.js,.jsx --fix", "prepublish": "npm run build", - "test": "jest" + "publish-docs": "npm run docs && concat-md --decrease-title-levels --dir-name-as-title docs/ > ../website/src/content/06-03-multimodal-api-reference.md", + "test": "jest", + "tsc": "tsc" }, "author": "Peter Szerzo ", "license": "MIT", @@ -29,6 +32,7 @@ "@types/node": "^11.15.11", "@types/ramda": "0.29.1", "concat-md": "^0.5.1", + "eslint-config-nlx": "*", "fast-check": "^3.16.0", "jest": "^29.7.0", "prettier": "^3.0.3", @@ -37,6 +41,7 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typedoc": "^0.25.12", + "typedoc-plugin-markdown": "^3.17.1", "typescript": "^5.2.2" }, "dependencies": { diff --git a/packages/voice-compass/src/index.ts b/packages/voice-compass/src/index.ts index 78fc157a..9c81a713 100644 --- a/packages/voice-compass/src/index.ts +++ b/packages/voice-compass/src/index.ts @@ -2,7 +2,6 @@ import fetch from "isomorphic-fetch"; /** * The starting point of the package. Call create to create a `VoiceCompass` client. - * * @example * ```typescript * const client = nlxai.voiceCompass.create({ @@ -17,11 +16,8 @@ import fetch from "isomorphic-fetch"; * * client.sendStep("REPLACE_WITH_STEP_ID"); * ``` - * * @category Setup - * - * @param options - the configuration object - * + * @param options - configuration options for the client * @returns a Voice Compass client */ export const create = ({ @@ -33,47 +29,47 @@ export const create = ({ debug = false, dev = false, }: Config): Client => { - if (!conversationId) { + if (typeof conversationId !== "string" || conversationId.length === 0) { console.warn( 'No conversation ID provided. Please call the Voice Compass client `create` method with a `conversationId` field extracted from the URL. Example code: `new URLSearchParams(window.location.search).get("cid")`', ); } - const sendStep = (stepId: string, context?: Context) => { - if (!stepIdRegex.test(stepId)) { - throw new Error("Invalid stepId. It should be formatted as a UUID."); - } + return { + sendStep: async (stepId: string, context?: Context) => { + if (!stepIdRegex.test(stepId)) { + throw new Error("Invalid stepId. It should be formatted as a UUID."); + } - const payload = { - stepId, - context, - conversationId, - journeyId, - languageCode, - }; + const payload = { + stepId, + context, + conversationId, + journeyId, + languageCode, + }; - return fetch(`https://${dev ? "dev." : ""}mm.nlx.ai/v1/track`, { - method: "POST", - headers: { - "x-api-key": apiKey, - "x-nlx-id": workspaceId, - "Content-Type": "application/json", - }, - body: JSON.stringify(payload), - }) - .then(() => { - if (debug) { - console.info(`✓ step: ${stepId}`, payload); - } + await fetch(`https://${dev ? "dev." : ""}mm.nlx.ai/v1/track`, { + method: "POST", + headers: { + "x-api-key": apiKey, + "x-nlx-id": workspaceId, + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), }) - .catch((err: Error) => { - if (debug) { - console.error(`× step: ${stepId}`, err, payload); - } - throw err; - }); + .then(() => { + if (debug) { + console.info(`✓ step: ${stepId}`, payload); + } + }) + .catch((err: Error) => { + if (debug) { + console.error(`× step: ${stepId}`, err, payload); + } + throw err; + }); + }, }; - - return { sendStep }; }; /** @@ -83,7 +79,7 @@ export const create = ({ export interface Client { /** * - * * @example + * @example * ```typescript * const client = nlxai.voiceCompass.create({ * // hard-coded params @@ -102,7 +98,6 @@ export interface Client { * * * _Note: Must be a valid UUID_ - * * @param context - context to send back to the voice bot, for usage later in the intent. */ sendStep: (stepId: string, context?: Context) => Promise; @@ -119,7 +114,7 @@ export type Context = Record; * @category Setup */ export interface Config { - /** * the API key generated for the journey. **/ + /** * the API key generated for the journey. */ apiKey: string; /** the ID of the journey. */ journeyId: string; @@ -127,16 +122,18 @@ export interface Config { /** your workspace id */ workspaceId: string; - /** the conversation id, passed from the active voice bot. + /** + * the conversation id, passed from the active voice bot. * * _Note: This must be dynamically set by the voice bot._ - * */ + */ conversationId: string; - /** the user's language code. + /** + * the user's language code. * * In the browser may be fetched from `navigator.language`, or if the journey doesn't support multiple languages, can be hardcoded. - * */ + */ languageCode: string; /** set to true to help debug issues or errors. Defaults to false */ @@ -147,7 +144,8 @@ export interface Config { } /** - * @internal @hidden + * @internal + * @hidden * this is exported so we can test it. Should be equivalent to a UUID v4 regex. */ export const stepIdRegex = diff --git a/packages/voice-compass/tsdoc.json b/packages/voice-compass/tsdoc.json new file mode 100644 index 00000000..4358f3d0 --- /dev/null +++ b/packages/voice-compass/tsdoc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", + "extends": ["typedoc/tsdoc.json"], + "noStandardTags": false, + "tagDefinitions": [] +} diff --git a/packages/voice-compass/typedoc.cjs b/packages/voice-compass/typedoc.cjs index df927646..7247eb5c 100644 --- a/packages/voice-compass/typedoc.cjs +++ b/packages/voice-compass/typedoc.cjs @@ -7,6 +7,6 @@ module.exports = { treatWarningsAsErrors: true, treatValidationWarningsAsErrors: true, categoryOrder: ["Setup", "Client"], - plugin: ["typedoc-plugin-markdown"], + // plugin: ["typedoc-plugin-markdown"], readme: "none", }; diff --git a/packages/website/.eslintrc.js b/packages/website/.eslintrc.js new file mode 100644 index 00000000..5889268a --- /dev/null +++ b/packages/website/.eslintrc.js @@ -0,0 +1,5 @@ +/** @type {import('eslint').Linter.Config } */ +module.exports = { + root: true, + extends: ["nlx"], +}; diff --git a/packages/website/package.json b/packages/website/package.json index b1dca233..19ac5e6c 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -3,8 +3,11 @@ "private": true, "version": "0.1.0", "scripts": { + "build": "tsc && vite build", "dev": "vite --force", - "build": "tsc && vite build" + "lint:check": "npx eslint src/ --ext .ts,.tsx,.js,.jsx,.mdx", + "lint": "npx eslint src/ --ext .ts,.tsx,.js,.jsx,.mdx --fix", + "tsc": "tsc" }, "dependencies": { "@nlxai/chat-core": "*", @@ -35,6 +38,7 @@ "@vitejs/plugin-react": "^4.0.3", "autoprefixer": "^10.4.16", "esbuild": "^0.18.20", + "eslint-config-nlx": "*", "postcss": "^8.4.31", "tailwindcss": "^3.3.5", "typescript": "^5.0.2", diff --git a/packages/website/src/components/ChatConfiguration.tsx b/packages/website/src/components/ChatConfiguration.tsx index bcec0f13..794ca83b 100644 --- a/packages/website/src/components/ChatConfiguration.tsx +++ b/packages/website/src/components/ChatConfiguration.tsx @@ -7,8 +7,14 @@ import { RadioList } from "./RadioList"; export const getInitialConfig = (): Config => { const searchParams = new URLSearchParams(window.location.search); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing const botUrl = searchParams.get("botUrl") || ""; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing const apiKey = searchParams.get("apiKey") || ""; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing const languageCode = searchParams.get("languageCode") || "en-US"; return { botUrl, @@ -109,6 +115,8 @@ export const ThemeEditor: FC<{ value={theme.fontFamily} onChange={(ev: any) => { props.onChange({ + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions fontFamily: ev.target.value || defaultTheme.fontFamily, }); }} @@ -230,6 +238,8 @@ export const BehaviorEditor: FC<{ ); }; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export const saveTheme = (theme: Partial) => { localStorage.setItem("nlxchat-theme", JSON.stringify(theme)); }; @@ -237,6 +247,8 @@ export const saveTheme = (theme: Partial) => { export const retrieveTheme = (): Partial | null => { try { const themeString = localStorage.getItem("nlxchat-theme"); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing const theme = JSON.parse(themeString || ""); if (typeof theme !== "object") { throw new Error("Invalid theme"); @@ -247,6 +259,8 @@ export const retrieveTheme = (): Partial | null => { } }; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export const saveTitleBar = (theme: TitleBar) => { localStorage.setItem("nlxchat-titleBar", JSON.stringify(theme)); }; @@ -254,6 +268,8 @@ export const saveTitleBar = (theme: TitleBar) => { export const retrieveTitleBar = (): TitleBar | null => { try { const titleBarString = localStorage.getItem("nlxchat-titleBar"); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing const titleBar = JSON.parse(titleBarString || ""); if (typeof titleBar !== "object") { throw new Error("Invalid theme"); diff --git a/packages/website/src/components/CodeEditor.tsx b/packages/website/src/components/CodeEditor.tsx index e07254f7..4e13afe4 100644 --- a/packages/website/src/components/CodeEditor.tsx +++ b/packages/website/src/components/CodeEditor.tsx @@ -1,6 +1,8 @@ import { DownloadIcon } from "./Icons"; import React, { type FC, useRef, useEffect } from "react"; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type const escapeForHighlightJs = (str: string) => str .replace(/&/g, "&") diff --git a/packages/website/src/components/Hero.tsx b/packages/website/src/components/Hero.tsx index ce7d6f77..0d52e9fa 100644 --- a/packages/website/src/components/Hero.tsx +++ b/packages/website/src/components/Hero.tsx @@ -5,6 +5,8 @@ import { FeedbackForm } from "../custom-components/FeedbackForm"; import { Carousel, carouselExampleData } from "../custom-components/Carousel"; import { InlineWidget } from "../components/InlineWidget"; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export const Hero = () => { const location = useLocation(); const pathname = @@ -25,7 +27,7 @@ export const Hero = () => {

SDK for rich conversational experiences powered by{" "} - + NLX ® diff --git a/packages/website/src/components/Icons.tsx b/packages/website/src/components/Icons.tsx index bb4f1a62..9163a4ae 100644 --- a/packages/website/src/components/Icons.tsx +++ b/packages/website/src/components/Icons.tsx @@ -1,29 +1,39 @@ import React, { type FC } from "react"; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const CloseIcon: FC<{}> = () => ( ); +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const GitHubIcon: FC<{}> = () => ( ); +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const DownloadIcon: FC<{}> = () => ( ); +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const ContentCopyIcon: FC<{}> = () => ( ); +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const CheckIcon: FC<{}> = () => ( diff --git a/packages/website/src/components/InlineWidget.tsx b/packages/website/src/components/InlineWidget.tsx index c273090a..ae2c78bd 100644 --- a/packages/website/src/components/InlineWidget.tsx +++ b/packages/website/src/components/InlineWidget.tsx @@ -12,6 +12,8 @@ export type Item = | { type: "bot"; message: string } | { type: "custom"; element: ReactNode }; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types const Loader: FC<{}> = () => (

@@ -42,6 +44,8 @@ export const InlineWidget: FC<{ }; }, [setTick]); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions const displayedItems = props.animated ? props.items.slice(0, 1 + (tick % props.items.length)) : props.items; @@ -49,6 +53,8 @@ export const InlineWidget: FC<{ const loader: "user" | "bot" | undefined = displayedItems.length === props.items.length ? undefined + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing : last(last(displayedItems) || [])?.type === "user" ? "bot" : "user"; @@ -65,6 +71,8 @@ export const InlineWidget: FC<{ const firstContentNode: Node | undefined = addedNodes[0]; if ( isFullyVisible.current && + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions firstContentNode && firstContentNode instanceof HTMLElement ) { @@ -75,6 +83,8 @@ export const InlineWidget: FC<{ } }; const observer = new MutationObserver(callback); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (messagesContainer.current) { observer.observe(messagesContainer.current, { childList: true, @@ -88,6 +98,8 @@ export const InlineWidget: FC<{ useEffect(() => { const observer = new IntersectionObserver( (event) => { + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (event[0]) { isFullyVisible.current = event[0].intersectionRatio > 0.95; } @@ -96,6 +108,8 @@ export const InlineWidget: FC<{ threshold: 0.95, }, ); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (messagesContainer.current) { observer.observe(messagesContainer.current); } @@ -107,6 +121,8 @@ export const InlineWidget: FC<{ return (
@@ -120,11 +136,15 @@ export const InlineWidget: FC<{ {displayedItems.map((items: Item[], index: number) => { return (
+ {/* initial eslint integration */} + {/* eslint-disable-next-line array-callback-return */} {items.map((item, itemIndex) => { if (item.type === "user") { return (
); })} + {/* initial eslint integration */} + {/* eslint-disable-next-line @typescript-eslint/strict-boolean-expressions */} {loader && (loader === "user" ? (
= ({ size }) => ( { const searchParams = new URLSearchParams(window.location.search); + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing const workspaceId = searchParams.get("workspaceId") || ""; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing const apiKey = searchParams.get("apiKey") || ""; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing const languageCode = searchParams.get("languageCode") || "en-US"; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing const journeyId = searchParams.get("journeyId") || ""; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing const conversationId = searchParams.get("conversationId") || ""; + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing const testStepId = searchParams.get("testStepId") || ""; return { workspaceId, diff --git a/packages/website/src/components/Nav.tsx b/packages/website/src/components/Nav.tsx index 97313cc4..4736a881 100644 --- a/packages/website/src/components/Nav.tsx +++ b/packages/website/src/components/Nav.tsx @@ -6,7 +6,7 @@ import { Logo } from "./Logo"; const MenuListItem: FC<{ heading: string; - items: { label: string; url: string }[]; + items: Array<{ label: string; url: string }>; }> = (props) => { const location = useLocation(); const pathname = @@ -42,6 +42,8 @@ const MenuListItem: FC<{ ); }; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types export const Nav: FC<{}> = () => (
@@ -146,6 +148,8 @@ export const MobileNav: FC<{
, + // initial eslint integration + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion document.querySelector("#portal")!, ); }; diff --git a/packages/website/src/components/NextPrevPage.tsx b/packages/website/src/components/NextPrevPage.tsx index e9827b91..17985d37 100644 --- a/packages/website/src/components/NextPrevPage.tsx +++ b/packages/website/src/components/NextPrevPage.tsx @@ -1,6 +1,8 @@ import React, { type FC } from "react"; import { Link } from "react-router-dom"; +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types const PrevArrow: FC<{}> = () => ( = () => ( ); +// initial eslint integration +// eslint-disable-next-line @typescript-eslint/ban-types const NextArrow: FC<{}> = () => ( = ( props, ) => (
+ {/* initial eslint integration */} + {/* eslint-disable-next-line @typescript-eslint/strict-boolean-expressions */} {props.prev && (
@@ -46,6 +52,8 @@ export const NextPrevPage: FC<{ prev?: LinkData; next?: LinkData }> = (
)} + {/* initial eslint integration */} + {/* eslint-disable-next-line @typescript-eslint/strict-boolean-expressions */} {props.next && (
Next
diff --git a/packages/website/src/components/PageContent.tsx b/packages/website/src/components/PageContent.tsx index 9d7df00b..63b920a1 100644 --- a/packages/website/src/components/PageContent.tsx +++ b/packages/website/src/components/PageContent.tsx @@ -15,10 +15,14 @@ const CopyToClipboardButton: FC<{ text: string; className?: string }> = ({ return (