Skip to content

Commit

Permalink
fix UI & show mentionable content on added
Browse files Browse the repository at this point in the history
  • Loading branch information
glowingjade committed Oct 23, 2024
1 parent 2cd6094 commit b38d7bb
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 113 deletions.
31 changes: 0 additions & 31 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
"@anthropic-ai/sdk": "^0.27.3",
"@lexical/react": "^0.17.1",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-hover-card": "^1.1.2",
"@tanstack/react-query": "^5.56.2",
"diff": "^7.0.0",
"fuzzysort": "^3.1.0",
Expand Down
17 changes: 17 additions & 0 deletions src/components/chat-view/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ import {
LLMAPIKeyInvalidException,
LLMAPIKeyNotSetException,
} from '../../utils/llm/exception'
import {
getMentionableKey,
serializeMentionable,
} from '../../utils/mentionable'
import { readTFileContent } from '../../utils/obsidian'
import { parseRequestMessages } from '../../utils/prompt'

Expand Down Expand Up @@ -87,6 +91,16 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
}
return newMessage
})
const [addedBlockKey, setAddedBlockKey] = useState<string | null>(
props.selectedBlock
? getMentionableKey(
serializeMentionable({
type: 'block',
...props.selectedBlock,
}),
)
: null,
)
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([])
const [focusedMessageId, setFocusedMessageId] = useState<string | null>(null)
const [currentConversationId, setCurrentConversationId] =
Expand Down Expand Up @@ -356,6 +370,8 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
...data,
}

setAddedBlockKey(getMentionableKey(serializeMentionable(mentionable)))

if (focusedMessageId === inputMessage.id) {
setInputMessage((prevInputMessage) => ({
...prevInputMessage,
Expand Down Expand Up @@ -498,6 +514,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
}))
}}
autoFocus
addedBlockKey={addedBlockKey}
/>
</div>
)
Expand Down
97 changes: 97 additions & 0 deletions src/components/chat-view/chat-input/ChatUserInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
import { useQuery } from '@tanstack/react-query'
import { $nodesOfType, LexicalEditor, SerializedEditorState } from 'lexical'
import { MarkdownView } from 'obsidian'
import {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState,
} from 'react'
import {
deserializeMentionable,
Expand All @@ -26,6 +29,7 @@ import { useApp } from '../../../contexts/app-context'
import { Mentionable, SerializedMentionable } from '../../../types/mentionable'
import { fuzzySearch } from '../../../utils/fuzzy-search'
import { getMentionableKey } from '../../../utils/mentionable'
import { readTFileContent } from '../../../utils/obsidian'

import MentionableBadge from './MentionableBadge'
import { ModelSelect } from './ModelSelect'
Expand Down Expand Up @@ -53,6 +57,7 @@ export type ChatUserInputProps = {
mentionables: Mentionable[]
setMentionables: (mentionables: Mentionable[]) => void
autoFocus?: boolean
addedBlockKey?: string | null
}

const ChatUserInput = forwardRef<ChatUserInputRef, ChatUserInputProps>(
Expand All @@ -65,6 +70,7 @@ const ChatUserInput = forwardRef<ChatUserInputRef, ChatUserInputProps>(
mentionables,
setMentionables,
autoFocus = false,
addedBlockKey,
},
ref,
) => {
Expand All @@ -74,6 +80,16 @@ const ChatUserInput = forwardRef<ChatUserInputRef, ChatUserInputProps>(
const contentEditableRef = useRef<HTMLDivElement>(null)
const updaterRef = useRef<UpdaterPluginRef | null>(null)

const [displayedMentionableKey, setDisplayedMentionableKey] = useState<
string | null
>(addedBlockKey ?? null)

useEffect(() => {
if (addedBlockKey) {
setDisplayedMentionableKey(addedBlockKey)
}
}, [addedBlockKey])

useImperativeHandle(ref, () => ({
focus: () => {
contentEditableRef.current?.focus()
Expand Down Expand Up @@ -120,6 +136,7 @@ const ChatUserInput = forwardRef<ChatUserInputRef, ChatUserInputProps>(
return
}
setMentionables([...mentionables, mentionable])
setDisplayedMentionableKey(mentionableKey)
}

const handleMentionNodeMutation = (
Expand Down Expand Up @@ -196,6 +213,48 @@ const ChatUserInput = forwardRef<ChatUserInputRef, ChatUserInputProps>(
})
}

const { data: fileContent } = useQuery({
queryKey: [
'file',
displayedMentionableKey,
mentionables.map((m) => getMentionableKey(serializeMentionable(m))), // should be updated when mentionables change (especially on delete)
],
queryFn: async () => {
if (!displayedMentionableKey) return null

const displayedMentionable = mentionables.find(
(m) =>
getMentionableKey(serializeMentionable(m)) ===
displayedMentionableKey,
)

if (!displayedMentionable) return null

if (
displayedMentionable.type === 'file' ||
displayedMentionable.type === 'current-file'
) {
if (!displayedMentionable.file) return null
return await readTFileContent(displayedMentionable.file, app.vault)
} else if (displayedMentionable.type === 'block') {
const fileContent = await readTFileContent(
displayedMentionable.file,
app.vault,
)

return fileContent
.split('\n')
.slice(
displayedMentionable.startLine - 1,
displayedMentionable.endLine,
)
.join('\n')
}

return null
},
})

return (
<div className="smtcmp-chat-user-input-container">
{mentionables.length > 0 && (
Expand All @@ -205,11 +264,49 @@ const ChatUserInput = forwardRef<ChatUserInputRef, ChatUserInputProps>(
key={getMentionableKey(serializeMentionable(m))}
mentionable={m}
onDelete={() => handleMentionableDelete(m)}
onClick={() => {
const mentionableKey = getMentionableKey(
serializeMentionable(m),
)
if (
(m.type === 'current-file' ||
m.type === 'file' ||
m.type === 'block') &&
m.file &&
mentionableKey === displayedMentionableKey
) {
setDisplayedMentionableKey(null)

// Open the file if clicked again
const existingLeaf = app.workspace
.getLeavesOfType('markdown')
.find(
(leaf) =>
leaf.view instanceof MarkdownView &&
leaf.view.file?.path === m.file?.path,
)

if (existingLeaf) {
app.workspace.setActiveLeaf(existingLeaf, { focus: true })
} else {
const leaf = app.workspace.getLeaf('tab')
leaf.openFile(m.file)
}
} else {
setDisplayedMentionableKey(mentionableKey)
}
}}
/>
))}
</div>
)}

{fileContent && (
<div className="smtcmp-chat-user-input-file-content-preview">
{fileContent}
</div>
)}

<LexicalComposer initialConfig={initialConfig}>
{/*
There was two approach to make mentionable node copy and pasteable.
Expand Down
Loading

0 comments on commit b38d7bb

Please sign in to comment.