feat: 添加文章修改保存功能及首页入口

This commit is contained in:
empty
2026-01-21 23:50:35 +08:00
parent 94301c81a6
commit 44848cd40f
8 changed files with 1360 additions and 16 deletions

View File

@@ -233,6 +233,29 @@ const seedDefaultReferences = async () => {
await seedDefaultReferences();
/**
* 同步现有的提纲素材到通用素材库
*/
const syncExistingOutlineMaterials = () => {
try {
const materials = db.prepare('SELECT * FROM outline_materials').all();
if (materials.length === 0) return;
console.log(`🔍 正在同步 ${materials.length} 个存量提纲素材到通用素材库...`);
db.transaction(() => {
materials.forEach(mat => {
syncToReferences(mat.id, mat.name, mat.content);
});
})();
console.log(`✅ 存量提纲素材同步完成`);
} catch (err) {
console.error('⚠️ 同步存量素材失败:', err.message);
}
};
syncExistingOutlineMaterials();
console.log('📦 SQLite 数据库初始化完成:', DB_PATH);
export function getAllReferences() {
@@ -618,6 +641,32 @@ export function getAllOutlineMaterials() {
return db.prepare('SELECT * FROM outline_materials ORDER BY created_at DESC').all();
}
/**
* 将提纲素材同步到通用素材库 (materials/excerpts)
* 提纲素材作为 'outline' 类型存储,便于在通用素材库中管理
*/
const syncToReferences = (id, name, content) => {
const refId = `ref-${id}`; // 使用固定前缀关联
// 1. 更新或插入到 materials 表
db.prepare(`
INSERT INTO materials (id, type, title, source, is_default, created_at, updated_at)
VALUES (?, 'outline', ?, '提纲写作', 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT(id) DO UPDATE SET
title = EXCLUDED.title,
updated_at = CURRENT_TIMESTAMP
`).run(refId, name);
// 2. 更新或插入到 reference_excerpts 表
const excerptId = `${refId}-content`;
db.prepare(`
INSERT INTO reference_excerpts (id, reference_id, topic, content)
VALUES (?, ?, '正文', ?)
ON CONFLICT(id) DO UPDATE SET
content = EXCLUDED.content
`).run(excerptId, refId, content);
};
export function addOutlineMaterial(material) {
const id = material.id || `omat-${Date.now()}`;
db.prepare(`
@@ -629,6 +678,14 @@ export function addOutlineMaterial(material) {
material.content,
material.content?.length || 0
);
// 同步到通用素材库
try {
syncToReferences(id, material.name, material.content);
} catch (err) {
console.error('同步素材到通用库失败:', err);
}
return id;
}
@@ -651,11 +708,32 @@ export function updateOutlineMaterial(id, updates) {
params.push(id);
db.prepare(`UPDATE outline_materials SET ${setClauses.join(', ')} WHERE id = ?`).run(...params);
// 如果更新了名称或内容,需要同步
try {
const current = db.prepare('SELECT * FROM outline_materials WHERE id = ?').get(id);
if (current) {
syncToReferences(id, current.name, current.content);
}
} catch (err) {
console.error('更新素材同步失败:', err);
}
return true;
}
export function deleteOutlineMaterial(id) {
db.prepare('DELETE FROM outline_materials WHERE id = ?').run(id);
// 同时从通用库删除 (如果存在)
try {
const refId = `ref-${id}`;
db.prepare('DELETE FROM reference_excerpts WHERE reference_id = ?').run(refId);
db.prepare('DELETE FROM materials WHERE id = ?').run(refId);
} catch (err) {
console.error('删除素材同步失败:', err);
}
return true;
}

View File

@@ -50,9 +50,6 @@ app.get('/api/llm/providers', (req, res) => {
});
app.post('/api/llm/stream', async (req, res) => {
const controller = new AbortController();
req.on('close', () => controller.abort());
try {
const { providerId, model, appId, messages, options } = req.body || {};
if (!providerId || !Array.isArray(messages)) {
@@ -66,10 +63,10 @@ app.post('/api/llm/stream', async (req, res) => {
appId,
messages,
options,
res,
signal: controller.signal
res
});
} catch (error) {
console.error('LLM 代理请求错误:', error);
if (!res.headersSent) {
res.status(500).json({ success: false, error: error.message });
}

View File

@@ -78,8 +78,7 @@ export const streamChat = async ({
appId,
messages,
options,
res,
signal
res
}) => {
const provider = providers[providerId];
@@ -107,19 +106,31 @@ export const streamChat = async ({
const headers = buildHeaders({ ...provider, appId: appId || provider.appId });
const response = await fetch(provider.apiUrl, {
method: 'POST',
headers,
body: JSON.stringify(payload),
signal
});
console.log('LLM 代理: 发起请求', { providerId, model: payload.model, apiUrl: provider.apiUrl });
let response;
try {
response = await fetch(provider.apiUrl, {
method: 'POST',
headers,
body: JSON.stringify(payload)
});
} catch (fetchError) {
console.error('LLM 代理: Fetch 错误', fetchError);
if (!res.headersSent) {
res.status(500).json({ success: false, error: fetchError.message || '网络请求失败' });
}
return;
}
if (!response.ok) {
const errorText = await response.text();
console.error('LLM 代理: 上游响应错误', response.status, errorText);
res.status(500).json({ success: false, error: errorText || `上游请求失败: ${response.status}` });
return;
}
console.log('LLM 代理: 开始流式响应');
res.status(200);
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
res.setHeader('Cache-Control', 'no-cache');
@@ -131,6 +142,7 @@ export const streamChat = async ({
res.write(chunk);
});
readable.on('end', () => {
console.log('LLM 代理: 流式响应完成');
res.end();
});
readable.on('error', (error) => {
@@ -138,3 +150,4 @@ export const streamChat = async ({
res.end();
});
};

View File

@@ -28,6 +28,8 @@
<ArticleFusionPanel />
<FusionResultPanel />
</template>
<!-- 文章修改页面 -->
<ArticleEditorPanel v-else-if="currentPage === 'articleEditor'" />
<DocumentsPanel
v-else-if="currentPage === 'documents'"
@toggle-version-panel="toggleVersionPanel"
@@ -36,8 +38,8 @@
<MaterialsPanel v-else-if="currentPage === 'materials'" />
<SettingsPanel v-else-if="currentPage === 'settings'" />
<!-- 右侧核心内容区comparerewritediffAnnotationarticleFusion outlineWriter 页面使用自己的内部布局 -->
<MainContent v-if="currentPage !== 'compare' && currentPage !== 'rewrite' && currentPage !== 'diffAnnotation' && currentPage !== 'articleFusion' && currentPage !== 'outlineWriter'" />
<!-- 右侧核心内容区comparerewritediffAnnotationarticleFusionoutlineWriter articleEditor 页面使用自己的内部布局 -->
<MainContent v-if="currentPage !== 'compare' && currentPage !== 'rewrite' && currentPage !== 'diffAnnotation' && currentPage !== 'articleFusion' && currentPage !== 'outlineWriter' && currentPage !== 'articleEditor'" />
<!-- 侧滑浮层面板 (仅文稿页) -->
<DocumentVersionPanel
@@ -74,6 +76,7 @@ import ArticleFusionPanel from './components/ArticleFusionPanel.vue'
import FusionResultPanel from './components/FusionResultPanel.vue'
import OutlineWriterPanel from './components/OutlineWriterPanel.vue'
import OutlineResultPanel from './components/OutlineResultPanel.vue'
import ArticleEditorPanel from './components/ArticleEditorPanel.vue'
const appStore = useAppStore()
const currentPage = computed(() => appStore.currentPage)

File diff suppressed because it is too large Load Diff

View File

@@ -65,6 +65,7 @@ const navItems = [
{ id: 'analysis', label: '范式库', icon: 'analysis' },
{ id: 'paradigmWriter', label: '范式写作', icon: 'article' },
{ id: 'outlineWriter', label: '提纲写作', icon: 'folder' },
{ id: 'articleEditor', label: '文章修改', icon: 'edit' },
{ id: 'articleFusion', label: '文章融合', icon: 'sparkles' },
{ id: 'documents', label: '文稿库', icon: 'folder' },
{ id: 'materials', label: '素材库', icon: 'chart' },

View File

@@ -159,6 +159,9 @@ const features = [
{ id: 'mimicWriter', name: '以稿写稿', icon: 'copy' },
{ id: 'analysis', name: '范式库', icon: 'analysis' },
{ id: 'paradigmWriter', name: '范式写作', icon: 'article' },
{ id: 'outlineWriter', name: '提纲写作', icon: 'folder' },
{ id: 'articleEditor', name: '文章修改', icon: 'edit' },
{ id: 'articleFusion', name: '文章融合', icon: 'sparkles' },
{ id: 'documents', name: '文稿库', icon: 'folder' },
{ id: 'materials', name: '素材库', icon: 'chart' },
{ id: 'rewrite', name: '范式润色', icon: 'sparkles' },

View File

@@ -89,7 +89,8 @@ const materialTypes = [
{ id: 'policy', label: '政策文件' },
{ id: 'speech', label: '领导讲话' },
{ id: 'case', label: '典型案例' },
{ id: 'reference', label: '参考范文' }
{ id: 'reference', label: '参考范文' },
{ id: 'outline', label: '提纲素材' }
]
const filteredMaterials = computed(() => {