feat: Enhance article generation and outline handling

- Added support for external markdown synchronization in ArticleEditorCanvas.vue.
- Implemented a new function to sync markdown from props, ensuring the editor reflects external changes.
- Introduced a removeOutlineSection function in TemplateWizardView.vue to manage outline sections dynamically.
- Updated UI components to improve user experience, including replacing a-input with a-textarea for better text handling.
- Refactored CSS styles for a cleaner layout and improved responsiveness in TemplateWizardView.vue.
- Enhanced LLM generation requests to include response format options in ark.go and client.go.
- Updated template assist logic to enforce structured outline outputs in template_assist.go.
- Added tests for outline result decoding and prompt generation to ensure robustness.
- Created migration scripts to update prompt templates with new writing requirements for top X articles.
This commit is contained in:
2026-04-02 10:58:39 +08:00
parent b31d8d0096
commit 7fa1809682
12 changed files with 605 additions and 106 deletions
@@ -20,7 +20,7 @@ import {
wrapInHeadingCommand,
wrapInOrderedListCommand,
} from "@milkdown/kit/preset/commonmark";
import { callCommand } from "@milkdown/kit/utils";
import { callCommand, replaceAll } from "@milkdown/kit/utils";
import { Crepe, CrepeFeature } from "@milkdown/crepe";
import "@milkdown/crepe/theme/common/style.css";
import "@milkdown/crepe/theme/frame.css";
@@ -41,6 +41,7 @@ const emit = defineEmits<{
const { t } = useI18n();
const crepeRef = ref<Crepe | null>(null);
const syncingExternalMarkdown = ref(false);
const { loading } = useEditor((root) => {
const crepe = new Crepe({
@@ -51,12 +52,17 @@ const { loading } = useEditor((root) => {
[CrepeFeature.Toolbar]: true,
[CrepeFeature.Latex]: false,
[CrepeFeature.BlockEdit]: false,
[CrepeFeature.Placeholder]: false,
},
});
crepe.setReadonly(!!props.disabled);
crepe.on((listener) => {
listener.markdownUpdated((_ctx, markdown) => {
if (syncingExternalMarkdown.value) {
return;
}
emit("update:modelValue", markdown);
});
});
@@ -68,6 +74,25 @@ const { loading } = useEditor((root) => {
const [instanceLoading, getEditor] = useInstance();
const editorDisabled = computed(() => props.disabled || instanceLoading.value || loading.value);
function syncMarkdownFromProps(nextValue: string): void {
const editor = getEditor();
const crepe = crepeRef.value;
if (!editor || !crepe) {
return;
}
const resolvedValue = nextValue || "";
if (crepe.getMarkdown() === resolvedValue) {
return;
}
syncingExternalMarkdown.value = true;
editor.action(replaceAll(resolvedValue, true));
queueMicrotask(() => {
syncingExternalMarkdown.value = false;
});
}
watch(
() => props.disabled,
(value) => {
@@ -76,6 +101,18 @@ watch(
{ immediate: true },
);
watch(
[() => props.modelValue, loading, instanceLoading],
([value, editorLoading, currentInstanceLoading]) => {
if (editorLoading || currentInstanceLoading) {
return;
}
syncMarkdownFromProps(value);
},
{ immediate: true },
);
onBeforeUnmount(() => {
crepeRef.value = null;
});