feat: ship v0.8.0 with writing logs and AI knowledge extraction

Deliver P5.2 writing day logs, cockpit heatmap, streak tracking, and chapter knowledge auto-extraction with merge review.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 18:26:29 +08:00
parent f331ddae86
commit a39e06ff07
51 changed files with 1436 additions and 89 deletions
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useBookStore } from '@renderer/stores/useBookStore'
import { useKnowledgeStore } from '@renderer/stores/useKnowledgeStore'
import { useSettingsStore } from '@renderer/stores/useSettingsStore'
import { ipcCall } from '@renderer/lib/ipc-client'
import { NamingCheckModal } from '@renderer/components/ai/NamingCheckModal'
import {
@@ -12,6 +13,8 @@ import {
export function KnowledgePanel(): React.JSX.Element {
const { t } = useTranslation()
const bookId = useBookStore((s) => s.currentBookId)
const selectedChapterId = useBookStore((s) => s.selectedChapterId)
const extractThreshold = useSettingsStore((s) => s.knowledgeExtractConfidenceThreshold)
const {
entries,
stats,
@@ -25,8 +28,11 @@ export function KnowledgePanel(): React.JSX.Element {
approve,
reject,
deleteEntry,
batchApprove
batchApprove,
extractChapter,
batchApproveHighConfidence
} = useKnowledgeStore()
const [extracting, setExtracting] = useState(false)
const [namingOpen, setNamingOpen] = useState(false)
const [selected, setSelected] = useState<Set<string>>(new Set())
const [pendingCount, setPendingCount] = useState(0)
@@ -71,6 +77,30 @@ export function KnowledgePanel(): React.JSX.Element {
>
{t('naming.open')}
</button>
{tab === 'pending' && bookId && (
<button
type="button"
className="btn"
data-testid="knowledge-batch-high-confidence"
onClick={() => void batchApproveHighConfidence(bookId, extractThreshold)}
>
{t('knowledge.batchHighConfidence')}
</button>
)}
{bookId && selectedChapterId && (
<button
type="button"
className="btn"
data-testid="knowledge-extract-current"
disabled={extracting}
onClick={() => {
setExtracting(true)
void extractChapter(bookId, selectedChapterId).finally(() => setExtracting(false))
}}
>
{extracting ? t('knowledge.extracting') : t('knowledge.extractFromCurrent')}
</button>
)}
</div>
<div className="knowledge-tabs">
<button
@@ -134,10 +164,29 @@ export function KnowledgePanel(): React.JSX.Element {
/>
)}
<div className="kb-item-main">
<div className="kb-item-title">{entry.title}</div>
<div className="kb-item-title">
{entry.title}
{entry.extractionConfidence != null && (
<span className="kb-confidence-badge">
{Math.round(entry.extractionConfidence * 100)}%
</span>
)}
</div>
<div className="kb-item-meta">
{t(`knowledge.type.${entry.type}`)} · {t(`knowledge.status.${entry.status}`)}
</div>
{entry.mergeTargetId && (
<div
className="kb-merge-hint"
data-testid={`knowledge-merge-review-${entry.id}`}
>
{t('knowledge.mergeSuggest', {
title:
entries.find((e) => e.id === entry.mergeTargetId)?.title ??
entry.mergeTargetId
})}
</div>
)}
{entry.content && <div className="kb-item-content">{entry.content}</div>}
</div>
<div className="kb-item-actions">