@rt-tools/ui-kit-v2 0.34.5 → 0.35.1
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/fesm2022/rt-tools-ui-kit-v2-ai-prompt-input.mjs +574 -0
- package/fesm2022/rt-tools-ui-kit-v2-ai-prompt-input.mjs.map +1 -0
- package/fesm2022/rt-tools-ui-kit-v2-ai-run-status.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-ai-run-status.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-breadcrumbs.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-breadcrumbs.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-core.mjs +62 -3
- package/fesm2022/rt-tools-ui-kit-v2-core.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-data-table.mjs +4 -2
- package/fesm2022/rt-tools-ui-kit-v2-data-table.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-draggable-tree.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-draggable-tree.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-dynamic-selector.mjs +4 -2
- package/fesm2022/rt-tools-ui-kit-v2-dynamic-selector.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-file-card.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-file-card.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree-selector.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree-selector.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-icon-button.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-icon-button.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-image-upload.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-image-upload.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-inline-edit.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-inline-edit.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-markdown-text.mjs +2 -2
- package/fesm2022/rt-tools-ui-kit-v2-markdown-text.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-page-header.mjs +4 -2
- package/fesm2022/rt-tools-ui-kit-v2-page-header.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-rich-editor.mjs +148 -24
- package/fesm2022/rt-tools-ui-kit-v2-rich-editor.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-scroll-area.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-scroll-area.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-side-menu.mjs +4 -2
- package/fesm2022/rt-tools-ui-kit-v2-side-menu.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-speed-menu.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-speed-menu.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-table.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-table.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-tabs.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-tabs.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-tag.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-tag.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-thread-list.mjs +52 -5
- package/fesm2022/rt-tools-ui-kit-v2-thread-list.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-toggle-button-group.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-toggle-button-group.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-tooltip.mjs +37 -0
- package/fesm2022/rt-tools-ui-kit-v2-tooltip.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-tree-selector.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-tree-selector.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-tree.mjs +2 -1
- package/fesm2022/rt-tools-ui-kit-v2-tree.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-usage-ring.mjs +164 -0
- package/fesm2022/rt-tools-ui-kit-v2-usage-ring.mjs.map +1 -0
- package/fesm2022/rt-tools-ui-kit-v2.mjs +2 -0
- package/package.json +9 -1
- package/src/lib/components/ai-prompt-input/CONTEXT.md +60 -0
- package/src/lib/components/ai-prompt-input/rt-ai-prompt-input.component.scss +113 -0
- package/src/lib/components/markdown-text/rt-markdown-text.component.scss +3 -0
- package/src/lib/components/thread-list/rt-thread-list.component.scss +26 -11
- package/src/lib/components/tooltip/CONTEXT.md +4 -0
- package/src/lib/components/usage-ring/CONTEXT.md +33 -0
- package/src/lib/components/usage-ring/rt-usage-ring.component.scss +64 -0
- package/src/rich-editor/lib/components/ai-chat/CONTEXT.md +1 -1
- package/src/rich-editor/lib/components/ai-chat/answer/rt-ai-chat-answer.component.scss +1 -0
- package/src/rich-editor/lib/components/ai-chat/rt-ai-chat.component.scss +6 -0
- package/src/rich-editor/lib/components/chat/rt-chat.component.scss +25 -0
- package/types/rt-tools-ui-kit-v2-ai-prompt-input.d.ts +193 -0
- package/types/rt-tools-ui-kit-v2-core.d.ts +4 -0
- package/types/rt-tools-ui-kit-v2-rich-editor.d.ts +56 -3
- package/types/rt-tools-ui-kit-v2-thread-list.d.ts +7 -1
- package/types/rt-tools-ui-kit-v2-tooltip.d.ts +12 -0
- package/types/rt-tools-ui-kit-v2-usage-ring.d.ts +47 -0
- package/types/rt-tools-ui-kit-v2.d.ts +2 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
@layer rt-kit.components {
|
|
2
|
+
.rt-ai-prompt-input {
|
|
3
|
+
--rt-ai-prompt-input-radius: var(--rt-radius-xl);
|
|
4
|
+
--rt-ai-prompt-input-border-width: var(--rt-border-width-thin);
|
|
5
|
+
--rt-ai-prompt-input-shadow: var(--rt-shadow-sm);
|
|
6
|
+
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
gap: calc(var(--rt-space-1) * 1.5);
|
|
10
|
+
|
|
11
|
+
/* Ширина приходит от хозяина: textarea без объявленной ширины считает её от знака. */
|
|
12
|
+
inline-size: 100%;
|
|
13
|
+
|
|
14
|
+
&__box {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
border: var(--rt-ai-prompt-input-border-width) solid var(--rt-input-color-border);
|
|
18
|
+
border-radius: var(--rt-ai-prompt-input-radius);
|
|
19
|
+
background: var(--rt-color-bg-surface);
|
|
20
|
+
box-shadow: var(--rt-ai-prompt-input-shadow);
|
|
21
|
+
padding-block-start: var(--rt-space-1);
|
|
22
|
+
|
|
23
|
+
// Фокус — как у полей кита: рамка фокуса и кольцо.
|
|
24
|
+
&:focus-within {
|
|
25
|
+
border-color: var(--rt-input-color-border-focus);
|
|
26
|
+
box-shadow: var(--rt-input-focus-shadow);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&--disabled {
|
|
30
|
+
background: var(--rt-input-color-bg-disabled);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&__files {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: column;
|
|
37
|
+
padding: var(--rt-space-2) var(--rt-space-2) 0;
|
|
38
|
+
gap: var(--rt-space-1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&__input {
|
|
42
|
+
/* Автовысота CDK пишет в height высоту самого текста: при border-box её съели бы
|
|
43
|
+
отступы, и вторая строка уезжала бы под верхний край. */
|
|
44
|
+
box-sizing: content-box;
|
|
45
|
+
padding: calc(var(--rt-space-1) * 3) calc(var(--rt-space-1) * 3) var(--rt-space-2) var(--rt-space-4);
|
|
46
|
+
border: 0;
|
|
47
|
+
margin: 0;
|
|
48
|
+
background: transparent;
|
|
49
|
+
color: var(--rt-input-color-text);
|
|
50
|
+
font: inherit;
|
|
51
|
+
font-size: var(--rt-text-base);
|
|
52
|
+
inline-size: auto;
|
|
53
|
+
line-height: var(--rt-leading-normal);
|
|
54
|
+
min-inline-size: 0;
|
|
55
|
+
outline: 0;
|
|
56
|
+
resize: none;
|
|
57
|
+
scrollbar-color: var(--rt-color-border-default) transparent;
|
|
58
|
+
scrollbar-gutter: stable;
|
|
59
|
+
scrollbar-width: thin;
|
|
60
|
+
|
|
61
|
+
&::placeholder {
|
|
62
|
+
color: var(--rt-input-color-text-placeholder);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
&:disabled {
|
|
66
|
+
cursor: not-allowed;
|
|
67
|
+
|
|
68
|
+
&::placeholder {
|
|
69
|
+
color: var(--rt-color-text-disabled);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Ряд действий стоит внутри рамки снизу: слева «+», справа контролы и отправка.
|
|
75
|
+
&__actions {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
padding: 0 var(--rt-space-2) var(--rt-space-2);
|
|
79
|
+
gap: var(--rt-space-1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&__controls {
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: var(--rt-space-1);
|
|
86
|
+
margin-inline-start: auto;
|
|
87
|
+
min-inline-size: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
&__control {
|
|
91
|
+
padding-inline: calc(var(--rt-space-1) * 1.5);
|
|
92
|
+
|
|
93
|
+
// Текстовая кнопка кита по умолчанию приглушена и красит подпись своим свойством
|
|
94
|
+
// двумя классами. Модель поднимается до основного тона, усилие и режим остаются
|
|
95
|
+
// приглушёнными.
|
|
96
|
+
&.rt-button {
|
|
97
|
+
--rt-btn-color: var(--rt-color-text-primary);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
&--muted.rt-button {
|
|
101
|
+
--rt-btn-color: var(--rt-color-text-muted);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
&__hint {
|
|
106
|
+
margin: 0;
|
|
107
|
+
color: var(--rt-color-text-muted);
|
|
108
|
+
font-size: var(--rt-text-xs);
|
|
109
|
+
line-height: var(--rt-leading-snug);
|
|
110
|
+
text-align: center;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
letter-spacing: var(--rt-letter-spacing-wide);
|
|
18
18
|
line-height: var(--rt-leading-snug);
|
|
19
19
|
|
|
20
|
+
/* Длинное слово и адрес переносятся: иначе текст шире своего места и тянет ленту вширь. */
|
|
21
|
+
overflow-wrap: anywhere;
|
|
22
|
+
|
|
20
23
|
&__heading {
|
|
21
24
|
margin: 0;
|
|
22
25
|
color: var(--rt-color-text-primary);
|
|
@@ -38,6 +38,13 @@
|
|
|
38
38
|
flex: 0 0 auto;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// Полоса вкладок стоит под шапкой во всю ширину колонки; отступ по краям держит её
|
|
42
|
+
// содержимое на одной линии с полем поиска
|
|
43
|
+
&__tabs {
|
|
44
|
+
flex: 0 0 auto;
|
|
45
|
+
padding: var(--rt-space-0-5) var(--rt-space-2) 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
41
48
|
&__filters-panel {
|
|
42
49
|
display: flex;
|
|
43
50
|
flex-direction: column;
|
|
@@ -52,6 +59,7 @@
|
|
|
52
59
|
flex-direction: column;
|
|
53
60
|
min-block-size: 0;
|
|
54
61
|
overflow-y: auto;
|
|
62
|
+
overscroll-behavior: contain;
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
&__row {
|
|
@@ -130,15 +138,6 @@
|
|
|
130
138
|
// кнопка в `<button>` недопустима. Подложка повторяет фон строки, чтобы закрыть время.
|
|
131
139
|
&__row-wrap {
|
|
132
140
|
position: relative;
|
|
133
|
-
|
|
134
|
-
// Открытое меню действий держит их видимыми: фокус ушёл в панель поверх страницы, и без
|
|
135
|
-
// этого кнопка меню гасла под своей же панелью, когда указатель покидал строку.
|
|
136
|
-
&:hover .rt-thread-list__row-actions,
|
|
137
|
-
&:focus-within .rt-thread-list__row-actions,
|
|
138
|
-
&:has(.rt-menu--open) .rt-thread-list__row-actions {
|
|
139
|
-
opacity: 1;
|
|
140
|
-
pointer-events: auto;
|
|
141
|
-
}
|
|
142
141
|
}
|
|
143
142
|
|
|
144
143
|
&__row-actions {
|
|
@@ -149,9 +148,25 @@
|
|
|
149
148
|
gap: var(--rt-space-1);
|
|
150
149
|
inset-block: 0;
|
|
151
150
|
inset-inline-end: 0;
|
|
152
|
-
opacity: 0;
|
|
153
151
|
padding-inline: var(--rt-space-2) var(--rt-space-4);
|
|
154
|
-
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Действия прячутся только там, где есть наведение. На сенсорном экране наведения нет,
|
|
155
|
+
// и кнопка меню видна всегда: иначе беседу не переименовать и не удалить.
|
|
156
|
+
@media (hover: hover) {
|
|
157
|
+
&__row-actions {
|
|
158
|
+
opacity: 0;
|
|
159
|
+
pointer-events: none;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Открытое меню действий держит их видимыми: фокус ушёл в панель поверх страницы, и без
|
|
163
|
+
// этого кнопка меню гасла под своей же панелью, когда указатель покидал строку.
|
|
164
|
+
&__row-wrap:hover .rt-thread-list__row-actions,
|
|
165
|
+
&__row-wrap:focus-within .rt-thread-list__row-actions,
|
|
166
|
+
&__row-wrap:has(.rt-menu--open) .rt-thread-list__row-actions {
|
|
167
|
+
opacity: 1;
|
|
168
|
+
pointer-events: auto;
|
|
169
|
+
}
|
|
155
170
|
}
|
|
156
171
|
|
|
157
172
|
&__row--active + .rt-thread-list__row-actions {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
| `rtTooltipWhenTruncated` | `boolean` | `false` — подсказка только у обрезанного текста |
|
|
13
13
|
| `rtTooltipHideOnClick` | `boolean` | `true` — клик прячет открытую подсказку |
|
|
14
14
|
| `rtTooltipShowDelay` | `number \| null` | `null` — задержка из настроек кита, без них 300 |
|
|
15
|
+
| `rtTooltipShowOnTap` | `boolean` | `false` — касание подсказку не показывает |
|
|
15
16
|
|
|
16
17
|
Выходов нет. Панель — `rt-tooltip` в оверлее CDK, `role="tooltip"`.
|
|
17
18
|
|
|
@@ -36,6 +37,9 @@ no-op, а не пустая панель под курсором. Поэтому
|
|
|
36
37
|
- У переключателя ставят `rtTooltipHideOnClick="false"`: клик оставляет подсказку, а новый текст
|
|
37
38
|
`[rtTooltip]` («Включить» → «Выключить») появляется в открытой панели на месте. Отложенный показ клик
|
|
38
39
|
снимает при любом значении, поэтому касание на тач-экране подсказку не показывает.
|
|
40
|
+
- У хоста без своего действия — значка или кольца расхода — ставят `rtTooltipShowOnTap`: касание
|
|
41
|
+
открывает подсказку сразу, повторное касание или касание вне хоста закрывает. У кнопки вход не
|
|
42
|
+
ставят: её касание выполняет действие. `rt-usage-ring` ставит его сам.
|
|
39
43
|
- Приложение, которому клик не должен прятать ни одну подсказку, задаёт это один раз:
|
|
40
44
|
`provideRtKit({ components: { tooltip: { hideOnClick: false } } })`. Там же `placement` и
|
|
41
45
|
`showDelay`. Вход на элементе перебивает настройку. Сторона из настроек применяется и тогда, когда
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# `rt-usage-ring`
|
|
2
|
+
|
|
3
|
+
```html
|
|
4
|
+
<rt-usage-ring [value]="usedPercent()" [hint]="usageHint()" />
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
| вход | тип | умолчание |
|
|
8
|
+
| -------- | -------- | --------- |
|
|
9
|
+
| `value` | `number` | `0` |
|
|
10
|
+
| `warnAt` | `number` | `80` |
|
|
11
|
+
| `hint` | `string` | `''` |
|
|
12
|
+
|
|
13
|
+
Выходов нет.
|
|
14
|
+
|
|
15
|
+
Спек — `docs/specs/ui-kit-v2/ai-chat/`; здесь только устройство папки.
|
|
16
|
+
|
|
17
|
+
## Главное, что нужно знать
|
|
18
|
+
|
|
19
|
+
**Кольцо рисует только долю.** Что за лимит и когда сброс, говорит подсказка `hint`: её текст
|
|
20
|
+
собирает приложение, кит не знает ни лимита, ни дат.
|
|
21
|
+
|
|
22
|
+
**С порога `warnAt` дуга оранжевая.** Доля прижимается к 0…100 и округляется до целого.
|
|
23
|
+
|
|
24
|
+
## Края
|
|
25
|
+
|
|
26
|
+
- С подсказкой кольцо берёт фокус: подсказку видно и с клавиатуры. Без подсказки кольцо в обход
|
|
27
|
+
фокуса не входит.
|
|
28
|
+
- Скринридер слышит долю подписью кита `aiUsageAria`.
|
|
29
|
+
|
|
30
|
+
## Рядом
|
|
31
|
+
|
|
32
|
+
- [`rt-ai-prompt-input`](../ai-prompt-input/CONTEXT.md) — поле ввода ИИ-чата, где кольцо стоит в
|
|
33
|
+
ряду действий.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
@layer rt-kit.components {
|
|
2
|
+
.rt-usage-ring {
|
|
3
|
+
--rt-usage-ring-size: var(--rt-control-height-sm);
|
|
4
|
+
--rt-usage-ring-mark-size: var(--rt-space-4);
|
|
5
|
+
--rt-usage-ring-stroke: 2.5;
|
|
6
|
+
--rt-usage-ring-color: var(--rt-color-action-primary);
|
|
7
|
+
--rt-usage-ring-track: var(--rt-color-border-default);
|
|
8
|
+
--rt-usage-ring-radius: var(--rt-radius-md);
|
|
9
|
+
--rt-usage-ring-focus-shadow: var(--rt-shadow-focus-ring);
|
|
10
|
+
--rt-usage-ring-duration: var(--rt-duration-fast);
|
|
11
|
+
--rt-usage-ring-arc-duration: var(--rt-duration-base);
|
|
12
|
+
|
|
13
|
+
display: inline-flex;
|
|
14
|
+
flex: none;
|
|
15
|
+
|
|
16
|
+
&__ring {
|
|
17
|
+
display: inline-flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
border-radius: var(--rt-usage-ring-radius);
|
|
21
|
+
block-size: var(--rt-usage-ring-size);
|
|
22
|
+
inline-size: var(--rt-usage-ring-size);
|
|
23
|
+
outline: 0;
|
|
24
|
+
transition: background var(--rt-usage-ring-duration) ease;
|
|
25
|
+
|
|
26
|
+
&:hover,
|
|
27
|
+
&:focus-visible {
|
|
28
|
+
background: var(--rt-color-bg-subtle);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&:focus-visible {
|
|
32
|
+
box-shadow: var(--rt-usage-ring-focus-shadow);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&--warning {
|
|
36
|
+
--rt-usage-ring-color: var(--rt-color-state-warning);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&__svg {
|
|
41
|
+
block-size: var(--rt-usage-ring-mark-size);
|
|
42
|
+
inline-size: var(--rt-usage-ring-mark-size);
|
|
43
|
+
|
|
44
|
+
/* Дуга начинается сверху и идёт по часовой стрелке. */
|
|
45
|
+
transform: rotate(-90deg);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&__track,
|
|
49
|
+
&__arc {
|
|
50
|
+
fill: none;
|
|
51
|
+
stroke-width: var(--rt-usage-ring-stroke);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&__track {
|
|
55
|
+
stroke: var(--rt-usage-ring-track);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&__arc {
|
|
59
|
+
stroke: var(--rt-usage-ring-color);
|
|
60
|
+
stroke-linecap: round;
|
|
61
|
+
transition: stroke-dashoffset var(--rt-usage-ring-arc-duration) ease;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
**Организм презентационный.** Сообщения с ходом работы, беседы и ошибка приходят входами, действия
|
|
32
32
|
уходят выходами. Поток событий модели разбирает приложение и отдаёт готовый `IRtAiChat.Message[]`.
|
|
33
33
|
|
|
34
|
-
**Живёт во входе `rich-editor`**: держит `rt-
|
|
34
|
+
**Живёт во входе `rich-editor`**: держит разметку ответа и общую с `rt-chat` логику ленты. Поле ввода — `rt-ai-prompt-input` основного входа: ряд действий с моделью, усилием, режимом и кольцом расхода.
|
|
35
35
|
|
|
36
36
|
**Высоту задаёт родитель.** Хост — колонка на `block-size: 100%`; без высоты у родителя лента
|
|
37
37
|
меряется по содержимому и не прокручивается.
|
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
gap: var(--rt-space-md);
|
|
97
97
|
min-block-size: 0;
|
|
98
98
|
overflow-y: auto;
|
|
99
|
+
overscroll-behavior: contain;
|
|
99
100
|
padding-block: var(--rt-space-md);
|
|
100
101
|
padding-inline: max(var(--rt-space-md), calc((100% - var(--rt-ai-chat-content-max-width)) / 2));
|
|
101
102
|
}
|
|
@@ -186,6 +187,11 @@
|
|
|
186
187
|
padding-block: var(--rt-space-xs) var(--rt-space-sm);
|
|
187
188
|
padding-inline: max(var(--rt-space-md), calc((100% - var(--rt-ai-chat-content-max-width)) / 2));
|
|
188
189
|
text-align: center;
|
|
190
|
+
|
|
191
|
+
// Над полем подпись отходит от ленты и прижимается к полю.
|
|
192
|
+
&--above {
|
|
193
|
+
padding-block: var(--rt-space-sm) 0;
|
|
194
|
+
}
|
|
189
195
|
}
|
|
190
196
|
|
|
191
197
|
&__threads {
|
|
@@ -87,6 +87,31 @@
|
|
|
87
87
|
line-height: var(--rt-leading-snug);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// Заголовок и строка под ним стоят столбцом; длинный контакт режется многоточием,
|
|
91
|
+
// кнопки шапки остаются на месте
|
|
92
|
+
&__names {
|
|
93
|
+
display: flex;
|
|
94
|
+
flex-direction: column;
|
|
95
|
+
gap: var(--rt-space-0-5);
|
|
96
|
+
min-inline-size: 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
&__subtitle {
|
|
100
|
+
overflow: hidden;
|
|
101
|
+
margin: 0;
|
|
102
|
+
color: var(--rt-color-text-muted);
|
|
103
|
+
font-family: var(--rt-font-family-sans);
|
|
104
|
+
font-size: var(--rt-text-xs);
|
|
105
|
+
line-height: var(--rt-leading-snug);
|
|
106
|
+
text-overflow: ellipsis;
|
|
107
|
+
white-space: nowrap;
|
|
108
|
+
|
|
109
|
+
// Пустая строка скрыта признаком `hidden`, и шапка без неё прежней высоты
|
|
110
|
+
&[hidden] {
|
|
111
|
+
display: none;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
90
115
|
&__thread-viewport {
|
|
91
116
|
position: relative;
|
|
92
117
|
display: flex;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { BooleanInput, NumberInput } from "@angular/cdk/coercion";
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
import { ElementRef, InputSignal, InputSignalWithTransform, ModelSignal, OutputEmitterRef, Signal, WritableSignal } from "@angular/core";
|
|
4
|
+
import { TRtKitLabelMap } from "@rt-tools/ui-kit-v2/core";
|
|
5
|
+
/**
|
|
6
|
+
* Модель `<rt-ai-prompt-input>`: один корневой неймспейс с префиксом `I`.
|
|
7
|
+
*/
|
|
8
|
+
export declare namespace IRtAiPromptInput {
|
|
9
|
+
/** Отправка: текст вопроса и приложенные файлы. */
|
|
10
|
+
interface SubmitPayload {
|
|
11
|
+
text: string;
|
|
12
|
+
files: File[];
|
|
13
|
+
}
|
|
14
|
+
/** Где стоит подсказка: под полем или над ним. */
|
|
15
|
+
type HintPosition = 'below' | 'above';
|
|
16
|
+
/** Кнопка строки действий, которую нажали: что она открывает, решает приложение. */
|
|
17
|
+
type Control = 'model' | 'effort' | 'mode';
|
|
18
|
+
}
|
|
19
|
+
/** Можно ли отправить: есть непустой текст или файл, поле свободно. */
|
|
20
|
+
export declare function rtPromptCanSend(text: string, files: number, busy: boolean): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Поле ввода ИИ-чата: одна рамка, текст сверху, ряд действий внутри рамки снизу.
|
|
23
|
+
*
|
|
24
|
+
* Слева в ряду «+» для файлов, справа модель, усилие, режим, кольцо расхода и отправка. Каждый
|
|
25
|
+
* элемент ряда включается своим входом: без значения его нет. Что открывает нажатие на модель,
|
|
26
|
+
* усилие и режим, решает приложение: поле сообщает, какую кнопку нажали.
|
|
27
|
+
*
|
|
28
|
+
* Подсказка стоит отдельной строкой под полем или над ним, в ряд действий она не входит.
|
|
29
|
+
*
|
|
30
|
+
* Отправка — `Enter` без Shift или кнопка со стрелкой; `Shift+Enter` переносит строку. Со
|
|
31
|
+
* `stoppable` во время `sending` стрелку сменяет «Стоп», а поле остаётся открытым.
|
|
32
|
+
*/
|
|
33
|
+
export declare class RtAiPromptInputComponent {
|
|
34
|
+
#private;
|
|
35
|
+
protected readonly t: Signal<TRtKitLabelMap>;
|
|
36
|
+
protected readonly fileInput: Signal<ElementRef<HTMLInputElement> | undefined>;
|
|
37
|
+
protected readonly textInput: Signal<ElementRef<HTMLTextAreaElement> | undefined>;
|
|
38
|
+
protected readonly files: WritableSignal<File[]>;
|
|
39
|
+
protected readonly placeholderText: Signal<string>;
|
|
40
|
+
/** Текст подсказки: `null` — английское умолчание кита, пустая строка — подсказки нет. */
|
|
41
|
+
protected readonly hintText: Signal<string>;
|
|
42
|
+
protected readonly hintAbove: Signal<boolean>;
|
|
43
|
+
protected readonly hintBelow: Signal<boolean>;
|
|
44
|
+
/** Поле закрыто для набора: выключено или идёт ответ, который нельзя остановить. */
|
|
45
|
+
protected readonly locked: Signal<boolean>;
|
|
46
|
+
protected readonly canSend: Signal<boolean>;
|
|
47
|
+
/** Идёт ответ, который можно остановить: вместо стрелки стоит «Стоп». */
|
|
48
|
+
protected readonly isStoppable: Signal<boolean>;
|
|
49
|
+
protected readonly hasUsage: Signal<boolean>;
|
|
50
|
+
protected readonly boxMods: Signal<Record<string, boolean>>;
|
|
51
|
+
/** Текст-подсказка поля. Пусто — переведённое умолчание кита. */
|
|
52
|
+
readonly placeholder: InputSignal<string>;
|
|
53
|
+
/** Подсказка отдельной строкой. `null` — английское умолчание кита, пустая строка убирает её. */
|
|
54
|
+
readonly hint: InputSignal<string | null>;
|
|
55
|
+
/** Где стоит подсказка: под полем, а где места под ним нет — над полем. */
|
|
56
|
+
readonly hintPosition: InputSignal<IRtAiPromptInput.HintPosition>;
|
|
57
|
+
/** Кнопка «+» и карточки приложенных файлов. */
|
|
58
|
+
readonly attachments: InputSignalWithTransform<boolean, BooleanInput>;
|
|
59
|
+
/** Фильтр типов файлов (`.pdf,.png`). */
|
|
60
|
+
readonly accept: InputSignal<string>;
|
|
61
|
+
/** Подпись модели. Пусто — кнопки модели нет. */
|
|
62
|
+
readonly model: InputSignal<string>;
|
|
63
|
+
/** Подпись усилия. Пусто — кнопки усилия нет. */
|
|
64
|
+
readonly effort: InputSignal<string>;
|
|
65
|
+
/** Подпись режима. Пусто — кнопки режима нет. */
|
|
66
|
+
readonly mode: InputSignal<string>;
|
|
67
|
+
/** Потраченная доля лимита в процентах. `null` — кольца нет. */
|
|
68
|
+
readonly usage: InputSignal<number | null>;
|
|
69
|
+
/** Подсказка кольца по наведению: доля и дата сброса словами приложения. */
|
|
70
|
+
readonly usageHint: InputSignal<string>;
|
|
71
|
+
/** С этой доли кольцо оранжевое. */
|
|
72
|
+
readonly usageWarnAt: InputSignalWithTransform<number, NumberInput>;
|
|
73
|
+
/** Отправка идёт: кнопка крутит индикатор, без `stoppable` поле закрыто. */
|
|
74
|
+
readonly sending: InputSignalWithTransform<boolean, BooleanInput>;
|
|
75
|
+
/** Ответ можно остановить: во время `sending` стрелку сменяет «Стоп», поле открыто. */
|
|
76
|
+
readonly stoppable: InputSignalWithTransform<boolean, BooleanInput>;
|
|
77
|
+
readonly disabled: InputSignalWithTransform<boolean, BooleanInput>;
|
|
78
|
+
/** Наименьшая высота поля в строках. */
|
|
79
|
+
readonly minRows: InputSignalWithTransform<number, NumberInput>;
|
|
80
|
+
/** Наибольшая высота поля в строках, дальше текст прокручивается. */
|
|
81
|
+
readonly maxRows: InputSignalWithTransform<number, NumberInput>;
|
|
82
|
+
/** Черновик: задаётся снаружи, правки уходят наружу, после отправки пуст. */
|
|
83
|
+
readonly text: ModelSignal<string>;
|
|
84
|
+
/** Отправка вопроса. */
|
|
85
|
+
readonly submitted: OutputEmitterRef<IRtAiPromptInput.SubmitPayload>;
|
|
86
|
+
/** Нажата «Стоп» во время ответа. */
|
|
87
|
+
readonly stopped: OutputEmitterRef<void>;
|
|
88
|
+
/** Нажата кнопка модели, усилия или режима. */
|
|
89
|
+
readonly controlPicked: OutputEmitterRef<IRtAiPromptInput.Control>;
|
|
90
|
+
/** Ставит фокус в поле. */
|
|
91
|
+
focus(): void;
|
|
92
|
+
protected onInput(event: Event): void;
|
|
93
|
+
protected onKeydown(event: KeyboardEvent): void;
|
|
94
|
+
protected pick(control: IRtAiPromptInput.Control): void;
|
|
95
|
+
protected stop(): void;
|
|
96
|
+
protected openPicker(): void;
|
|
97
|
+
protected onFilesPicked(event: Event): void;
|
|
98
|
+
protected removeFile(index: number): void;
|
|
99
|
+
protected submit(): void;
|
|
100
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<RtAiPromptInputComponent, never>;
|
|
101
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<RtAiPromptInputComponent, "rt-ai-prompt-input", never, {
|
|
102
|
+
"placeholder": {
|
|
103
|
+
"alias": "placeholder";
|
|
104
|
+
"required": false;
|
|
105
|
+
"isSignal": true;
|
|
106
|
+
};
|
|
107
|
+
"hint": {
|
|
108
|
+
"alias": "hint";
|
|
109
|
+
"required": false;
|
|
110
|
+
"isSignal": true;
|
|
111
|
+
};
|
|
112
|
+
"hintPosition": {
|
|
113
|
+
"alias": "hintPosition";
|
|
114
|
+
"required": false;
|
|
115
|
+
"isSignal": true;
|
|
116
|
+
};
|
|
117
|
+
"attachments": {
|
|
118
|
+
"alias": "attachments";
|
|
119
|
+
"required": false;
|
|
120
|
+
"isSignal": true;
|
|
121
|
+
};
|
|
122
|
+
"accept": {
|
|
123
|
+
"alias": "accept";
|
|
124
|
+
"required": false;
|
|
125
|
+
"isSignal": true;
|
|
126
|
+
};
|
|
127
|
+
"model": {
|
|
128
|
+
"alias": "model";
|
|
129
|
+
"required": false;
|
|
130
|
+
"isSignal": true;
|
|
131
|
+
};
|
|
132
|
+
"effort": {
|
|
133
|
+
"alias": "effort";
|
|
134
|
+
"required": false;
|
|
135
|
+
"isSignal": true;
|
|
136
|
+
};
|
|
137
|
+
"mode": {
|
|
138
|
+
"alias": "mode";
|
|
139
|
+
"required": false;
|
|
140
|
+
"isSignal": true;
|
|
141
|
+
};
|
|
142
|
+
"usage": {
|
|
143
|
+
"alias": "usage";
|
|
144
|
+
"required": false;
|
|
145
|
+
"isSignal": true;
|
|
146
|
+
};
|
|
147
|
+
"usageHint": {
|
|
148
|
+
"alias": "usageHint";
|
|
149
|
+
"required": false;
|
|
150
|
+
"isSignal": true;
|
|
151
|
+
};
|
|
152
|
+
"usageWarnAt": {
|
|
153
|
+
"alias": "usageWarnAt";
|
|
154
|
+
"required": false;
|
|
155
|
+
"isSignal": true;
|
|
156
|
+
};
|
|
157
|
+
"sending": {
|
|
158
|
+
"alias": "sending";
|
|
159
|
+
"required": false;
|
|
160
|
+
"isSignal": true;
|
|
161
|
+
};
|
|
162
|
+
"stoppable": {
|
|
163
|
+
"alias": "stoppable";
|
|
164
|
+
"required": false;
|
|
165
|
+
"isSignal": true;
|
|
166
|
+
};
|
|
167
|
+
"disabled": {
|
|
168
|
+
"alias": "disabled";
|
|
169
|
+
"required": false;
|
|
170
|
+
"isSignal": true;
|
|
171
|
+
};
|
|
172
|
+
"minRows": {
|
|
173
|
+
"alias": "minRows";
|
|
174
|
+
"required": false;
|
|
175
|
+
"isSignal": true;
|
|
176
|
+
};
|
|
177
|
+
"maxRows": {
|
|
178
|
+
"alias": "maxRows";
|
|
179
|
+
"required": false;
|
|
180
|
+
"isSignal": true;
|
|
181
|
+
};
|
|
182
|
+
"text": {
|
|
183
|
+
"alias": "text";
|
|
184
|
+
"required": false;
|
|
185
|
+
"isSignal": true;
|
|
186
|
+
};
|
|
187
|
+
}, {
|
|
188
|
+
"text": "textChange";
|
|
189
|
+
"submitted": "submitted";
|
|
190
|
+
"stopped": "stopped";
|
|
191
|
+
"controlPicked": "controlPicked";
|
|
192
|
+
}, never, never, true, never>;
|
|
193
|
+
}
|
|
@@ -487,12 +487,16 @@ export declare const RT_KIT_LABELS_EN: {
|
|
|
487
487
|
readonly aiLike: "Good answer";
|
|
488
488
|
readonly aiNewThread: "New conversation";
|
|
489
489
|
readonly aiNoThreads: "No conversations yet";
|
|
490
|
+
readonly aiPromptEffortAria: "Effort";
|
|
491
|
+
readonly aiPromptModeAria: "Mode";
|
|
492
|
+
readonly aiPromptModelAria: "Model";
|
|
490
493
|
readonly aiReference: "Reference";
|
|
491
494
|
readonly aiRetry: "Ask again";
|
|
492
495
|
readonly aiSearchThreads: "Search conversations";
|
|
493
496
|
readonly aiShowSteps: "Show steps";
|
|
494
497
|
readonly aiThreads: "Conversations";
|
|
495
498
|
readonly aiTitle: "Assistant";
|
|
499
|
+
readonly aiUsageAria: "Used {{percent}}% of the limit";
|
|
496
500
|
readonly aiYou: "You";
|
|
497
501
|
readonly asideCopyErrorInfo: "Copy error info";
|
|
498
502
|
readonly asideRequestError: "Request Error";
|