mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 20:17:29 +08:00
This commit is contained in:
parent
504138bb23
commit
2a3cc2951b
@ -45,6 +45,13 @@ type ChatInputAreaProps = {
|
|||||||
theme?: Theme | null
|
theme?: Theme | null
|
||||||
isResponding?: boolean
|
isResponding?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
/**
|
||||||
|
* Controls whether pressing Enter sends the message.
|
||||||
|
* - true (default): Enter sends, Shift+Enter inserts newline
|
||||||
|
* - false: Enter inserts newline, Shift+Enter sends
|
||||||
|
* Useful for CJK (Japanese/Korean/Chinese) IME users who expect Enter to insert newlines.
|
||||||
|
*/
|
||||||
|
sendOnEnter?: boolean
|
||||||
}
|
}
|
||||||
const ChatInputArea = ({
|
const ChatInputArea = ({
|
||||||
readonly,
|
readonly,
|
||||||
@ -61,6 +68,7 @@ const ChatInputArea = ({
|
|||||||
theme,
|
theme,
|
||||||
isResponding,
|
isResponding,
|
||||||
disabled,
|
disabled,
|
||||||
|
sendOnEnter = true,
|
||||||
}: ChatInputAreaProps) => {
|
}: ChatInputAreaProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { notify } = useToastContext()
|
const { notify } = useToastContext()
|
||||||
@ -131,7 +139,14 @@ const ChatInputArea = ({
|
|||||||
}, 50)
|
}, 50)
|
||||||
}
|
}
|
||||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {
|
// Determine if this key combo should trigger send:
|
||||||
|
// sendOnEnter=true (default): Enter sends, Shift+Enter inserts newline
|
||||||
|
// sendOnEnter=false: Shift+Enter sends, Enter inserts newline
|
||||||
|
const isSendCombo = sendOnEnter
|
||||||
|
? (e.key === 'Enter' && !e.shiftKey)
|
||||||
|
: (e.key === 'Enter' && e.shiftKey)
|
||||||
|
|
||||||
|
if (isSendCombo && !e.nativeEvent.isComposing) {
|
||||||
// if isComposing, exit
|
// if isComposing, exit
|
||||||
if (isComposingRef.current)
|
if (isComposingRef.current)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -75,6 +75,7 @@ export type ChatProps = {
|
|||||||
inputDisabled?: boolean
|
inputDisabled?: boolean
|
||||||
sidebarCollapseState?: boolean
|
sidebarCollapseState?: boolean
|
||||||
hideAvatar?: boolean
|
hideAvatar?: boolean
|
||||||
|
sendOnEnter?: boolean
|
||||||
onHumanInputFormSubmit?: (formToken: string, formData: any) => Promise<void>
|
onHumanInputFormSubmit?: (formToken: string, formData: any) => Promise<void>
|
||||||
getHumanInputNodeData?: (nodeID: string) => any
|
getHumanInputNodeData?: (nodeID: string) => any
|
||||||
}
|
}
|
||||||
@ -119,6 +120,7 @@ const Chat: FC<ChatProps> = ({
|
|||||||
inputDisabled,
|
inputDisabled,
|
||||||
sidebarCollapseState,
|
sidebarCollapseState,
|
||||||
hideAvatar,
|
hideAvatar,
|
||||||
|
sendOnEnter,
|
||||||
onHumanInputFormSubmit,
|
onHumanInputFormSubmit,
|
||||||
getHumanInputNodeData,
|
getHumanInputNodeData,
|
||||||
}) => {
|
}) => {
|
||||||
@ -363,6 +365,7 @@ const Chat: FC<ChatProps> = ({
|
|||||||
theme={themeBuilder?.theme}
|
theme={themeBuilder?.theme}
|
||||||
isResponding={isResponding}
|
isResponding={isResponding}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
|
sendOnEnter={sendOnEnter}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,6 +58,15 @@ const ChatWrapper = () => {
|
|||||||
appSourceType,
|
appSourceType,
|
||||||
} = useEmbeddedChatbotContext()
|
} = useEmbeddedChatbotContext()
|
||||||
|
|
||||||
|
// Read sendOnEnter from URL params (e.g., ?sendOnEnter=false)
|
||||||
|
const sendOnEnter = useMemo(() => {
|
||||||
|
if (typeof window === 'undefined')
|
||||||
|
return true
|
||||||
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
|
const param = urlParams.get('sendOnEnter')
|
||||||
|
return param !== 'false'
|
||||||
|
}, [])
|
||||||
|
|
||||||
const appConfig = useMemo(() => {
|
const appConfig = useMemo(() => {
|
||||||
const config = appParams || {}
|
const config = appParams || {}
|
||||||
|
|
||||||
@ -321,6 +330,7 @@ const ChatWrapper = () => {
|
|||||||
themeBuilder={themeBuilder}
|
themeBuilder={themeBuilder}
|
||||||
switchSibling={doSwitchSibling}
|
switchSibling={doSwitchSibling}
|
||||||
inputDisabled={inputDisabled}
|
inputDisabled={inputDisabled}
|
||||||
|
sendOnEnter={sendOnEnter}
|
||||||
questionIcon={
|
questionIcon={
|
||||||
initUserVariables?.avatar_url
|
initUserVariables?.avatar_url
|
||||||
? (
|
? (
|
||||||
|
|||||||
@ -135,6 +135,11 @@
|
|||||||
config.baseUrl || `https://${config.isDev ? "dev." : ""}udify.app`;
|
config.baseUrl || `https://${config.isDev ? "dev." : ""}udify.app`;
|
||||||
const targetOrigin = new URL(baseUrl).origin;
|
const targetOrigin = new URL(baseUrl).origin;
|
||||||
|
|
||||||
|
// Pass sendOnEnter config as URL parameter
|
||||||
|
if (config.sendOnEnter === false) {
|
||||||
|
params.set('sendOnEnter', 'false');
|
||||||
|
}
|
||||||
|
|
||||||
// pre-check the length of the URL
|
// pre-check the length of the URL
|
||||||
const iframeUrl = `${baseUrl}/chatbot/${config.token}?${params}`;
|
const iframeUrl = `${baseUrl}/chatbot/${config.token}?${params}`;
|
||||||
// 1) CREATE the iframe immediately, so it can load in the background:
|
// 1) CREATE the iframe immediately, so it can load in the background:
|
||||||
|
|||||||
2
web/public/embed.min.js
vendored
2
web/public/embed.min.js
vendored
@ -48,7 +48,7 @@
|
|||||||
transition-property: width, height;
|
transition-property: width, height;
|
||||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
transition-duration: 150ms;
|
transition-duration: 150ms;
|
||||||
`;async function embedChatbot(){let isDragging=false;if(!config||!config.token){console.error(`${configKey} is empty or token is not provided`);return}async function compressAndEncodeBase64(input){const uint8Array=(new TextEncoder).encode(input);const compressedStream=new Response(new Blob([uint8Array]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer();const compressedUint8Array=new Uint8Array(await compressedStream);return btoa(String.fromCharCode(...compressedUint8Array))}async function getCompressedInputsFromConfig(){const inputs=config?.inputs||{};const compressedInputs={};await Promise.all(Object.entries(inputs).map(async([key,value])=>{compressedInputs[key]=await compressAndEncodeBase64(value)}));return compressedInputs}async function getCompressedSystemVariablesFromConfig(){const systemVariables=config?.systemVariables||{};const compressedSystemVariables={};await Promise.all(Object.entries(systemVariables).map(async([key,value])=>{compressedSystemVariables[`sys.${key}`]=await compressAndEncodeBase64(value)}));return compressedSystemVariables}async function getCompressedUserVariablesFromConfig(){const userVariables=config?.userVariables||{};const compressedUserVariables={};await Promise.all(Object.entries(userVariables).map(async([key,value])=>{compressedUserVariables[`user.${key}`]=await compressAndEncodeBase64(value)}));return compressedUserVariables}const params=new URLSearchParams({...await getCompressedInputsFromConfig(),...await getCompressedSystemVariablesFromConfig(),...await getCompressedUserVariablesFromConfig()});const baseUrl=config.baseUrl||`https://${config.isDev?"dev.":""}udify.app`;const targetOrigin=new URL(baseUrl).origin;const iframeUrl=`${baseUrl}/chatbot/${config.token}?${params}`;const preloadedIframe=createIframe();preloadedIframe.style.display="none";document.body.appendChild(preloadedIframe);if(iframeUrl.length>2048){console.error("The URL is too long, please reduce the number of inputs to prevent the bot from failing to load")}function createIframe(){const iframe=document.createElement("iframe");iframe.allow="fullscreen;microphone";iframe.title="dify chatbot bubble window";iframe.id=iframeId;iframe.src=iframeUrl;iframe.style.cssText=originalIframeStyleText;return iframe}function resetIframePosition(){if(window.innerWidth<=640)return;const targetIframe=document.getElementById(iframeId);const targetButton=document.getElementById(buttonId);if(targetIframe&&targetButton){const buttonRect=targetButton.getBoundingClientRect();const viewportCenterY=window.innerHeight/2;const buttonCenterY=buttonRect.top+buttonRect.height/2;if(buttonCenterY<viewportCenterY){targetIframe.style.top=`var(--${buttonId}-bottom, 1rem)`;targetIframe.style.bottom="unset"}else{targetIframe.style.bottom=`var(--${buttonId}-bottom, 1rem)`;targetIframe.style.top="unset"}const viewportCenterX=window.innerWidth/2;const buttonCenterX=buttonRect.left+buttonRect.width/2;if(buttonCenterX<viewportCenterX){targetIframe.style.left=`var(--${buttonId}-right, 1rem)`;targetIframe.style.right="unset"}else{targetIframe.style.right=`var(--${buttonId}-right, 1rem)`;targetIframe.style.left="unset"}}}function toggleExpand(){isExpanded=!isExpanded;const targetIframe=document.getElementById(iframeId);if(!targetIframe)return;if(isExpanded){targetIframe.style.cssText=expandedIframeStyleText}else{targetIframe.style.cssText=originalIframeStyleText}resetIframePosition()}window.addEventListener("message",event=>{if(event.origin!==targetOrigin)return;const targetIframe=document.getElementById(iframeId);if(!targetIframe||event.source!==targetIframe.contentWindow)return;if(event.data.type==="dify-chatbot-iframe-ready"){targetIframe.contentWindow?.postMessage({type:"dify-chatbot-config",payload:{isToggledByButton:true,isDraggable:!!config.draggable}},targetOrigin)}if(event.data.type==="dify-chatbot-expand-change"){toggleExpand()}});function createButton(){const containerDiv=document.createElement("div");Object.entries(config.containerProps||{}).forEach(([key,value])=>{if(key==="className"){containerDiv.classList.add(...value.split(" "))}else if(key==="style"){if(typeof value==="object"){Object.assign(containerDiv.style,value)}else{containerDiv.style.cssText=value}}else if(typeof value==="function"){containerDiv.addEventListener(key.replace(/^on/,"").toLowerCase(),value)}else{containerDiv[key]=value}});containerDiv.id=buttonId;const styleSheet=document.createElement("style");document.head.appendChild(styleSheet);styleSheet.sheet.insertRule(`
|
`;async function embedChatbot(){let isDragging=false;if(!config||!config.token){console.error(`${configKey} is empty or token is not provided`);return}async function compressAndEncodeBase64(input){const uint8Array=(new TextEncoder).encode(input);const compressedStream=new Response(new Blob([uint8Array]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer();const compressedUint8Array=new Uint8Array(await compressedStream);return btoa(String.fromCharCode(...compressedUint8Array))}async function getCompressedInputsFromConfig(){const inputs=config?.inputs||{};const compressedInputs={};await Promise.all(Object.entries(inputs).map(async([key,value])=>{compressedInputs[key]=await compressAndEncodeBase64(value)}));return compressedInputs}async function getCompressedSystemVariablesFromConfig(){const systemVariables=config?.systemVariables||{};const compressedSystemVariables={};await Promise.all(Object.entries(systemVariables).map(async([key,value])=>{compressedSystemVariables[`sys.${key}`]=await compressAndEncodeBase64(value)}));return compressedSystemVariables}async function getCompressedUserVariablesFromConfig(){const userVariables=config?.userVariables||{};const compressedUserVariables={};await Promise.all(Object.entries(userVariables).map(async([key,value])=>{compressedUserVariables[`user.${key}`]=await compressAndEncodeBase64(value)}));return compressedUserVariables}const params=new URLSearchParams({...await getCompressedInputsFromConfig(),...await getCompressedSystemVariablesFromConfig(),...await getCompressedUserVariablesFromConfig()});const baseUrl=config.baseUrl||`https://${config.isDev?"dev.":""}udify.app`;const targetOrigin=new URL(baseUrl).origin;if(config.sendOnEnter===false){params.set("sendOnEnter","false")}const iframeUrl=`${baseUrl}/chatbot/${config.token}?${params}`;const preloadedIframe=createIframe();preloadedIframe.style.display="none";document.body.appendChild(preloadedIframe);if(iframeUrl.length>2048){console.error("The URL is too long, please reduce the number of inputs to prevent the bot from failing to load")}function createIframe(){const iframe=document.createElement("iframe");iframe.allow="fullscreen;microphone";iframe.title="dify chatbot bubble window";iframe.id=iframeId;iframe.src=iframeUrl;iframe.style.cssText=originalIframeStyleText;return iframe}function resetIframePosition(){if(window.innerWidth<=640)return;const targetIframe=document.getElementById(iframeId);const targetButton=document.getElementById(buttonId);if(targetIframe&&targetButton){const buttonRect=targetButton.getBoundingClientRect();const viewportCenterY=window.innerHeight/2;const buttonCenterY=buttonRect.top+buttonRect.height/2;if(buttonCenterY<viewportCenterY){targetIframe.style.top=`var(--${buttonId}-bottom, 1rem)`;targetIframe.style.bottom="unset"}else{targetIframe.style.bottom=`var(--${buttonId}-bottom, 1rem)`;targetIframe.style.top="unset"}const viewportCenterX=window.innerWidth/2;const buttonCenterX=buttonRect.left+buttonRect.width/2;if(buttonCenterX<viewportCenterX){targetIframe.style.left=`var(--${buttonId}-right, 1rem)`;targetIframe.style.right="unset"}else{targetIframe.style.right=`var(--${buttonId}-right, 1rem)`;targetIframe.style.left="unset"}}}function toggleExpand(){isExpanded=!isExpanded;const targetIframe=document.getElementById(iframeId);if(!targetIframe)return;if(isExpanded){targetIframe.style.cssText=expandedIframeStyleText}else{targetIframe.style.cssText=originalIframeStyleText}resetIframePosition()}window.addEventListener("message",event=>{if(event.origin!==targetOrigin)return;const targetIframe=document.getElementById(iframeId);if(!targetIframe||event.source!==targetIframe.contentWindow)return;if(event.data.type==="dify-chatbot-iframe-ready"){targetIframe.contentWindow?.postMessage({type:"dify-chatbot-config",payload:{isToggledByButton:true,isDraggable:!!config.draggable}},targetOrigin)}if(event.data.type==="dify-chatbot-expand-change"){toggleExpand()}});function createButton(){const containerDiv=document.createElement("div");Object.entries(config.containerProps||{}).forEach(([key,value])=>{if(key==="className"){containerDiv.classList.add(...value.split(" "))}else if(key==="style"){if(typeof value==="object"){Object.assign(containerDiv.style,value)}else{containerDiv.style.cssText=value}}else if(typeof value==="function"){containerDiv.addEventListener(key.replace(/^on/,"").toLowerCase(),value)}else{containerDiv[key]=value}});containerDiv.id=buttonId;const styleSheet=document.createElement("style");document.head.appendChild(styleSheet);styleSheet.sheet.insertRule(`
|
||||||
#${containerDiv.id} {
|
#${containerDiv.id} {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: var(--${containerDiv.id}-bottom, 1rem);
|
bottom: var(--${containerDiv.id}-bottom, 1rem);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user