Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perform AI On Selection In Extension, Allow Editing Of Query Content #412

Merged
merged 4 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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