@qoretechnologies/reqraft 0.10.13-pr.74.gccefa54 → 0.10.13
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/.claude/CLAUDE.md +8 -11
- package/dist/components/form/engine/FormEngine.d.ts +1 -1
- package/dist/helpers/common.d.ts +0 -5
- package/dist/helpers/common.d.ts.map +1 -1
- package/dist/helpers/common.js +1 -20
- package/dist/helpers/common.js.map +1 -1
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -7
- package/dist/index.js.map +1 -1
- package/dist/providers/StorageProvider.d.ts +13 -0
- package/dist/providers/StorageProvider.d.ts.map +1 -1
- package/dist/providers/StorageProvider.js +29 -5
- package/dist/providers/StorageProvider.js.map +1 -1
- package/package.json +3 -3
- package/src/helpers/common.ts +0 -19
- package/src/hooks/useStorage/useStorage.stories.tsx +52 -0
- package/src/index.tsx +0 -5
- package/src/providers/StorageProvider.tsx +30 -4
- package/dist/components/composer/Composer.d.ts +0 -108
- package/dist/components/composer/Composer.d.ts.map +0 -1
- package/dist/components/composer/Composer.js +0 -238
- package/dist/components/composer/Composer.js.map +0 -1
- package/dist/components/composer/helpers.d.ts +0 -21
- package/dist/components/composer/helpers.d.ts.map +0 -1
- package/dist/components/composer/helpers.js +0 -75
- package/dist/components/composer/helpers.js.map +0 -1
- package/dist/components/composer/index.d.ts +0 -3
- package/dist/components/composer/index.d.ts.map +0 -1
- package/dist/components/composer/index.js +0 -19
- package/dist/components/composer/index.js.map +0 -1
- package/dist/components/imageLightbox/ImageLightbox.d.ts +0 -17
- package/dist/components/imageLightbox/ImageLightbox.d.ts.map +0 -1
- package/dist/components/imageLightbox/ImageLightbox.js +0 -129
- package/dist/components/imageLightbox/ImageLightbox.js.map +0 -1
- package/dist/components/imageLightbox/index.d.ts +0 -2
- package/dist/components/imageLightbox/index.d.ts.map +0 -1
- package/dist/components/imageLightbox/index.js +0 -18
- package/dist/components/imageLightbox/index.js.map +0 -1
- package/dist/components/supportTicket/AttachmentChips.d.ts +0 -10
- package/dist/components/supportTicket/AttachmentChips.d.ts.map +0 -1
- package/dist/components/supportTicket/AttachmentChips.js +0 -119
- package/dist/components/supportTicket/AttachmentChips.js.map +0 -1
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts +0 -35
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts.map +0 -1
- package/dist/components/supportTicket/InterfaceReferenceTags.js +0 -31
- package/dist/components/supportTicket/InterfaceReferenceTags.js.map +0 -1
- package/dist/components/supportTicket/ReferencePicker.d.ts +0 -41
- package/dist/components/supportTicket/ReferencePicker.d.ts.map +0 -1
- package/dist/components/supportTicket/ReferencePicker.js +0 -86
- package/dist/components/supportTicket/ReferencePicker.js.map +0 -1
- package/dist/components/supportTicket/TicketHeader.d.ts +0 -86
- package/dist/components/supportTicket/TicketHeader.d.ts.map +0 -1
- package/dist/components/supportTicket/TicketHeader.js +0 -149
- package/dist/components/supportTicket/TicketHeader.js.map +0 -1
- package/dist/components/supportTicket/TicketMetaTags.d.ts +0 -32
- package/dist/components/supportTicket/TicketMetaTags.d.ts.map +0 -1
- package/dist/components/supportTicket/TicketMetaTags.js +0 -21
- package/dist/components/supportTicket/TicketMetaTags.js.map +0 -1
- package/dist/components/supportTicket/TicketReplyBox.d.ts +0 -63
- package/dist/components/supportTicket/TicketReplyBox.d.ts.map +0 -1
- package/dist/components/supportTicket/TicketReplyBox.js +0 -115
- package/dist/components/supportTicket/TicketReplyBox.js.map +0 -1
- package/dist/components/supportTicket/fileToAttachment.d.ts +0 -9
- package/dist/components/supportTicket/fileToAttachment.d.ts.map +0 -1
- package/dist/components/supportTicket/fileToAttachment.js +0 -28
- package/dist/components/supportTicket/fileToAttachment.js.map +0 -1
- package/dist/components/supportTicket/index.d.ts +0 -9
- package/dist/components/supportTicket/index.d.ts.map +0 -1
- package/dist/components/supportTicket/index.js +0 -25
- package/dist/components/supportTicket/index.js.map +0 -1
- package/dist/components/supportTicket/meta.d.ts +0 -63
- package/dist/components/supportTicket/meta.d.ts.map +0 -1
- package/dist/components/supportTicket/meta.js +0 -79
- package/dist/components/supportTicket/meta.js.map +0 -1
- package/dist/components/ticketReferences/TicketReferences.d.ts +0 -52
- package/dist/components/ticketReferences/TicketReferences.d.ts.map +0 -1
- package/dist/components/ticketReferences/TicketReferences.js +0 -453
- package/dist/components/ticketReferences/TicketReferences.js.map +0 -1
- package/dist/components/ticketReferences/index.d.ts +0 -3
- package/dist/components/ticketReferences/index.d.ts.map +0 -1
- package/dist/components/ticketReferences/index.js +0 -6
- package/dist/components/ticketReferences/index.js.map +0 -1
- package/dist/components/ticketThread/TicketThread.d.ts +0 -52
- package/dist/components/ticketThread/TicketThread.d.ts.map +0 -1
- package/dist/components/ticketThread/TicketThread.js +0 -248
- package/dist/components/ticketThread/TicketThread.js.map +0 -1
- package/dist/components/ticketThread/index.d.ts +0 -3
- package/dist/components/ticketThread/index.d.ts.map +0 -1
- package/dist/components/ticketThread/index.js +0 -7
- package/dist/components/ticketThread/index.js.map +0 -1
- package/dist/helpers/scrollbar.d.ts +0 -2
- package/dist/helpers/scrollbar.d.ts.map +0 -1
- package/dist/helpers/scrollbar.js +0 -25
- package/dist/helpers/scrollbar.js.map +0 -1
- package/src/components/composer/Composer.stories.tsx +0 -238
- package/src/components/composer/Composer.tsx +0 -545
- package/src/components/composer/helpers.ts +0 -74
- package/src/components/composer/index.ts +0 -2
- package/src/components/imageLightbox/ImageLightbox.stories.tsx +0 -156
- package/src/components/imageLightbox/ImageLightbox.tsx +0 -169
- package/src/components/imageLightbox/index.ts +0 -1
- package/src/components/supportTicket/AttachmentChips.stories.tsx +0 -122
- package/src/components/supportTicket/AttachmentChips.tsx +0 -142
- package/src/components/supportTicket/InterfaceReferenceTags.stories.tsx +0 -134
- package/src/components/supportTicket/InterfaceReferenceTags.tsx +0 -85
- package/src/components/supportTicket/ReferencePicker.stories.tsx +0 -296
- package/src/components/supportTicket/ReferencePicker.tsx +0 -237
- package/src/components/supportTicket/TicketConversation.stories.tsx +0 -122
- package/src/components/supportTicket/TicketHeader.stories.tsx +0 -183
- package/src/components/supportTicket/TicketHeader.tsx +0 -247
- package/src/components/supportTicket/TicketMetaTags.stories.tsx +0 -103
- package/src/components/supportTicket/TicketMetaTags.tsx +0 -85
- package/src/components/supportTicket/TicketReplyBox.stories.tsx +0 -355
- package/src/components/supportTicket/TicketReplyBox.tsx +0 -146
- package/src/components/supportTicket/fileToAttachment.ts +0 -24
- package/src/components/supportTicket/index.ts +0 -8
- package/src/components/supportTicket/meta.ts +0 -145
- package/src/components/ticketReferences/TicketReferences.stories.tsx +0 -301
- package/src/components/ticketReferences/TicketReferences.tsx +0 -899
- package/src/components/ticketReferences/index.ts +0 -2
- package/src/components/ticketThread/TicketThread.stories.tsx +0 -751
- package/src/components/ticketThread/TicketThread.tsx +0 -650
- package/src/components/ticketThread/index.ts +0 -6
- package/src/helpers/scrollbar.ts +0 -32
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
import { ReqoreButton, ReqoreUIProvider } from '@qoretechnologies/reqore';
|
|
2
|
-
import { StoryObj } from '@storybook/react-vite';
|
|
3
|
-
import { expect, fn, screen, userEvent, waitFor, within } from 'storybook/test';
|
|
4
|
-
import { StoryMeta } from '../../types';
|
|
5
|
-
import { InterfaceReferenceTags } from './InterfaceReferenceTags';
|
|
6
|
-
import { TicketReplyBox } from './TicketReplyBox';
|
|
7
|
-
|
|
8
|
-
const meta = {
|
|
9
|
-
component: TicketReplyBox,
|
|
10
|
-
title: 'Components/TicketReplyBox',
|
|
11
|
-
args: {
|
|
12
|
-
onSend: fn(),
|
|
13
|
-
},
|
|
14
|
-
} as StoryMeta<typeof TicketReplyBox>;
|
|
15
|
-
|
|
16
|
-
export default meta;
|
|
17
|
-
type Story = StoryObj<typeof meta>;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* The customer composer, plain-editor variant (used here for a deterministic
|
|
21
|
-
* type→send test): an attach button (no extra menu → a plain button, not a
|
|
22
|
-
* dropdown) and Send. No internal-note toggle. Typing enables Send; clicking it
|
|
23
|
-
* calls `onSend` with the body and `internal: false`.
|
|
24
|
-
*/
|
|
25
|
-
export const Default: Story = {
|
|
26
|
-
parameters: {
|
|
27
|
-
docs: {
|
|
28
|
-
description: {
|
|
29
|
-
story:
|
|
30
|
-
"Renders the customer composer's plain-editor variant — an attach button and Send, no internal-note toggle; typing a reply enables Send, which calls onSend with the body and internal: false.",
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
args: { editor: 'plain' },
|
|
35
|
-
async play({ canvasElement, args }) {
|
|
36
|
-
const canvas = within(canvasElement);
|
|
37
|
-
const textarea = canvasElement.querySelector('textarea');
|
|
38
|
-
await expect(textarea).not.toBeNull();
|
|
39
|
-
await expect(canvas.queryByText('Internal note')).not.toBeInTheDocument();
|
|
40
|
-
|
|
41
|
-
await userEvent.type(textarea!, 'Here is my reply');
|
|
42
|
-
await userEvent.click(canvas.getByText('Send reply'));
|
|
43
|
-
await waitFor(() =>
|
|
44
|
-
expect(args.onSend).toHaveBeenCalledWith(
|
|
45
|
-
'Here is my reply',
|
|
46
|
-
expect.objectContaining({ internal: false })
|
|
47
|
-
)
|
|
48
|
-
);
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* The staff composer (`allowInternalNote`): no toggle — the send button splits, so
|
|
54
|
-
* the primary action posts a public reply and the caret opens "Add internal note",
|
|
55
|
-
* which sends with `internal: true`.
|
|
56
|
-
*/
|
|
57
|
-
export const StaffWithInternalNote: Story = {
|
|
58
|
-
parameters: {
|
|
59
|
-
docs: {
|
|
60
|
-
description: {
|
|
61
|
-
story:
|
|
62
|
-
"Renders the staff composer with a split send button — the primary action posts a public reply, while the caret opens 'Add internal note', which sends with internal: true.",
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
args: { allowInternalNote: true, editor: 'plain' },
|
|
67
|
-
async play({ canvasElement, args }) {
|
|
68
|
-
const canvas = within(canvasElement);
|
|
69
|
-
// no toggle any more; the primary action is a plain "Send reply"
|
|
70
|
-
await expect(canvas.queryByText('Internal note')).not.toBeInTheDocument();
|
|
71
|
-
await expect(canvas.getByText('Send reply')).toBeInTheDocument();
|
|
72
|
-
|
|
73
|
-
const textarea = canvasElement.querySelector('textarea');
|
|
74
|
-
await userEvent.type(textarea!, 'internal context');
|
|
75
|
-
|
|
76
|
-
// "Add internal note" lives behind the split-send caret
|
|
77
|
-
const caret = canvasElement.querySelector('.ticket-send-more');
|
|
78
|
-
await expect(caret).not.toBeNull();
|
|
79
|
-
await userEvent.click(caret as Element);
|
|
80
|
-
await userEvent.click(await screen.findByText('Add internal note'));
|
|
81
|
-
|
|
82
|
-
await waitFor(() =>
|
|
83
|
-
expect(args.onSend).toHaveBeenCalledWith(
|
|
84
|
-
'internal context',
|
|
85
|
-
expect.objectContaining({ internal: true })
|
|
86
|
-
)
|
|
87
|
-
);
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
/** A closed ticket replaces the editor with a muted notice. */
|
|
92
|
-
export const Closed: Story = {
|
|
93
|
-
parameters: {
|
|
94
|
-
docs: {
|
|
95
|
-
description: {
|
|
96
|
-
story:
|
|
97
|
-
"Renders a closed ticket, where the disabled composer replaces the editor with a muted 'ticket is closed' notice.",
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
args: { disabled: true },
|
|
102
|
-
async play({ canvasElement }) {
|
|
103
|
-
await expect(within(canvasElement).getByText(/ticket is closed/i)).toBeInTheDocument();
|
|
104
|
-
await expect(canvasElement.querySelector('textarea')).toBeNull();
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* The helpdesk composer as it now ships: the shared `<Composer>` with the rich,
|
|
110
|
-
* Qonsole-style editor (`ReqoreRichTextEditor`) instead of a plain textarea. Staff
|
|
111
|
-
* split-send, an attach dropdown (Upload + injected screenshot/capture), the
|
|
112
|
-
* reference-interfaces toggle, and a reference chip below — all through the same
|
|
113
|
-
* slots. Markdown still flows to `onSend` as a plain string. This is the input the
|
|
114
|
-
* helpdesk uses.
|
|
115
|
-
*/
|
|
116
|
-
export const RichHelpdeskReply: Story = {
|
|
117
|
-
parameters: {
|
|
118
|
-
docs: {
|
|
119
|
-
description: {
|
|
120
|
-
story:
|
|
121
|
-
'Renders the shipping helpdesk composer with the rich Qonsole-style editor — staff split-send, an attach dropdown, a reference-interfaces toggle, and an interface reference chip above the input.',
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
// Rendered under the Qorus theme (as in qorus-ide / admin-portal) so the
|
|
126
|
-
// `custom1` accent shows as the Qorus purple — a bare reqore theme greys it out.
|
|
127
|
-
decorators: [
|
|
128
|
-
(Story) => (
|
|
129
|
-
<ReqoreUIProvider theme={{ main: '#121212', intents: { custom1: '#762f7e' } }}>
|
|
130
|
-
<div style={{ padding: 20, background: '#121212' }}>
|
|
131
|
-
<Story />
|
|
132
|
-
</div>
|
|
133
|
-
</ReqoreUIProvider>
|
|
134
|
-
),
|
|
135
|
-
],
|
|
136
|
-
args: {
|
|
137
|
-
allowInternalNote: true,
|
|
138
|
-
defaultText:
|
|
139
|
-
"Thanks for the detailed report — we've reproduced the timeout and a fix is in review. In the meantime, can you confirm the datasource pool size configured for the sftp-partner connection?",
|
|
140
|
-
attachMenuItems: () => [
|
|
141
|
-
{ label: 'Take screenshot', icon: 'CameraAiLine', onClick: fn() },
|
|
142
|
-
{ label: 'Capture area', icon: 'CropLine', onClick: fn() },
|
|
143
|
-
],
|
|
144
|
-
// the reference toggle, styled to the same custom1 accent as the send
|
|
145
|
-
footerActions: (
|
|
146
|
-
<ReqoreButton
|
|
147
|
-
icon='LinksLine'
|
|
148
|
-
minimal
|
|
149
|
-
flat
|
|
150
|
-
badge={1}
|
|
151
|
-
customTheme={{ main: 'custom1' }}
|
|
152
|
-
tooltip='Reference interfaces'
|
|
153
|
-
/>
|
|
154
|
-
),
|
|
155
|
-
// references on top of the editor (inside the bar), tinted to the accent — no
|
|
156
|
-
// "References" label, the chips read as the referenced interfaces on their own
|
|
157
|
-
aboveInput: (
|
|
158
|
-
<InterfaceReferenceTags
|
|
159
|
-
size='tiny'
|
|
160
|
-
references={[{ interface_kind: 'connection', interface_name: 'sftp-partner' }]}
|
|
161
|
-
customTheme={{ main: 'custom1' }}
|
|
162
|
-
/>
|
|
163
|
-
),
|
|
164
|
-
},
|
|
165
|
-
async play({ canvasElement }) {
|
|
166
|
-
const canvas = within(canvasElement);
|
|
167
|
-
// rich editor renders a Slate contenteditable (not a <textarea>) …
|
|
168
|
-
await expect(canvasElement.querySelector('[contenteditable="true"]')).not.toBeNull();
|
|
169
|
-
await expect(canvasElement.querySelector('textarea')).toBeNull();
|
|
170
|
-
// … the staff split-send caret is present …
|
|
171
|
-
await expect(canvasElement.querySelector('.ticket-send-more')).not.toBeNull();
|
|
172
|
-
// … and the reference chip renders on top (no "References" label).
|
|
173
|
-
await expect(canvas.getByText('sftp-partner')).toBeInTheDocument();
|
|
174
|
-
await expect(canvas.queryByText('References')).not.toBeInTheDocument();
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Every referenceable interface kind on one message, so the per-kind icons +
|
|
180
|
-
* accent chip styling can be reviewed together: workflow, service, job, fsm,
|
|
181
|
-
* connection, mapper, class, value-map, qog, ai-collection, ai-endpoint,
|
|
182
|
-
* ai-guardrail. (`qog` has no built-in icon → the generic fallback.)
|
|
183
|
-
*/
|
|
184
|
-
export const AllReferenceTypes: Story = {
|
|
185
|
-
parameters: {
|
|
186
|
-
docs: {
|
|
187
|
-
description: {
|
|
188
|
-
story:
|
|
189
|
-
'Renders a reply carrying every referenceable interface kind at once, so the per-kind icons and accent chip styling can be reviewed together (qog falls back to the generic glyph).',
|
|
190
|
-
},
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
decorators: [
|
|
194
|
-
(Story) => (
|
|
195
|
-
<ReqoreUIProvider theme={{ main: '#121212', intents: { custom1: '#762f7e' } }}>
|
|
196
|
-
<div style={{ padding: 20, background: '#121212' }}>
|
|
197
|
-
<Story />
|
|
198
|
-
</div>
|
|
199
|
-
</ReqoreUIProvider>
|
|
200
|
-
),
|
|
201
|
-
],
|
|
202
|
-
args: {
|
|
203
|
-
defaultText: 'This one references every interface kind.',
|
|
204
|
-
aboveInput: (
|
|
205
|
-
<InterfaceReferenceTags
|
|
206
|
-
size='tiny'
|
|
207
|
-
customTheme={{ main: 'custom1' }}
|
|
208
|
-
references={[
|
|
209
|
-
{ interface_kind: 'workflow', interface_name: 'order-sync:1.2' },
|
|
210
|
-
{ interface_kind: 'service', interface_name: 'notifier' },
|
|
211
|
-
{ interface_kind: 'job', interface_name: 'nightly-recon' },
|
|
212
|
-
{ interface_kind: 'fsm', interface_name: 'onboarding' },
|
|
213
|
-
{ interface_kind: 'connection', interface_name: 'sftp-partner' },
|
|
214
|
-
{ interface_kind: 'mapper', interface_name: 'csv-to-order' },
|
|
215
|
-
{ interface_kind: 'class', interface_name: 'OrderUtils' },
|
|
216
|
-
{ interface_kind: 'value-map', interface_name: 'country-codes' },
|
|
217
|
-
{ interface_kind: 'qog', interface_name: 'daily-report' },
|
|
218
|
-
{ interface_kind: 'ai-collection', interface_name: 'kb-support' },
|
|
219
|
-
{ interface_kind: 'ai-endpoint', interface_name: 'gpt-router' },
|
|
220
|
-
{ interface_kind: 'ai-guardrail', interface_name: 'pii-filter' },
|
|
221
|
-
]}
|
|
222
|
-
/>
|
|
223
|
-
),
|
|
224
|
-
},
|
|
225
|
-
async play({ canvasElement }) {
|
|
226
|
-
const canvas = within(canvasElement);
|
|
227
|
-
await expect(canvas.getByText('workflow')).toBeInTheDocument();
|
|
228
|
-
await expect(canvas.getByText('ai-guardrail')).toBeInTheDocument();
|
|
229
|
-
},
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// Stand-in app logos (a coloured rounded square + initial) as data-URIs, so the
|
|
233
|
-
// story needs no network. Real logos come from the app catalogue (`app.logo`).
|
|
234
|
-
const appLogo = (hex: string, letter: string) =>
|
|
235
|
-
`data:image/svg+xml,${encodeURIComponent(
|
|
236
|
-
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect width="24" height="24" rx="6" fill="${hex}"/><text x="12" y="17" font-size="13" font-family="sans-serif" font-weight="bold" fill="#fff" text-anchor="middle">${letter}</text></svg>`
|
|
237
|
-
)}`;
|
|
238
|
-
const APP_LOGOS: Record<string, string> = {
|
|
239
|
-
Telegram: appLogo('#2AABEE', 'T'),
|
|
240
|
-
Stripe: appLogo('#635BFF', 'S'),
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Faithful qog icons — a qog referenced by its trigger app shows that app's logo,
|
|
245
|
-
* exactly like the qogs list. The customer's IDE resolves it live from the
|
|
246
|
-
* reference's `trigger_app` via `resolveInterfaceImage` (it has the app catalogue);
|
|
247
|
-
* a stored `logo` travels to viewers that don't (staff triage, the Qonsole card).
|
|
248
|
-
* A qog with no trigger app falls back to the qog glyph.
|
|
249
|
-
*/
|
|
250
|
-
export const QogTriggerAppLogo: Story = {
|
|
251
|
-
parameters: {
|
|
252
|
-
docs: {
|
|
253
|
-
description: {
|
|
254
|
-
story:
|
|
255
|
-
"Renders qogs referenced by their trigger apps, each showing that app's logo — resolved live via resolveInterfaceImage or from a stored logo — while a qog with no trigger app falls back to the qog glyph.",
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
},
|
|
259
|
-
decorators: [
|
|
260
|
-
(Story) => (
|
|
261
|
-
<ReqoreUIProvider theme={{ main: '#121212', intents: { custom1: '#762f7e' } }}>
|
|
262
|
-
<div style={{ padding: 20, background: '#121212' }}>
|
|
263
|
-
<Story />
|
|
264
|
-
</div>
|
|
265
|
-
</ReqoreUIProvider>
|
|
266
|
-
),
|
|
267
|
-
],
|
|
268
|
-
args: {
|
|
269
|
-
defaultText: 'These qogs are referenced by their trigger apps.',
|
|
270
|
-
aboveInput: (
|
|
271
|
-
<InterfaceReferenceTags
|
|
272
|
-
size='tiny'
|
|
273
|
-
customTheme={{ main: 'custom1' }}
|
|
274
|
-
references={[
|
|
275
|
-
// resolved live from trigger_app (the customer/IDE path)
|
|
276
|
-
{ interface_kind: 'qog', interface_name: 'telegram-intake', trigger_app: 'Telegram' },
|
|
277
|
-
{ interface_kind: 'qog', interface_name: 'stripe-billing', trigger_app: 'Stripe' },
|
|
278
|
-
// stored logo travels to viewers without the app catalogue (staff / Qonsole)
|
|
279
|
-
{ interface_kind: 'qog', interface_name: 'slack-alerts', logo: appLogo('#4A154B', 'S') },
|
|
280
|
-
// no trigger app → the qog glyph fallback
|
|
281
|
-
{ interface_kind: 'qog', interface_name: 'manual-recon' },
|
|
282
|
-
]}
|
|
283
|
-
resolveInterfaceImage={(ref) => (ref.trigger_app ? APP_LOGOS[ref.trigger_app] : undefined)}
|
|
284
|
-
/>
|
|
285
|
-
),
|
|
286
|
-
},
|
|
287
|
-
async play({ canvasElement }) {
|
|
288
|
-
const canvas = within(canvasElement);
|
|
289
|
-
await expect(canvas.getByText('telegram-intake')).toBeInTheDocument();
|
|
290
|
-
// the trigger-app / stored-logo qogs render their app logo image
|
|
291
|
-
await expect(canvasElement.innerHTML).toContain('data:image');
|
|
292
|
-
},
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
// A stand-in "screenshot" as an SVG File so the story needs no real capture — the
|
|
296
|
-
// composer's attachment strip renders any image File as a thumbnail (the real IDE
|
|
297
|
-
// stages a PNG from captureScreenshot / captureRegion the same way).
|
|
298
|
-
const screenshotFile = () => {
|
|
299
|
-
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="260" height="150" viewBox="0 0 260 150">
|
|
300
|
-
<rect width="260" height="150" rx="8" fill="#0f0b16"/>
|
|
301
|
-
<rect width="260" height="26" fill="#1c1428"/>
|
|
302
|
-
<circle cx="16" cy="13" r="4" fill="#ff5f57"/><circle cx="32" cy="13" r="4" fill="#febc2e"/><circle cx="48" cy="13" r="4" fill="#28c840"/>
|
|
303
|
-
<rect x="16" y="44" width="110" height="12" rx="3" fill="#8e39a0"/>
|
|
304
|
-
<rect x="16" y="68" width="228" height="9" rx="3" fill="#2c2440"/>
|
|
305
|
-
<rect x="16" y="86" width="200" height="9" rx="3" fill="#2c2440"/>
|
|
306
|
-
<rect x="16" y="104" width="170" height="9" rx="3" fill="#2c2440"/>
|
|
307
|
-
<rect x="16" y="122" width="92" height="9" rx="3" fill="#2c2440"/>
|
|
308
|
-
</svg>`;
|
|
309
|
-
return new File([svg], 'region-capture.svg', { type: 'image/svg+xml' });
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* A composed reply with both a staged screenshot (the attachment strip renders any
|
|
314
|
-
* image File as a thumbnail — the real IDE stages a PNG from Take-screenshot /
|
|
315
|
-
* Capture-area) and interface references on top.
|
|
316
|
-
*/
|
|
317
|
-
export const WithScreenshotAndReferences: Story = {
|
|
318
|
-
parameters: {
|
|
319
|
-
docs: {
|
|
320
|
-
description: {
|
|
321
|
-
story:
|
|
322
|
-
'Renders a composed reply carrying both a staged screenshot thumbnail in the attachment strip and interface references above the input.',
|
|
323
|
-
},
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
decorators: [
|
|
327
|
-
(Story) => (
|
|
328
|
-
<ReqoreUIProvider theme={{ main: '#121212', intents: { custom1: '#762f7e' } }}>
|
|
329
|
-
<div style={{ padding: 20, background: '#121212' }}>
|
|
330
|
-
<Story />
|
|
331
|
-
</div>
|
|
332
|
-
</ReqoreUIProvider>
|
|
333
|
-
),
|
|
334
|
-
],
|
|
335
|
-
args: {
|
|
336
|
-
defaultText: "Here's the timeout I'm seeing on the nightly run — screenshot attached.",
|
|
337
|
-
defaultFiles: [screenshotFile()],
|
|
338
|
-
aboveInput: (
|
|
339
|
-
<InterfaceReferenceTags
|
|
340
|
-
size='tiny'
|
|
341
|
-
customTheme={{ main: 'custom1' }}
|
|
342
|
-
references={[
|
|
343
|
-
{ interface_kind: 'connection', interface_name: 'sftp-partner' },
|
|
344
|
-
{ interface_kind: 'qog', interface_name: 'telegram-intake', logo: appLogo('#2AABEE', 'T') },
|
|
345
|
-
]}
|
|
346
|
-
/>
|
|
347
|
-
),
|
|
348
|
-
},
|
|
349
|
-
async play({ canvasElement }) {
|
|
350
|
-
const canvas = within(canvasElement);
|
|
351
|
-
// the staged screenshot thumbnail (an <img>) and the references both render
|
|
352
|
-
await expect(canvasElement.querySelector('img')).not.toBeNull();
|
|
353
|
-
await expect(canvas.getByText('sftp-partner')).toBeInTheDocument();
|
|
354
|
-
},
|
|
355
|
-
};
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { ReqoreDropdown } from '@qoretechnologies/reqore';
|
|
2
|
-
import { ComponentProps, ReactNode } from 'react';
|
|
3
|
-
import { Composer, IComposerSendAction, TComposerEditor } from '../composer/Composer';
|
|
4
|
-
import { fileToAttachment as defaultFileToAttachment } from './fileToAttachment';
|
|
5
|
-
import { IAttachmentUpload } from './meta';
|
|
6
|
-
|
|
7
|
-
/** The exact `items` shape ReqoreDropdown accepts — no import-path guessing. */
|
|
8
|
-
type TAttachMenuItems = NonNullable<ComponentProps<typeof ReqoreDropdown>['items']>;
|
|
9
|
-
|
|
10
|
-
export interface ITicketReplyBoxProps {
|
|
11
|
-
/** Send the composed message. `internal` is only ever true when
|
|
12
|
-
* `allowInternalNote` is set (the staff surface). */
|
|
13
|
-
onSend: (
|
|
14
|
-
body: string,
|
|
15
|
-
options: { internal: boolean; attachments?: IAttachmentUpload[] }
|
|
16
|
-
) => void | Promise<void>;
|
|
17
|
-
/** File → base64 upload conversion; defaults to the shared `fileToAttachment`. */
|
|
18
|
-
fileToAttachment?: (file: File) => Promise<IAttachmentUpload>;
|
|
19
|
-
/** the send round-trip is in flight */
|
|
20
|
-
sending?: boolean;
|
|
21
|
-
/** the ticket can't be replied to (e.g. closed) — renders a notice instead */
|
|
22
|
-
disabled?: boolean;
|
|
23
|
-
disabledReason?: string;
|
|
24
|
-
/** placeholder text. (A function is called with `false` — the mode is chosen at
|
|
25
|
-
* send time, so there is no live internal-note mode.) */
|
|
26
|
-
placeholder?: string | ((internal: boolean) => string);
|
|
27
|
-
/** send labels; a function is called with `false` for the primary "Send reply"
|
|
28
|
-
* and `true` for the "Add internal note" split-menu item. Defaults to
|
|
29
|
-
* "Send reply" / "Add internal note". */
|
|
30
|
-
sendLabel?: string | ((internal: boolean) => string);
|
|
31
|
-
/** Staff surface: split the send button so it also offers "Add internal note"
|
|
32
|
-
* (customer composers omit it and just send a reply). */
|
|
33
|
-
allowInternalNote?: boolean;
|
|
34
|
-
/** Extra attach-menu entries beyond "Upload files" — e.g. the IDE's screenshot /
|
|
35
|
-
* region capture. Receives the composer's `addFiles`. With any entry the attach
|
|
36
|
-
* control becomes a dropdown; otherwise it's a plain button. */
|
|
37
|
-
attachMenuItems?: (addFiles: (files: File[]) => void) => TAttachMenuItems;
|
|
38
|
-
/** Extra footer controls left of the send button — e.g. the IDE's
|
|
39
|
-
* reference-interfaces toggle. */
|
|
40
|
-
footerActions?: ReactNode;
|
|
41
|
-
/** Rendered on top of the editor, inside the bar — e.g. reference chips. */
|
|
42
|
-
aboveInput?: ReactNode;
|
|
43
|
-
/** Rendered below the editor — e.g. the IDE's interface-reference picker. */
|
|
44
|
-
belowInput?: ReactNode;
|
|
45
|
-
/** Editor flavour. Helpdesk uses the rich, Qonsole-style editor by default; pass
|
|
46
|
-
* `plain` for a simple textarea (e.g. deterministic play tests). */
|
|
47
|
-
editor?: TComposerEditor;
|
|
48
|
-
/** Initial draft text to seed the editor with (e.g. a restored draft). */
|
|
49
|
-
defaultText?: string;
|
|
50
|
-
/** Initial staged files (e.g. a restored draft's attachments, or a demo). */
|
|
51
|
-
defaultFiles?: File[];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* The shared ticket reply composer — now a thin adapter over the universal
|
|
56
|
-
* `<Composer>` (the input extracted from qorus-ide's Qonsole chat). It maps the
|
|
57
|
-
* ticket vocabulary onto the composer: the staff `allowInternalNote` becomes a
|
|
58
|
-
* split send (primary "Send reply" + a caret "Add internal note", mode chosen at
|
|
59
|
-
* send time), and staged files convert to `IAttachmentUpload[]` via
|
|
60
|
-
* `fileToAttachment` before `onSend`. App-specific affordances still arrive
|
|
61
|
-
* through slots (`attachMenuItems`, `footerActions`, `belowInput`), so the
|
|
62
|
-
* IDE-only bits (screenshot capture, the reference picker) stay in qorus-ide. A
|
|
63
|
-
* closed ticket renders a muted notice.
|
|
64
|
-
*/
|
|
65
|
-
export const TicketReplyBox = ({
|
|
66
|
-
onSend,
|
|
67
|
-
fileToAttachment = defaultFileToAttachment,
|
|
68
|
-
sending,
|
|
69
|
-
disabled,
|
|
70
|
-
disabledReason = 'This ticket is closed. Open a new ticket to continue the conversation.',
|
|
71
|
-
placeholder = 'Write a reply…',
|
|
72
|
-
sendLabel,
|
|
73
|
-
allowInternalNote,
|
|
74
|
-
attachMenuItems,
|
|
75
|
-
footerActions,
|
|
76
|
-
aboveInput,
|
|
77
|
-
belowInput,
|
|
78
|
-
editor = 'rich',
|
|
79
|
-
defaultText,
|
|
80
|
-
defaultFiles,
|
|
81
|
-
}: ITicketReplyBoxProps) => {
|
|
82
|
-
const resolvedPlaceholder =
|
|
83
|
-
typeof placeholder === 'function' ? placeholder(false) : placeholder;
|
|
84
|
-
// Label for a given send mode: `false` = public reply, `true` = internal note.
|
|
85
|
-
const labelFor = (note: boolean): string =>
|
|
86
|
-
sendLabel !== undefined
|
|
87
|
-
? typeof sendLabel === 'function'
|
|
88
|
-
? sendLabel(note)
|
|
89
|
-
: sendLabel
|
|
90
|
-
: note
|
|
91
|
-
? 'Add internal note'
|
|
92
|
-
: 'Send reply';
|
|
93
|
-
|
|
94
|
-
// Staff surface splits the send button; customer surface is a single "Send reply".
|
|
95
|
-
// Both use the Qonsole-style minimal `custom1` accent (the Composer default); the
|
|
96
|
-
// internal note stays amber (`warning`) so it can't be mistaken for a public reply.
|
|
97
|
-
const sendActions: IComposerSendAction[] | undefined = allowInternalNote
|
|
98
|
-
? [
|
|
99
|
-
{
|
|
100
|
-
id: 'reply',
|
|
101
|
-
label: labelFor(false),
|
|
102
|
-
icon: 'SendPlane2Line',
|
|
103
|
-
minimal: true,
|
|
104
|
-
customTheme: { main: 'custom1' },
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
id: 'internal',
|
|
108
|
-
label: labelFor(true),
|
|
109
|
-
icon: 'StickyNoteLine',
|
|
110
|
-
minimal: true,
|
|
111
|
-
intent: 'warning',
|
|
112
|
-
},
|
|
113
|
-
]
|
|
114
|
-
: undefined;
|
|
115
|
-
|
|
116
|
-
const handleSend = async (
|
|
117
|
-
body: string,
|
|
118
|
-
{ files, action }: { files: File[]; action?: string }
|
|
119
|
-
): Promise<void> => {
|
|
120
|
-
const internal = action === 'internal';
|
|
121
|
-
const attachments = files.length
|
|
122
|
-
? await Promise.all(files.map(fileToAttachment))
|
|
123
|
-
: undefined;
|
|
124
|
-
await onSend(body, { internal, attachments });
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
<Composer
|
|
129
|
-
editor={editor}
|
|
130
|
-
defaultText={defaultText}
|
|
131
|
-
defaultFiles={defaultFiles}
|
|
132
|
-
placeholder={resolvedPlaceholder}
|
|
133
|
-
sending={sending}
|
|
134
|
-
disabled={disabled}
|
|
135
|
-
disabledReason={disabledReason}
|
|
136
|
-
onSend={handleSend}
|
|
137
|
-
sendLabel={labelFor(false)}
|
|
138
|
-
sendActions={sendActions}
|
|
139
|
-
sendMenuClassName='ticket-send-more'
|
|
140
|
-
attachMenuItems={attachMenuItems}
|
|
141
|
-
footerActions={footerActions}
|
|
142
|
-
aboveInput={aboveInput}
|
|
143
|
-
belowInput={belowInput}
|
|
144
|
-
/>
|
|
145
|
-
);
|
|
146
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { IAttachmentUpload } from './meta';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Read a picked `File` into the inline base64 upload shape the support API expects
|
|
5
|
-
* (the SaaS plane accepts attachments as base64 on the create/reply payload, with
|
|
6
|
-
* no separate multipart upload). Shared by every composer so the customer and
|
|
7
|
-
* staff surfaces encode files identically.
|
|
8
|
-
*/
|
|
9
|
-
export const fileToAttachment = (file: File): Promise<IAttachmentUpload> =>
|
|
10
|
-
new Promise((resolve, reject) => {
|
|
11
|
-
const reader = new FileReader();
|
|
12
|
-
reader.onerror = () => reject(new Error(`Could not read ${file.name}`));
|
|
13
|
-
reader.onload = () => {
|
|
14
|
-
// reader.result is a data URL: "data:<type>;base64,<content>"
|
|
15
|
-
const result = String(reader.result);
|
|
16
|
-
const content = result.slice(result.indexOf(',') + 1);
|
|
17
|
-
resolve({
|
|
18
|
-
filename: file.name,
|
|
19
|
-
content_type: file.type || 'application/octet-stream',
|
|
20
|
-
content,
|
|
21
|
-
});
|
|
22
|
-
};
|
|
23
|
-
reader.readAsDataURL(file);
|
|
24
|
-
});
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export * from './meta';
|
|
2
|
-
export * from './AttachmentChips';
|
|
3
|
-
export * from './TicketMetaTags';
|
|
4
|
-
export * from './TicketHeader';
|
|
5
|
-
export * from './InterfaceReferenceTags';
|
|
6
|
-
export * from './ReferencePicker';
|
|
7
|
-
export * from './fileToAttachment';
|
|
8
|
-
export * from './TicketReplyBox';
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { TReqoreIntent } from '@qoretechnologies/reqore/dist/constants/theme';
|
|
2
|
-
import { IReqoreIconName } from '@qoretechnologies/reqore/dist/types/icons';
|
|
3
|
-
|
|
4
|
-
/*
|
|
5
|
-
* The single source of truth for the Qorus support-ticket presentation
|
|
6
|
-
* vocabulary — how each status / priority / category / audit-action value
|
|
7
|
-
* renders (label + Reqore intent + icon), plus the interface-kind icon map and
|
|
8
|
-
* the small enum helpers. Shared by every Qorus surface that shows a ticket
|
|
9
|
-
* (the qorus-ide customer view, the admin-portal staff queue, the Qonsole
|
|
10
|
-
* renderer) so the chips read identically everywhere and can't drift apart.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export type TTicketStatus = 'new' | 'open' | 'awaiting_customer' | 'resolved' | 'closed';
|
|
14
|
-
export type TTicketPriority = 'low' | 'normal' | 'high' | 'urgent';
|
|
15
|
-
export type TTicketCategory = 'bug' | 'question' | 'billing' | 'feature' | 'other';
|
|
16
|
-
export type TTicketAuditAction = 'replied' | 'noted' | 'status_changed' | 'assigned' | 'updated';
|
|
17
|
-
|
|
18
|
-
/** A message author. Staff identities are masked from the customer by the backend. */
|
|
19
|
-
export type TTicketMessageAuthor = 'customer' | 'staff' | 'system';
|
|
20
|
-
|
|
21
|
-
/** Which side "you" sit on — decides self-alignment and audience-specific labels. */
|
|
22
|
-
export type TTicketViewerRole = 'customer' | 'staff';
|
|
23
|
-
|
|
24
|
-
export interface ITicketEnumMeta {
|
|
25
|
-
label: string;
|
|
26
|
-
intent: TReqoreIntent;
|
|
27
|
-
icon: IReqoreIconName;
|
|
28
|
-
/** Audience-specific label for the customer view; falls back to `label` (the
|
|
29
|
-
* staff / neutral wording). Only a few values differ by audience. */
|
|
30
|
-
customerLabel?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const TICKET_STATUS_META: Record<TTicketStatus, ITicketEnumMeta> = {
|
|
34
|
-
new: { label: 'New', intent: 'info', icon: 'SparklingLine' },
|
|
35
|
-
open: { label: 'Open', intent: 'pending', icon: 'Chat3Line' },
|
|
36
|
-
awaiting_customer: {
|
|
37
|
-
label: 'Awaiting customer',
|
|
38
|
-
customerLabel: 'Awaiting your reply',
|
|
39
|
-
intent: 'warning',
|
|
40
|
-
icon: 'UserReceivedLine',
|
|
41
|
-
},
|
|
42
|
-
resolved: { label: 'Resolved', intent: 'success', icon: 'CheckLine' },
|
|
43
|
-
closed: { label: 'Closed', intent: 'muted', icon: 'CloseCircleLine' },
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export const TICKET_PRIORITY_META: Record<TTicketPriority, ITicketEnumMeta> = {
|
|
47
|
-
low: { label: 'Low', intent: 'muted', icon: 'ArrowDownLine' },
|
|
48
|
-
normal: { label: 'Normal', intent: 'info', icon: 'SubtractLine' },
|
|
49
|
-
high: { label: 'High', intent: 'warning', icon: 'ArrowUpLine' },
|
|
50
|
-
urgent: { label: 'Urgent', intent: 'danger', icon: 'AlarmWarningLine' },
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export const TICKET_CATEGORY_META: Record<TTicketCategory, ITicketEnumMeta> = {
|
|
54
|
-
bug: { label: 'Bug', intent: 'danger', icon: 'BugLine' },
|
|
55
|
-
question: { label: 'Question', intent: 'info', icon: 'QuestionLine' },
|
|
56
|
-
billing: { label: 'Billing', intent: 'warning', icon: 'BankCardLine' },
|
|
57
|
-
feature: { label: 'Feature request', intent: 'success', icon: 'LightbulbLine' },
|
|
58
|
-
other: { label: 'Other', intent: 'muted', icon: 'MoreLine' },
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/** Staff-only audit trail vocabulary (used by the admin-portal staff surface).
|
|
62
|
-
* These are exactly the actions the backend writes (qorus-api
|
|
63
|
-
* `QorusSaasTicketStore::insertAudit`): a public reply, an internal note, a
|
|
64
|
-
* status change, an assignment, or `updated` (a priority/category change). */
|
|
65
|
-
export const TICKET_AUDIT_ACTION_META: Record<TTicketAuditAction, ITicketEnumMeta> = {
|
|
66
|
-
replied: { label: 'Replied', intent: 'success', icon: 'ReplyLine' },
|
|
67
|
-
noted: { label: 'Internal note', intent: 'info', icon: 'StickyNoteLine' },
|
|
68
|
-
status_changed: { label: 'Status changed', intent: 'warning', icon: 'ExchangeLine' },
|
|
69
|
-
assigned: { label: 'Assigned', intent: 'pending', icon: 'UserFollowLine' },
|
|
70
|
-
updated: { label: 'Updated', intent: 'muted', icon: 'Edit2Line' },
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/** The status label for a given viewer — the customer sees "Awaiting your reply"
|
|
74
|
-
* where staff see "Awaiting customer"; all other values are audience-neutral. */
|
|
75
|
-
export const ticketStatusLabel = (
|
|
76
|
-
status: TTicketStatus,
|
|
77
|
-
viewerRole?: TTicketViewerRole
|
|
78
|
-
): string => {
|
|
79
|
-
const meta = TICKET_STATUS_META[status];
|
|
80
|
-
return viewerRole === 'customer' && meta.customerLabel ? meta.customerLabel : meta.label;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
/** A reply may be sent while a ticket is in any state except `closed`. */
|
|
84
|
-
export const REPLYABLE_STATUSES: TTicketStatus[] = [
|
|
85
|
-
'new',
|
|
86
|
-
'open',
|
|
87
|
-
'awaiting_customer',
|
|
88
|
-
'resolved',
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
/** Whether a ticket can still be replied to (closed ⇒ no). */
|
|
92
|
-
export const isTicketReplyable = (status: TTicketStatus): boolean => status !== 'closed';
|
|
93
|
-
|
|
94
|
-
/** A file to upload with a ticket or reply — raw bytes as base64, inline on the
|
|
95
|
-
* create/reply payload (the SaaS plane has no separate multipart upload). */
|
|
96
|
-
export interface IAttachmentUpload {
|
|
97
|
-
filename: string;
|
|
98
|
-
content_type: string;
|
|
99
|
-
/** base64-encoded file content */
|
|
100
|
-
content: string;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** A reference to a Qorus interface on the subscriber's instance, stored as a
|
|
104
|
-
* `{kind, name}` snapshot (the interface itself lives on the instance, not the
|
|
105
|
-
* SaaS plane). Attaches to a ticket (at creation) or a message (in a reply). */
|
|
106
|
-
export interface IInterfaceReference {
|
|
107
|
-
/** interface type: workflow / service / job / fsm / qog / connection / ... */
|
|
108
|
-
interface_kind: string;
|
|
109
|
-
/** interface name (may include a version) */
|
|
110
|
-
interface_name: string;
|
|
111
|
-
/** server-assigned reference UUID (present on read, absent when submitting) */
|
|
112
|
-
reference_id?: string;
|
|
113
|
-
/** For an app-triggered interface (a qog): the triggering app's name. A viewer
|
|
114
|
-
* with the app catalogue (the customer's IDE) resolves it to the app's logo the
|
|
115
|
-
* same way the qogs list does — see `resolveInterfaceImage` on the tags. */
|
|
116
|
-
trigger_app?: string;
|
|
117
|
-
/** A pre-resolved logo/image (data-URI or URL) for the chip, e.g. the trigger
|
|
118
|
-
* app's logo. Travels with the reference so a viewer without the app catalogue
|
|
119
|
-
* (staff triage, the Qonsole card) still shows it. */
|
|
120
|
-
logo?: string;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Built-in per-kind icons for referenced interfaces, kept in step with the
|
|
124
|
-
// qorus-ide interface vocabulary. Used when a consumer doesn't pass its own
|
|
125
|
-
// `resolveInterfaceIcon`, so the staff view and the Qonsole renderer show the
|
|
126
|
-
// same chips as the customer view without each re-declaring the mapping.
|
|
127
|
-
const DEFAULT_INTERFACE_ICONS: Record<string, IReqoreIconName> = {
|
|
128
|
-
workflow: 'GitBranchLine',
|
|
129
|
-
service: 'ServerLine',
|
|
130
|
-
job: 'CalendarLine',
|
|
131
|
-
fsm: 'DashboardLine',
|
|
132
|
-
// qog fallback glyph (used only when there's no trigger-app logo to show)
|
|
133
|
-
qog: 'FlowChart',
|
|
134
|
-
connection: 'Plug2Line',
|
|
135
|
-
mapper: 'MindMap',
|
|
136
|
-
class: 'CodeSLine',
|
|
137
|
-
'value-map': 'BringToFront',
|
|
138
|
-
'ai-collection': 'FileMarkedLine',
|
|
139
|
-
'ai-endpoint': 'RobotLine',
|
|
140
|
-
'ai-guardrail': 'ShieldLine',
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
/** Default per-kind icon for a referenced interface, with a generic fallback. */
|
|
144
|
-
export const defaultInterfaceIcon = (kind: string): IReqoreIconName =>
|
|
145
|
-
DEFAULT_INTERFACE_ICONS[kind] ?? 'CodeBoxLine';
|