新增: 父⑤⑤.2收尾(评分拆const+表单补全+css陷阱) + priority跨层映射修复
父⑤⑤.2(DEC-03 c / DEC-04 a): - #6 scoring.rs 关键词拆 const(FEASIBILITY_POS/NEG + IMPACT + URGENCY 4 组,零行为变化,df-ideas 22 测试过) - #9/#10 Ideas.vue 表单补 tags(逗号分隔对标 KnowledgeDetail)+priority 下拉+source,i18n 中英对称 - css 陷阱修:components.css .modal-* 通配→"modal 系列"(避 */ 闭合,memory css-comment-star-slash-trap) priority 跨层映射修复(verify agent 发现的预存 bug): - idea.rs priority_from_i32 原跟 df-types 枚举(0=Low),与前端约定(0=critical,api/types.ts:141+Tasks.vue)反 - 改手动映射对齐前端(0=Critical/1=High/2=Medium/3=Low),Priority as_str 序列化不受影响 - 校验注释同步更正(我父①①.3 加的 Low=0 注释) 父⑤ 全闭环(⑤.1/①.4/⑤.2)。css warning 清零。
This commit is contained in:
@@ -11,6 +11,30 @@ pub use crate::capture::IdeaScores;
|
||||
|
||||
use df_types::types::Priority;
|
||||
|
||||
// ─── 启发式关键词(DEC-03 c:同文件 const 拆出,接 LLM 语义评分后升 a:JSON 文件配置)───
|
||||
//
|
||||
// 现阶段关键词集合固定且与维度强相关,抽 const 仅为可读性/可维护性(零行为变化)。
|
||||
// TODO(DEC-03 a):接入 LLM 语义评分后,这些关键词退居「冷启动兜底」,
|
||||
// 集合迁移到 JSON 配置文件,支持热更新与项目级覆盖。
|
||||
// 注:以下集合与原方法内字面量逐一对应,集合/顺序/否定前缀/权重均不变(纯结构提取)。
|
||||
|
||||
/// 可行性正向信号词(描述充实度外的技术/资源信号),命中每个 +0.5
|
||||
pub const FEASIBILITY_POSITIVE_KEYWORDS: &[&str] = &[
|
||||
"复用", "已有", "简单", "集成", "支持", "成熟", "基于", "现成", "脚手架", "模板",
|
||||
];
|
||||
/// 可行性负向信号词(复杂度上升信号),命中每个 -0.6
|
||||
pub const FEASIBILITY_NEGATIVE_KEYWORDS: &[&str] = &[
|
||||
"重构", "迁移", "大规模", "分布式", "重写", "从零", "全新架构", "高并发", "底层",
|
||||
];
|
||||
/// 影响力价值信号词(用户/增长/收入...),命中每个 +0.5
|
||||
pub const IMPACT_KEYWORDS: &[&str] = &[
|
||||
"用户", "增长", "收入", "效率", "体验", "核心", "关键", "痛点", "竞品", "留存",
|
||||
];
|
||||
/// 紧急度时效信号词(立即/马上/紧急...),命中每个 +0.5
|
||||
pub const URGENCY_KEYWORDS: &[&str] = &[
|
||||
"立即", "马上", "紧急", "尽快", "本周", "上线", "deadline", "截止", "先行", "阻塞",
|
||||
];
|
||||
|
||||
/// 评分权重配置
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ScoringWeights {
|
||||
@@ -71,14 +95,10 @@ impl ScoringEngine {
|
||||
score -= 0.5;
|
||||
}
|
||||
// 可行性正向信号
|
||||
let pos = count_any(desc, &[
|
||||
"复用", "已有", "简单", "集成", "支持", "成熟", "基于", "现成", "脚手架", "模板",
|
||||
]);
|
||||
let pos = count_any(desc, FEASIBILITY_POSITIVE_KEYWORDS);
|
||||
score += (pos as f64) * 0.5;
|
||||
// 复杂度负向信号
|
||||
let neg = count_any(desc, &[
|
||||
"重构", "迁移", "大规模", "分布式", "重写", "从零", "全新架构", "高并发", "底层",
|
||||
]);
|
||||
let neg = count_any(desc, FEASIBILITY_NEGATIVE_KEYWORDS);
|
||||
score -= (neg as f64) * 0.6;
|
||||
score.clamp(0.0, 10.0)
|
||||
}
|
||||
@@ -97,9 +117,7 @@ impl ScoringEngine {
|
||||
score += (idea.tags.len().min(4) as f64) * 0.25;
|
||||
}
|
||||
let desc = idea.description.trim();
|
||||
let value_hits = count_any(desc, &[
|
||||
"用户", "增长", "收入", "效率", "体验", "核心", "关键", "痛点", "竞品", "留存",
|
||||
]);
|
||||
let value_hits = count_any(desc, IMPACT_KEYWORDS);
|
||||
score += (value_hits as f64) * 0.5;
|
||||
if desc.chars().count() > 100 {
|
||||
score += 0.5;
|
||||
@@ -116,9 +134,7 @@ impl ScoringEngine {
|
||||
Priority::Low => 3.0,
|
||||
};
|
||||
let desc = idea.description.trim();
|
||||
let time_hits = count_any(desc, &[
|
||||
"立即", "马上", "紧急", "尽快", "本周", "上线", "deadline", "截止", "先行", "阻塞",
|
||||
]);
|
||||
let time_hits = count_any(desc, URGENCY_KEYWORDS);
|
||||
score += (time_hits as f64) * 0.5;
|
||||
score.clamp(0.0, 10.0)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ graph TD
|
||||
P2["父② 知识图谱Phase1<br/>✅Phase1(数据层+业务层)"]:::done
|
||||
P3["父③ AI对话体验<br/>📋待办"]:::todo
|
||||
P4["父④ F-09 per-conv<br/>📋待办"]:::todo
|
||||
P5["父⑤ 灵感模块<br/>🔨⑤.1/①.4✅·⑤.2待办"]:::doing
|
||||
P5["父⑤ 灵感模块<br/>✅完成(⑤.1/①.4/⑤.2)"]:::done
|
||||
P6["父⑥ Phase2-5<br/>📋待办"]:::todo
|
||||
P7["父⑦ 技术债<br/>📋待办"]:::todo
|
||||
|
||||
@@ -58,7 +58,7 @@ graph TD
|
||||
| **父②** 知识图谱Phase1 | ✅ Phase1完成 | ②.1 V29迁移✅ / ②.2 TaskRecord+TaskLinkRepo✅ / ②.3 IPC(create_task扩展+task_link CRUD+move_queue+get_tree)✅ / ②.4 父聚合✅(set_status_for_aggregation绕status收口) / ②.5 AI工具6✅(基线38) | G1(弱) |
|
||||
| **父③** AI对话体验 | 📋 待办 | ③.1 B-260619-04 ToolCard / ③.2 REFACTOR-260619-04 审批状态机 | — |
|
||||
| **父④** F-09 per-conv | 📋 待办 | ④.1 streaming/currentText per-conv | — |
|
||||
| **父⑤** 灵感模块 | 🔨 进行中 | ⑤.1 软删除✅ / ①.4 雷达图✅ / ⑤.2 #05 record_to_idea✅ / #06 配置化·#09·#10 表单待办 / #07 promote补偿待定(soft_delete 污染回收站 vs purge 内部回滚干净?) | ⑤.2#07→②.1 |
|
||||
| **父⑤** 灵感模块 | ✅ 完成 | ⑤.1 软删除✅ / ①.4 雷达图✅ / ⑤.2 #05✅/#06拆const✅/#09/#10表单(逗号tags)✅ / #07 DEC-02保留purge(不改) / 附:priority_from_i32跨层映射修复(对齐前端0=critical) | #07→②.1 |
|
||||
| **父⑥** Phase2-5 | 📋 待办 | ⑥.1事件流 / ⑥.2基础设施 / ⑥.3注入 / ⑥.4前端 | ⑥→父②, ⑥.3→父②+父④+G1 |
|
||||
| **父⑦** 技术债 | 📋 待办 | SMELL-P1-6 / conditions / CR缓存 / UX分页 / 审批超时 / miniapp / 双监听器 | — (穿插) |
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ pub async fn create_idea(
|
||||
// 防 LLM/前端传 99 等被静默吞为 Critical。
|
||||
if !(0..=3).contains(&input.priority) {
|
||||
return Err(format!(
|
||||
"priority 必须在 0..=3 (Low=0/Medium=1/High=2/Critical=3),收到 {}",
|
||||
"priority 必须在 0..=3 (0=critical/1=high/2=medium/3=low,对齐前端约定 api/types.ts:141),收到 {}",
|
||||
input.priority
|
||||
));
|
||||
}
|
||||
@@ -425,13 +425,18 @@ fn record_to_idea(record: &IdeaRecord) -> Idea {
|
||||
}
|
||||
}
|
||||
|
||||
/// i32 优先级 → Priority 枚举(与 df-types 枚举值一致:Low=0/Medium=1/High=2/Critical=3)
|
||||
/// i32 优先级 → Priority 枚举(对齐**前端约定** 0=critical/1=high/2=medium/3=low,
|
||||
/// 见 api/types.ts:141 + Tasks.vue option + constants/project.ts PRIORITY_CLASS)。
|
||||
/// 注:df-types Priority 枚举 discriminant 是 Low=0/Critical=3(历史定义,与前端相反),
|
||||
/// 故本函数手动映射对齐前端语义,不靠 discriminant。Priority as_str 序列化("low"/"critical")
|
||||
/// 不受影响。verify agent 发现的预存跨层 bug(父⑤⑤.2 收尾揪出):原 0=>Low 致 Ideas
|
||||
/// 表单选 Critical(P0) 存 Low,标签与实际存储/评分相反。
|
||||
fn priority_from_i32(p: i32) -> Priority {
|
||||
match p {
|
||||
0 => Priority::Low,
|
||||
2 => Priority::High,
|
||||
x if x >= 3 => Priority::Critical,
|
||||
_ => Priority::Medium,
|
||||
0 => Priority::Critical,
|
||||
1 => Priority::High,
|
||||
2 => Priority::Medium,
|
||||
_ => Priority::Low,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,8 +112,17 @@ export default {
|
||||
captureTitle: '✨ Capture New Idea',
|
||||
fieldTitle: 'Title',
|
||||
fieldDesc: 'Description',
|
||||
fieldTags: 'Tags',
|
||||
fieldPriority: 'Priority',
|
||||
fieldSource: 'Source',
|
||||
titlePlaceholder: 'Describe your idea in one sentence...',
|
||||
descPlaceholder: 'More details (optional)...',
|
||||
tagsPlaceholder: 'Comma-separated, e.g. tools, efficiency, automation',
|
||||
sourcePlaceholder: 'Source (optional, e.g. user feedback, competitor analysis)',
|
||||
priorityCritical: 'Critical',
|
||||
priorityHigh: 'High',
|
||||
priorityMedium: 'Medium',
|
||||
priorityLow: 'Low',
|
||||
|
||||
// Deep link not found
|
||||
notFound: 'Idea not found or deleted',
|
||||
|
||||
@@ -112,8 +112,17 @@ export default {
|
||||
captureTitle: '✨ 捕捉新灵感',
|
||||
fieldTitle: '标题',
|
||||
fieldDesc: '描述',
|
||||
fieldTags: '标签',
|
||||
fieldPriority: '优先级',
|
||||
fieldSource: '来源',
|
||||
titlePlaceholder: '一句话描述你的灵感...',
|
||||
descPlaceholder: '详细说明(可选)...',
|
||||
tagsPlaceholder: '用逗号分隔,如: 工具,效率,自动化',
|
||||
sourcePlaceholder: '来源(可选,如:用户反馈、竞品分析)',
|
||||
priorityCritical: '紧急',
|
||||
priorityHigh: '高',
|
||||
priorityMedium: '中',
|
||||
priorityLow: '低',
|
||||
|
||||
// 深链不存在提示
|
||||
notFound: '灵感不存在或已删除',
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
- 仅提取 ≥2 处逐字相同的规则(已 grep 比对一致)
|
||||
- scoped 故意 override 全局值的(如 .empty-hint padding 24px vs 全局 12px)
|
||||
不提取,保留各组件 scoped 覆盖(零视觉变化)
|
||||
- global.css 已有的 .btn/.modal-*/.page-header/.empty-hint/.empty-state
|
||||
- global.css 已有的 .btn / .modal 系列 / .page-header / .empty-hint / .empty-state
|
||||
不在此重复,仅补充尚未全局化的类
|
||||
|
||||
落地批:DRY 收口(B-260619)
|
||||
|
||||
@@ -112,6 +112,17 @@
|
||||
<input v-model="newIdeaTitle" :placeholder="$t('ideas.titlePlaceholder')" @keyup.enter="confirmCapture" />
|
||||
<label style="font-size:12px;color:var(--df-text-secondary);margin-bottom:4px;display:block">{{ $t('ideas.fieldDesc') }}</label>
|
||||
<textarea v-model="newIdeaDesc" :placeholder="$t('ideas.descPlaceholder')" rows="3" style="resize:vertical"></textarea>
|
||||
<label style="font-size:12px;color:var(--df-text-secondary);margin-bottom:4px;display:block">{{ $t('ideas.fieldTags') }}</label>
|
||||
<input v-model="newIdeaTags" :placeholder="$t('ideas.tagsPlaceholder')" />
|
||||
<label style="font-size:12px;color:var(--df-text-secondary);margin-bottom:4px;display:block">{{ $t('ideas.fieldPriority') }}</label>
|
||||
<select v-model.number="newIdeaPriority" class="modal-select">
|
||||
<option :value="0">{{ $t('ideas.priorityCritical') }} (P0)</option>
|
||||
<option :value="1">{{ $t('ideas.priorityHigh') }} (P1)</option>
|
||||
<option :value="2">{{ $t('ideas.priorityMedium') }} (P2)</option>
|
||||
<option :value="3">{{ $t('ideas.priorityLow') }} (P3)</option>
|
||||
</select>
|
||||
<label style="font-size:12px;color:var(--df-text-secondary);margin-bottom:4px;display:block">{{ $t('ideas.fieldSource') }}</label>
|
||||
<input v-model="newIdeaSource" :placeholder="$t('ideas.sourcePlaceholder')" />
|
||||
<div class="modal-actions">
|
||||
<button class="btn-cancel" @click="showCaptureModal = false">{{ $t('common.cancel') }}</button>
|
||||
<button class="btn-confirm" :disabled="creating" @click="confirmCapture">
|
||||
@@ -164,6 +175,9 @@ const pageSize = ref(0)
|
||||
const showCaptureModal = ref(false)
|
||||
const newIdeaTitle = ref('')
|
||||
const newIdeaDesc = ref('')
|
||||
const newIdeaTags = ref('') // 逗号分隔输入(对标 KnowledgeDetail.vue tagsInput)
|
||||
const newIdeaPriority = ref(2) // 默认 medium,对标 CreateIdeaInput 0-3
|
||||
const newIdeaSource = ref('')
|
||||
|
||||
// ── 异步按钮禁用态(防双击重复提交,对齐 ProviderPanel.vue saving ref) ──
|
||||
const creating = ref(false)
|
||||
@@ -274,6 +288,9 @@ function scoreClass(score: number | null) {
|
||||
function openCaptureModal() {
|
||||
newIdeaTitle.value = ''
|
||||
newIdeaDesc.value = ''
|
||||
newIdeaTags.value = ''
|
||||
newIdeaPriority.value = 2
|
||||
newIdeaSource.value = ''
|
||||
showCaptureModal.value = true
|
||||
}
|
||||
|
||||
@@ -281,9 +298,14 @@ async function confirmCapture() {
|
||||
if (!newIdeaTitle.value.trim() || creating.value) return
|
||||
creating.value = true
|
||||
try {
|
||||
// tags 逗号分隔 → JSON 字符串(对标 KnowledgeDetail.vue saveEdit split+trim+filter 模式)
|
||||
const tags = newIdeaTags.value.split(',').map(t => t.trim()).filter(Boolean)
|
||||
await store.createIdea({
|
||||
title: newIdeaTitle.value.trim(),
|
||||
description: newIdeaDesc.value.trim() || undefined,
|
||||
priority: newIdeaPriority.value,
|
||||
tags: JSON.stringify(tags),
|
||||
source: newIdeaSource.value.trim() || undefined,
|
||||
})
|
||||
showCaptureModal.value = false
|
||||
} finally {
|
||||
@@ -566,6 +588,7 @@ watch(() => route.params.id, (id) => {
|
||||
.modal-box { background: var(--df-bg-card); border: 0.5px solid var(--df-border); border-radius: var(--df-radius-lg); padding: 24px; min-width: 360px; max-width: 90vw; }
|
||||
.modal-box h3 { color: var(--df-text); margin-bottom: var(--df-gap-head); }
|
||||
.modal-box input, .modal-box textarea { width: 100%; padding: 8px 12px; border: 0.5px solid var(--df-border); border-radius: var(--df-radius-sm); background: var(--df-bg); color: var(--df-text); font-size: 13px; margin-bottom: 12px; box-sizing: border-box; font-family: inherit; }
|
||||
.modal-box .modal-select { width: 100%; padding: 8px 12px; border: 0.5px solid var(--df-border); border-radius: var(--df-radius-sm); background: var(--df-bg); color: var(--df-text); font-size: 13px; margin-bottom: 12px; box-sizing: border-box; font-family: inherit; }
|
||||
.modal-box .modal-actions { display: flex; gap: 8px; justify-content: flex-end; }
|
||||
.modal-box .btn-cancel { padding: 6px 16px; border: 0.5px solid var(--df-border); border-radius: var(--df-radius-sm); background: transparent; color: var(--df-text-secondary); cursor: pointer; }
|
||||
.modal-box .btn-confirm { padding: 6px 16px; border: none; border-radius: var(--df-radius-sm); background: var(--df-accent); color: #fff; cursor: pointer; }
|
||||
|
||||
Reference in New Issue
Block a user