Add thai support in summary

This commit is contained in:
Adrien
2026-04-18 19:55:19 +02:00
parent c7a77af2f4
commit ff97c24a55
7 changed files with 147 additions and 25 deletions
+3 -1
View File
@@ -48,7 +48,7 @@
Read
</router-link>
<button
v-if="book.status === 'READY'"
v-if="book.status === 'READY' && uploadEnabled"
class="btn btn-secondary"
:disabled="enrichRunning"
@click="handleEnrich"
@@ -73,6 +73,7 @@
import { computed, onUnmounted, ref } from 'vue'
import type { Book, EnrichmentProgress } from '@/stores/bookStore'
import { useBookStore } from '@/stores/bookStore'
import { env } from '@/env';
const props = defineProps<{
book: Book
@@ -90,6 +91,7 @@ const enrichFeedback = ref<string | null>(null)
let pollTimer: ReturnType<typeof setInterval> | null = null
const enrichRunning = computed(() => enrichProgress.value?.status === 'RUNNING')
const uploadEnabled = env('VITE_UPLOAD_ENABLED') !== 'false'
async function handleEnrich() {
enrichFeedback.value = null
+12 -4
View File
@@ -120,13 +120,17 @@ export const useTopicStore = defineStore('topics', () => {
}
}
async function generateSummary(topicId: string): Promise<TopicSummary | null> {
async function generateSummary(topicId: string, language: 'en' | 'th' = 'en'): Promise<TopicSummary | null> {
summaryLoading.value = true
activeSummaryTopicId.value = topicId
activeSummary.value = null
error.value = null
try {
const response = await api.post<TopicSummary>(`/topics/${topicId}/summary`)
const response = await api.post<TopicSummary>(
`/topics/${topicId}/summary`,
null,
{ params: { language } }
)
activeSummary.value = response.data
return response.data
} catch (err: any) {
@@ -168,12 +172,16 @@ export const useTopicStore = defineStore('topics', () => {
}
}
async function generateConceptReport(topicId: string): Promise<ConceptReport | null> {
async function generateConceptReport(topicId: string, language: 'en' | 'th' = 'en'): Promise<ConceptReport | null> {
conceptReportLoading.value = true
activeConceptReport.value = null
error.value = null
try {
const response = await api.post<ConceptReport>(`/topics/${topicId}/concept-reports`)
const response = await api.post<ConceptReport>(
`/topics/${topicId}/concept-reports`,
null,
{ params: { language } }
)
activeConceptReport.value = response.data
return response.data
} catch (err: any) {
+94 -10
View File
@@ -38,10 +38,28 @@
<div v-if="selectedTopicId && mode === 'summary'" class="history-panel card">
<div class="history-header">
<span class="history-title">Saved summaries</span>
<button class="btn btn-primary btn-sm" :disabled="topicStore.summaryLoading" @click="handleGenerate(selectedTopicId!)">
<span v-if="topicStore.summaryLoading" class="spinner" style="width:14px;height:14px;display:inline-block;vertical-align:middle;margin-right:4px;"></span>
Generate New
</button>
<div class="history-actions">
<div class="lang-toggle" role="group" aria-label="Summary language">
<button
type="button"
class="lang-toggle-btn"
:class="{ 'lang-toggle-btn--active': summaryLanguage === 'en' }"
:disabled="topicStore.summaryLoading"
@click="summaryLanguage = 'en'"
>EN</button>
<button
type="button"
class="lang-toggle-btn"
:class="{ 'lang-toggle-btn--active': summaryLanguage === 'th' }"
:disabled="topicStore.summaryLoading"
@click="summaryLanguage = 'th'"
>TH</button>
</div>
<button class="btn btn-primary btn-sm" :disabled="topicStore.summaryLoading" @click="handleGenerate(selectedTopicId!)">
<span v-if="topicStore.summaryLoading" class="spinner" style="width:14px;height:14px;display:inline-block;vertical-align:middle;margin-right:4px;"></span>
Generate New
</button>
</div>
</div>
<div v-if="topicStore.summaryListLoading" class="history-loading">
@@ -71,10 +89,28 @@
<div v-if="selectedTopicId && mode === 'concept'" class="history-panel card">
<div class="history-header">
<span class="history-title">Saved concept reports</span>
<button class="btn btn-primary btn-sm" :disabled="topicStore.conceptReportLoading" @click="handleGenerateConcept(selectedTopicId!)">
<span v-if="topicStore.conceptReportLoading" class="spinner" style="width:14px;height:14px;display:inline-block;vertical-align:middle;margin-right:4px;"></span>
Generate New
</button>
<div class="history-actions">
<div class="lang-toggle" role="group" aria-label="Report language">
<button
type="button"
class="lang-toggle-btn"
:class="{ 'lang-toggle-btn--active': conceptLanguage === 'en' }"
:disabled="topicStore.conceptReportLoading"
@click="conceptLanguage = 'en'"
>EN</button>
<button
type="button"
class="lang-toggle-btn"
:class="{ 'lang-toggle-btn--active': conceptLanguage === 'th' }"
:disabled="topicStore.conceptReportLoading"
@click="conceptLanguage = 'th'"
>TH</button>
</div>
<button class="btn btn-primary btn-sm" :disabled="topicStore.conceptReportLoading" @click="handleGenerateConcept(selectedTopicId!)">
<span v-if="topicStore.conceptReportLoading" class="spinner" style="width:14px;height:14px;display:inline-block;vertical-align:middle;margin-right:4px;"></span>
Generate New
</button>
</div>
</div>
<div v-if="topicStore.conceptReportListLoading" class="history-loading">
@@ -269,6 +305,8 @@ const showSources = ref(true)
const summaryError = ref<string | null>(null)
const conceptError = ref<string | null>(null)
const isNoBooks = ref(false)
const conceptLanguage = ref<'en' | 'th'>('en')
const summaryLanguage = ref<'en' | 'th'>('en')
const sourcesSection = ref<HTMLElement | null>(null)
const selectedTopicId = ref<string | null>(null)
const mode = ref<'summary' | 'concept'>('summary')
@@ -401,7 +439,7 @@ async function handleGenerateConcept(topicId: string) {
conceptError.value = null
isNoBooks.value = false
showSources.value = true
const result = await topicStore.generateConceptReport(topicId)
const result = await topicStore.generateConceptReport(topicId, conceptLanguage.value)
if (!result) {
conceptError.value = topicStore.error ?? 'Failed to generate concept report.'
isNoBooks.value =
@@ -425,7 +463,7 @@ async function handleGenerate(topicId: string) {
isNoBooks.value = false
showSources.value = true
const result = await topicStore.generateSummary(topicId)
const result = await topicStore.generateSummary(topicId, summaryLanguage.value)
if (!result) {
summaryError.value = topicStore.error ?? 'Failed to generate summary.'
isNoBooks.value =
@@ -528,6 +566,52 @@ function formatDateShort(iso: string): string {
margin-bottom: 0.75rem;
}
.history-actions {
display: flex;
align-items: center;
gap: 0.5rem;
}
.lang-toggle {
display: inline-flex;
border: 1px solid var(--border-color, #d0d7de);
border-radius: 6px;
overflow: hidden;
}
.lang-toggle-btn {
padding: 0.25rem 0.55rem;
font-size: 0.75rem;
font-weight: 600;
background: transparent;
color: var(--text-secondary, #57606a);
border: none;
cursor: pointer;
line-height: 1;
}
.lang-toggle-btn:not(:last-child) {
border-right: 1px solid var(--border-color, #d0d7de);
}
.lang-toggle-btn:hover:not(:disabled) {
background: var(--hover-bg, #f3f4f6);
}
.lang-toggle-btn--active {
background: var(--primary-color, #0969da);
color: #fff;
}
.lang-toggle-btn--active:hover:not(:disabled) {
background: var(--primary-color, #0969da);
}
.lang-toggle-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.history-title {
font-size: 0.875rem;
font-weight: 600;