feat: 프로젝트 문서의 AI 반영 상태 배지 표시
AI 추천이 문서 내용을 반영하지 못해도(추출 실패·미지원 형식) 추천은 정상 응답되어 사용자가 인지할 수 없던 문제를 해소한다. 문서 목록에 반영 상태 배지를 노출한다. - ProjectAttachmentResponse.aiStatus 추가(ready/failed/unsupported) · ready: 추출 성공(반영 가능) · failed: 지원 형식이나 추출 실패 · unsupported: 미지원 형식(이미지·xlsx·ppt·zip 등) - isExtractableType + deriveAiStatus 로 상태 산출 (추출 본문은 응답에 노출하지 않고 상태 판별에만 사용) - listFiles 에서 extracted_text 를 명시적으로 선택(select:false 우회) - 공용 AttachmentAiBadge 컴포넌트 추가 → 프로젝트 상세·설정 문서 목록에 적용 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
// 프로젝트 문서의 AI 추천 반영 상태 배지
|
||||
// ready: 반영 가능 / failed: 추출 실패 / unsupported: 미지원 형식
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Props {
|
||||
status: 'ready' | 'failed' | 'unsupported'
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// 상태별 라벨·툴팁·스타일 클래스
|
||||
const META: Record<Props['status'], { label: string; title: string; cls: string }> = {
|
||||
ready: {
|
||||
label: 'AI 반영',
|
||||
title: 'AI 업무 추천에 이 문서 내용이 반영됩니다.',
|
||||
cls: 'ok',
|
||||
},
|
||||
failed: {
|
||||
label: '추출 실패',
|
||||
title: '문서에서 텍스트를 추출하지 못해 AI 추천에 반영되지 않습니다.',
|
||||
cls: 'warn',
|
||||
},
|
||||
unsupported: {
|
||||
label: 'AI 반영 불가',
|
||||
title: '미지원 형식이라 AI 추천에 반영되지 않습니다. (txt·csv·pdf·docx 지원)',
|
||||
cls: 'muted',
|
||||
},
|
||||
}
|
||||
|
||||
const meta = computed(() => META[props.status])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="ai-badge"
|
||||
:class="meta.cls"
|
||||
:title="meta.title"
|
||||
>
|
||||
{{ meta.label }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ai-badge {
|
||||
flex-shrink: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 1.25rem;
|
||||
padding: 0 0.4375rem;
|
||||
border-radius: 0.3125rem;
|
||||
border: 1px solid transparent;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
cursor: default;
|
||||
}
|
||||
.ai-badge.ok {
|
||||
color: var(--green);
|
||||
background: var(--green-weak);
|
||||
border-color: var(--green-border);
|
||||
}
|
||||
.ai-badge.warn {
|
||||
color: var(--amber);
|
||||
background: var(--amber-weak);
|
||||
border-color: var(--amber-border);
|
||||
}
|
||||
.ai-badge.muted {
|
||||
color: var(--text-3);
|
||||
background: #f3f4f6;
|
||||
border-color: var(--border);
|
||||
}
|
||||
</style>
|
||||
@@ -12,6 +12,7 @@ import DropdownSelect from '@/components/DropdownSelect.vue'
|
||||
import SegmentedTabs from '@/components/common/SegmentedTabs.vue'
|
||||
import LoadMore from '@/components/common/LoadMore.vue'
|
||||
import EmptyState from '@/components/common/EmptyState.vue'
|
||||
import AttachmentAiBadge from '@/components/common/AttachmentAiBadge.vue'
|
||||
import { useProjectStore } from '@/stores/project.store'
|
||||
import { useTaskStore } from '@/stores/task.store'
|
||||
import type { TaskListQuery, TaskSegment, TaskSort } from '@/types/task'
|
||||
@@ -199,6 +200,7 @@ const overdueCount = computed(() => tasks.value.filter((t) => t.overdue).length)
|
||||
download
|
||||
>
|
||||
<span class="pd-name">{{ a.name }}</span>
|
||||
<AttachmentAiBadge :status="a.aiStatus" />
|
||||
<span class="pd-size">{{ formatBytes(a.size) }}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRoute, useRouter, RouterLink } from 'vue-router'
|
||||
import AppShell from '@/layouts/AppShell.vue'
|
||||
import ProjectHeader from '@/components/ProjectHeader.vue'
|
||||
import ProjectTabs from '@/components/ProjectTabs.vue'
|
||||
import AttachmentAiBadge from '@/components/common/AttachmentAiBadge.vue'
|
||||
import { useProjectStore } from '@/stores/project.store'
|
||||
import { useProject } from '@/composables/useProject'
|
||||
import { useDialog } from '@/composables/useDialog'
|
||||
@@ -262,6 +263,7 @@ async function removeProject() {
|
||||
download
|
||||
>
|
||||
<span class="fr-name">{{ a.name }}</span>
|
||||
<AttachmentAiBadge :status="a.aiStatus" />
|
||||
<span class="fr-size">{{ formatBytes(a.size) }}</span>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -11,6 +11,8 @@ export interface ApiProjectAttachment {
|
||||
url: string // 다운로드 경로(/api/...)
|
||||
uploader: { id: string; name: string; avatarUrl: string | null } | null
|
||||
createdAt: string
|
||||
// AI 추천 반영 상태 — ready: 반영 가능 / failed: 추출 실패 / unsupported: 미지원 형식
|
||||
aiStatus: 'ready' | 'failed' | 'unsupported'
|
||||
}
|
||||
|
||||
// 프로젝트(API 원시) — 백엔드 ProjectResponse 와 1:1
|
||||
|
||||
Reference in New Issue
Block a user