Skip to content

Commit

Permalink
fix: allow custom css stylesheets (CT-000) (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
DecathectZero authored Oct 26, 2023
1 parent db420fb commit 7062e0a
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 33 deletions.
4 changes: 2 additions & 2 deletions packages/react-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
},
"dependencies": {
"@stitches/react": "^1.2.8",
"@voiceflow/base-types": "2.90.1",
"@voiceflow/base-types": "2.96.0",
"@voiceflow/sdk-runtime": "1.7.0",
"@voiceflow/slate-serializer": "1.5.5",
"@voiceflow/voiceflow-types": "3.24.0",
"@voiceflow/voiceflow-types": "3.26.9",
"bowser": "^2.11.0",
"chroma-js": "2.4.2",
"clsx": "1.2.1",
Expand Down
3 changes: 2 additions & 1 deletion packages/react-chat/src/browser/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const DEFAULT_ASSISTANT: Assistant = {

const sanitizeAssistant = (assistant: unknown): PartialDeep<Assistant> => {
const ref = isObject(assistant) ? assistant : {};
const { title, watermark, description, image, launcher, avatar, spacing, color, position, persistence, feedback } = ref;
const { title, watermark, description, image, launcher, avatar, spacing, color, position, persistence, feedback, stylesheet } = ref;

return {
...(typeof title === 'string' && { title }),
Expand All @@ -33,6 +33,7 @@ const sanitizeAssistant = (assistant: unknown): PartialDeep<Assistant> => {
...(typeof launcher === 'string' && { launcher }),
...(typeof watermark === 'boolean' && { watermark }),
...(typeof feedback === 'boolean' && { feedback }),
...(typeof stylesheet === 'string' && { stylesheet }),
...(typeof description === 'string' && { description }),
...(isEnumValue(position, ChatPosition) && { position }),
...(isEnumValue(persistence, ChatPersistence) && { persistence }),
Expand Down
2 changes: 1 addition & 1 deletion packages/react-chat/src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface SessionOptions {
status?: SessionStatus;
}

export type Assistant = Omit<ChatPublishing & Required<Omit<ChatPublishing, 'launcher'>>, 'selectedIntents'>;
export type Assistant = Omit<ChatPublishing & Required<Omit<ChatPublishing, 'launcher' | 'stylesheet'>>, 'selectedIntents'>;

export interface ChatConfig extends RuntimeOptions<PublicVerify> {
assistant?: Assistant;
Expand Down
44 changes: 44 additions & 0 deletions packages/react-chat/src/utils/stylesheet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint-disable no-console */
import { useEffect, useState } from 'react';

import { Assistant } from '@/common';

// used to add stylesheets dynamically, resolves when loaded
export const addStyleSheetURL = async (url: string) => {
const link = document.createElement('link');
const load = new Promise((resolve, reject) => {
link.onload = resolve;
link.onerror = reject;
});

link.rel = 'stylesheet';
link.href = url;
document.head.appendChild(link);

await load;
};

// do not load until stylesheet is resolved
export const useResolveAssistantStyleSheet = (assistant?: Assistant): boolean => {
const [isStyleSheetResolved, setStyleSheetResolved] = useState(false);

useEffect(() => {
if (!assistant || isStyleSheetResolved) return;

if (!assistant.stylesheet) {
setStyleSheetResolved(true);
return;
}

// inject stylesheet url
(async () => {
await addStyleSheetURL(assistant.stylesheet!).catch((error) => {
console.error(`failed to load stylesheet: ${assistant.stylesheet}`);
console.error(error);
});
setStyleSheetResolved(true);
})();
}, [assistant]);

return isStyleSheetResolved;
};
5 changes: 4 additions & 1 deletion packages/react-chat/src/views/ChatWidget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import { Assistant, ChatPosition, isObject, Listeners, PostMessage, useTheme } from '@/common';
import Launcher from '@/components/Launcher';
import { noop } from '@/utils/functional';
import { useResolveAssistantStyleSheet } from '@/utils/stylesheet';

import { ChatContainer, Container, LauncherContainer } from './styled';
import { ChatAPI } from './types';
Expand Down Expand Up @@ -56,9 +57,11 @@ const ChatWidget: React.FC<ChatWidgetProps> = ({ children, chatAPI, sendMessage,
const side = assistant?.position ?? ChatPosition.RIGHT;
const position = { bottom: assistant?.spacing.bottom, [side]: assistant?.spacing.side };

const isStyleSheetResolved = useResolveAssistantStyleSheet(assistant);

return (
<Container withChat={isOpen} isHidden={isHidden} className={theme}>
{!!assistant && (
{!!assistant && isStyleSheetResolved && (
<LauncherContainer style={position}>
<Launcher onClick={open} image={assistant.launcher} />
</LauncherContainer>
Expand Down
4 changes: 4 additions & 0 deletions packages/react-chat/src/views/ChatWindow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Chat, SystemResponse, UserResponse } from '@/components';
import { RuntimeAPIProvider } from '@/contexts';
import { FeedbackName, useRuntime } from '@/hooks';
import { TurnType, UserTurnProps } from '@/types';
import { useResolveAssistantStyleSheet } from '@/utils/stylesheet';

import { ChatWindowContainer } from './styled';
import { sendMessage } from './utils';
Expand Down Expand Up @@ -54,6 +55,9 @@ const ChatWindow: React.FC<ChatConfig & { assistant: Assistant; session: Session
[runtime.session.turns]
);

const isStyleSheetResolved = useResolveAssistantStyleSheet(assistant);
if (!isStyleSheetResolved) return null;

return (
<RuntimeAPIProvider {...runtime}>
<ChatWindowContainer className={theme}>
Expand Down
3 changes: 2 additions & 1 deletion packages/widget/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const validateVerify = (verify: unknown): verify is ChatConfig['verify'] => {

export const sanitizeConfig = (config: unknown): Partial<ChatConfig> & Pick<ChatConfig, 'verify'> => {
const ref = isObject(config) ? config : {};
const { url, user, userID, versionID, verify } = ref;
const { url, user, userID, versionID, verify, assistant } = ref;

if (!validateVerify(verify)) {
throw new Error('no projectID on load');
Expand All @@ -26,5 +26,6 @@ export const sanitizeConfig = (config: unknown): Partial<ChatConfig> & Pick<Chat
...(typeof user.image === 'string' && { image: user.image }),
},
}),
...(isObject(assistant) && ({ assistant } as Partial<Pick<ChatConfig, 'assistant'>>)),
};
};
2 changes: 1 addition & 1 deletion packages/widget/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface WidgetProps extends React.PropsWithChildren, RuntimeOptions<PublicVeri
const Widget: React.FC<WidgetProps> = ({ children, widgetURL, ...config }) => {
/** initialization */
const chatRef = useRef<HTMLIFrameElement>(null);
const [assistant, setAssistant, assistantRef] = useStateRef<Assistant | undefined>(config.assistant);
const [assistant, setAssistant, assistantRef] = useStateRef<Assistant | undefined>();

const sendMessage = useSendMessage(chatRef, widgetURL);
const onLoad = useCallback(() => sendMessage({ type: PostMessage.Type.FETCH_ASSISTANT, payload: config }), [config]);
Expand Down
52 changes: 26 additions & 26 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5208,7 +5208,7 @@ __metadata:
languageName: node
linkType: hard

"@voiceflow/base-types@npm:2.85.0, @voiceflow/base-types@npm:^2.85.0":
"@voiceflow/base-types@npm:2.85.0":
version: 2.85.0
resolution: "@voiceflow/base-types@npm:2.85.0"
dependencies:
Expand All @@ -5218,23 +5218,23 @@ __metadata:
languageName: node
linkType: hard

"@voiceflow/base-types@npm:2.90.1":
version: 2.90.1
resolution: "@voiceflow/base-types@npm:2.90.1"
"@voiceflow/base-types@npm:2.96.0, @voiceflow/base-types@npm:^2.96.0":
version: 2.96.0
resolution: "@voiceflow/base-types@npm:2.96.0"
dependencies:
"@voiceflow/common": ^8.2.1
slate: 0.94.1
checksum: f086d2251470ab0a92c5bfa8b658637eea708c9fc81286f10db96f26ea043b9fe885c26eaf8633d0aad7e1da3399a5d21650da88c85bae1074fd1f0a78f2c243
checksum: 9a8450d0004a1b1957217a70d08c2bfd29c0f8873266c274cb1084c7ecf3fb2716593b28dd9a6ed68950bb7185b257bfb6145eb887f4ce6997e8ca725ac83566
languageName: node
linkType: hard

"@voiceflow/chat-types@npm:^2.13.49":
version: 2.13.49
resolution: "@voiceflow/chat-types@npm:2.13.49"
"@voiceflow/chat-types@npm:^2.13.72":
version: 2.13.72
resolution: "@voiceflow/chat-types@npm:2.13.72"
dependencies:
"@voiceflow/base-types": ^2.85.0
"@voiceflow/common": ^8.2.0
checksum: 01d869ad06e0372b1e1e14357a347d9299262da7bac0cbb035ede1e7c08b50d515a76d11b53d7733b235ed089828b33b71af4ab39818bcbb02e6cd081e562b82
"@voiceflow/base-types": ^2.96.0
"@voiceflow/common": ^8.2.1
checksum: 2604ae4cc863dd8e8b655ec43c5746595d59c08200d9ecacd748e27a29b31c38803e70c2b45fb0e7074e66b404e3a03688596a8381ca5ea58d71a502d780c445
languageName: node
linkType: hard

Expand Down Expand Up @@ -5364,13 +5364,13 @@ __metadata:
"@types/react-dom": ^18.0.6
"@vitejs/plugin-react": ^2.0.1
"@vitest/coverage-c8": ^0.23.1
"@voiceflow/base-types": 2.90.1
"@voiceflow/base-types": 2.96.0
"@voiceflow/eslint-config": 6.1.0
"@voiceflow/prettier-config": 1.2.1
"@voiceflow/sdk-runtime": 1.7.0
"@voiceflow/slate-serializer": 1.5.5
"@voiceflow/tsconfig": 1.4.8
"@voiceflow/voiceflow-types": 3.24.0
"@voiceflow/voiceflow-types": 3.26.9
bowser: ^2.11.0
chroma-js: 2.4.2
clsx: 1.2.1
Expand Down Expand Up @@ -5446,24 +5446,24 @@ __metadata:
languageName: node
linkType: hard

"@voiceflow/voice-types@npm:^2.9.30":
version: 2.9.30
resolution: "@voiceflow/voice-types@npm:2.9.30"
"@voiceflow/voice-types@npm:^2.9.53":
version: 2.9.53
resolution: "@voiceflow/voice-types@npm:2.9.53"
dependencies:
"@voiceflow/base-types": ^2.85.0
"@voiceflow/common": ^8.2.0
checksum: f785ca476f5bf606dfac2544587d5c4746418abb62fea6cd4257776045baf15232cea51752d706a29dcf7447edca1a3f1247dc4544a7c4b0cede9554520526d1
"@voiceflow/base-types": ^2.96.0
"@voiceflow/common": ^8.2.1
checksum: 1e3109b65ad8fece69c5a4b957d7c9f5cc5789a8ca24a5807f401992aeba753cb188687e76f70460a331e21759ab2e085c8f5ade9b3a6c931d5be3d6977bd441
languageName: node
linkType: hard

"@voiceflow/voiceflow-types@npm:3.24.0":
version: 3.24.0
resolution: "@voiceflow/voiceflow-types@npm:3.24.0"
"@voiceflow/voiceflow-types@npm:3.26.9":
version: 3.26.9
resolution: "@voiceflow/voiceflow-types@npm:3.26.9"
dependencies:
"@voiceflow/base-types": ^2.85.0
"@voiceflow/chat-types": ^2.13.49
"@voiceflow/voice-types": ^2.9.30
checksum: b496565aeb6ecf1c945d059adf9345595f49a990f5b7c753537fdc4f967341dd22acc5e16397965fb62d5747382b159e2f771c305f28d82a0870f274934ae94a
"@voiceflow/base-types": ^2.96.0
"@voiceflow/chat-types": ^2.13.72
"@voiceflow/voice-types": ^2.9.53
checksum: 3b13dfeaf39d241d4c19b23eebf9548f8966021302d8e1f932766296375711465d4c416fe8962f9e5cc564743bb7bc2714328485d2e88b65b72499d3c4f98cf9
languageName: node
linkType: hard

Expand Down

0 comments on commit 7062e0a

Please sign in to comment.