feat: LLM 应用网页开发及内联建议功能实现

This commit is contained in:
2026-04-05 13:42:29 +08:00
parent 9904b9bd78
commit 68ed783d6c
13 changed files with 800 additions and 513 deletions
+1 -1
View File
@@ -286,7 +286,7 @@ onUnmounted(() => {
}
.doc-card__editor :deep(.ProseMirror) {
min-height: 80px;
min-height: 0;
padding: 10px 12px 12px !important;
font-size: 13px !important;
line-height: 1.6;
+46 -64
View File
@@ -251,7 +251,6 @@ const docUploadButtonTitle = computed(() => {
let crepe = null
let markdownSyncTimer = null
let rootResizeObserver = null
let editorCopyHandler = null
const objectUrls = new Set()
const IMAGE_NODE_TYPES = new Set(['image', 'image-block', 'imageBlock'])
@@ -437,16 +436,9 @@ const clearCurrentSuggestion = (view) => {
const clearCurrentGhost = () => {
if (!crepe) return
crepe.editor.action((ctx) => {
const view = ctx.get(editorViewCtx)
clearGhostSuggestion(view)
})
}
const updateEditorTailSpace = () => {
if (!root.value) return
const viewportHeight = root.value.clientHeight
const tailSpace = Math.max(viewportHeight - 32, 160)
root.value.style.setProperty('--editor-tail-space', `${tailSpace}px`)
const view = ctx.get(editorViewCtx)
clearGhostSuggestion(view)
})
}
const updateHistoryState = (view) => {
@@ -655,18 +647,11 @@ onMounted(async () => {
if (!e.target.closest('.image-btn-wrapper')) {
showImageDropdown.value = false
}
})
if (!root.value) throw new Error('root.value is null')
updateEditorTailSpace()
if (typeof ResizeObserver !== 'undefined') {
rootResizeObserver = new ResizeObserver(() => {
updateEditorTailSpace()
})
rootResizeObserver.observe(root.value)
}
crepe = new Crepe({
})
if (!root.value) throw new Error('root.value is null')
crepe = new Crepe({
root: root.value,
defaultValue: transformSpecialDocBlocksToLegacy(initialMarkdown.value || ''),
features: {
@@ -729,9 +714,18 @@ onMounted(async () => {
crepe.editor.use(docBlockView)
await crepe.create()
crepe.on((listener) => {
await crepe.create()
crepe.editor.action((ctx) => {
const view = ctx.get(editorViewCtx)
const { doc } = view.state
const endPos = doc.content.size
const tr = view.state.tr.setSelection(Selection.near(doc.resolve(endPos), 1))
view.dispatch(tr)
view.focus()
})
crepe.on((listener) => {
listener.updated((ctx, doc) => {
const view = ctx.get(editorViewCtx)
syncObjectUrls(doc)
@@ -973,25 +967,19 @@ const insertMultipleDocBlocks = (blocks) => {
blocks.forEach((block, index) => {
const maxPos = tr.doc.content.size
if (index > 0) {
const insertPos = Math.min(currentPos, maxPos)
tr = tr.insertText('\n', insertPos, insertPos)
currentPos = insertPos + 1
}
const blockNode = docBlockType.create({
docType: block.docType,
docName: block.docName,
uploadTime: block.uploadTime,
content: block.content,
collapsed: Boolean(block.collapsed),
})
const blockNode = docBlockType.create({
docType: block.docType,
docName: block.docName,
uploadTime: block.uploadTime,
content: block.content,
collapsed: Boolean(block.collapsed),
})
const insertBlockPos = Math.min(currentPos, tr.doc.content.size)
tr = tr.replaceRangeWith(insertBlockPos, insertBlockPos, blockNode)
currentPos = insertBlockPos + blockNode.nodeSize
})
tr = tr.replaceRangeWith(insertPos, insertPos, blockNode)
currentPos = insertPos + blockNode.nodeSize
})
const finalPos = Math.min(currentPos, tr.doc.content.size)
if (finalPos >= 0 && finalPos <= tr.doc.content.size) {
@@ -1133,16 +1121,11 @@ const insertImageFromUrl = () => {
onUnmounted(() => {
if (markdownSyncTimer) {
clearTimeout(markdownSyncTimer)
markdownSyncTimer = null
}
clearTimeout(markdownSyncTimer)
markdownSyncTimer = null
}
if (rootResizeObserver) {
rootResizeObserver.disconnect()
rootResizeObserver = null
}
for (const url of Array.from(objectUrls)) {
for (const url of Array.from(objectUrls)) {
revokeObjectUrl(url)
}
@@ -1168,12 +1151,12 @@ onUnmounted(() => {
}
.history-buttons {
position: fixed;
top: calc(16px + env(safe-area-inset-top));
right: calc(16px + env(safe-area-inset-right));
display: flex;
gap: 6px;
z-index: 9000;
position: fixed;
bottom: 20px;
left: 80px;
display: flex;
gap: 6px;
z-index: 9000;
}
.history-btn {
@@ -1520,11 +1503,10 @@ onUnmounted(() => {
}
.milkdown-editor {
--editor-tail-space: calc(100vh - 32px);
width: 100%;
height: 100%;
background-color: transparent !important;
overflow-y: auto;
width: 100%;
height: 100%;
background-color: transparent !important;
overflow-y: auto;
}
.milkdown-editor :deep(.milkdown) {
@@ -1551,8 +1533,8 @@ onUnmounted(() => {
}
.milkdown-editor :deep(.ProseMirror) {
margin: 0 !important;
padding: 0 0 var(--editor-tail-space) 0 !important;
margin: 0 !important;
padding: 10px 0 24px 0 !important;
}
.milkdown-editor :deep(.ProseMirror img) {
+7 -1
View File
@@ -312,13 +312,15 @@ const t = (key) => store.t[key]
.settings-panel {
position: fixed;
top: 0;
bottom: 0;
bottom: auto;
left: 0;
width: 350px;
max-height: 100vh;
background: var(--panel-bg);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-right: 1px solid var(--panel-border);
border-radius: 0 8px 8px 0;
box-shadow: var(--panel-shadow);
z-index: 10000;
transform: translateX(-100%);
@@ -349,8 +351,11 @@ const t = (key) => store.t[key]
/* Mobile Fullscreen */
@media (max-width: 640px) {
.settings-panel {
top: 10vh;
max-height: 80vh;
width: 100%;
border-right: none;
border-radius: 0;
}
}
@@ -384,6 +389,7 @@ const t = (key) => store.t[key]
flex: 1;
overflow-y: auto;
padding: 20px;
min-height: 0;
}
.settings-section {