feat: 히스토리 자동 타이틀 + 카드 UI 분리
전사 완료 후 LLM이 10단어 이내 제목 자동 생성 (fire-and-forget). DB에 title 컬럼 추가 + 마이그레이션. HistoryEntryCard: 제목(볼드, ellipsis) + 내용(2줄 미리보기) 분리.
This commit is contained in:
parent
b452ebbe52
commit
239fb820b8
5 changed files with 68 additions and 1 deletions
|
|
@ -191,6 +191,17 @@ export function initDatabase(): BetterSQLite3Database<typeof schema> {
|
|||
logger.warn('edited_transcript migration check failed:', err)
|
||||
}
|
||||
|
||||
// Phase 15: history 테이블에 title 컬럼 마이그레이션
|
||||
try {
|
||||
const histCols = sqlite.pragma('table_info(history)') as Array<{ name: string }>
|
||||
if (!histCols.some((c) => c.name === 'title')) {
|
||||
sqlite.exec('ALTER TABLE history ADD COLUMN title TEXT')
|
||||
logger.info('Migrated: added title column to history')
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn('title migration check failed:', err)
|
||||
}
|
||||
|
||||
// Phase 12.2: history 테이블에 summary_text 컬럼 마이그레이션
|
||||
try {
|
||||
const columns = sqlite.pragma('table_info(history)') as Array<{ name: string }>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const history = sqliteTable(
|
|||
'history',
|
||||
{
|
||||
id: text('id').primaryKey(),
|
||||
title: text('title'),
|
||||
originalText: text('original_text').notNull(),
|
||||
polishedText: text('polished_text'),
|
||||
focusedApp: text('focused_app'),
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ class HistoryService {
|
|||
this._updateStats(input.duration, input.wordCount)
|
||||
|
||||
logger.info(`History entry created: ${id}`)
|
||||
|
||||
// 비동기로 LLM 타이틀 자동 생성 (fire-and-forget)
|
||||
this.generateTitle(id).catch(() => { /* ignore */ })
|
||||
|
||||
return this._toEntry(entry as History)
|
||||
}
|
||||
|
||||
|
|
@ -182,9 +186,42 @@ class HistoryService {
|
|||
.run()
|
||||
}
|
||||
|
||||
/** 전사 완료 후 LLM으로 자동 타이틀 생성 */
|
||||
async generateTitle(id: string): Promise<string | null> {
|
||||
const entry = this.getById(id)
|
||||
if (!entry) return null
|
||||
|
||||
const text = entry.polishedText || entry.originalText
|
||||
if (!text || text.length < 10) return null
|
||||
|
||||
try {
|
||||
const { getLocalLLMService } = await import('./LocalLLMService')
|
||||
const llm = getLocalLLMService()
|
||||
const result = await llm.generate(
|
||||
text.slice(0, 2000),
|
||||
{
|
||||
systemPrompt: '다음 텍스트의 핵심 내용을 10단어 이내의 짧은 제목으로 만들어주세요. 제목만 출력하세요. 따옴표나 마침표를 붙이지 마세요.',
|
||||
temperature: 0.3,
|
||||
maxTokens: 50,
|
||||
},
|
||||
)
|
||||
const title = result.text.trim().replace(/^["']|["']$/g, '').slice(0, 80)
|
||||
if (title) {
|
||||
const db = getDatabase()
|
||||
db.update(history).set({ title, updatedAt: Date.now() }).where(eq(history.id, id)).run()
|
||||
logger.info(`Auto title generated: ${id} → "${title}"`)
|
||||
return title
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn(`Auto title generation failed: ${err instanceof Error ? err.message : String(err)}`)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private _toEntry(row: History): HistoryEntry {
|
||||
return {
|
||||
id: row.id,
|
||||
title: row.title ?? null,
|
||||
originalText: row.originalText,
|
||||
polishedText: row.polishedText,
|
||||
focusedApp: row.focusedApp,
|
||||
|
|
@ -204,7 +241,8 @@ class HistoryService {
|
|||
llmLatencyMs: row.llmLatencyMs,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
appVersion: row.appVersion
|
||||
appVersion: row.appVersion,
|
||||
summaryText: row.summaryText ?? null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,13 +100,29 @@ export function HistoryEntryCard({ entry, onCopy, onDelete, showTags = false, on
|
|||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
|
||||
<Led color={entry.status === 'completed' ? 'green' : 'red'} size={6} />
|
||||
<Box sx={{ flex: 1, minWidth: 0 }}>
|
||||
{/* 타이틀 */}
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: d3roTypo.compact.size,
|
||||
fontWeight: 600,
|
||||
color: d3roPalette.text.primary,
|
||||
lineHeight: d3roTypo.compact.line,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
mb: 0.5,
|
||||
}}
|
||||
>
|
||||
{entry.title ?? displayText.slice(0, 60)}
|
||||
</Box>
|
||||
{/* 내용 미리보기 */}
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: d3roTypo.small.size,
|
||||
color: d3roPalette.text.secondary,
|
||||
lineHeight: 1.5,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
|
|
|
|||
|
|
@ -427,6 +427,7 @@ export interface ConfigChangedEvent {
|
|||
|
||||
export interface HistoryEntry {
|
||||
id: string
|
||||
title: string | null
|
||||
originalText: string
|
||||
polishedText: string | null
|
||||
focusedApp: string | null
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue