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:
@@ -29,8 +29,14 @@ export function KnowledgeEditorDialog({
|
||||
const entries = useKnowledgeStore((s) => s.entries)
|
||||
const create = useKnowledgeStore((s) => s.create)
|
||||
const update = useKnowledgeStore((s) => s.update)
|
||||
const approveMerge = useKnowledgeStore((s) => s.approveMerge)
|
||||
const saveMergeAsNew = useKnowledgeStore((s) => s.saveMergeAsNew)
|
||||
|
||||
const existing = entryId ? entries.find((e) => e.id === entryId) : null
|
||||
const mergeTarget = existing?.mergeTargetId
|
||||
? entries.find((e) => e.id === existing.mergeTargetId)
|
||||
: undefined
|
||||
const isMergeMode = Boolean(existing?.mergeTargetId && mergeTarget)
|
||||
|
||||
const [type, setType] = useState<KnowledgeType>('fact')
|
||||
const [title, setTitle] = useState('')
|
||||
@@ -179,6 +185,30 @@ export function KnowledgeEditorDialog({
|
||||
)}
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
{isMergeMode && bookId && existing && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
data-testid="knowledge-approve-merge"
|
||||
onClick={() => {
|
||||
void approveMerge(bookId, existing.id).then(onClose)
|
||||
}}
|
||||
>
|
||||
{t('knowledge.approveMerge')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn"
|
||||
data-testid="knowledge-save-as-new"
|
||||
onClick={() => {
|
||||
void saveMergeAsNew(bookId, existing.id).then(onClose)
|
||||
}}
|
||||
>
|
||||
{t('knowledge.saveAsNew')}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button type="button" className="btn" onClick={onClose}>
|
||||
{t('dialog.cancel')}
|
||||
</button>
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user