Skip to content

Commit

Permalink
Perform AI On Selection In Extension, Allow Editing Of Query Content (#…
Browse files Browse the repository at this point in the history
…412)

#412

* Allow selection editing, Perform AI actions on selection in extension

* Remove logs

* changeset added

* Added ID in AI Preview
  • Loading branch information
dineshsingh1 authored Apr 17, 2023
1 parent 915be01 commit c89e5cd
Show file tree
Hide file tree
Showing 59 changed files with 770 additions and 294 deletions.
6 changes: 6 additions & 0 deletions .changeset/gentle-hairs-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'mexit': patch
'mexit-webapp': patch
---

AI-powered actions in extension, Editable query selection
41 changes: 41 additions & 0 deletions apps/extension/src/Components/AIPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useMemo } from 'react'

import { NodeEditorContent } from '@mexit/core'
import { AIPreview, useAIOptions } from '@mexit/shared'

import { generateEditorPluginsWithComponents } from '../Editor/plugins'
import { useSaveChanges } from '../Hooks/useSaveChanges'
import { getElementById } from '../Utils/cs-utils'

import components from './Editor/EditorPreviewComponents'

const AIPreviewContainer = () => {
const { appendAndSave } = useSaveChanges()
const { getAIMenuItems } = useAIOptions()

const handleOnInsert = (content: NodeEditorContent, nodeId: string) => {
appendAndSave({ nodeid: nodeId, content })
}

const plugins = useMemo(
() =>
generateEditorPluginsWithComponents(components, {
exclude: {
dnd: true
}
}),
[]
)

return (
<AIPreview
plugins={plugins}
insertInNote
onInsert={handleOnInsert}
getDefaultItems={getAIMenuItems}
root={getElementById('mexit-container')}
/>
)
}

export default AIPreviewContainer
2 changes: 1 addition & 1 deletion apps/extension/src/Components/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import React, { useEffect, useState } from 'react'
import { createPlateEditor, createPlateUI } from '@udecode/plate'

import { ELEMENT_TAG, getDefaultContent, NodeEditorContent, QuickLinkType, useContentStore } from '@mexit/core'
import { getDeserializeSelectionToNodes } from '@mexit/shared'

import { CopyTag } from '../../Editor/components/Tags/CopyTag'
import { generateEditorPluginsWithComponents } from '../../Editor/plugins/index'
import { useEditorStore } from '../../Hooks/useEditorStore'
import { useSnippets } from '../../Hooks/useSnippets'
import { useSputlitContext } from '../../Hooks/useSputlitContext'
import { useSputlitStore } from '../../Stores/useSputlitStore'
import { getDeserializeSelectionToNodes } from '../../Utils/deserialize'
import Results from '../Results'

import { StyledContent } from './styled'
Expand Down
31 changes: 30 additions & 1 deletion apps/extension/src/Components/InternalEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ import * as Sentry from '@sentry/react'
import mixpanel from 'mixpanel-browser'
import Highlighter from 'web-highlighter'

import { API_BASE_URLS, mog, useHighlightStore } from '@mexit/core'
import {
API_BASE_URLS,
FloatingElementType,
mog,
useFloatingStore,
useHighlightStore,
useHistoryStore
} from '@mexit/core'
import { getScrollbarWidth, isInputField } from '@mexit/shared'

import { useEditorStore } from '../Hooks/useEditorStore'
Expand Down Expand Up @@ -50,6 +57,8 @@ function useToggleHandler() {
const { previewMode, setPreviewMode } = useEditorStore()
const setTooltipState = useSputlitStore((s) => s.setHighlightTooltipState)
const resetSputlitState = useSputlitStore((s) => s.reset)
const addAIEvent = useHistoryStore((store) => store.addInitialEvent)
const setFloatingElement = useFloatingStore((s) => s.setFloatingElement)

const timeoutRef = useRef<Timeout>()
const runAnimateTimer = useCallback((vs: VisualState.animatingIn | VisualState.animatingOut) => {
Expand All @@ -71,6 +80,22 @@ function useToggleHandler() {
}, ms)
}, [])

const handleOpenAIPreview = (highlighter) => {
const { html } = getSelectionHTML()
const range = window.getSelection()?.getRangeAt(0)
const content = sanitizeHTML(html)

if (content) {
addAIEvent({ role: 'assistant', content, inputFormat: 'html' })

highlighter.fromRange(range)

setFloatingElement(FloatingElementType.AI_POPOVER, {
range
})
}
}

useEffect(() => {
function messageHandler(request: any, sender: chrome.runtime.MessageSender, sendResponse: (response: any) => void) {
const highlighter = new Highlighter({ style: { className: 'mexit-highlight' } })
Expand All @@ -94,6 +119,10 @@ function useToggleHandler() {
setVisualState(VisualState.animatingOut)
}
sendResponse(true)
break
case 'open-ai-tools':
handleOpenAIPreview(highlighter)
sendResponse(true)
}
}

Expand Down
4 changes: 2 additions & 2 deletions apps/extension/src/Editor/plugins/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const generatePlugins = (options: PluginOptionType) => {
// Special Elements
createImagePlugin({
options: {
uploadImage: options.uploadImage
uploadImage: options?.uploadImage
}
}),
createLinkPlugin(), // Link
Expand Down Expand Up @@ -192,7 +192,7 @@ export const useMemoizedPlugins = (components: Record<string, any>, options?: Pl
const plugins = createPlugins(
generatePlugins({
...options,
uploadImage: options.uploadImage ?? uploadImageToWDCDN
uploadImage: options?.uploadImage ?? uploadImageToWDCDN
}),
{
components: wrappedComponents
Expand Down
2 changes: 1 addition & 1 deletion apps/extension/src/Hooks/useSaveChanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export interface AppendAndSaveProps {

export function useSaveChanges() {
const workspaceDetails = useAuthStore((store) => store.workspaceDetails)
const { setPreviewMode, setNodeContent } = useEditorStore()
const { setPreviewMode } = useEditorStore()
const { getParentILink, getEntirePathILinks, updateMultipleILinks, updateSingleILink, createNoteHierarchyString } =
useInternalLinks()
const { updateDocument, updateBlocks } = useSearch()
Expand Down
15 changes: 13 additions & 2 deletions apps/extension/src/Styles/GlobalStyle.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createGlobalStyle } from 'styled-components'

import { customStyles, EditorBalloonStyles, normalize, ThinScrollbar,TippyBalloonStyles } from '@mexit/shared'
import { customStyles, EditorBalloonStyles, normalize, ThinScrollbar, TippyBalloonStyles } from '@mexit/shared'

export const GlobalStyle = createGlobalStyle`
Expand Down Expand Up @@ -36,7 +36,14 @@ export const GlobalStyle = createGlobalStyle`
display: none;
}
#sputlit-container, #dibba-container, #mexit-tooltip, #ext-side-nav, #notif {
.highlight {
color: ${({ theme }) => theme.tokens.text.heading};
background: ${({ theme }) => `rgba(${theme.rgbTokens.colors.primary.default}, 0.4)`};
}
#sputlit-container, #dibba-container, #ai-preview, #mexit-tooltip, #ext-side-nav, #notif, #mexit-ai-performer {
${normalize}; // NormalizeCSS normalization
letter-spacing: normal;
font-family: "Inter", sans-serif;
Expand Down Expand Up @@ -66,4 +73,8 @@ export const GlobalStyle = createGlobalStyle`
${({ theme }) => theme.custom && customStyles[theme.custom]}
}
#mexit-ai-performer {
font-size: 14px;
}
`
3 changes: 2 additions & 1 deletion apps/extension/src/Utils/getSelectionHTML.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ export function getSelectionHTML() {
url = selection?.anchorNode.baseURI

const container = document.createElement('div')

for (let i = 0, len = selection.rangeCount; i < len; ++i) {
const t = selection.getRangeAt(i).cloneContents()
container.appendChild(t)
range = selection.getRangeAt(i)
}
console.log('Container: ', container)

html = container.innerHTML
}
}
Expand Down
19 changes: 18 additions & 1 deletion apps/extension/src/Utils/requestHandler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { apiURLs, DEFAULT_NAMESPACE, defaultContent, ListItemType, mog } from '@mexit/core'
import { AIEvent, apiURLs, DEFAULT_NAMESPACE, defaultContent, ListItemType, mog } from '@mexit/core'

import { Tab } from '../Types/Tabs'

Expand Down Expand Up @@ -71,6 +71,23 @@ export const handleCaptureRequest = ({ subType, data }) => {
}
}

export const handlePerformAIRequest = async ({ data, workspaceId }) => {
return await client
.post(apiURLs.openAi.perform, {
json: data,
headers: {
'mex-workspace-id': workspaceId
}
})
.json()
.then((event: AIEvent) => {
return { message: event, error: null }
})
.catch((error) => {
return { message: null, error: error }
})
}

export const handleSnippetRequest = ({ data }) => {
const reqData = {
id: data.id,
Expand Down
16 changes: 16 additions & 0 deletions apps/extension/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import React from 'react'
import { createPortal } from 'react-dom'

import { useAuthStore } from '@mexit/core'

import AIPreviewContainer from './Components/AIPreview'
import Dibba from './Components/Dibba'
import { DibbaPortal } from './Components/Dibba/DibbaPortal'
import { InternalEvents } from './Components/InternalEvents'
Expand All @@ -11,6 +15,15 @@ import Tooltip from './Components/Tooltip'
import { TooltipPortal } from './Components/Tooltip/TooltipPortal'
import { HighlighterProvider } from './Hooks/useHighlighterContext'
import { SputlitProvider } from './Hooks/useSputlitContext'
import { styleSlot } from './Utils/cs-utils'

interface Props {
children: React.ReactNode
}

export function AIPreviewPortal(props: Props) {
return createPortal(props.children, styleSlot)
}

const Extension = () => {
const authenticated = useAuthStore((a) => a.authenticated)
Expand All @@ -28,6 +41,9 @@ const Extension = () => {
<TooltipPortal>
<Tooltip />
</TooltipPortal>
<AIPreviewPortal>
<AIPreviewContainer />
</AIPreviewPortal>
</>
)}

Expand Down
21 changes: 17 additions & 4 deletions apps/extension/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
handleCaptureRequest,
handleHighlightRequest,
handleNodeContentRequest,
handlePerformAIRequest,
handleSharingRequest,
handleShortenerRequest,
handleSnippetRequest
Expand Down Expand Up @@ -82,16 +83,22 @@ chrome.action.onClicked.addListener((command) => {
})

chrome.contextMenus.create({
id: 'open-sputlit',
id: 'sputlit',
title: 'Open Sputlit',
contexts: ['page', 'selection']
})

chrome.contextMenus.onClicked.addListener((onClickData) => {
chrome.contextMenus.create({
id: 'open-ai-tools',
title: 'Enhance with AI',
contexts: ['page', 'selection']
})

chrome.contextMenus.onClicked.addListener((info) => {
chrome.tabs?.query({ active: true, currentWindow: true }, (tabs) => {
const tabId = tabs[0].id

chrome.tabs.sendMessage(tabId, { type: 'sputlit' }, (response) => {
chrome.tabs.sendMessage(tabId, { type: info?.menuItemId }, (response) => {
handleResponseCallback(tabId, response)
})
})
Expand Down Expand Up @@ -198,6 +205,13 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
return true
}

case 'PERFORM_AI_ACTION': {
handlePerformAIRequest(request).then((res) => {
sendResponse(res?.message)
})
return true
}

default: {
return true
}
Expand Down Expand Up @@ -244,7 +258,6 @@ chrome.notifications.onClosed.addListener((notificationId, byUser) => {
chrome.omnibox.onInputChanged.addListener((text, suggest) => {
const workspaceDetails = useAuthStore.getState().workspaceDetails
const linkCaptures = useLinkStore.getState().links?.filter((item) => item.alias) ?? []

const suggestions = fuzzySearch(linkCaptures, text, (item) => item.alias).map((item) => {
return {
content: `${API_BASE_URLS.url}/${workspaceDetails.id}/${item.alias}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ type TaskEditorType = {
content: NodeEditorContent
readOnly?: boolean
onChange?: (val: any) => void
withCombobox?: boolean
}

const TaskEditor = ({ editorId, readOnly, content, onChange }: TaskEditorType) => {
const TaskEditor = ({ editorId, readOnly, content, onChange, withCombobox = true }: TaskEditorType) => {
const config = useEditorPluginConfig(editorId)
const { uploadImageToS3 } = useAuth()
const { uploadImageToWDCDN } = useUploadToCDN(uploadImageToS3)
Expand Down Expand Up @@ -66,7 +67,7 @@ const TaskEditor = ({ editorId, readOnly, content, onChange }: TaskEditorType) =
onChange={onChangeContent}
editableProps={editableProps}
>
<MultiComboboxContainer config={config.onKeyDownConfig} />
{withCombobox && <MultiComboboxContainer config={config.onKeyDownConfig} />}
</Plate>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import { optionsCreateNodeIdPlugin, optionsSelectOnBackspacePlugin } from '../..
import { parseTwitterUrl } from '../../../Editor/Plugins/parseTwitterUrl'
import Todo from '../../Todo'

const generateTodoPlugins = (uploadImage?: UploadImageFn) => {
const generateTodoPlugins = (uploadImage: UploadImageFn, inline?: boolean) => {
return [
// elements
createParagraphPlugin(), // paragraph element
Expand Down Expand Up @@ -106,7 +106,7 @@ const generateTodoPlugins = (uploadImage?: UploadImageFn) => {
createMentionPlugin(), // Mentions
createILinkPlugin(), // Internal Links ILinks
createInlineBlockPlugin(),
createSingleLinePlugin()
inline && createSingleLinePlugin()
]
}

Expand All @@ -129,7 +129,7 @@ export const getComponents = () =>
[ELEMENT_MEDIA_EMBED]: MediaEmbedElement as any
})

export const getTodoPlugins = (uploadImage?: UploadImageFn) => {
const plugins = createPlugins(generateTodoPlugins(uploadImage), { components: getComponents() })
export const getTodoPlugins = (uploadImage: UploadImageFn, isInline = true) => {
const plugins = createPlugins(generateTodoPlugins(uploadImage, isInline), { components: getComponents() })
return plugins
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const BallonMarkToolbarButtons = () => {
<>
<ToolbarButton
tooltip={{ content: 'Ask AI anything...', ...tooltip }}
icon={<IconDisplay size={20} icon={DefaultMIcons.AI} />}
icon={<IconDisplay color={theme.tokens.colors.primary.hover} size={20} icon={DefaultMIcons.AI} />}
onMouseDown={handleOpenAIPreview}
/>
<ButtonSeparator />
Expand Down
4 changes: 2 additions & 2 deletions apps/webapp/src/Components/Editor/Plateless.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,11 @@ const RenderPlateless = React.memo<RenderPlatelessProps>(
({ content, typeMap, multiline = false }: RenderPlatelessProps) => {
const childrenRender =
content &&
content.map((node) => {
content.map((node, i) => {
if (Object.keys(typeMap).includes(node?.type)) {
const RenderItem = typeMap[node?.type]
return (
<RenderItem node={node}>
<RenderItem key={`${node?.type}-${i}`} node={node}>
<RenderPlateless typeMap={typeMap} content={node.children} multiline={multiline} />
</RenderItem>
)
Expand Down
Loading

0 comments on commit c89e5cd

Please sign in to comment.