@starwish-ai/xingyuan-dsh 0.5.9 → 0.6.0
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/CHANGELOG.md +45 -0
- package/README.en-US.md +1 -0
- package/README.md +2 -0
- package/lib/client/cards.js +3 -1
- package/lib/client/chart-labels.js +4 -5
- package/lib/client/i18n.js +21 -10
- package/lib/client/pages/wishes.js +43 -31
- package/lib/client.js +59 -41
- package/lib/growth.js +3 -1
- package/lib/preset/charts.js +11 -19
- package/lib/preset/prompts.js +23 -6
- package/lib/preset/tools.js +198 -33
- package/lib/routes/api.js +4 -0
- package/lib/routes/pages-html.js +2 -2
- package/lib/store.js +82 -22
- package/lib/types/client/chart-labels.d.ts +1 -1
- package/lib/types/client/i18n.d.ts +10 -5
- package/lib/types/client/pages/types.d.ts +4 -0
- package/lib/types/events.d.ts +10 -0
- package/lib/types/store.d.ts +36 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,51 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.6.0] - 2026-09-05
|
|
9
|
+
|
|
10
|
+
> **Note:** behavioral revision — long-term ratios (wish progress, progress charts) join
|
|
11
|
+
> the commitment semantics: unclaimed tasks no longer count in the denominator, and
|
|
12
|
+
> achievement additionally requires zero unclaimed tasks. No data format change; upgrades
|
|
13
|
+
> from 0.5.x are seamless — the switch can only raise progress, and every newly-qualifying
|
|
14
|
+
> wish carries unclaimed candidates that the achievement gate holds back (nothing archives
|
|
15
|
+
> by accident).
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Achievement gate and the "wrapping up" state**: a wish achieves only at 100% progress
|
|
19
|
+
with no unclaimed tasks (single formula `wishProgressFromAgg` shared by the write path and
|
|
20
|
+
the read side). Full progress with unclaimed tasks parks the wish at "wrapping up" — the
|
|
21
|
+
bar is full, the wish is not archived — and the card shows the pending count with the
|
|
22
|
+
guidance line "claim to continue, or delete to achieve". The call is always the user's:
|
|
23
|
+
the assistant never claims or deletes on its own to force achievement.
|
|
24
|
+
- **Gate flips always reach the model**: check-in / claim / update / cancel / delete / create
|
|
25
|
+
(including batches) compare before/after wish views and, on achieve / wrap-up / rollback,
|
|
26
|
+
say so in the tool reply and re-emit the wish event, so chat card, reply and wishes page
|
|
27
|
+
stay in sync. Count drift without a flip also re-emits (the "N to claim" badge never
|
|
28
|
+
freezes).
|
|
29
|
+
- **Single-source display bits**: `pendingCount` / `settled` / `planning` / `achieved` are
|
|
30
|
+
produced by `freshWishes` and delivered via `/api/wishes` and `xingyuan/wish` events;
|
|
31
|
+
replays of older events degrade honestly to a plain progress display.
|
|
32
|
+
- **Wording standard**: all user-facing and verbatim-relay surfaces (i18n, tool replies,
|
|
33
|
+
prompt guides, standalone pages) say "待领取的任务" / "待收尾" — no internal jargon.
|
|
34
|
+
Locked by a banned-word test (`test/prompts.test.ts`) and a shared guidance-line constant.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- `wishProgress` and `taskCompletionRate` now divide by claimed-task days only; the
|
|
38
|
+
subtitle is fixed to "已领取任务" (the conditional "含未领取任务" wording is gone —
|
|
39
|
+
one metric, one meaning).
|
|
40
|
+
- Growth-page achievement counts and the `wishAchievement` chart consume the derived
|
|
41
|
+
achieved bit instead of `progress >= 100`, matching the wish card exactly.
|
|
42
|
+
- The session-opening overview reads fresh derived views (not the stored `archived` bit)
|
|
43
|
+
and distinguishes "all wishes achieved" from "no wishes yet".
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
- Creating a bare wish showed "进度 0%" on the chat card while the wishes page said
|
|
47
|
+
"计划中" (the create event dropped the derived bits).
|
|
48
|
+
- Claiming a standalone task (no parent wish) claimed its days were folded into "wish
|
|
49
|
+
progress".
|
|
50
|
+
- Batch-delete replies glued the achievement guidance onto the summary line; batch paths
|
|
51
|
+
no longer re-scan the task table per wish on the after side.
|
|
52
|
+
|
|
8
53
|
## [0.5.9] - 2026-09-04
|
|
9
54
|
|
|
10
55
|
> **Note:** this release migrates the plugin to DeepSeek Harness `0.1.2-rc.1`
|
package/README.en-US.md
CHANGED
|
@@ -12,6 +12,7 @@ XingYuan is a wish & habit-building companion, shipped as a DeepSeek Harness plu
|
|
|
12
12
|
|
|
13
13
|
- **Conversation-driven**: create wishes / tasks in natural language with duplicate detection
|
|
14
14
|
- **Opportunity-day check-ins**: check-in, make-up, cancel, and future pre-checks; progress always recomputed against today
|
|
15
|
+
- **Commitment-based progress**: only claimed tasks count; a wish achieves when everything committed is done and nothing is left to claim — full progress with unclaimed tasks leaves it "wrapping up" for your call (claim to continue, or delete to achieve), never nagging, never deciding for you
|
|
15
16
|
- **Micro-actions**: break an overwhelming goal into 3–7 tiny steps and walk through them guided
|
|
16
17
|
- **Growth system**: levels Lv.1–Lv.10, streak bonuses, stat cards, 30-day bar chart
|
|
17
18
|
- **Session view tabs**: Today / Wishes / Tasks / Calendar / Growth / Memory — buttons call action endpoints directly; shown only in XingYuan-preset sessions by default, switchable to always show/hide and per-tab in Settings
|
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
- **对话驱动**:自然语言创建愿望 / 任务,自动查重相似项
|
|
14
14
|
- **机会日打卡**:周期任务打卡、补卡、取消、未来预勾,进度跨天恒为最新
|
|
15
|
+
- **承诺口径进度**:只有领取的任务算进度;达成 = 承诺全部完成且没有待领取的任务——
|
|
16
|
+
做完但有没领的,愿望停在「待收尾」等你决定(领了继续,或删掉就达成),不催也不代劳
|
|
15
17
|
- **微行动拆解**:把无从下手的目标拆成 3–7 个小步,引导式逐个完成或跳过
|
|
16
18
|
- **成长体系**:等级 Lv.1–Lv.10、连续加成、统计卡与近 30 天柱状图
|
|
17
19
|
- **会话视图页**:今日 / 愿望 / 任务 / 日历 / 成长 / 记忆 六个标签页,页面按钮直连动作接口;
|
package/lib/client/cards.js
CHANGED
|
@@ -22,9 +22,11 @@ function WishView(props) {
|
|
|
22
22
|
style: categoryVars(wish.wish.colorKey, wish.wish.categoryName),
|
|
23
23
|
}, t('badge.wish')), createElement('span', { className: 'xy-title' }, deleted ? t('state.deleted', { title: wish.wish.title }) : wish.wish.title)),
|
|
24
24
|
// 已删除卡不再渲染活体进度(删除态 × 进度百分比自相矛盾)
|
|
25
|
+
// 三态位(achieved/planning/settled/pendingCount)来自事件载荷(wishProgressFromAgg 派生),
|
|
26
|
+
// 与愿望页/工具回包同一称呼;旧事件无这些位则如实降级为纯「进度 X%」显示(不重建谓词,§5.2 规则 7)
|
|
25
27
|
createElement('div', { className: 'xy-meta' }, deleted
|
|
26
28
|
? wish.wish.categoryName
|
|
27
|
-
: `${wish.wish.categoryName} · ${t('wish.progress', { percent: wish.wish.progress })}`));
|
|
29
|
+
: `${wish.wish.categoryName} · ${wish.wish.achieved ? t('wish.achieved') : wish.wish.planning ? t('wish.planning') : t('wish.progress', { percent: wish.wish.progress })}${wish.wish.pendingCount ? ` · ${t('wish.pendingCount', { n: wish.wish.pendingCount })}` : ''}${wish.wish.settled ? ` · ${t('wish.settled')}` : ''}`));
|
|
28
30
|
}
|
|
29
31
|
function TaskView(props) {
|
|
30
32
|
const t = useXyT();
|
|
@@ -81,12 +81,11 @@ export const CHART_SUBTITLE_KEYS = {
|
|
|
81
81
|
'按任务 TopN': 'chart.subtitle.byTaskTopN',
|
|
82
82
|
'按愿望 TopN': 'chart.subtitle.byWishTopN',
|
|
83
83
|
'按实际打卡时刻分桶;过滤范围为打卡日区间': 'chart.subtitle.byHourBucket',
|
|
84
|
-
'
|
|
85
|
-
'%(应打天数完成率,含未领取任务)': 'chart.subtitle.progressPercentPending',
|
|
84
|
+
'%(已领取任务完成率)': 'chart.subtitle.claimedPercent',
|
|
86
85
|
};
|
|
87
86
|
/**
|
|
88
87
|
* 副标题本地化:固定短语查表;含数字的模板(近 N 天 / A/B 天 / 当前 A 天 / 最长 B 天 /
|
|
89
|
-
*
|
|
88
|
+
* 已领取任务口径)按正则重排——服务端模板变化时未知副标题原样回显(诚实降级)。
|
|
90
89
|
*/
|
|
91
90
|
export function localizeChartSubtitle(subtitle) {
|
|
92
91
|
const fixed = CHART_SUBTITLE_KEYS[subtitle];
|
|
@@ -95,9 +94,9 @@ export function localizeChartSubtitle(subtitle) {
|
|
|
95
94
|
let m = /^近 (\d+) 天$/.exec(subtitle);
|
|
96
95
|
if (m !== null)
|
|
97
96
|
return t('chart.subtitle.lastDays', { n: m[1] });
|
|
98
|
-
m = /^(\d+)\/(\d+)
|
|
97
|
+
m = /^(\d+)\/(\d+) 天(已领取任务)$/.exec(subtitle);
|
|
99
98
|
if (m !== null)
|
|
100
|
-
return t('chart.subtitle.
|
|
99
|
+
return t('chart.subtitle.daysClaimed', { done: m[1], due: m[2] });
|
|
101
100
|
m = /^(\d+)\/(\d+) 天$/.exec(subtitle);
|
|
102
101
|
if (m !== null)
|
|
103
102
|
return t('chart.subtitle.daysRatio', { done: m[1], due: m[2] });
|
package/lib/client/i18n.js
CHANGED
|
@@ -58,11 +58,18 @@ export const ZH_DICT = {
|
|
|
58
58
|
'state.done': '已完成',
|
|
59
59
|
// --- 愿望卡 / 愿望页 ---
|
|
60
60
|
'wish.progress': '进度 {percent}%',
|
|
61
|
-
'wish.
|
|
61
|
+
'wish.planning': '计划中',
|
|
62
|
+
'wish.achieved': '已达成 🎉',
|
|
63
|
+
'wish.pendingCount': '{n} 个任务待领取',
|
|
64
|
+
'wish.settled': '待收尾',
|
|
65
|
+
'wish.settleHint': '答应自己的都做完了——待领取的领了继续,或删掉就达成',
|
|
62
66
|
'wish.eta': '预计完成 {date}',
|
|
63
67
|
'wish.noTasks': '暂无下属任务——在对话里告诉我,让我为它推荐打卡任务',
|
|
64
68
|
'wish.pageTitle': '我的愿望',
|
|
65
|
-
'wish.summary': '{active} 个进行中{
|
|
69
|
+
'wish.summary': '{active} 个进行中{planning}{settled}{achieved}',
|
|
70
|
+
'wish.planningSuffix': ' · {n} 个计划中',
|
|
71
|
+
'wish.settledSuffix': ' · {n} 个待收尾',
|
|
72
|
+
'wish.achievedSuffix': ' · {n} 个已达成',
|
|
66
73
|
'wish.sectionAchieved': '已达成',
|
|
67
74
|
'wish.empty.title': '还没有愿望',
|
|
68
75
|
'wish.empty.hint': '告诉我想实现什么,我来帮你拆解成可坚持的计划。',
|
|
@@ -343,10 +350,9 @@ export const ZH_DICT = {
|
|
|
343
350
|
'chart.subtitle.byTaskTopN': '按任务 TopN',
|
|
344
351
|
'chart.subtitle.byWishTopN': '按愿望 TopN',
|
|
345
352
|
'chart.subtitle.byHourBucket': '按实际打卡时刻分桶;过滤范围为打卡日区间',
|
|
346
|
-
'chart.subtitle.
|
|
347
|
-
'chart.subtitle.progressPercentPending': '%(应打天数完成率,含未领取任务)',
|
|
353
|
+
'chart.subtitle.claimedPercent': '%(已领取任务完成率)',
|
|
348
354
|
'chart.subtitle.lastDays': '近 {n} 天',
|
|
349
|
-
'chart.subtitle.
|
|
355
|
+
'chart.subtitle.daysClaimed': '{done}/{due} 天(已领取任务)',
|
|
350
356
|
'chart.subtitle.daysRatio': '{done}/{due} 天',
|
|
351
357
|
'chart.subtitle.streak': '当前 {current} 天 / 最长 {best} 天',
|
|
352
358
|
// --- 详情聚合视图 ---
|
|
@@ -480,11 +486,17 @@ const EN_DICT = {
|
|
|
480
486
|
'state.deleted': 'Deleted “{title}”',
|
|
481
487
|
'state.done': 'Done',
|
|
482
488
|
'wish.progress': 'Progress {percent}%',
|
|
483
|
-
'wish.
|
|
489
|
+
'wish.planning': 'Planning',
|
|
490
|
+
'wish.achieved': 'Achieved 🎉',
|
|
491
|
+
'wish.pendingCount': '{n} to claim',
|
|
492
|
+
'wish.settled': 'Wrapping up',
|
|
493
|
+
'wish.settleHint': 'Everything you committed to is done — claim the rest to keep going, or delete them to achieve the wish',
|
|
484
494
|
'wish.eta': 'Target date {date}',
|
|
485
495
|
'wish.noTasks': 'No tasks yet — tell me in chat and I will suggest check-in tasks for it',
|
|
486
496
|
'wish.pageTitle': 'My Wishes',
|
|
487
|
-
'wish.summary': '{active} in progress{achieved}',
|
|
497
|
+
'wish.summary': '{active} in progress{planning}{settled}{achieved}',
|
|
498
|
+
'wish.planningSuffix': ' · {n} planning',
|
|
499
|
+
'wish.settledSuffix': ' · {n} wrapping up',
|
|
488
500
|
'wish.achievedSuffix': ' · {n} achieved',
|
|
489
501
|
'wish.sectionAchieved': 'Achieved',
|
|
490
502
|
'wish.empty.title': 'No wishes yet',
|
|
@@ -751,10 +763,9 @@ const EN_DICT = {
|
|
|
751
763
|
'chart.subtitle.byTaskTopN': 'By task (top N)',
|
|
752
764
|
'chart.subtitle.byWishTopN': 'By wish (top N)',
|
|
753
765
|
'chart.subtitle.byHourBucket': 'Bucketed by actual check-in time; filtered to the check-in date range',
|
|
754
|
-
'chart.subtitle.
|
|
755
|
-
'chart.subtitle.progressPercentPending': '% of scheduled-day completion (incl. unclaimed)',
|
|
766
|
+
'chart.subtitle.claimedPercent': '% of committed-task completion',
|
|
756
767
|
'chart.subtitle.lastDays': 'Last {n} days',
|
|
757
|
-
'chart.subtitle.
|
|
768
|
+
'chart.subtitle.daysClaimed': '{done}/{due} days (claimed tasks)',
|
|
758
769
|
'chart.subtitle.daysRatio': '{done}/{due} days',
|
|
759
770
|
'chart.subtitle.streak': 'Current {current}d / best {best}d',
|
|
760
771
|
'detail.grid.title': 'Check-in record',
|
|
@@ -39,8 +39,12 @@ export function WishesPage() {
|
|
|
39
39
|
const data = page.data;
|
|
40
40
|
if (data === undefined)
|
|
41
41
|
return createElement(PageSkeleton);
|
|
42
|
-
|
|
42
|
+
// 页头四态计数:进行中/计划中/待收尾/已达成各计各的,与卡头徽章同一称呼
|
|
43
|
+
// (消灭「摘要算进行中、卡上写计划中」的并见歧义);全部取服务端派生位,展示层不重建谓词
|
|
43
44
|
const achieved = data.wishes.filter((w) => w.archived);
|
|
45
|
+
const settledCount = data.wishes.filter((w) => w.settled).length;
|
|
46
|
+
const planningCount = data.wishes.filter((w) => !w.archived && w.planning).length;
|
|
47
|
+
const active = data.wishes.filter((w) => !w.archived);
|
|
44
48
|
/** 删除愿望(级联下属任务与打卡记录,服务端同一写路径):确认后直连动作并整页刷新。
|
|
45
49
|
* 行移除后触发按钮销毁、焦点落空——刷新完成后把焦点交给页面标题兜底。 */
|
|
46
50
|
const removeWish = (wish) => {
|
|
@@ -70,37 +74,45 @@ export function WishesPage() {
|
|
|
70
74
|
? [line, createElement(TaskDetailPanel, { key: `d-${task.taskId}`, id: `xy-task-detail-${task.taskId}`, taskId: task.taskId, today: data.today, onChanged: page.reload })]
|
|
71
75
|
: [line];
|
|
72
76
|
});
|
|
73
|
-
const card = (wish) =>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
'
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
className: 'xy-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
'
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
77
|
+
const card = (wish) => {
|
|
78
|
+
// 承诺口径(§5.2 规则 7):进度分母只计已领取任务。展示三态:
|
|
79
|
+
// 计划中(无已领取任务)/ 进行中(正常百分比)/ 待结算(满进度而有候选——不归档,附收尾指引)。
|
|
80
|
+
// settled/pendingCount/planning 直取服务端 wishProgressFromAgg 派生位(随 /api/wishes 下发),
|
|
81
|
+
// 展示层禁止重建 `progress>=100 && 有候选` 闸门或 planning 谓词(单一判定源,防与工具回包分叉)
|
|
82
|
+
const { pendingCount, settled, planning } = wish;
|
|
83
|
+
const progressLabel = planning ? t('wish.planning') : t('wish.progress', { percent: wish.progress });
|
|
84
|
+
const pendingMeta = pendingCount > 0 ? ` · ${t('wish.pendingCount', { n: pendingCount })}` : '';
|
|
85
|
+
return createElement('div', { key: wish.wishId, className: 'xy-wishcard' }, createElement('div', { className: 'xy-card-head' }, createElement('span', {
|
|
86
|
+
className: 'xy-badge xy-badge-cat',
|
|
87
|
+
style: categoryVars(wish.colorKey, wish.categoryName),
|
|
88
|
+
}, wish.categoryName), createElement('span', { className: 'xy-title' }, wish.title), createElement('span', { className: 'xy-progress-num' }, progressLabel, pendingCount > 0
|
|
89
|
+
? createElement('span', { className: 'xy-meta' }, pendingMeta)
|
|
90
|
+
: null), createElement('button', {
|
|
91
|
+
className: 'xy-btn xy-btn-danger xy-btn-icon',
|
|
92
|
+
disabled: deleting,
|
|
93
|
+
'aria-label': t('common.delete') + ' · ' + wish.title,
|
|
94
|
+
title: t('common.delete'),
|
|
95
|
+
onClick: () => removeWish(wish),
|
|
96
|
+
}, createElement(IconTrash))), wish.description !== undefined ? createElement('div', { className: 'xy-meta' }, wish.description) : null, wish.estimatedCompletionDate !== undefined
|
|
97
|
+
? createElement('div', { className: 'xy-meta' }, t('wish.eta', { date: formatMediumDate(wish.estimatedCompletionDate) }))
|
|
98
|
+
: null,
|
|
99
|
+
// 与今日页 hero 进度条同一语义语法:读屏可感知进度(role+valuenow),不只是一根哑条
|
|
100
|
+
createElement('div', {
|
|
101
|
+
className: 'xy-bar',
|
|
102
|
+
role: 'progressbar',
|
|
103
|
+
'aria-valuemin': 0,
|
|
104
|
+
'aria-valuemax': 100,
|
|
105
|
+
'aria-valuenow': wish.progress,
|
|
106
|
+
'aria-label': progressLabel + pendingMeta,
|
|
107
|
+
}, createElement('div', { className: 'xy-bar-fill', style: { transform: `scaleX(${Math.min(wish.progress, 100) / 100})` } })), settled ? createElement('div', { className: 'xy-meta' }, t('wish.settleHint')) : null, wish.tasks.length > 0
|
|
108
|
+
? createElement('div', { className: 'xy-wishtasks' }, ...wishTasks(wish))
|
|
109
|
+
: createElement('div', { className: 'xy-meta' }, t('wish.noTasks')));
|
|
110
|
+
};
|
|
102
111
|
return createElement('div', { className: 'xy-page', ref: stabilize }, page.error !== undefined ? createElement(StaleBanner, { onRetry: () => void page.reload() }) : null, showNoPresetHint ? createElement('p', { className: 'xy-hint' }, t('today.noPresetHint')) : null, createElement('div', { className: 'xy-page-head' }, createElement('h2', { className: 'xy-page-title' }, t('wish.pageTitle')), createElement('span', { className: 'xy-meta' }, t('wish.summary', {
|
|
103
|
-
|
|
112
|
+
// 四态计数各计各的(进行中扣除计划中与待收尾,不相套);列表渲染仍归未完成组(未完成≠进行中计数)
|
|
113
|
+
active: active.length - settledCount - planningCount,
|
|
114
|
+
planning: planningCount > 0 ? t('wish.planningSuffix', { n: planningCount }) : '',
|
|
115
|
+
settled: settledCount > 0 ? t('wish.settledSuffix', { n: settledCount }) : '',
|
|
104
116
|
achieved: achieved.length > 0 ? t('wish.achievedSuffix', { n: achieved.length }) : '',
|
|
105
117
|
})),
|
|
106
118
|
// 动作组整体右置(单一 margin-left:auto),避免多按钮各自 auto margin 平分剩余空间
|
package/lib/client.js
CHANGED
|
@@ -61,11 +61,17 @@ const ZH_DICT = {
|
|
|
61
61
|
"state.deleted": "已删除「{title}」",
|
|
62
62
|
"state.done": "已完成",
|
|
63
63
|
"wish.progress": "进度 {percent}%",
|
|
64
|
-
"wish.
|
|
64
|
+
"wish.planning": "计划中",
|
|
65
|
+
"wish.achieved": "已达成 🎉",
|
|
66
|
+
"wish.pendingCount": "{n} 个任务待领取",
|
|
67
|
+
"wish.settled": "待收尾",
|
|
68
|
+
"wish.settleHint": "答应自己的都做完了——待领取的领了继续,或删掉就达成",
|
|
65
69
|
"wish.eta": "预计完成 {date}",
|
|
66
70
|
"wish.noTasks": "暂无下属任务——在对话里告诉我,让我为它推荐打卡任务",
|
|
67
71
|
"wish.pageTitle": "我的愿望",
|
|
68
|
-
"wish.summary": "{active} 个进行中{achieved}",
|
|
72
|
+
"wish.summary": "{active} 个进行中{planning}{settled}{achieved}",
|
|
73
|
+
"wish.planningSuffix": " · {n} 个计划中",
|
|
74
|
+
"wish.settledSuffix": " · {n} 个待收尾",
|
|
69
75
|
"wish.achievedSuffix": " · {n} 个已达成",
|
|
70
76
|
"wish.sectionAchieved": "已达成",
|
|
71
77
|
"wish.empty.title": "还没有愿望",
|
|
@@ -332,10 +338,9 @@ const ZH_DICT = {
|
|
|
332
338
|
"chart.subtitle.byTaskTopN": "按任务 TopN",
|
|
333
339
|
"chart.subtitle.byWishTopN": "按愿望 TopN",
|
|
334
340
|
"chart.subtitle.byHourBucket": "按实际打卡时刻分桶;过滤范围为打卡日区间",
|
|
335
|
-
"chart.subtitle.
|
|
336
|
-
"chart.subtitle.progressPercentPending": "%(应打天数完成率,含未领取任务)",
|
|
341
|
+
"chart.subtitle.claimedPercent": "%(已领取任务完成率)",
|
|
337
342
|
"chart.subtitle.lastDays": "近 {n} 天",
|
|
338
|
-
"chart.subtitle.
|
|
343
|
+
"chart.subtitle.daysClaimed": "{done}/{due} 天(已领取任务)",
|
|
339
344
|
"chart.subtitle.daysRatio": "{done}/{due} 天",
|
|
340
345
|
"chart.subtitle.streak": "当前 {current} 天 / 最长 {best} 天",
|
|
341
346
|
"detail.grid.title": "打卡记录",
|
|
@@ -462,11 +467,17 @@ const EN_DICT = {
|
|
|
462
467
|
"state.deleted": "Deleted “{title}”",
|
|
463
468
|
"state.done": "Done",
|
|
464
469
|
"wish.progress": "Progress {percent}%",
|
|
465
|
-
"wish.
|
|
470
|
+
"wish.planning": "Planning",
|
|
471
|
+
"wish.achieved": "Achieved 🎉",
|
|
472
|
+
"wish.pendingCount": "{n} to claim",
|
|
473
|
+
"wish.settled": "Wrapping up",
|
|
474
|
+
"wish.settleHint": "Everything you committed to is done — claim the rest to keep going, or delete them to achieve the wish",
|
|
466
475
|
"wish.eta": "Target date {date}",
|
|
467
476
|
"wish.noTasks": "No tasks yet — tell me in chat and I will suggest check-in tasks for it",
|
|
468
477
|
"wish.pageTitle": "My Wishes",
|
|
469
|
-
"wish.summary": "{active} in progress{achieved}",
|
|
478
|
+
"wish.summary": "{active} in progress{planning}{settled}{achieved}",
|
|
479
|
+
"wish.planningSuffix": " · {n} planning",
|
|
480
|
+
"wish.settledSuffix": " · {n} wrapping up",
|
|
470
481
|
"wish.achievedSuffix": " · {n} achieved",
|
|
471
482
|
"wish.sectionAchieved": "Achieved",
|
|
472
483
|
"wish.empty.title": "No wishes yet",
|
|
@@ -733,10 +744,9 @@ const EN_DICT = {
|
|
|
733
744
|
"chart.subtitle.byTaskTopN": "By task (top N)",
|
|
734
745
|
"chart.subtitle.byWishTopN": "By wish (top N)",
|
|
735
746
|
"chart.subtitle.byHourBucket": "Bucketed by actual check-in time; filtered to the check-in date range",
|
|
736
|
-
"chart.subtitle.
|
|
737
|
-
"chart.subtitle.progressPercentPending": "% of scheduled-day completion (incl. unclaimed)",
|
|
747
|
+
"chart.subtitle.claimedPercent": "% of committed-task completion",
|
|
738
748
|
"chart.subtitle.lastDays": "Last {n} days",
|
|
739
|
-
"chart.subtitle.
|
|
749
|
+
"chart.subtitle.daysClaimed": "{done}/{due} days (claimed tasks)",
|
|
740
750
|
"chart.subtitle.daysRatio": "{done}/{due} days",
|
|
741
751
|
"chart.subtitle.streak": "Current {current}d / best {best}d",
|
|
742
752
|
"detail.grid.title": "Check-in record",
|
|
@@ -2107,20 +2117,19 @@ const CHART_SUBTITLE_KEYS = {
|
|
|
2107
2117
|
"按任务 TopN": "chart.subtitle.byTaskTopN",
|
|
2108
2118
|
"按愿望 TopN": "chart.subtitle.byWishTopN",
|
|
2109
2119
|
"按实际打卡时刻分桶;过滤范围为打卡日区间": "chart.subtitle.byHourBucket",
|
|
2110
|
-
"
|
|
2111
|
-
"%(应打天数完成率,含未领取任务)": "chart.subtitle.progressPercentPending"
|
|
2120
|
+
"%(已领取任务完成率)": "chart.subtitle.claimedPercent"
|
|
2112
2121
|
};
|
|
2113
2122
|
/**
|
|
2114
2123
|
* 副标题本地化:固定短语查表;含数字的模板(近 N 天 / A/B 天 / 当前 A 天 / 最长 B 天 /
|
|
2115
|
-
*
|
|
2124
|
+
* 已领取任务口径)按正则重排——服务端模板变化时未知副标题原样回显(诚实降级)。
|
|
2116
2125
|
*/
|
|
2117
2126
|
function localizeChartSubtitle(subtitle) {
|
|
2118
2127
|
const fixed = CHART_SUBTITLE_KEYS[subtitle];
|
|
2119
2128
|
if (fixed !== void 0) return t(fixed);
|
|
2120
2129
|
let m = /^近 (\d+) 天$/.exec(subtitle);
|
|
2121
2130
|
if (m !== null) return t("chart.subtitle.lastDays", { n: m[1] });
|
|
2122
|
-
m = /^(\d+)\/(\d+)
|
|
2123
|
-
if (m !== null) return t("chart.subtitle.
|
|
2131
|
+
m = /^(\d+)\/(\d+) 天(已领取任务)$/.exec(subtitle);
|
|
2132
|
+
if (m !== null) return t("chart.subtitle.daysClaimed", {
|
|
2124
2133
|
done: m[1],
|
|
2125
2134
|
due: m[2]
|
|
2126
2135
|
});
|
|
@@ -2392,7 +2401,7 @@ function WishView(props) {
|
|
|
2392
2401
|
return (0, react.createElement)("div", { className: `xy-card ${deleted ? "xy-deleted" : ""}` }, (0, react.createElement)("div", { className: "xy-card-head" }, (0, react.createElement)("span", {
|
|
2393
2402
|
className: "xy-badge xy-badge-cat",
|
|
2394
2403
|
style: categoryVars(wish.wish.colorKey, wish.wish.categoryName)
|
|
2395
|
-
}, t("badge.wish")), (0, react.createElement)("span", { className: "xy-title" }, deleted ? t("state.deleted", { title: wish.wish.title }) : wish.wish.title)), (0, react.createElement)("div", { className: "xy-meta" }, deleted ? wish.wish.categoryName : `${wish.wish.categoryName} · ${t("wish.progress", { percent: wish.wish.progress })}`));
|
|
2404
|
+
}, t("badge.wish")), (0, react.createElement)("span", { className: "xy-title" }, deleted ? t("state.deleted", { title: wish.wish.title }) : wish.wish.title)), (0, react.createElement)("div", { className: "xy-meta" }, deleted ? wish.wish.categoryName : `${wish.wish.categoryName} · ${wish.wish.achieved ? t("wish.achieved") : wish.wish.planning ? t("wish.planning") : t("wish.progress", { percent: wish.wish.progress })}${wish.wish.pendingCount ? ` · ${t("wish.pendingCount", { n: wish.wish.pendingCount })}` : ""}${wish.wish.settled ? ` · ${t("wish.settled")}` : ""}`));
|
|
2396
2405
|
}
|
|
2397
2406
|
function TaskView(props) {
|
|
2398
2407
|
const t = useXyT();
|
|
@@ -3660,8 +3669,10 @@ function WishesPage() {
|
|
|
3660
3669
|
});
|
|
3661
3670
|
const data = page.data;
|
|
3662
3671
|
if (data === void 0) return (0, react.createElement)(PageSkeleton);
|
|
3663
|
-
const active = data.wishes.filter((w) => !w.archived);
|
|
3664
3672
|
const achieved = data.wishes.filter((w) => w.archived);
|
|
3673
|
+
const settledCount = data.wishes.filter((w) => w.settled).length;
|
|
3674
|
+
const planningCount = data.wishes.filter((w) => !w.archived && w.planning).length;
|
|
3675
|
+
const active = data.wishes.filter((w) => !w.archived);
|
|
3665
3676
|
/** 删除愿望(级联下属任务与打卡记录,服务端同一写路径):确认后直连动作并整页刷新。
|
|
3666
3677
|
* 行移除后触发按钮销毁、焦点落空——刷新完成后把焦点交给页面标题兜底。 */
|
|
3667
3678
|
const removeWish = (wish) => {
|
|
@@ -3696,34 +3707,41 @@ function WishesPage() {
|
|
|
3696
3707
|
onChanged: page.reload
|
|
3697
3708
|
})] : [line];
|
|
3698
3709
|
});
|
|
3699
|
-
const card = (wish) =>
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
className: "xy-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
title:
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
"
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3710
|
+
const card = (wish) => {
|
|
3711
|
+
const { pendingCount, settled, planning } = wish;
|
|
3712
|
+
const progressLabel = planning ? t("wish.planning") : t("wish.progress", { percent: wish.progress });
|
|
3713
|
+
const pendingMeta = pendingCount > 0 ? ` · ${t("wish.pendingCount", { n: pendingCount })}` : "";
|
|
3714
|
+
return (0, react.createElement)("div", {
|
|
3715
|
+
key: wish.wishId,
|
|
3716
|
+
className: "xy-wishcard"
|
|
3717
|
+
}, (0, react.createElement)("div", { className: "xy-card-head" }, (0, react.createElement)("span", {
|
|
3718
|
+
className: "xy-badge xy-badge-cat",
|
|
3719
|
+
style: categoryVars(wish.colorKey, wish.categoryName)
|
|
3720
|
+
}, wish.categoryName), (0, react.createElement)("span", { className: "xy-title" }, wish.title), (0, react.createElement)("span", { className: "xy-progress-num" }, progressLabel, pendingCount > 0 ? (0, react.createElement)("span", { className: "xy-meta" }, pendingMeta) : null), (0, react.createElement)("button", {
|
|
3721
|
+
className: "xy-btn xy-btn-danger xy-btn-icon",
|
|
3722
|
+
disabled: deleting,
|
|
3723
|
+
"aria-label": t("common.delete") + " · " + wish.title,
|
|
3724
|
+
title: t("common.delete"),
|
|
3725
|
+
onClick: () => removeWish(wish)
|
|
3726
|
+
}, (0, react.createElement)(IconTrash))), wish.description !== void 0 ? (0, react.createElement)("div", { className: "xy-meta" }, wish.description) : null, wish.estimatedCompletionDate !== void 0 ? (0, react.createElement)("div", { className: "xy-meta" }, t("wish.eta", { date: formatMediumDate(wish.estimatedCompletionDate) })) : null, (0, react.createElement)("div", {
|
|
3727
|
+
className: "xy-bar",
|
|
3728
|
+
role: "progressbar",
|
|
3729
|
+
"aria-valuemin": 0,
|
|
3730
|
+
"aria-valuemax": 100,
|
|
3731
|
+
"aria-valuenow": wish.progress,
|
|
3732
|
+
"aria-label": progressLabel + pendingMeta
|
|
3733
|
+
}, (0, react.createElement)("div", {
|
|
3734
|
+
className: "xy-bar-fill",
|
|
3735
|
+
style: { transform: `scaleX(${Math.min(wish.progress, 100) / 100})` }
|
|
3736
|
+
})), settled ? (0, react.createElement)("div", { className: "xy-meta" }, t("wish.settleHint")) : null, wish.tasks.length > 0 ? (0, react.createElement)("div", { className: "xy-wishtasks" }, ...wishTasks(wish)) : (0, react.createElement)("div", { className: "xy-meta" }, t("wish.noTasks")));
|
|
3737
|
+
};
|
|
3722
3738
|
return (0, react.createElement)("div", {
|
|
3723
3739
|
className: "xy-page",
|
|
3724
3740
|
ref: stabilize
|
|
3725
3741
|
}, page.error !== void 0 ? (0, react.createElement)(StaleBanner, { onRetry: () => void page.reload() }) : null, showNoPresetHint ? (0, react.createElement)("p", { className: "xy-hint" }, t("today.noPresetHint")) : null, (0, react.createElement)("div", { className: "xy-page-head" }, (0, react.createElement)("h2", { className: "xy-page-title" }, t("wish.pageTitle")), (0, react.createElement)("span", { className: "xy-meta" }, t("wish.summary", {
|
|
3726
|
-
active: active.length,
|
|
3742
|
+
active: active.length - settledCount - planningCount,
|
|
3743
|
+
planning: planningCount > 0 ? t("wish.planningSuffix", { n: planningCount }) : "",
|
|
3744
|
+
settled: settledCount > 0 ? t("wish.settledSuffix", { n: settledCount }) : "",
|
|
3727
3745
|
achieved: achieved.length > 0 ? t("wish.achievedSuffix", { n: achieved.length }) : ""
|
|
3728
3746
|
})), (0, react.createElement)("div", { className: "xy-page-actions" }, (0, react.createElement)("button", {
|
|
3729
3747
|
className: "xy-btn",
|
package/lib/growth.js
CHANGED
|
@@ -117,7 +117,9 @@ export function resolveLevel(totalExperience) {
|
|
|
117
117
|
export function growthSummary(store, today = todayIso()) {
|
|
118
118
|
// 单遍任务索引批量新鲜化(freshWishes),替代逐愿望嵌套全表扫描 O(W×T)
|
|
119
119
|
const wishes = freshWishes(store);
|
|
120
|
-
|
|
120
|
+
// 达成口径与愿望卡一致(freshWishes 派生的 archived 谓词):承诺全部完成且无未处理候选——
|
|
121
|
+
// 禁止在此重复写 progress >= 100 谓词(候选拦达成会漏掉,见 §5.2 规则 7)
|
|
122
|
+
const completedWishes = wishes.filter((wish) => wish.archived).length;
|
|
121
123
|
let totalTasks = 0;
|
|
122
124
|
let completedTasks = 0;
|
|
123
125
|
for (const [, task] of store.domain.table('tasks').entries()) {
|
package/lib/preset/charts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { anchorOf, checkinCountIndex, freshTask, freshWishes, isClaimed } from '../store.js';
|
|
1
|
+
import { anchorOf, checkinCountIndex, claimedDaysSum, freshTask, freshWishes, isClaimed } from '../store.js';
|
|
2
2
|
import { computeCheckInStats } from '../growth.js';
|
|
3
3
|
import { calculateOpportunityDates, todayIso } from '../opportunity.js';
|
|
4
4
|
export const CHART_KEYS = [
|
|
@@ -137,23 +137,17 @@ export function buildChart(key, params, store, config, today = todayIso()) {
|
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
139
|
case 'taskCompletionRate': {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
for (const task of tasksOf(store, params.wishId)) {
|
|
144
|
-
required += task.requiredDays;
|
|
145
|
-
completed += task.completedDays;
|
|
146
|
-
if (!isClaimed(task))
|
|
147
|
-
hasPending = true;
|
|
148
|
-
}
|
|
140
|
+
// 承诺口径(§5.2 规则 7):分母只计已领取任务的应打天数——候选不进比率、不拖累完成率,
|
|
141
|
+
// 求和走 store.claimedDaysSum(与愿望进度公式族单一收口,消费侧不重复 isClaimed 聚合)
|
|
142
|
+
const { required, completed } = claimedDaysSum(tasksOf(store, params.wishId));
|
|
149
143
|
if (required <= 0)
|
|
150
144
|
return undefined;
|
|
151
145
|
const ratio = completed / required;
|
|
152
146
|
return {
|
|
153
147
|
chartKey: key,
|
|
154
148
|
title: params.title ?? '任务完成率',
|
|
155
|
-
//
|
|
156
|
-
subtitle: `${completed}/${required}
|
|
149
|
+
// 口径自述:副标题恒标注分母范围,读图无需区分有无候选(候选可见性由愿望卡与任务页承担)
|
|
150
|
+
subtitle: `${completed}/${required} 天(已领取任务)`,
|
|
157
151
|
chartType: 'arcbars',
|
|
158
152
|
data: [{ label: '完成率', value: completed, ratio }],
|
|
159
153
|
};
|
|
@@ -308,18 +302,16 @@ function buildTailChart(key, params, store, today, days, limit, config) {
|
|
|
308
302
|
return undefined;
|
|
309
303
|
const shown = wishes.slice().sort((a, b) => b.progress - a.progress).slice(0, limit);
|
|
310
304
|
const data = shown.map((wish) => ({ label: wish.title, value: wish.progress }));
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
.map((task) => task.wishId).filter((id) => id !== undefined));
|
|
315
|
-
const hasPending = shown.some((wish) => pendingWishIds.has(wish.wishId));
|
|
316
|
-
return { chartKey: key, title: params.title ?? '愿望进度', subtitle: hasPending ? '%(应打天数完成率,含未领取任务)' : '%(应打天数完成率)', chartType: 'bar', data };
|
|
305
|
+
// 承诺口径(§5.2 规则 7):分母只计已领取任务,候选不进比率、不拖累进度;
|
|
306
|
+
// 副标题恒标注口径,候选的可见性由愿望卡候选行与任务页待领取组承担
|
|
307
|
+
return { chartKey: key, title: params.title ?? '愿望进度', subtitle: '%(已领取任务完成率)', chartType: 'bar', data };
|
|
317
308
|
}
|
|
318
309
|
if (key === 'wishAchievement') {
|
|
319
310
|
const wishes = freshWishes(store);
|
|
320
311
|
if (!wishes.length)
|
|
321
312
|
return undefined;
|
|
322
|
-
|
|
313
|
+
// 达成口径与愿望卡一致(freshWishes 派生的 archived 谓词):承诺全部完成且无未处理候选
|
|
314
|
+
const achieved = wishes.filter((wish) => wish.archived).length;
|
|
323
315
|
return {
|
|
324
316
|
chartKey: key,
|
|
325
317
|
title: params.title ?? '愿望达成率',
|
package/lib/preset/prompts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CYCLE_LABELS, isClaimed, planForDay } from '../store.js';
|
|
1
|
+
import { CYCLE_LABELS, freshWishes, isClaimed, planForDay } from '../store.js';
|
|
2
2
|
import { todayIso } from '../opportunity.js';
|
|
3
3
|
/** 未领取任务单列的用户可见文案:提示词上下文与工具输出共用同一份,防口径漂移(§5.2 规则 7)。 */
|
|
4
4
|
export const UNCLAIMED_NOTE = '可领取后打卡;是否领取由用户决定,不得主动领取';
|
|
@@ -304,6 +304,13 @@ const WISH_GUIDE = `# 愿望操作指南
|
|
|
304
304
|
- 用户未提供预计完成日期时:按主题推算合理期限(语言/乐器/学习类 3-6 个月,健身/健康类 2-3 个月,工作类 3 个月,具体事项类 1 个月),格式 yyyy-MM-dd
|
|
305
305
|
- 用户已明确提供的信息(日期、描述)必须优先使用,不得覆盖;只在缺失时补全
|
|
306
306
|
|
|
307
|
+
## 进度与达成规则(转述数字时如实说明)
|
|
308
|
+
- 愿望进度 = 已领取任务的应打天数完成率;待领取的任务不算进进度——不拖累完成率,删除待领取的任务也不抬高进度
|
|
309
|
+
- 愿望达成 = 进度 100% 且没有待领取任务:都做完但还有待领取的任务时,愿望停在「待收尾」(进度条满了,先不归档)——向用户说明并让其决定:领了继续,或删掉就达成
|
|
310
|
+
- 不得为促成达成主动领取或删除任务(领取不可逆,删除是计划修订,都是用户的决定)
|
|
311
|
+
- 还没开始的愿望显示「计划中」;进度只反映已承诺的部分,待领取的任务在任务页「待领取」组可见
|
|
312
|
+
- 对用户说话用日常用语:统一说「待领取的任务」,不说「候选」「待结算」「口径」「锚点」这类内部词
|
|
313
|
+
|
|
307
314
|
## 注意事项
|
|
308
315
|
- 分类名称优先2-3个中文字符(如:学习、健康、工作)`;
|
|
309
316
|
const TASK_GUIDE = `# 任务操作指南
|
|
@@ -325,6 +332,8 @@ const TASK_GUIDE = `# 任务操作指南
|
|
|
325
332
|
## 状态流转
|
|
326
333
|
pending(待领取)→ claim_task → in_progress(进行中)→ check_in 达标 → closed(完结)
|
|
327
334
|
截止日过期未达标也会关闭(closed/expired);对已过期任务延长截止日即触发重新开始。
|
|
335
|
+
- 领取即扩大承诺范围:任务的应打天数计入所属愿望进度(进度只统计已领取任务),进度可能因此回落——回包会注明,如实转述
|
|
336
|
+
- 待领取的任务不计入愿望进度、不拖累完成率,但进度满了而还有待领取的任务时愿望不达成(停在「待收尾」,等用户决定领取或删除收尾)
|
|
328
337
|
|
|
329
338
|
## 字段自动补全
|
|
330
339
|
- 用户未指定截止日期时:按周期推算合理日期(daily→约30天后、weekly→8周后、monthly→3个月后、once→2周内),格式 yyyy-MM-dd 且不早于今天
|
|
@@ -381,7 +390,7 @@ const CHART_GUIDE = `# 图表展示指南(均为内部能力,通过 generate
|
|
|
381
390
|
- 「一般几点打卡/打卡时间规律」→ chartKey=checkinTimeDistribution(雷达,按时段)
|
|
382
391
|
- 「周几打卡最多/周活跃规律」→ chartKey=weeklyActivity(雷达,按星期)
|
|
383
392
|
|
|
384
|
-
##
|
|
393
|
+
## 数据规则与相互区别(易混淆图表,按维度选)
|
|
385
394
|
- checkinTrend vs checkinRateTrend:前者每日打卡次数,后者每日完成率(已打卡/应打卡)
|
|
386
395
|
- checkinTrend vs checkinCalendar:前者近N天逐日折线,后者自然日网格热力图
|
|
387
396
|
- checkinByCategory vs wishCategory:前者打卡次数按分类,后者愿望数量按分类
|
|
@@ -390,9 +399,9 @@ const CHART_GUIDE = `# 图表展示指南(均为内部能力,通过 generate
|
|
|
390
399
|
- wishProgress vs wishAchievement:前者逐愿望进度排行(条形),后者整体达成率(环状)
|
|
391
400
|
- checkinTimeDistribution vs weeklyActivity:前者一天内时段分布,后者星期几分布
|
|
392
401
|
|
|
393
|
-
##
|
|
402
|
+
## 统计规则(回复涉及数字时按此措辞,不臆测)
|
|
394
403
|
- 统计类图表只统计今天(含)以前的打卡;提前勾选的未来日期不计入统计,只体现在打卡日历热力图上
|
|
395
|
-
- taskCompletionRate
|
|
404
|
+
- taskCompletionRate 与 wishProgress 都只算已领取任务的应打天数(与愿望进度同一规则),待领取的不算进也不拖累完成率,副标题恒标注「已领取任务」——转述时说「只算已领取的任务」
|
|
396
405
|
- checkinRateTrend 的无安排日(当日没有应打卡任务)不产出比率点,不代表完成率为 0%
|
|
397
406
|
- checkinTrend/checkinCalendar 的取消打卡会即时反映(记录已撤销);但已生成的图表卡是生成时刻的快照,回放时以卡片标注的「生成于」为准,需要最新数据应重新生成
|
|
398
407
|
|
|
@@ -486,7 +495,11 @@ export function registerPrompts(ctx, config) {
|
|
|
486
495
|
const due = claimed.filter((item) => !item.checked);
|
|
487
496
|
const done = claimed.length - due.length;
|
|
488
497
|
const pending = plan.items.filter((item) => !isClaimed(item.task));
|
|
489
|
-
|
|
498
|
+
// 承诺口径读侧统一走 freshWishes:库存 progress/archived 只在写路径刷新,
|
|
499
|
+
// 达成闸门(候选拦达成)变更后库存位可与派生面分叉——上下文与列表/页面必须同源
|
|
500
|
+
const wishViews = freshWishes(store);
|
|
501
|
+
const openWishes = wishViews.filter((w) => !w.archived);
|
|
502
|
+
const settledWishes = wishViews.filter((w) => w.settled);
|
|
490
503
|
const parts = [`<today_context>`, `今天是 ${today}。`];
|
|
491
504
|
if (claimed.length > 0) {
|
|
492
505
|
parts.push(`今日打卡进度:${done}/${claimed.length}。`);
|
|
@@ -507,7 +520,11 @@ export function registerPrompts(ctx, config) {
|
|
|
507
520
|
}
|
|
508
521
|
}
|
|
509
522
|
if (openWishes.length > 0)
|
|
510
|
-
parts.push(`进行中的愿望 ${openWishes.length}
|
|
523
|
+
parts.push(`进行中的愿望 ${openWishes.length} 个${settledWishes.length > 0 ? `(其中 ${settledWishes.length} 个待收尾:答应自己的都做完了,还有任务待领取,等用户决定领了继续还是删掉达成)` : ''}。`);
|
|
524
|
+
// 全达成与从零两态分开:曾一律说「还没有任何愿望」,完成全部愿望的老用户
|
|
525
|
+
// 被模型当新客介绍产品、被引导说「第一个愿望」,如实说成就再谈下一步
|
|
526
|
+
else if (wishViews.length > 0)
|
|
527
|
+
parts.push(`用户的 ${wishViews.length} 个愿望均已达成:先肯定这份坚持,再自然地邀请许下一个新愿望。`);
|
|
511
528
|
else
|
|
512
529
|
parts.push('用户还没有任何愿望:新会话请友好自我介绍(你是星愿AI助手,帮用户拆解愿望、制定计划、坚持打卡),引导说出第一个愿望;已有会话则自然衔接。');
|
|
513
530
|
parts.push('</today_context>');
|