|
|
@@ -1,5 +1,9 @@
|
|
|
+import { createApp, h, reactive, shallowRef } from 'vue'
|
|
|
import { TextNode } from 'lexical'
|
|
|
+import type { App, ShallowRef } from 'vue'
|
|
|
import type { EditorConfig, SerializedTextNode } from 'lexical'
|
|
|
+import VarLabel from '@/components/VarLabel/index.vue'
|
|
|
+import type { NodeVar } from '@/types/var'
|
|
|
|
|
|
export type SerializedVarLabelNode = SerializedTextNode & {
|
|
|
type: 'var-label'
|
|
|
@@ -7,90 +11,102 @@ export type SerializedVarLabelNode = SerializedTextNode & {
|
|
|
key?: string
|
|
|
}
|
|
|
|
|
|
-type LabelInfo = {
|
|
|
- type: 'env' | 'sys' | 'node' | 'custom'
|
|
|
- value: string
|
|
|
- nodeName?: string
|
|
|
+type MountedVarLabelState = {
|
|
|
+ app: App<Element>
|
|
|
+ props: {
|
|
|
+ label: string
|
|
|
+ contextId: string
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-function parseLabelInfo(rawText: string, className: string): LabelInfo {
|
|
|
- if (rawText.startsWith('#{') && rawText.endsWith('}')) {
|
|
|
- const expression = rawText.slice(2, -1)
|
|
|
- const [prefix, ...rest] = expression.split('.')
|
|
|
- const value = rest.join('.')
|
|
|
+const mountedVarLabels = new WeakMap<HTMLElement, MountedVarLabelState>()
|
|
|
+const promptEditorNodeVarsRefs = new Map<string, ShallowRef<NodeVar[]>>()
|
|
|
+const emptyNodeVars: NodeVar[] = []
|
|
|
|
|
|
- if (prefix === 'env' && value) {
|
|
|
- return { type: 'env', value }
|
|
|
- }
|
|
|
+function getNodeVarsRef(contextId: string) {
|
|
|
+ const existingRef = promptEditorNodeVarsRefs.get(contextId)
|
|
|
+ if (existingRef) return existingRef
|
|
|
|
|
|
- if (prefix === 'sys' && value) {
|
|
|
- return { type: 'sys', value }
|
|
|
- }
|
|
|
+ const nextRef = shallowRef<NodeVar[]>([])
|
|
|
+ promptEditorNodeVarsRefs.set(contextId, nextRef)
|
|
|
+ return nextRef
|
|
|
+}
|
|
|
|
|
|
- if (prefix && value) {
|
|
|
- return {
|
|
|
- type: 'node',
|
|
|
- nodeName: prefix,
|
|
|
- value
|
|
|
+function syncMountedContextId(dom: HTMLElement, props: MountedVarLabelState['props'], attempt = 0) {
|
|
|
+ queueMicrotask(() => {
|
|
|
+ if (!dom.isConnected) {
|
|
|
+ if (attempt < 10) {
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ syncMountedContextId(dom, props, attempt + 1)
|
|
|
+ })
|
|
|
}
|
|
|
+ return
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if (className.includes('env-label')) {
|
|
|
- return { type: 'env', value: rawText }
|
|
|
- }
|
|
|
+ const nextContextId =
|
|
|
+ dom
|
|
|
+ .closest<HTMLElement>('[data-prompt-editor-context-id]')
|
|
|
+ ?.getAttribute('data-prompt-editor-context-id') || ''
|
|
|
|
|
|
- if (className.includes('sys-label')) {
|
|
|
- return { type: 'sys', value: rawText }
|
|
|
- }
|
|
|
+ if (props.contextId !== nextContextId) {
|
|
|
+ props.contextId = nextContextId
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
|
|
|
- if (className.includes('node-label')) {
|
|
|
- return { type: 'node', nodeName: 'node', value: rawText }
|
|
|
- }
|
|
|
+export function syncPromptEditorVarLabelNodeVars(
|
|
|
+ contextId: string,
|
|
|
+ nodeVars: NodeVar[] | undefined
|
|
|
+) {
|
|
|
+ if (!contextId) return
|
|
|
|
|
|
- return { type: 'custom', value: rawText }
|
|
|
-}
|
|
|
+ const nextNodeVars =
|
|
|
+ nodeVars?.map((item) => ({
|
|
|
+ ...item,
|
|
|
+ variableList: [...item.variableList]
|
|
|
+ })) || emptyNodeVars
|
|
|
|
|
|
-function createSpan(className: string, text: string) {
|
|
|
- const span = document.createElement('span')
|
|
|
- span.className = className
|
|
|
- span.textContent = text
|
|
|
- return span
|
|
|
+ getNodeVarsRef(contextId).value = nextNodeVars
|
|
|
}
|
|
|
|
|
|
function renderLabel(dom: HTMLElement, className: string, rawText: string) {
|
|
|
- const info = parseLabelInfo(rawText, className)
|
|
|
-
|
|
|
dom.className = `${className} var-label-token`
|
|
|
dom.contentEditable = 'false'
|
|
|
- dom.title =
|
|
|
- info.type === 'node' && info.nodeName ? `${info.nodeName} / ${info.value}` : info.value || rawText
|
|
|
-
|
|
|
- const container = document.createElement('span')
|
|
|
- container.className = 'var-label-content'
|
|
|
-
|
|
|
- if (info.type === 'env' || info.type === 'sys') {
|
|
|
- const prefix = document.createElement('span')
|
|
|
- prefix.className = `var-select__item-prefix ${info.type} text-6px`
|
|
|
- prefix.appendChild(createSpan('', info.type.toUpperCase()))
|
|
|
- container.appendChild(prefix)
|
|
|
- container.appendChild(createSpan('var-label-value text-gray-600', info.value))
|
|
|
- dom.replaceChildren(container)
|
|
|
- return
|
|
|
- }
|
|
|
+ dom.removeAttribute('title')
|
|
|
|
|
|
- if (info.type === 'node') {
|
|
|
- const prefix = createSpan('var-select__item-prefix node text-10px', 'VAR')
|
|
|
- container.appendChild(prefix)
|
|
|
- container.appendChild(createSpan('var-label-node-name', info.nodeName || 'node'))
|
|
|
- container.appendChild(createSpan('var-label-separator mx-2px text-gray-400', '/'))
|
|
|
- container.appendChild(createSpan('var-label-value text-gray-600', info.value))
|
|
|
- dom.replaceChildren(container)
|
|
|
+ const mountedState = mountedVarLabels.get(dom)
|
|
|
+ if (mountedState) {
|
|
|
+ mountedState.props.label = rawText
|
|
|
+ syncMountedContextId(dom, mountedState.props)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- container.appendChild(createSpan('var-label-value text-gray-600', info.value))
|
|
|
- dom.replaceChildren(container)
|
|
|
+ const mountPoint = document.createElement('span')
|
|
|
+ dom.replaceChildren(mountPoint)
|
|
|
+
|
|
|
+ const props = reactive({
|
|
|
+ label: rawText,
|
|
|
+ contextId: ''
|
|
|
+ })
|
|
|
+
|
|
|
+ const app = createApp({
|
|
|
+ render() {
|
|
|
+ const nodeVars = props.contextId ? getNodeVarsRef(props.contextId).value : emptyNodeVars
|
|
|
+
|
|
|
+ return h(VarLabel, {
|
|
|
+ label: props.label,
|
|
|
+ nodeVars
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ app.mount(mountPoint)
|
|
|
+ syncMountedContextId(dom, props)
|
|
|
+
|
|
|
+ mountedVarLabels.set(dom, {
|
|
|
+ app,
|
|
|
+ props
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
export class VarLabelNode extends TextNode {
|