@linto-ai/transcript-ui-ui 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +17 -0
- package/package.json +55 -0
- package/src/atoms/Badge.vue +26 -0
- package/src/atoms/Button.vue +264 -0
- package/src/atoms/CodeBlock.vue +98 -0
- package/src/atoms/CopyButton.vue +98 -0
- package/src/atoms/EditableText.vue +115 -0
- package/src/atoms/EditorCheckbox.vue +66 -0
- package/src/atoms/EditorIcon.vue +59 -0
- package/src/atoms/MarkdownEditor.vue +542 -0
- package/src/atoms/MarkdownView.vue +159 -0
- package/src/atoms/PopoverList.vue +92 -0
- package/src/atoms/SelectableListItem.vue +183 -0
- package/src/atoms/SpeakerIndicator.vue +19 -0
- package/src/atoms/SwitchToggle.vue +90 -0
- package/src/atoms/UserAvatar.vue +38 -0
- package/src/atoms/icons.ts +108 -0
- package/src/index.ts +36 -0
- package/src/molecules/DocumentArticle.vue +169 -0
- package/src/molecules/DownloadMenu.vue +48 -0
- package/src/molecules/FormInput.vue +510 -0
- package/src/molecules/SpeakerMenu.vue +43 -0
- package/src/molecules/Tabs.vue +119 -0
- package/src/molecules/TurnTextEditor.vue +93 -0
- package/src/styles/base.css +110 -0
- package/src/styles/fonts.css +45 -0
- package/src/styles/popover-list.css +67 -0
- package/src/styles/variables.css +131 -0
- package/src/turndown-plugin-gfm.d.ts +10 -0
- package/src/utils/computeInitials.ts +10 -0
- package/src/utils/computeSelectionOffset.ts +10 -0
- package/src/utils/computeTextOffsetInContainer.ts +54 -0
- package/src/utils/highlight.ts +46 -0
- package/src/utils/markdown.ts +56 -0
- package/src/utils/placeCaretAt.ts +23 -0
- package/src/utils/shadowAwareSelection.ts +52 -0
- package/src/utils/test/computeInitials.test.ts +15 -0
- package/src/utils/test/computeTextOffsetInContainer.test.ts +53 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// ── Atoms ──────────────────────────────────────────────────────────────
|
|
2
|
+
export { default as Badge } from './atoms/Badge.vue'
|
|
3
|
+
export { default as Button } from './atoms/Button.vue'
|
|
4
|
+
export { default as CodeBlock } from './atoms/CodeBlock.vue'
|
|
5
|
+
export { default as CopyButton } from './atoms/CopyButton.vue'
|
|
6
|
+
export { default as EditableText } from './atoms/EditableText.vue'
|
|
7
|
+
export { default as EditorCheckbox } from './atoms/EditorCheckbox.vue'
|
|
8
|
+
export { default as EditorIcon } from './atoms/EditorIcon.vue'
|
|
9
|
+
export { default as MarkdownEditor } from './atoms/MarkdownEditor.vue'
|
|
10
|
+
export { default as MarkdownView } from './atoms/MarkdownView.vue'
|
|
11
|
+
export { default as PopoverList } from './atoms/PopoverList.vue'
|
|
12
|
+
export { default as SelectableListItem } from './atoms/SelectableListItem.vue'
|
|
13
|
+
export { default as SpeakerIndicator } from './atoms/SpeakerIndicator.vue'
|
|
14
|
+
export { default as SwitchToggle } from './atoms/SwitchToggle.vue'
|
|
15
|
+
export { default as UserAvatar } from './atoms/UserAvatar.vue'
|
|
16
|
+
export { resolveIcon, iconMap, ICON_SIZES } from './atoms/icons'
|
|
17
|
+
|
|
18
|
+
// ── Molecules ──────────────────────────────────────────────────────────
|
|
19
|
+
export { default as DocumentArticle } from './molecules/DocumentArticle.vue'
|
|
20
|
+
export type { DocumentArticleStatus } from './molecules/DocumentArticle.vue'
|
|
21
|
+
export { default as DownloadMenu } from './molecules/DownloadMenu.vue'
|
|
22
|
+
export type { DownloadFormat } from './molecules/DownloadMenu.vue'
|
|
23
|
+
export { default as FormInput, EMPTY_FIELD } from './molecules/FormInput.vue'
|
|
24
|
+
export type { FormField } from './molecules/FormInput.vue'
|
|
25
|
+
export { default as SpeakerMenu } from './molecules/SpeakerMenu.vue'
|
|
26
|
+
export { default as Tabs } from './molecules/Tabs.vue'
|
|
27
|
+
export type { TabItem } from './molecules/Tabs.vue'
|
|
28
|
+
export { default as TurnTextEditor } from './molecules/TurnTextEditor.vue'
|
|
29
|
+
|
|
30
|
+
// ── Utils ──────────────────────────────────────────────────────────────
|
|
31
|
+
export { renderMarkdown, renderMarkdownSegments } from './utils/markdown'
|
|
32
|
+
export { computeInitials } from './utils/computeInitials'
|
|
33
|
+
export { placeCaretAt } from './utils/placeCaretAt'
|
|
34
|
+
export { computeSelectionOffset } from './utils/computeSelectionOffset'
|
|
35
|
+
export { computeTextOffsetInContainer } from './utils/computeTextOffsetInContainer'
|
|
36
|
+
export { getShadowAwareSelection, getSelectionFocus } from './utils/shadowAwareSelection'
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue"
|
|
3
|
+
import EditorIcon from "../atoms/EditorIcon.vue"
|
|
4
|
+
import Button from "../atoms/Button.vue"
|
|
5
|
+
import { useI18n } from "@linto-ai/transcript-ui-i18n"
|
|
6
|
+
|
|
7
|
+
export type DocumentArticleStatus = "done" | "processing" | "error"
|
|
8
|
+
|
|
9
|
+
const props = withDefaults(
|
|
10
|
+
defineProps<{
|
|
11
|
+
status?: DocumentArticleStatus
|
|
12
|
+
progress?: number
|
|
13
|
+
errorMessage?: string
|
|
14
|
+
}>(),
|
|
15
|
+
{ status: "done" },
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const emit = defineEmits<{
|
|
19
|
+
retry: []
|
|
20
|
+
}>()
|
|
21
|
+
|
|
22
|
+
const { t } = useI18n()
|
|
23
|
+
|
|
24
|
+
const errorText = computed(
|
|
25
|
+
() => props.errorMessage || t("llmService.errorTemporary"),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const progressValue = computed(() => {
|
|
29
|
+
const v = props.progress
|
|
30
|
+
if (v == null || !Number.isFinite(v)) return null
|
|
31
|
+
return Math.max(0, Math.min(100, Math.round(v)))
|
|
32
|
+
})
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<article class="document-article" :data-status="props.status">
|
|
37
|
+
<div
|
|
38
|
+
class="document-article__toolbar"
|
|
39
|
+
role="toolbar"
|
|
40
|
+
v-if="
|
|
41
|
+
$slots['toolbar-left'] ||
|
|
42
|
+
$slots['toolbar-center'] ||
|
|
43
|
+
$slots['toolbar-right']
|
|
44
|
+
">
|
|
45
|
+
<div class="document-article__toolbar-left">
|
|
46
|
+
<slot name="toolbar-left" />
|
|
47
|
+
</div>
|
|
48
|
+
<div class="document-article__toolbar-center">
|
|
49
|
+
<slot name="toolbar-center" />
|
|
50
|
+
</div>
|
|
51
|
+
<div class="document-article__toolbar-right">
|
|
52
|
+
<slot name="toolbar-right" />
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="document-article__body">
|
|
57
|
+
<div
|
|
58
|
+
v-if="props.status === 'processing'"
|
|
59
|
+
class="document-article__center document-article__center--processing"
|
|
60
|
+
role="status"
|
|
61
|
+
aria-live="polite">
|
|
62
|
+
<EditorIcon name="spinner" spin :size="24" />
|
|
63
|
+
<progress
|
|
64
|
+
class="document-article__progress"
|
|
65
|
+
:max="100"
|
|
66
|
+
:value="progressValue ?? undefined" />
|
|
67
|
+
<span
|
|
68
|
+
v-if="progressValue !== null"
|
|
69
|
+
class="document-article__progress-value">
|
|
70
|
+
{{ progressValue }}%
|
|
71
|
+
</span>
|
|
72
|
+
</div>
|
|
73
|
+
<div
|
|
74
|
+
v-else-if="props.status === 'error'"
|
|
75
|
+
class="document-article__center document-article__center--error"
|
|
76
|
+
role="alert">
|
|
77
|
+
<p class="document-article__error-text">{{ errorText }}</p>
|
|
78
|
+
<Button variant="primary" icon="refresh-cw" @click="emit('retry')">
|
|
79
|
+
{{ t("llmService.retry") }}
|
|
80
|
+
</Button>
|
|
81
|
+
</div>
|
|
82
|
+
<slot v-else />
|
|
83
|
+
</div>
|
|
84
|
+
</article>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<style scoped>
|
|
88
|
+
.document-article {
|
|
89
|
+
width: 100%;
|
|
90
|
+
max-width: 760px;
|
|
91
|
+
margin: var(--spacing-lg) auto;
|
|
92
|
+
background-color: var(--color-surface);
|
|
93
|
+
border: 1px solid var(--color-border);
|
|
94
|
+
border-radius: var(--radius-md);
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-direction: column;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.document-article__toolbar {
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: var(--spacing-sm);
|
|
103
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
104
|
+
border-bottom: 1px solid var(--color-border);
|
|
105
|
+
position: sticky;
|
|
106
|
+
top: 0;
|
|
107
|
+
background-color: var(--color-surface);
|
|
108
|
+
border-radius: var(--radius-md) var(--radius-md) 0 0;
|
|
109
|
+
z-index: 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.document-article__toolbar-left,
|
|
113
|
+
.document-article__toolbar-right {
|
|
114
|
+
display: flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: var(--spacing-xs);
|
|
117
|
+
flex-shrink: 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.document-article__toolbar-center {
|
|
121
|
+
flex: 1;
|
|
122
|
+
display: flex;
|
|
123
|
+
justify-content: center;
|
|
124
|
+
align-items: center;
|
|
125
|
+
min-width: 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.document-article__body {
|
|
129
|
+
flex: 1;
|
|
130
|
+
min-height: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.document-article__center {
|
|
134
|
+
display: flex;
|
|
135
|
+
flex-direction: column;
|
|
136
|
+
align-items: center;
|
|
137
|
+
justify-content: center;
|
|
138
|
+
gap: var(--spacing-sm);
|
|
139
|
+
padding: var(--spacing-xl) var(--spacing-md);
|
|
140
|
+
text-align: center;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.document-article__center--processing {
|
|
144
|
+
color: var(--color-primary);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.document-article__center--error {
|
|
148
|
+
color: var(--color-danger, #d33);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.document-article__progress {
|
|
152
|
+
width: min(280px, 100%);
|
|
153
|
+
height: 6px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.document-article__progress-value {
|
|
157
|
+
font-size: var(--font-size-xs);
|
|
158
|
+
font-variant-numeric: tabular-nums;
|
|
159
|
+
color: var(--color-text-muted);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.document-article__error-text {
|
|
163
|
+
margin: 0;
|
|
164
|
+
max-width: 480px;
|
|
165
|
+
font-size: var(--font-size-sm);
|
|
166
|
+
line-height: var(--line-height);
|
|
167
|
+
color: var(--color-text-secondary);
|
|
168
|
+
}
|
|
169
|
+
</style>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import PopoverList from "../atoms/PopoverList.vue"
|
|
3
|
+
import Button from "../atoms/Button.vue"
|
|
4
|
+
import { useI18n, type TranslationKey } from "@linto-ai/transcript-ui-i18n"
|
|
5
|
+
|
|
6
|
+
export interface DownloadFormat {
|
|
7
|
+
format: string
|
|
8
|
+
labelKey: TranslationKey
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
formats: DownloadFormat[]
|
|
13
|
+
disabled?: boolean
|
|
14
|
+
loading?: boolean
|
|
15
|
+
}>()
|
|
16
|
+
|
|
17
|
+
const emit = defineEmits<{
|
|
18
|
+
select: [format: string]
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
const { t } = useI18n()
|
|
22
|
+
|
|
23
|
+
function onSelect(item: DownloadFormat): void {
|
|
24
|
+
emit("select", item.format)
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<PopoverList
|
|
30
|
+
:items="props.formats"
|
|
31
|
+
:item-key="(f: DownloadFormat) => f.format"
|
|
32
|
+
align="end"
|
|
33
|
+
@select="onSelect">
|
|
34
|
+
<template #trigger>
|
|
35
|
+
<Button
|
|
36
|
+
variant="primary"
|
|
37
|
+
icon="download"
|
|
38
|
+
icon-right="chevron-down"
|
|
39
|
+
:disabled="disabled"
|
|
40
|
+
:loading="loading">
|
|
41
|
+
{{ t("llmService.download") }}
|
|
42
|
+
</Button>
|
|
43
|
+
</template>
|
|
44
|
+
<template #item="{ item }">
|
|
45
|
+
<span>{{ t(item.labelKey) }}</span>
|
|
46
|
+
</template>
|
|
47
|
+
</PopoverList>
|
|
48
|
+
</template>
|