autosnippet 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +230 -324
- package/bin/api-server.js +1 -1
- package/bin/cli.js +204 -244
- package/bin/mcp-server.js +5 -3
- package/config/knowledge-base.config.js +132 -132
- package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
- package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
- package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/bootstrap.js +8 -8
- package/lib/cli/AiScanService.js +86 -40
- package/lib/cli/KnowledgeSyncService.js +113 -74
- package/lib/cli/SetupService.js +439 -277
- package/lib/cli/UpgradeService.js +63 -100
- package/lib/core/AstAnalyzer.js +276 -597
- package/lib/core/ast/ProjectGraph.js +101 -40
- package/lib/core/ast/ensure-grammars.js +232 -0
- package/lib/core/ast/index.js +115 -0
- package/lib/core/ast/lang-dart.js +661 -0
- package/lib/core/ast/lang-go.js +530 -0
- package/lib/core/ast/lang-java.js +435 -0
- package/lib/core/ast/lang-javascript.js +272 -0
- package/lib/core/ast/lang-kotlin.js +423 -0
- package/lib/core/ast/lang-objc.js +388 -0
- package/lib/core/ast/lang-python.js +371 -0
- package/lib/core/ast/lang-swift.js +337 -0
- package/lib/core/ast/lang-typescript.js +503 -0
- package/lib/core/capability/CapabilityProbe.js +18 -9
- package/lib/core/constitution/Constitution.js +2 -3
- package/lib/core/constitution/ConstitutionValidator.js +65 -24
- package/lib/core/discovery/DartDiscoverer.js +534 -0
- package/lib/core/discovery/DiscovererRegistry.js +83 -0
- package/lib/core/discovery/GenericDiscoverer.js +225 -0
- package/lib/core/discovery/GoDiscoverer.js +541 -0
- package/lib/core/discovery/JvmDiscoverer.js +506 -0
- package/lib/core/discovery/NodeDiscoverer.js +466 -0
- package/lib/core/discovery/ProjectDiscoverer.js +93 -0
- package/lib/core/discovery/PythonDiscoverer.js +338 -0
- package/lib/core/discovery/SpmDiscoverer.js +5 -0
- package/lib/core/discovery/index.js +53 -0
- package/lib/core/enhancement/EnhancementPack.js +71 -0
- package/lib/core/enhancement/EnhancementRegistry.js +47 -0
- package/lib/core/enhancement/android-enhancement.js +102 -0
- package/lib/core/enhancement/django-enhancement.js +70 -0
- package/lib/core/enhancement/fastapi-enhancement.js +63 -0
- package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
- package/lib/core/enhancement/go-web-enhancement.js +201 -0
- package/lib/core/enhancement/index.js +65 -0
- package/lib/core/enhancement/node-server-enhancement.js +88 -0
- package/lib/core/enhancement/react-enhancement.js +86 -0
- package/lib/core/enhancement/spring-enhancement.js +112 -0
- package/lib/core/enhancement/vue-enhancement.js +96 -0
- package/lib/core/gateway/Gateway.js +8 -9
- package/lib/core/gateway/GatewayActionRegistry.js +1 -1
- package/lib/core/permission/PermissionManager.js +12 -8
- package/lib/domain/index.js +13 -9
- package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
- package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
- package/lib/domain/knowledge/Lifecycle.js +22 -22
- package/lib/domain/knowledge/index.js +9 -12
- package/lib/domain/knowledge/values/Constraints.js +31 -21
- package/lib/domain/knowledge/values/Content.js +21 -13
- package/lib/domain/knowledge/values/Quality.js +31 -18
- package/lib/domain/knowledge/values/Reasoning.js +20 -12
- package/lib/domain/knowledge/values/Relations.js +37 -25
- package/lib/domain/knowledge/values/Stats.js +18 -12
- package/lib/domain/knowledge/values/index.js +4 -3
- package/lib/domain/snippet/Snippet.js +35 -10
- package/lib/external/ai/AiFactory.js +48 -16
- package/lib/external/ai/AiProvider.js +184 -90
- package/lib/external/ai/providers/ClaudeProvider.js +25 -12
- package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
- package/lib/external/ai/providers/MockProvider.js +9 -3
- package/lib/external/ai/providers/OpenAiProvider.js +51 -29
- package/lib/external/mcp/McpServer.js +66 -36
- package/lib/external/mcp/errorHandler.js +23 -11
- package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
- package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
- package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
- package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
- package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
- package/lib/external/mcp/handlers/bootstrap.js +691 -168
- package/lib/external/mcp/handlers/browse.js +66 -22
- package/lib/external/mcp/handlers/candidate.js +118 -35
- package/lib/external/mcp/handlers/consolidated.js +49 -17
- package/lib/external/mcp/handlers/guard.js +104 -39
- package/lib/external/mcp/handlers/knowledge.js +60 -36
- package/lib/external/mcp/handlers/search.js +43 -14
- package/lib/external/mcp/handlers/skill.js +120 -45
- package/lib/external/mcp/handlers/structure.js +240 -86
- package/lib/external/mcp/handlers/system.js +42 -12
- package/lib/external/mcp/handlers/wiki.js +58 -33
- package/lib/external/mcp/tools.js +306 -123
- package/lib/http/HttpServer.js +72 -47
- package/lib/http/middleware/RateLimiter.js +5 -3
- package/lib/http/middleware/errorHandler.js +6 -1
- package/lib/http/middleware/requestLogger.js +14 -3
- package/lib/http/middleware/roleResolver.js +30 -23
- package/lib/http/routes/ai.js +387 -265
- package/lib/http/routes/auth.js +81 -61
- package/lib/http/routes/candidates.js +430 -320
- package/lib/http/routes/commands.js +289 -189
- package/lib/http/routes/extract.js +158 -125
- package/lib/http/routes/guardRules.js +309 -217
- package/lib/http/routes/knowledge.js +213 -154
- package/lib/http/routes/modules.js +578 -0
- package/lib/http/routes/monitoring.js +6 -6
- package/lib/http/routes/recipes.js +104 -93
- package/lib/http/routes/search.js +361 -305
- package/lib/http/routes/skills.js +145 -98
- package/lib/http/routes/snippets.js +42 -30
- package/lib/http/routes/spm.js +3 -405
- package/lib/http/routes/violations.js +113 -93
- package/lib/http/routes/wiki.js +211 -170
- package/lib/http/utils/routeHelpers.js +3 -1
- package/lib/http/utils/sse-sessions.js +16 -6
- package/lib/http/utils/sse.js +15 -5
- package/lib/infrastructure/audit/AuditLogger.js +5 -2
- package/lib/infrastructure/audit/AuditStore.js +10 -7
- package/lib/infrastructure/cache/CacheService.js +3 -1
- package/lib/infrastructure/cache/GraphCache.js +8 -4
- package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
- package/lib/infrastructure/config/ConfigLoader.js +9 -5
- package/lib/infrastructure/config/Defaults.js +30 -10
- package/lib/infrastructure/config/Paths.js +28 -8
- package/lib/infrastructure/config/TriggerSymbol.js +22 -10
- package/lib/infrastructure/database/DatabaseConnection.js +15 -10
- package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
- package/lib/infrastructure/external/ClipboardManager.js +6 -2
- package/lib/infrastructure/external/NativeUi.js +50 -43
- package/lib/infrastructure/external/OpenBrowser.js +14 -17
- package/lib/infrastructure/external/XcodeAutomation.js +14 -258
- package/lib/infrastructure/logging/Logger.js +46 -30
- package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
- package/lib/infrastructure/paths/HeaderResolver.js +25 -9
- package/lib/infrastructure/paths/PathFinder.js +34 -12
- package/lib/infrastructure/plugin/PluginManager.js +26 -8
- package/lib/infrastructure/realtime/RealtimeService.js +2 -2
- package/lib/infrastructure/vector/Chunker.js +22 -7
- package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
- package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
- package/lib/infrastructure/vector/VectorStore.js +28 -10
- package/lib/injection/ServiceContainer.js +247 -93
- package/lib/platform/ios/index.js +63 -0
- package/lib/platform/ios/routes/spm.js +437 -0
- package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
- package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
- package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
- package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
- package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
- package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
- package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
- package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
- package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
- package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
- package/lib/repository/base/BaseRepository.js +7 -9
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
- package/lib/repository/token/TokenUsageStore.js +4 -2
- package/lib/service/automation/ActionPipeline.js +1 -1
- package/lib/service/automation/AutomationOrchestrator.js +8 -4
- package/lib/service/automation/ContextCollector.js +7 -5
- package/lib/service/automation/DirectiveDetector.js +23 -16
- package/lib/service/automation/FileWatcher.js +112 -56
- package/lib/service/automation/TriggerResolver.js +6 -4
- package/lib/service/automation/handlers/AlinkHandler.js +24 -12
- package/lib/service/automation/handlers/CreateHandler.js +19 -20
- package/lib/service/automation/handlers/DraftHandler.js +14 -8
- package/lib/service/automation/handlers/GuardHandler.js +93 -63
- package/lib/service/automation/handlers/HeaderHandler.js +1 -6
- package/lib/service/automation/handlers/SearchHandler.js +155 -88
- package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
- package/lib/service/candidate/SimilarityService.js +25 -9
- package/lib/service/chat/AnalystAgent.js +50 -24
- package/lib/service/chat/CandidateGuardrail.js +143 -17
- package/lib/service/chat/ChatAgent.js +759 -243
- package/lib/service/chat/ContextWindow.js +116 -71
- package/lib/service/chat/ConversationStore.js +77 -36
- package/lib/service/chat/EpisodicConsolidator.js +47 -23
- package/lib/service/chat/HandoffProtocol.js +98 -22
- package/lib/service/chat/Memory.js +34 -14
- package/lib/service/chat/ProducerAgent.js +40 -20
- package/lib/service/chat/ProjectSemanticMemory.js +109 -78
- package/lib/service/chat/ReasoningLayer.js +148 -70
- package/lib/service/chat/ReasoningTrace.js +44 -32
- package/lib/service/chat/TaskPipeline.js +39 -19
- package/lib/service/chat/ToolRegistry.js +48 -29
- package/lib/service/chat/WorkingMemory.js +44 -18
- package/lib/service/chat/tools.js +1096 -494
- package/lib/service/context/RecipeExtractor.js +132 -51
- package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
- package/lib/service/cursor/KnowledgeCompressor.js +25 -22
- package/lib/service/cursor/RulesGenerator.js +13 -7
- package/lib/service/cursor/SkillsSyncer.js +77 -27
- package/lib/service/cursor/TokenBudget.js +2 -2
- package/lib/service/cursor/TopicClassifier.js +54 -20
- package/lib/service/guard/ComplianceReporter.js +55 -43
- package/lib/service/guard/ExclusionManager.js +67 -29
- package/lib/service/guard/GuardCheckEngine.js +381 -86
- package/lib/service/guard/GuardFeedbackLoop.js +22 -10
- package/lib/service/guard/GuardService.js +29 -19
- package/lib/service/guard/RuleLearner.js +55 -23
- package/lib/service/guard/SourceFileCollector.js +27 -20
- package/lib/service/guard/ViolationsStore.js +43 -38
- package/lib/service/knowledge/CodeEntityGraph.js +147 -82
- package/lib/service/knowledge/ConfidenceRouter.js +12 -10
- package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
- package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
- package/lib/service/knowledge/KnowledgeService.js +222 -112
- package/lib/service/module/ModuleService.js +969 -0
- package/lib/service/quality/FeedbackCollector.js +27 -15
- package/lib/service/quality/QualityScorer.js +78 -24
- package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
- package/lib/service/recipe/RecipeParser.js +78 -45
- package/lib/service/search/CoarseRanker.js +43 -28
- package/lib/service/search/CrossEncoderReranker.js +32 -21
- package/lib/service/search/InvertedIndex.js +21 -7
- package/lib/service/search/MultiSignalRanker.js +90 -28
- package/lib/service/search/RetrievalFunnel.js +45 -24
- package/lib/service/search/SearchEngine.js +255 -103
- package/lib/service/skills/EventAggregator.js +32 -15
- package/lib/service/skills/SignalCollector.js +140 -64
- package/lib/service/skills/SkillAdvisor.js +79 -42
- package/lib/service/skills/SkillHooks.js +16 -14
- package/lib/service/snippet/PlaceholderConverter.js +5 -0
- package/lib/service/snippet/SnippetFactory.js +116 -99
- package/lib/service/snippet/SnippetInstaller.js +234 -62
- package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
- package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
- package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
- package/lib/service/wiki/WikiGenerator.js +637 -263
- package/lib/shared/DimensionCopyRegistry.js +472 -0
- package/lib/shared/LanguageService.js +399 -0
- package/lib/shared/PathGuard.js +45 -28
- package/lib/shared/RecipeReadinessChecker.js +72 -12
- package/lib/shared/constants.js +41 -41
- package/lib/shared/errors/BaseError.js +2 -2
- package/lib/shared/errors/index.js +4 -4
- package/lib/shared/similarity.js +25 -8
- package/lib/shared/token-utils.js +6 -2
- package/lib/shared/utils/common.js +12 -4
- package/package.json +49 -13
- package/scripts/bench-real-projects.mjs +256 -0
- package/scripts/build-native-ui.js +30 -30
- package/scripts/clear-old-vector-index.js +5 -35
- package/scripts/clear-vector-cache.js +7 -37
- package/scripts/collect-test-project-stats.mjs +160 -0
- package/scripts/diagnose-mcp.js +41 -32
- package/scripts/ensure-parse-package.js +6 -9
- package/scripts/generate-recipe-drafts.js +116 -77
- package/scripts/init-db.js +3 -20
- package/scripts/init-snippets.js +305 -0
- package/scripts/init-vector-db.js +173 -170
- package/scripts/install-cursor-skill.js +148 -104
- package/scripts/install-full.js +8 -21
- package/scripts/install-vscode-copilot.js +146 -145
- package/scripts/migrate-md-to-knowledge.mjs +139 -151
- package/scripts/postinstall-safe.js +5 -17
- package/scripts/recipe-audit.js +106 -82
- package/scripts/release.js +283 -323
- package/scripts/setup-mcp-config.js +60 -52
- package/scripts/verify-context-api.js +20 -20
- package/skills/autosnippet-analysis/SKILL.md +10 -6
- package/skills/autosnippet-candidates/SKILL.md +27 -26
- package/skills/autosnippet-coldstart/SKILL.md +555 -38
- package/skills/autosnippet-concepts/SKILL.md +349 -337
- package/skills/autosnippet-create/SKILL.md +5 -5
- package/skills/autosnippet-reference-dart/SKILL.md +543 -0
- package/skills/autosnippet-reference-go/SKILL.md +539 -0
- package/skills/autosnippet-reference-java/SKILL.md +534 -0
- package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
- package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
- package/skills/autosnippet-reference-objc/SKILL.md +29 -6
- package/skills/autosnippet-reference-python/SKILL.md +800 -0
- package/skills/autosnippet-reference-swift/SKILL.md +70 -14
- package/skills/autosnippet-structure/SKILL.md +4 -4
- package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
- package/templates/recipes-setup/README.md +2 -2
- package/templates/recipes-setup/_template.md +1 -1
- package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
- package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
- package/resources/asd-entry/main.swift +0 -159
- package/scripts/build-asd-entry.js +0 -51
- package/scripts/init-xcode-snippets.js +0 -311
- package/template.json +0 -39
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import express from 'express';
|
|
7
|
-
import
|
|
7
|
+
import Logger from '../../infrastructure/logging/Logger.js';
|
|
8
8
|
import { getServiceContainer } from '../../injection/ServiceContainer.js';
|
|
9
9
|
import { ValidationError } from '../../shared/errors/index.js';
|
|
10
|
-
import
|
|
10
|
+
import { asyncHandler } from '../middleware/errorHandler.js';
|
|
11
11
|
import { createStreamSession, getStreamSession } from '../utils/sse-sessions.js';
|
|
12
12
|
|
|
13
13
|
const router = express.Router();
|
|
@@ -20,127 +20,137 @@ const logger = Logger.getInstance();
|
|
|
20
20
|
* 对若干候选条目进行 AI 语义字段补全
|
|
21
21
|
* Body: { candidateIds: string[] }
|
|
22
22
|
*/
|
|
23
|
-
router.post(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const container = getServiceContainer();
|
|
33
|
-
const knowledgeService = container.get('knowledgeService');
|
|
34
|
-
const aiProvider = container.get('aiProvider');
|
|
35
|
-
|
|
36
|
-
// 收集候选条目
|
|
37
|
-
const candidates = [];
|
|
38
|
-
for (const id of candidateIds) {
|
|
39
|
-
try {
|
|
40
|
-
const entry = await knowledgeService.get(id);
|
|
41
|
-
if (entry) {
|
|
42
|
-
const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
|
|
43
|
-
candidates.push({
|
|
44
|
-
id: json.id,
|
|
45
|
-
title: json.title,
|
|
46
|
-
language: json.language,
|
|
47
|
-
category: json.category,
|
|
48
|
-
description: json.description,
|
|
49
|
-
code: json.content?.pattern || '',
|
|
50
|
-
rationale: json.content?.rationale,
|
|
51
|
-
knowledgeType: json.knowledgeType,
|
|
52
|
-
complexity: json.complexity,
|
|
53
|
-
scope: json.scope,
|
|
54
|
-
steps: json.content?.steps,
|
|
55
|
-
constraints: json.constraints,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
} catch (err) {
|
|
59
|
-
logger.warn(`enrich: failed to load candidate ${id}`, { error: err.message });
|
|
23
|
+
router.post(
|
|
24
|
+
'/enrich',
|
|
25
|
+
asyncHandler(async (req, res) => {
|
|
26
|
+
const { candidateIds } = req.body;
|
|
27
|
+
if (!Array.isArray(candidateIds) || candidateIds.length === 0) {
|
|
28
|
+
throw new ValidationError('candidateIds array is required and must not be empty');
|
|
29
|
+
}
|
|
30
|
+
if (candidateIds.length > 20) {
|
|
31
|
+
throw new ValidationError('Max 20 candidates per enrichment call');
|
|
60
32
|
}
|
|
61
|
-
}
|
|
62
33
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
34
|
+
const container = getServiceContainer();
|
|
35
|
+
const knowledgeService = container.get('knowledgeService');
|
|
36
|
+
const aiProvider = container.get('aiProvider');
|
|
66
37
|
|
|
67
|
-
|
|
68
|
-
|
|
38
|
+
// 收集候选条目
|
|
39
|
+
const candidates = [];
|
|
40
|
+
for (const id of candidateIds) {
|
|
41
|
+
try {
|
|
42
|
+
const entry = await knowledgeService.get(id);
|
|
43
|
+
if (entry) {
|
|
44
|
+
const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
|
|
45
|
+
candidates.push({
|
|
46
|
+
id: json.id,
|
|
47
|
+
title: json.title,
|
|
48
|
+
language: json.language,
|
|
49
|
+
category: json.category,
|
|
50
|
+
description: json.description,
|
|
51
|
+
code: json.content?.pattern || '',
|
|
52
|
+
rationale: json.content?.rationale,
|
|
53
|
+
knowledgeType: json.knowledgeType,
|
|
54
|
+
complexity: json.complexity,
|
|
55
|
+
scope: json.scope,
|
|
56
|
+
steps: json.content?.steps,
|
|
57
|
+
constraints: json.constraints,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
logger.warn(`enrich: failed to load candidate ${id}`, { error: err.message });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
69
64
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
enriched = await aiProvider.enrichCandidates(candidates);
|
|
74
|
-
} catch (err) {
|
|
75
|
-
logger.warn('AI enrichCandidates failed', { error: err.message });
|
|
65
|
+
if (candidates.length === 0) {
|
|
66
|
+
return res.json({ success: true, data: { enriched: 0, total: 0, results: [] } });
|
|
76
67
|
}
|
|
77
68
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const idx = typeof item.index === 'number' ? item.index : enriched.indexOf(item);
|
|
81
|
-
const cand = candidates[idx];
|
|
82
|
-
if (!cand) continue;
|
|
69
|
+
let enrichedCount = 0;
|
|
70
|
+
const results = [];
|
|
83
71
|
|
|
72
|
+
if (aiProvider) {
|
|
73
|
+
let enriched = [];
|
|
84
74
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const needsContentMerge = (item.rationale && !cand.rationale) ||
|
|
90
|
-
(item.steps && (!cand.steps || cand.steps.length === 0));
|
|
91
|
-
let contentBase = null;
|
|
92
|
-
if (needsContentMerge) {
|
|
93
|
-
const entry = await knowledgeService.get(cand.id);
|
|
94
|
-
const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
|
|
95
|
-
contentBase = { ...(json.content || {}) };
|
|
96
|
-
}
|
|
75
|
+
enriched = await aiProvider.enrichCandidates(candidates);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
logger.warn('AI enrichCandidates failed', { error: err.message });
|
|
78
|
+
}
|
|
97
79
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
changed = true;
|
|
105
|
-
}
|
|
106
|
-
if (contentBase && changed) {
|
|
107
|
-
updateData.content = contentBase;
|
|
80
|
+
for (const item of enriched) {
|
|
81
|
+
// 安全的 index 映射:AI 未返回 index 时根据数组位置推断
|
|
82
|
+
const idx = typeof item.index === 'number' ? item.index : enriched.indexOf(item);
|
|
83
|
+
const cand = candidates[idx];
|
|
84
|
+
if (!cand) {
|
|
85
|
+
continue;
|
|
108
86
|
}
|
|
109
87
|
|
|
110
|
-
|
|
111
|
-
updateData
|
|
112
|
-
changed =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
changed = true;
|
|
125
|
-
}
|
|
88
|
+
try {
|
|
89
|
+
const updateData = {};
|
|
90
|
+
let changed = false;
|
|
91
|
+
|
|
92
|
+
// content 嵌套字段(rationale / steps)共用一次 DB 读取
|
|
93
|
+
const needsContentMerge =
|
|
94
|
+
(item.rationale && !cand.rationale) ||
|
|
95
|
+
(item.steps && (!cand.steps || cand.steps.length === 0));
|
|
96
|
+
let contentBase = null;
|
|
97
|
+
if (needsContentMerge) {
|
|
98
|
+
const entry = await knowledgeService.get(cand.id);
|
|
99
|
+
const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
|
|
100
|
+
contentBase = { ...(json.content || {}) };
|
|
101
|
+
}
|
|
126
102
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
103
|
+
if (item.rationale && !cand.rationale) {
|
|
104
|
+
contentBase.rationale = item.rationale;
|
|
105
|
+
changed = true;
|
|
106
|
+
}
|
|
107
|
+
if (item.steps && (!cand.steps || cand.steps.length === 0)) {
|
|
108
|
+
contentBase.steps = item.steps;
|
|
109
|
+
changed = true;
|
|
110
|
+
}
|
|
111
|
+
if (contentBase && changed) {
|
|
112
|
+
updateData.content = contentBase;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (item.knowledgeType && !cand.knowledgeType) {
|
|
116
|
+
updateData.knowledgeType = item.knowledgeType;
|
|
117
|
+
changed = true;
|
|
118
|
+
}
|
|
119
|
+
if (item.complexity && !cand.complexity) {
|
|
120
|
+
updateData.complexity = item.complexity;
|
|
121
|
+
changed = true;
|
|
122
|
+
}
|
|
123
|
+
if (item.scope && !cand.scope) {
|
|
124
|
+
updateData.scope = item.scope;
|
|
125
|
+
changed = true;
|
|
126
|
+
}
|
|
127
|
+
if (item.constraints && !cand.constraints?.preconditions?.length) {
|
|
128
|
+
updateData.constraints = item.constraints;
|
|
129
|
+
changed = true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (changed) {
|
|
133
|
+
await knowledgeService.update(cand.id, updateData, { userId: 'dashboard-enrich' });
|
|
134
|
+
enrichedCount++;
|
|
135
|
+
}
|
|
136
|
+
results.push({
|
|
137
|
+
id: cand.id,
|
|
138
|
+
enriched: changed,
|
|
139
|
+
filledFields: Object.keys(item).filter((k) => k !== 'index'),
|
|
140
|
+
});
|
|
141
|
+
} catch (err) {
|
|
142
|
+
logger.warn(`enrich: failed to update candidate ${cand.id}`, { error: err.message });
|
|
143
|
+
results.push({ id: cand.id, enriched: false, filledFields: [], error: err.message });
|
|
130
144
|
}
|
|
131
|
-
results.push({ id: cand.id, enriched: changed, filledFields: Object.keys(item).filter(k => k !== 'index') });
|
|
132
|
-
} catch (err) {
|
|
133
|
-
logger.warn(`enrich: failed to update candidate ${cand.id}`, { error: err.message });
|
|
134
|
-
results.push({ id: cand.id, enriched: false, filledFields: [], error: err.message });
|
|
135
145
|
}
|
|
136
146
|
}
|
|
137
|
-
}
|
|
138
147
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
})
|
|
148
|
+
res.json({
|
|
149
|
+
success: true,
|
|
150
|
+
data: { enriched: enrichedCount, total: candidates.length, results },
|
|
151
|
+
});
|
|
152
|
+
})
|
|
153
|
+
);
|
|
144
154
|
|
|
145
155
|
/* ═══ Bootstrap 内容润色 ═════════════════════════════════ */
|
|
146
156
|
|
|
@@ -149,21 +159,24 @@ router.post('/enrich', asyncHandler(async (req, res) => {
|
|
|
149
159
|
* AI 内容润色(适用于 Bootstrap 产出的批量候选)
|
|
150
160
|
* Body: { candidateIds?: string[], userPrompt?: string, dryRun?: boolean }
|
|
151
161
|
*/
|
|
152
|
-
router.post(
|
|
153
|
-
|
|
162
|
+
router.post(
|
|
163
|
+
'/bootstrap-refine',
|
|
164
|
+
asyncHandler(async (req, res) => {
|
|
165
|
+
const { candidateIds, userPrompt, dryRun } = req.body;
|
|
154
166
|
|
|
155
|
-
|
|
167
|
+
const container = getServiceContainer();
|
|
156
168
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
169
|
+
// 复用 MCP handler 的 bootstrapRefine 逻辑
|
|
170
|
+
const { bootstrapRefine } = await import('../../external/mcp/handlers/bootstrap.js');
|
|
171
|
+
const ctx = { container, logger };
|
|
172
|
+
const result = await bootstrapRefine(ctx, { candidateIds, userPrompt, dryRun });
|
|
161
173
|
|
|
162
|
-
|
|
163
|
-
|
|
174
|
+
// envelope 返回 { success, data, meta, ... },直接取 data
|
|
175
|
+
const data = result?.data ?? { refined: 0, total: 0, errors: [], results: [] };
|
|
164
176
|
|
|
165
|
-
|
|
166
|
-
})
|
|
177
|
+
res.json({ success: true, data });
|
|
178
|
+
})
|
|
179
|
+
);
|
|
167
180
|
|
|
168
181
|
/* ═══ 对话式润色 — 工具函数 ═══════════════════════════════ */
|
|
169
182
|
|
|
@@ -281,27 +294,63 @@ function buildUpdateFromRefineResult(before, parsed) {
|
|
|
281
294
|
// ─── key 别名归一化:AI 可能返回不精确的 key,统一映射到标准 key ───
|
|
282
295
|
const KEY_ALIASES = {
|
|
283
296
|
// description 别名
|
|
284
|
-
summary: 'description',
|
|
297
|
+
summary: 'description',
|
|
298
|
+
desc: 'description',
|
|
299
|
+
摘要: 'description',
|
|
300
|
+
描述: 'description',
|
|
285
301
|
// pattern 别名
|
|
286
|
-
content: 'pattern',
|
|
287
|
-
|
|
302
|
+
content: 'pattern',
|
|
303
|
+
designPattern: 'pattern',
|
|
304
|
+
内容: 'pattern',
|
|
305
|
+
代码: 'pattern',
|
|
306
|
+
标准用法: 'pattern',
|
|
288
307
|
// markdown 别名
|
|
289
|
-
markdownDoc: 'markdown',
|
|
308
|
+
markdownDoc: 'markdown',
|
|
309
|
+
Markdown文档: 'markdown',
|
|
310
|
+
文档: 'markdown',
|
|
311
|
+
doc: 'markdown',
|
|
290
312
|
// rationale 别名
|
|
291
|
-
design: 'rationale',
|
|
313
|
+
design: 'rationale',
|
|
314
|
+
设计原理: 'rationale',
|
|
315
|
+
原理: 'rationale',
|
|
316
|
+
design_rationale: 'rationale',
|
|
317
|
+
designRationale: 'rationale',
|
|
292
318
|
// tags 别名
|
|
293
|
-
tag: 'tags',
|
|
319
|
+
tag: 'tags',
|
|
320
|
+
label: 'tags',
|
|
321
|
+
labels: 'tags',
|
|
322
|
+
标签: 'tags',
|
|
294
323
|
// confidence 别名
|
|
295
|
-
score: 'confidence',
|
|
324
|
+
score: 'confidence',
|
|
325
|
+
置信度: 'confidence',
|
|
326
|
+
评分: 'confidence',
|
|
296
327
|
// aiInsight 别名
|
|
297
|
-
ai_insight: 'aiInsight',
|
|
328
|
+
ai_insight: 'aiInsight',
|
|
329
|
+
insight: 'aiInsight',
|
|
330
|
+
aiinsight: 'aiInsight',
|
|
331
|
+
洞察: 'aiInsight',
|
|
298
332
|
// agentNotes 别名
|
|
299
|
-
agent_notes: 'agentNotes',
|
|
333
|
+
agent_notes: 'agentNotes',
|
|
334
|
+
notes: 'agentNotes',
|
|
335
|
+
agentnotes: 'agentNotes',
|
|
336
|
+
笔记: 'agentNotes',
|
|
300
337
|
// relations 别名
|
|
301
|
-
relation: 'relations',
|
|
338
|
+
relation: 'relations',
|
|
339
|
+
关联: 'relations',
|
|
340
|
+
关联关系: 'relations',
|
|
302
341
|
};
|
|
303
342
|
|
|
304
|
-
const VALID_KEYS = new Set([
|
|
343
|
+
const VALID_KEYS = new Set([
|
|
344
|
+
'description',
|
|
345
|
+
'pattern',
|
|
346
|
+
'markdown',
|
|
347
|
+
'rationale',
|
|
348
|
+
'tags',
|
|
349
|
+
'confidence',
|
|
350
|
+
'aiInsight',
|
|
351
|
+
'agentNotes',
|
|
352
|
+
'relations',
|
|
353
|
+
]);
|
|
305
354
|
const normalized = {};
|
|
306
355
|
|
|
307
356
|
for (const [key, value] of Object.entries(parsed)) {
|
|
@@ -317,7 +366,9 @@ function buildUpdateFromRefineResult(before, parsed) {
|
|
|
317
366
|
|
|
318
367
|
// 确保未返回的字段保留 before 值
|
|
319
368
|
for (const k of VALID_KEYS) {
|
|
320
|
-
if (!(k in normalized))
|
|
369
|
+
if (!(k in normalized)) {
|
|
370
|
+
normalized[k] = before[k];
|
|
371
|
+
}
|
|
321
372
|
}
|
|
322
373
|
|
|
323
374
|
const after = { ...before };
|
|
@@ -389,38 +440,49 @@ function buildUpdateFromRefineResult(before, parsed) {
|
|
|
389
440
|
* 直接用用户提示词调用 AI 润色,返回 before/after 对比
|
|
390
441
|
* Body: { candidateId: string, userPrompt: string }
|
|
391
442
|
*/
|
|
392
|
-
router.post(
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const entry = await knowledgeService.get(candidateId);
|
|
403
|
-
if (!entry) throw new ValidationError('Candidate not found');
|
|
404
|
-
const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
|
|
405
|
-
const before = extractBeforeFields(json);
|
|
406
|
-
|
|
407
|
-
const prompt = buildRefinePrompt(before, userPrompt.trim());
|
|
408
|
-
const parsed = await aiProvider.chatWithStructuredOutput(prompt, { temperature: 0.3 });
|
|
409
|
-
|
|
410
|
-
if (!parsed) {
|
|
411
|
-
return res.json({
|
|
412
|
-
success: true,
|
|
413
|
-
data: { candidateId, before, after: before, preview: {} },
|
|
414
|
-
});
|
|
415
|
-
}
|
|
443
|
+
router.post(
|
|
444
|
+
'/refine-preview',
|
|
445
|
+
asyncHandler(async (req, res) => {
|
|
446
|
+
const { candidateId, userPrompt } = req.body;
|
|
447
|
+
if (!candidateId) {
|
|
448
|
+
throw new ValidationError('candidateId is required');
|
|
449
|
+
}
|
|
450
|
+
if (!userPrompt || !userPrompt.trim()) {
|
|
451
|
+
throw new ValidationError('userPrompt is required');
|
|
452
|
+
}
|
|
416
453
|
|
|
417
|
-
|
|
454
|
+
const container = getServiceContainer();
|
|
455
|
+
const knowledgeService = container.get('knowledgeService');
|
|
456
|
+
const aiProvider = container.get('aiProvider');
|
|
457
|
+
if (!aiProvider) {
|
|
458
|
+
throw new ValidationError('AI provider not configured');
|
|
459
|
+
}
|
|
418
460
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
461
|
+
const entry = await knowledgeService.get(candidateId);
|
|
462
|
+
if (!entry) {
|
|
463
|
+
throw new ValidationError('Candidate not found');
|
|
464
|
+
}
|
|
465
|
+
const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
|
|
466
|
+
const before = extractBeforeFields(json);
|
|
467
|
+
|
|
468
|
+
const prompt = buildRefinePrompt(before, userPrompt.trim());
|
|
469
|
+
const parsed = await aiProvider.chatWithStructuredOutput(prompt, { temperature: 0.3 });
|
|
470
|
+
|
|
471
|
+
if (!parsed) {
|
|
472
|
+
return res.json({
|
|
473
|
+
success: true,
|
|
474
|
+
data: { candidateId, before, after: before, preview: {} },
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const { after } = buildUpdateFromRefineResult(before, parsed);
|
|
479
|
+
|
|
480
|
+
res.json({
|
|
481
|
+
success: true,
|
|
482
|
+
data: { candidateId, before, after, preview: parsed },
|
|
483
|
+
});
|
|
484
|
+
})
|
|
485
|
+
);
|
|
424
486
|
|
|
425
487
|
/* ═══ 对话式润色 — 流式预览 (SSE) ═══════════════════════ */
|
|
426
488
|
|
|
@@ -436,110 +498,144 @@ router.post('/refine-preview', asyncHandler(async (req, res) => {
|
|
|
436
498
|
*
|
|
437
499
|
* Body: { candidateId: string, userPrompt: string }
|
|
438
500
|
*/
|
|
439
|
-
router.post(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const entry = await knowledgeService.get(candidateId);
|
|
450
|
-
if (!entry) throw new ValidationError('Candidate not found');
|
|
451
|
-
const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
|
|
452
|
-
const before = extractBeforeFields(json);
|
|
453
|
-
|
|
454
|
-
// ─── Session + EventSource 架构 ───
|
|
455
|
-
const session = createStreamSession('refine');
|
|
456
|
-
const prompt = buildRefinePrompt(before, userPrompt.trim());
|
|
457
|
-
|
|
458
|
-
// 立即返回 sessionId
|
|
459
|
-
res.json({ sessionId: session.sessionId });
|
|
460
|
-
|
|
461
|
-
// 异步执行 AI 润色,通过 session 推送进度事件
|
|
462
|
-
setImmediate(async () => {
|
|
463
|
-
try {
|
|
464
|
-
// 进度事件: AI 调用开始
|
|
465
|
-
session.send({ type: 'data:progress', stage: 'ai_calling', message: 'AI 润色中...' });
|
|
466
|
-
|
|
467
|
-
// 定时进度心跳 — AI 调用是阻塞的,前端需要看到动态变化
|
|
468
|
-
const progressMsgs = [
|
|
469
|
-
{ delay: 3000, stage: 'analyzing', message: '正在分析候选内容...' },
|
|
470
|
-
{ delay: 8000, stage: 'generating', message: '正在生成润色建议...' },
|
|
471
|
-
{ delay: 16000, stage: 'thinking', message: 'AI 深度分析中...' },
|
|
472
|
-
{ delay: 28000, stage: 'almost_done', message: '即将完成,请稍候...' },
|
|
473
|
-
];
|
|
474
|
-
const progressTimers = [];
|
|
475
|
-
let aiDone = false;
|
|
476
|
-
for (const pm of progressMsgs) {
|
|
477
|
-
const t = setTimeout(() => {
|
|
478
|
-
if (!aiDone) session.send({ type: 'data:progress', stage: pm.stage, message: pm.message });
|
|
479
|
-
}, pm.delay);
|
|
480
|
-
progressTimers.push(t);
|
|
481
|
-
}
|
|
482
|
-
// 超过 35 秒后每 15 秒报一次耗时
|
|
483
|
-
const longTimer = setInterval(() => {
|
|
484
|
-
if (aiDone) return;
|
|
485
|
-
const elapsed = Math.round((Date.now() - session.createdAt) / 1000);
|
|
486
|
-
session.send({ type: 'data:progress', stage: 'waiting', message: `AI 仍在处理中 (${elapsed}s)...` });
|
|
487
|
-
}, 15_000);
|
|
488
|
-
const longTimerStart = setTimeout(() => {}, 35_000); // placeholder
|
|
489
|
-
progressTimers.push(longTimerStart);
|
|
490
|
-
|
|
491
|
-
function clearProgressTimers() {
|
|
492
|
-
aiDone = true;
|
|
493
|
-
for (const t of progressTimers) clearTimeout(t);
|
|
494
|
-
clearInterval(longTimer);
|
|
495
|
-
}
|
|
501
|
+
router.post(
|
|
502
|
+
'/refine-preview-stream',
|
|
503
|
+
asyncHandler(async (req, res) => {
|
|
504
|
+
const { candidateId, userPrompt } = req.body;
|
|
505
|
+
if (!candidateId) {
|
|
506
|
+
throw new ValidationError('candidateId is required');
|
|
507
|
+
}
|
|
508
|
+
if (!userPrompt || !userPrompt.trim()) {
|
|
509
|
+
throw new ValidationError('userPrompt is required');
|
|
510
|
+
}
|
|
496
511
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
512
|
+
const container = getServiceContainer();
|
|
513
|
+
const knowledgeService = container.get('knowledgeService');
|
|
514
|
+
const aiProvider = container.get('aiProvider');
|
|
515
|
+
if (!aiProvider) {
|
|
516
|
+
throw new ValidationError('AI provider not configured');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const entry = await knowledgeService.get(candidateId);
|
|
520
|
+
if (!entry) {
|
|
521
|
+
throw new ValidationError('Candidate not found');
|
|
522
|
+
}
|
|
523
|
+
const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
|
|
524
|
+
const before = extractBeforeFields(json);
|
|
507
525
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
526
|
+
// ─── Session + EventSource 架构 ───
|
|
527
|
+
const session = createStreamSession('refine');
|
|
528
|
+
const prompt = buildRefinePrompt(before, userPrompt.trim());
|
|
511
529
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
} else {
|
|
515
|
-
// 结构化输出失败,回退到 chat() 重试
|
|
516
|
-
session.send({ type: 'data:progress', stage: 'fallback', message: 'AI 正在重新生成...' });
|
|
517
|
-
const fullText = await aiProvider.chat(prompt, { temperature: 0.3 });
|
|
530
|
+
// 立即返回 sessionId
|
|
531
|
+
res.json({ sessionId: session.sessionId });
|
|
518
532
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
533
|
+
// 异步执行 AI 润色,通过 session 推送进度事件
|
|
534
|
+
setImmediate(async () => {
|
|
535
|
+
try {
|
|
536
|
+
// 进度事件: AI 调用开始
|
|
537
|
+
session.send({ type: 'data:progress', stage: 'ai_calling', message: 'AI 润色中...' });
|
|
538
|
+
|
|
539
|
+
// 定时进度心跳 — AI 调用是阻塞的,前端需要看到动态变化
|
|
540
|
+
const progressMsgs = [
|
|
541
|
+
{ delay: 3000, stage: 'analyzing', message: '正在分析候选内容...' },
|
|
542
|
+
{ delay: 8000, stage: 'generating', message: '正在生成润色建议...' },
|
|
543
|
+
{ delay: 16000, stage: 'thinking', message: 'AI 深度分析中...' },
|
|
544
|
+
{ delay: 28000, stage: 'almost_done', message: '即将完成,请稍候...' },
|
|
545
|
+
];
|
|
546
|
+
const progressTimers = [];
|
|
547
|
+
let aiDone = false;
|
|
548
|
+
for (const pm of progressMsgs) {
|
|
549
|
+
const t = setTimeout(() => {
|
|
550
|
+
if (!aiDone) {
|
|
551
|
+
session.send({ type: 'data:progress', stage: pm.stage, message: pm.message });
|
|
552
|
+
}
|
|
553
|
+
}, pm.delay);
|
|
554
|
+
progressTimers.push(t);
|
|
555
|
+
}
|
|
556
|
+
// 超过 35 秒后每 15 秒报一次耗时
|
|
557
|
+
const longTimer = setInterval(() => {
|
|
558
|
+
if (aiDone) {
|
|
559
|
+
return;
|
|
527
560
|
}
|
|
561
|
+
const elapsed = Math.round((Date.now() - session.createdAt) / 1000);
|
|
562
|
+
session.send({
|
|
563
|
+
type: 'data:progress',
|
|
564
|
+
stage: 'waiting',
|
|
565
|
+
message: `AI 仍在处理中 (${elapsed}s)...`,
|
|
566
|
+
});
|
|
567
|
+
}, 15_000);
|
|
568
|
+
const longTimerStart = setTimeout(() => {}, 35_000); // placeholder
|
|
569
|
+
progressTimers.push(longTimerStart);
|
|
570
|
+
|
|
571
|
+
function clearProgressTimers() {
|
|
572
|
+
aiDone = true;
|
|
573
|
+
for (const t of progressTimers) {
|
|
574
|
+
clearTimeout(t);
|
|
575
|
+
}
|
|
576
|
+
clearInterval(longTimer);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// 使用 chatWithStructuredOutput 获取可靠的 JSON 结果(非流式),120 秒超时
|
|
580
|
+
let parsed;
|
|
581
|
+
try {
|
|
582
|
+
parsed = await Promise.race([
|
|
583
|
+
aiProvider.chatWithStructuredOutput(prompt, { temperature: 0.3 }),
|
|
584
|
+
new Promise((_, reject) =>
|
|
585
|
+
setTimeout(() => reject(new Error('AI refine timeout (120s)')), 120_000)
|
|
586
|
+
),
|
|
587
|
+
]);
|
|
588
|
+
} finally {
|
|
589
|
+
clearProgressTimers();
|
|
528
590
|
}
|
|
529
591
|
|
|
530
|
-
if (
|
|
531
|
-
|
|
532
|
-
session.
|
|
592
|
+
if (parsed) {
|
|
593
|
+
// 进度事件: 构建 diff
|
|
594
|
+
session.send({
|
|
595
|
+
type: 'data:progress',
|
|
596
|
+
stage: 'building_diff',
|
|
597
|
+
message: '生成修改对比...',
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
const { after } = buildUpdateFromRefineResult(before, parsed);
|
|
601
|
+
session.end({ candidateId, before, after, preview: parsed });
|
|
533
602
|
} else {
|
|
534
|
-
|
|
603
|
+
// 结构化输出失败,回退到 chat() 重试
|
|
604
|
+
session.send({ type: 'data:progress', stage: 'fallback', message: 'AI 正在重新生成...' });
|
|
605
|
+
const fullText = await aiProvider.chat(prompt, { temperature: 0.3 });
|
|
606
|
+
|
|
607
|
+
let fallbackParsed = null;
|
|
608
|
+
try {
|
|
609
|
+
const jsonStr = fullText
|
|
610
|
+
.replace(/^```(?:json)?\s*\n?/m, '')
|
|
611
|
+
.replace(/\n?```\s*$/m, '')
|
|
612
|
+
.trim();
|
|
613
|
+
fallbackParsed = JSON.parse(jsonStr);
|
|
614
|
+
} catch {
|
|
615
|
+
const match = fullText.match(/\{[\s\S]*\}/);
|
|
616
|
+
if (match) {
|
|
617
|
+
try {
|
|
618
|
+
fallbackParsed = JSON.parse(match[0]);
|
|
619
|
+
} catch {
|
|
620
|
+
/* ignore */
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (fallbackParsed) {
|
|
626
|
+
const { after } = buildUpdateFromRefineResult(before, fallbackParsed);
|
|
627
|
+
session.end({ candidateId, before, after, preview: fallbackParsed });
|
|
628
|
+
} else {
|
|
629
|
+
session.end({ candidateId, before, after: before, preview: null, rawText: fullText });
|
|
630
|
+
}
|
|
535
631
|
}
|
|
632
|
+
} catch (err) {
|
|
633
|
+
logger.warn('SSE refine-preview stream error', { error: err.message });
|
|
634
|
+
session.error(err.message, 'REFINE_ERROR');
|
|
536
635
|
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
}));
|
|
636
|
+
});
|
|
637
|
+
})
|
|
638
|
+
);
|
|
543
639
|
|
|
544
640
|
/**
|
|
545
641
|
* GET /api/v1/candidates/refine-preview/events/:sessionId
|
|
@@ -566,7 +662,9 @@ router.get('/refine-preview/events/:sessionId', (req, res) => {
|
|
|
566
662
|
}
|
|
567
663
|
|
|
568
664
|
function writeEvent(event) {
|
|
569
|
-
if (res.writableEnded)
|
|
665
|
+
if (res.writableEnded) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
570
668
|
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
571
669
|
}
|
|
572
670
|
|
|
@@ -618,79 +716,91 @@ router.get('/refine-preview/events/:sessionId', (req, res) => {
|
|
|
618
716
|
* 若未提供 preview 则 fallback 重新调用 AI。
|
|
619
717
|
* Body: { candidateId: string, userPrompt?: string, preview?: object }
|
|
620
718
|
*/
|
|
621
|
-
router.post(
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
const entry = await knowledgeService.get(candidateId);
|
|
629
|
-
if (!entry) throw new ValidationError('Candidate not found');
|
|
630
|
-
const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
|
|
631
|
-
const before = extractBeforeFields(json);
|
|
632
|
-
|
|
633
|
-
// 优先使用前端传回的 preview(与预览阶段完全一致),否则重新调 AI
|
|
634
|
-
let parsed = preview || null;
|
|
635
|
-
if (!parsed) {
|
|
636
|
-
if (!userPrompt || !userPrompt.trim()) {
|
|
637
|
-
throw new ValidationError('Either preview or userPrompt is required');
|
|
719
|
+
router.post(
|
|
720
|
+
'/refine-apply',
|
|
721
|
+
asyncHandler(async (req, res) => {
|
|
722
|
+
const { candidateId, userPrompt, preview } = req.body;
|
|
723
|
+
if (!candidateId) {
|
|
724
|
+
throw new ValidationError('candidateId is required');
|
|
638
725
|
}
|
|
639
|
-
const aiProvider = container.get('aiProvider');
|
|
640
|
-
if (!aiProvider) throw new ValidationError('AI provider not configured');
|
|
641
|
-
const prompt = buildRefinePrompt(before, userPrompt.trim());
|
|
642
|
-
parsed = await aiProvider.chatWithStructuredOutput(prompt, { temperature: 0.3 });
|
|
643
|
-
}
|
|
644
726
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
success: true,
|
|
648
|
-
data: { refined: 0, total: 1, candidate: json },
|
|
649
|
-
});
|
|
650
|
-
}
|
|
727
|
+
const container = getServiceContainer();
|
|
728
|
+
const knowledgeService = container.get('knowledgeService');
|
|
651
729
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
// 处理需要嵌套写入的字段
|
|
656
|
-
const finalUpdate = { ...updateData };
|
|
657
|
-
delete finalUpdate._patternChanged;
|
|
658
|
-
delete finalUpdate._confidenceChanged;
|
|
659
|
-
delete finalUpdate._markdownChanged;
|
|
660
|
-
delete finalUpdate._rationaleChanged;
|
|
661
|
-
|
|
662
|
-
const contentPatch = { ...(json.content || {}) };
|
|
663
|
-
let contentChanged = false;
|
|
664
|
-
if (updateData._patternChanged != null) {
|
|
665
|
-
contentPatch.pattern = updateData._patternChanged;
|
|
666
|
-
contentChanged = true;
|
|
667
|
-
}
|
|
668
|
-
if (updateData._markdownChanged != null) {
|
|
669
|
-
contentPatch.markdown = updateData._markdownChanged;
|
|
670
|
-
contentChanged = true;
|
|
730
|
+
const entry = await knowledgeService.get(candidateId);
|
|
731
|
+
if (!entry) {
|
|
732
|
+
throw new ValidationError('Candidate not found');
|
|
671
733
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
734
|
+
const json = typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
|
|
735
|
+
const before = extractBeforeFields(json);
|
|
736
|
+
|
|
737
|
+
// 优先使用前端传回的 preview(与预览阶段完全一致),否则重新调 AI
|
|
738
|
+
let parsed = preview || null;
|
|
739
|
+
if (!parsed) {
|
|
740
|
+
if (!userPrompt || !userPrompt.trim()) {
|
|
741
|
+
throw new ValidationError('Either preview or userPrompt is required');
|
|
742
|
+
}
|
|
743
|
+
const aiProvider = container.get('aiProvider');
|
|
744
|
+
if (!aiProvider) {
|
|
745
|
+
throw new ValidationError('AI provider not configured');
|
|
746
|
+
}
|
|
747
|
+
const prompt = buildRefinePrompt(before, userPrompt.trim());
|
|
748
|
+
parsed = await aiProvider.chatWithStructuredOutput(prompt, { temperature: 0.3 });
|
|
678
749
|
}
|
|
679
|
-
|
|
680
|
-
|
|
750
|
+
|
|
751
|
+
if (!parsed) {
|
|
752
|
+
return res.json({
|
|
753
|
+
success: true,
|
|
754
|
+
data: { refined: 0, total: 1, candidate: json },
|
|
755
|
+
});
|
|
681
756
|
}
|
|
682
757
|
|
|
683
|
-
|
|
684
|
-
|
|
758
|
+
const { updateData, changed } = buildUpdateFromRefineResult(before, parsed);
|
|
759
|
+
|
|
760
|
+
if (changed) {
|
|
761
|
+
// 处理需要嵌套写入的字段
|
|
762
|
+
const finalUpdate = { ...updateData };
|
|
763
|
+
delete finalUpdate._patternChanged;
|
|
764
|
+
delete finalUpdate._confidenceChanged;
|
|
765
|
+
delete finalUpdate._markdownChanged;
|
|
766
|
+
delete finalUpdate._rationaleChanged;
|
|
767
|
+
|
|
768
|
+
const contentPatch = { ...(json.content || {}) };
|
|
769
|
+
let contentChanged = false;
|
|
770
|
+
if (updateData._patternChanged != null) {
|
|
771
|
+
contentPatch.pattern = updateData._patternChanged;
|
|
772
|
+
contentChanged = true;
|
|
773
|
+
}
|
|
774
|
+
if (updateData._markdownChanged != null) {
|
|
775
|
+
contentPatch.markdown = updateData._markdownChanged;
|
|
776
|
+
contentChanged = true;
|
|
777
|
+
}
|
|
778
|
+
if (updateData._rationaleChanged != null) {
|
|
779
|
+
contentPatch.rationale = updateData._rationaleChanged;
|
|
780
|
+
contentChanged = true;
|
|
781
|
+
}
|
|
782
|
+
if (contentChanged) {
|
|
783
|
+
finalUpdate.content = contentPatch;
|
|
784
|
+
}
|
|
785
|
+
if (updateData._confidenceChanged != null) {
|
|
786
|
+
finalUpdate.reasoning = {
|
|
787
|
+
...(json.reasoning || {}),
|
|
788
|
+
confidence: updateData._confidenceChanged,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
685
791
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
const updatedJson = typeof updated?.toJSON === 'function' ? updated.toJSON() : updated;
|
|
792
|
+
await knowledgeService.update(candidateId, finalUpdate, { userId: 'dashboard-refine' });
|
|
793
|
+
}
|
|
689
794
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
795
|
+
// 返回更新后的条目
|
|
796
|
+
const updated = changed ? await knowledgeService.get(candidateId) : entry;
|
|
797
|
+
const updatedJson = typeof updated?.toJSON === 'function' ? updated.toJSON() : updated;
|
|
798
|
+
|
|
799
|
+
res.json({
|
|
800
|
+
success: true,
|
|
801
|
+
data: { refined: changed ? 1 : 0, total: 1, candidate: updatedJson },
|
|
802
|
+
});
|
|
803
|
+
})
|
|
804
|
+
);
|
|
695
805
|
|
|
696
806
|
export default router;
|