@things-factory/board-ai 10.0.0-beta.108
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/PLAN-styling-tools.md +1253 -0
- package/client/components/board-ai-chat.test.ts +149 -0
- package/client/components/board-ai-chat.ts +2574 -0
- package/client/components/chat-echo-dedup.test.ts +116 -0
- package/client/components/chat-echo-dedup.ts +89 -0
- package/client/components/chat-input-builder.ts +47 -0
- package/client/components/default-catalog-entries.test.ts +59 -0
- package/client/components/default-catalog-entries.ts +100 -0
- package/client/components/default-slash-templates.test.ts +55 -0
- package/client/components/default-slash-templates.ts +129 -0
- package/client/components/markdown.test.ts +518 -0
- package/client/components/markdown.ts +508 -0
- package/client/components/mention-popup-helpers.ts +433 -0
- package/client/components/mention-popup.test.ts +588 -0
- package/client/components/mention-popup.ts +238 -0
- package/client/index.ts +11 -0
- package/client/tsconfig.json +13 -0
- package/client/utils/board-edit-patch.ts +374 -0
- package/config/config.development.js +43 -0
- package/config/config.production.js +15 -0
- package/dist-client/client/components/board-ai-chat.d.ts +277 -0
- package/dist-client/client/components/board-ai-chat.js +2444 -0
- package/dist-client/client/components/board-ai-chat.js.map +1 -0
- package/dist-client/client/components/board-ai-chat.test.d.ts +1 -0
- package/dist-client/client/components/board-ai-chat.test.js +138 -0
- package/dist-client/client/components/board-ai-chat.test.js.map +1 -0
- package/dist-client/client/components/chat-echo-dedup.d.ts +45 -0
- package/dist-client/client/components/chat-echo-dedup.js +58 -0
- package/dist-client/client/components/chat-echo-dedup.js.map +1 -0
- package/dist-client/client/components/chat-echo-dedup.test.d.ts +1 -0
- package/dist-client/client/components/chat-echo-dedup.test.js +101 -0
- package/dist-client/client/components/chat-echo-dedup.test.js.map +1 -0
- package/dist-client/client/components/chat-input-builder.d.ts +39 -0
- package/dist-client/client/components/chat-input-builder.js +26 -0
- package/dist-client/client/components/chat-input-builder.js.map +1 -0
- package/dist-client/client/components/default-catalog-entries.d.ts +15 -0
- package/dist-client/client/components/default-catalog-entries.js +86 -0
- package/dist-client/client/components/default-catalog-entries.js.map +1 -0
- package/dist-client/client/components/default-catalog-entries.test.d.ts +1 -0
- package/dist-client/client/components/default-catalog-entries.test.js +52 -0
- package/dist-client/client/components/default-catalog-entries.test.js.map +1 -0
- package/dist-client/client/components/default-slash-templates.d.ts +17 -0
- package/dist-client/client/components/default-slash-templates.js +113 -0
- package/dist-client/client/components/default-slash-templates.js.map +1 -0
- package/dist-client/client/components/default-slash-templates.test.d.ts +1 -0
- package/dist-client/client/components/default-slash-templates.test.js +51 -0
- package/dist-client/client/components/default-slash-templates.test.js.map +1 -0
- package/dist-client/client/components/markdown.d.ts +60 -0
- package/dist-client/client/components/markdown.js +422 -0
- package/dist-client/client/components/markdown.js.map +1 -0
- package/dist-client/client/components/markdown.test.d.ts +1 -0
- package/dist-client/client/components/markdown.test.js +418 -0
- package/dist-client/client/components/markdown.test.js.map +1 -0
- package/dist-client/client/components/mention-popup-helpers.d.ts +165 -0
- package/dist-client/client/components/mention-popup-helpers.js +330 -0
- package/dist-client/client/components/mention-popup-helpers.js.map +1 -0
- package/dist-client/client/components/mention-popup.d.ts +35 -0
- package/dist-client/client/components/mention-popup.js +223 -0
- package/dist-client/client/components/mention-popup.js.map +1 -0
- package/dist-client/client/components/mention-popup.test.d.ts +1 -0
- package/dist-client/client/components/mention-popup.test.js +474 -0
- package/dist-client/client/components/mention-popup.test.js.map +1 -0
- package/dist-client/client/index.d.ts +11 -0
- package/dist-client/client/index.js +12 -0
- package/dist-client/client/index.js.map +1 -0
- package/dist-client/client/utils/board-edit-patch.d.ts +157 -0
- package/dist-client/client/utils/board-edit-patch.js +284 -0
- package/dist-client/client/utils/board-edit-patch.js.map +1 -0
- package/dist-client/server/service/agentic-loop.d.ts +87 -0
- package/dist-client/server/service/agentic-loop.js +276 -0
- package/dist-client/server/service/agentic-loop.js.map +1 -0
- package/dist-client/server/service/assistant.d.ts +84 -0
- package/dist-client/server/service/assistant.js +2220 -0
- package/dist-client/server/service/assistant.js.map +1 -0
- package/dist-client/server/service/catalog-resolver.d.ts +18 -0
- package/dist-client/server/service/catalog-resolver.js +93 -0
- package/dist-client/server/service/catalog-resolver.js.map +1 -0
- package/dist-client/server/service/component-tree.d.ts +68 -0
- package/dist-client/server/service/component-tree.js +68 -0
- package/dist-client/server/service/component-tree.js.map +1 -0
- package/dist-client/server/service/mention-resolver.d.ts +96 -0
- package/dist-client/server/service/mention-resolver.js +172 -0
- package/dist-client/server/service/mention-resolver.js.map +1 -0
- package/dist-client/server/service/prefab-registry.d.ts +73 -0
- package/dist-client/server/service/prefab-registry.js +252 -0
- package/dist-client/server/service/prefab-registry.js.map +1 -0
- package/dist-client/server/service/styling/clear-tools.d.ts +15 -0
- package/dist-client/server/service/styling/clear-tools.js +50 -0
- package/dist-client/server/service/styling/clear-tools.js.map +1 -0
- package/dist-client/server/service/styling/color-utils.d.ts +43 -0
- package/dist-client/server/service/styling/color-utils.js +205 -0
- package/dist-client/server/service/styling/color-utils.js.map +1 -0
- package/dist-client/server/service/styling/copy-tools.d.ts +27 -0
- package/dist-client/server/service/styling/copy-tools.js +78 -0
- package/dist-client/server/service/styling/copy-tools.js.map +1 -0
- package/dist-client/server/service/styling/effect-tools.d.ts +45 -0
- package/dist-client/server/service/styling/effect-tools.js +85 -0
- package/dist-client/server/service/styling/effect-tools.js.map +1 -0
- package/dist-client/server/service/styling/fill-tools.d.ts +60 -0
- package/dist-client/server/service/styling/fill-tools.js +226 -0
- package/dist-client/server/service/styling/fill-tools.js.map +1 -0
- package/dist-client/server/service/styling/material-tools.d.ts +32 -0
- package/dist-client/server/service/styling/material-tools.js +88 -0
- package/dist-client/server/service/styling/material-tools.js.map +1 -0
- package/dist-client/server/service/styling/read-tools.d.ts +34 -0
- package/dist-client/server/service/styling/read-tools.js +42 -0
- package/dist-client/server/service/styling/read-tools.js.map +1 -0
- package/dist-client/server/service/styling/registry.d.ts +91 -0
- package/dist-client/server/service/styling/registry.js +345 -0
- package/dist-client/server/service/styling/registry.js.map +1 -0
- package/dist-client/server/service/styling/stroke-tools.d.ts +35 -0
- package/dist-client/server/service/styling/stroke-tools.js +94 -0
- package/dist-client/server/service/styling/stroke-tools.js.map +1 -0
- package/dist-client/server/service/styling/text-tools.d.ts +32 -0
- package/dist-client/server/service/styling/text-tools.js +101 -0
- package/dist-client/server/service/styling/text-tools.js.map +1 -0
- package/dist-client/server/service/types.d.ts +265 -0
- package/dist-client/server/service/types.js +2 -0
- package/dist-client/server/service/types.js.map +1 -0
- package/dist-client/server/service/validation/board-model-schema.d.ts +8311 -0
- package/dist-client/server/service/validation/board-model-schema.js +402 -0
- package/dist-client/server/service/validation/board-model-schema.js.map +1 -0
- package/dist-client/server/service/validation/tool-validation.d.ts +31 -0
- package/dist-client/server/service/validation/tool-validation.js +193 -0
- package/dist-client/server/service/validation/tool-validation.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-server/index.d.ts +23 -0
- package/dist-server/index.js +27 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/service/agentic-loop.d.ts +87 -0
- package/dist-server/service/agentic-loop.js +280 -0
- package/dist-server/service/agentic-loop.js.map +1 -0
- package/dist-server/service/apply-patch.d.ts +47 -0
- package/dist-server/service/apply-patch.js +369 -0
- package/dist-server/service/apply-patch.js.map +1 -0
- package/dist-server/service/assistant.d.ts +84 -0
- package/dist-server/service/assistant.js +2237 -0
- package/dist-server/service/assistant.js.map +1 -0
- package/dist-server/service/board-ai-resolver.d.ts +54 -0
- package/dist-server/service/board-ai-resolver.js +409 -0
- package/dist-server/service/board-ai-resolver.js.map +1 -0
- package/dist-server/service/catalog-resolver.d.ts +18 -0
- package/dist-server/service/catalog-resolver.js +98 -0
- package/dist-server/service/catalog-resolver.js.map +1 -0
- package/dist-server/service/chat-message/chat-message-broadcast.d.ts +13 -0
- package/dist-server/service/chat-message/chat-message-broadcast.js +13 -0
- package/dist-server/service/chat-message/chat-message-broadcast.js.map +1 -0
- package/dist-server/service/chat-message/chat-message-publish.d.ts +6 -0
- package/dist-server/service/chat-message/chat-message-publish.js +27 -0
- package/dist-server/service/chat-message/chat-message-publish.js.map +1 -0
- package/dist-server/service/chat-message/chat-message-subscription.d.ts +8 -0
- package/dist-server/service/chat-message/chat-message-subscription.js +53 -0
- package/dist-server/service/chat-message/chat-message-subscription.js.map +1 -0
- package/dist-server/service/chat-message/chat-message.d.ts +56 -0
- package/dist-server/service/chat-message/chat-message.js +149 -0
- package/dist-server/service/chat-message/chat-message.js.map +1 -0
- package/dist-server/service/chat-message/index.d.ts +5 -0
- package/dist-server/service/chat-message/index.js +12 -0
- package/dist-server/service/chat-message/index.js.map +1 -0
- package/dist-server/service/chat-session/chat-session.d.ts +31 -0
- package/dist-server/service/chat-session/chat-session.js +131 -0
- package/dist-server/service/chat-session/chat-session.js.map +1 -0
- package/dist-server/service/chat-session/index.d.ts +3 -0
- package/dist-server/service/chat-session/index.js +7 -0
- package/dist-server/service/chat-session/index.js.map +1 -0
- package/dist-server/service/chat-session-participant/chat-session-participant.d.ts +30 -0
- package/dist-server/service/chat-session-participant/chat-session-participant.js +112 -0
- package/dist-server/service/chat-session-participant/chat-session-participant.js.map +1 -0
- package/dist-server/service/chat-session-participant/index.d.ts +4 -0
- package/dist-server/service/chat-session-participant/index.js +10 -0
- package/dist-server/service/chat-session-participant/index.js.map +1 -0
- package/dist-server/service/chat-session-participant/invite-notification.d.ts +27 -0
- package/dist-server/service/chat-session-participant/invite-notification.js +30 -0
- package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -0
- package/dist-server/service/chat-session-participant/presence.d.ts +4 -0
- package/dist-server/service/chat-session-participant/presence.js +22 -0
- package/dist-server/service/chat-session-participant/presence.js.map +1 -0
- package/dist-server/service/chat-session-resolver.d.ts +20 -0
- package/dist-server/service/chat-session-resolver.js +356 -0
- package/dist-server/service/chat-session-resolver.js.map +1 -0
- package/dist-server/service/component-tree.d.ts +68 -0
- package/dist-server/service/component-tree.js +73 -0
- package/dist-server/service/component-tree.js.map +1 -0
- package/dist-server/service/index.d.ts +20 -0
- package/dist-server/service/index.js +34 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/mention-marker.d.ts +78 -0
- package/dist-server/service/mention-marker.js +162 -0
- package/dist-server/service/mention-marker.js.map +1 -0
- package/dist-server/service/mention-resolver.d.ts +96 -0
- package/dist-server/service/mention-resolver.js +177 -0
- package/dist-server/service/mention-resolver.js.map +1 -0
- package/dist-server/service/patch-entry/board-patch-broadcast.d.ts +13 -0
- package/dist-server/service/patch-entry/board-patch-broadcast.js +13 -0
- package/dist-server/service/patch-entry/board-patch-broadcast.js.map +1 -0
- package/dist-server/service/patch-entry/board-patch-subscription.d.ts +12 -0
- package/dist-server/service/patch-entry/board-patch-subscription.js +63 -0
- package/dist-server/service/patch-entry/board-patch-subscription.js.map +1 -0
- package/dist-server/service/patch-entry/index.d.ts +4 -0
- package/dist-server/service/patch-entry/index.js +12 -0
- package/dist-server/service/patch-entry/index.js.map +1 -0
- package/dist-server/service/patch-entry/patch-entry.d.ts +16 -0
- package/dist-server/service/patch-entry/patch-entry.js +96 -0
- package/dist-server/service/patch-entry/patch-entry.js.map +1 -0
- package/dist-server/service/prefab-registry.d.ts +73 -0
- package/dist-server/service/prefab-registry.js +258 -0
- package/dist-server/service/prefab-registry.js.map +1 -0
- package/dist-server/service/styling/clear-tools.d.ts +15 -0
- package/dist-server/service/styling/clear-tools.js +53 -0
- package/dist-server/service/styling/clear-tools.js.map +1 -0
- package/dist-server/service/styling/color-utils.d.ts +43 -0
- package/dist-server/service/styling/color-utils.js +210 -0
- package/dist-server/service/styling/color-utils.js.map +1 -0
- package/dist-server/service/styling/copy-tools.d.ts +27 -0
- package/dist-server/service/styling/copy-tools.js +81 -0
- package/dist-server/service/styling/copy-tools.js.map +1 -0
- package/dist-server/service/styling/effect-tools.d.ts +45 -0
- package/dist-server/service/styling/effect-tools.js +90 -0
- package/dist-server/service/styling/effect-tools.js.map +1 -0
- package/dist-server/service/styling/fill-tools.d.ts +60 -0
- package/dist-server/service/styling/fill-tools.js +229 -0
- package/dist-server/service/styling/fill-tools.js.map +1 -0
- package/dist-server/service/styling/material-tools.d.ts +32 -0
- package/dist-server/service/styling/material-tools.js +92 -0
- package/dist-server/service/styling/material-tools.js.map +1 -0
- package/dist-server/service/styling/read-tools.d.ts +34 -0
- package/dist-server/service/styling/read-tools.js +45 -0
- package/dist-server/service/styling/read-tools.js.map +1 -0
- package/dist-server/service/styling/registry.d.ts +91 -0
- package/dist-server/service/styling/registry.js +352 -0
- package/dist-server/service/styling/registry.js.map +1 -0
- package/dist-server/service/styling/stroke-tools.d.ts +35 -0
- package/dist-server/service/styling/stroke-tools.js +97 -0
- package/dist-server/service/styling/stroke-tools.js.map +1 -0
- package/dist-server/service/styling/text-tools.d.ts +32 -0
- package/dist-server/service/styling/text-tools.js +105 -0
- package/dist-server/service/styling/text-tools.js.map +1 -0
- package/dist-server/service/subscribe-auth.d.ts +14 -0
- package/dist-server/service/subscribe-auth.js +33 -0
- package/dist-server/service/subscribe-auth.js.map +1 -0
- package/dist-server/service/types.d.ts +265 -0
- package/dist-server/service/types.js +3 -0
- package/dist-server/service/types.js.map +1 -0
- package/dist-server/service/validation/board-model-schema.d.ts +8311 -0
- package/dist-server/service/validation/board-model-schema.js +409 -0
- package/dist-server/service/validation/board-model-schema.js.map +1 -0
- package/dist-server/service/validation/tool-validation.d.ts +31 -0
- package/dist-server/service/validation/tool-validation.js +196 -0
- package/dist-server/service/validation/tool-validation.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +48 -0
- package/server/index.ts +23 -0
- package/server/service/agentic-loop.test.ts +705 -0
- package/server/service/agentic-loop.ts +431 -0
- package/server/service/apply-patch-drift.test.ts +829 -0
- package/server/service/apply-patch.test.ts +757 -0
- package/server/service/apply-patch.ts +411 -0
- package/server/service/assistant-integration.test.ts +922 -0
- package/server/service/assistant-llm-smoke.test.ts +296 -0
- package/server/service/assistant.test.ts +2034 -0
- package/server/service/assistant.ts +2378 -0
- package/server/service/board-ai-resolver.ts +407 -0
- package/server/service/catalog-resolver.test.ts +137 -0
- package/server/service/catalog-resolver.ts +102 -0
- package/server/service/chat-message/chat-message-broadcast.integration.test.ts +73 -0
- package/server/service/chat-message/chat-message-broadcast.test.ts +40 -0
- package/server/service/chat-message/chat-message-broadcast.ts +27 -0
- package/server/service/chat-message/chat-message-publish.ts +22 -0
- package/server/service/chat-message/chat-message-subscription.ts +41 -0
- package/server/service/chat-message/chat-message.ts +169 -0
- package/server/service/chat-message/index.ts +12 -0
- package/server/service/chat-session/chat-session.ts +130 -0
- package/server/service/chat-session/index.ts +5 -0
- package/server/service/chat-session-participant/chat-session-participant.ts +110 -0
- package/server/service/chat-session-participant/index.ts +6 -0
- package/server/service/chat-session-participant/invite-notification.test.ts +30 -0
- package/server/service/chat-session-participant/invite-notification.ts +48 -0
- package/server/service/chat-session-participant/presence.test.ts +39 -0
- package/server/service/chat-session-participant/presence.ts +21 -0
- package/server/service/chat-session-resolver.ts +312 -0
- package/server/service/component-tree.test.ts +248 -0
- package/server/service/component-tree.ts +135 -0
- package/server/service/index.ts +32 -0
- package/server/service/mention-marker.extract.test.ts +61 -0
- package/server/service/mention-marker.test.ts +228 -0
- package/server/service/mention-marker.ts +184 -0
- package/server/service/mention-resolver.test.ts +273 -0
- package/server/service/mention-resolver.ts +264 -0
- package/server/service/patch-entry/board-patch-broadcast.ts +24 -0
- package/server/service/patch-entry/board-patch-subscription.test.ts +40 -0
- package/server/service/patch-entry/board-patch-subscription.ts +50 -0
- package/server/service/patch-entry/index.ts +12 -0
- package/server/service/patch-entry/patch-entry.ts +89 -0
- package/server/service/prefab-registry.test.ts +172 -0
- package/server/service/prefab-registry.ts +332 -0
- package/server/service/styling/clear-tools.test.ts +164 -0
- package/server/service/styling/clear-tools.ts +69 -0
- package/server/service/styling/color-utils.test.ts +130 -0
- package/server/service/styling/color-utils.ts +226 -0
- package/server/service/styling/copy-tools.test.ts +152 -0
- package/server/service/styling/copy-tools.ts +115 -0
- package/server/service/styling/effect-tools.test.ts +277 -0
- package/server/service/styling/effect-tools.ts +124 -0
- package/server/service/styling/fill-tools.test.ts +331 -0
- package/server/service/styling/fill-tools.ts +293 -0
- package/server/service/styling/material-tools.test.ts +294 -0
- package/server/service/styling/material-tools.ts +114 -0
- package/server/service/styling/read-tools.test.ts +78 -0
- package/server/service/styling/read-tools.ts +67 -0
- package/server/service/styling/registry.test.ts +203 -0
- package/server/service/styling/registry.ts +423 -0
- package/server/service/styling/stroke-tools.test.ts +245 -0
- package/server/service/styling/stroke-tools.ts +145 -0
- package/server/service/styling/text-tools.test.ts +253 -0
- package/server/service/styling/text-tools.ts +131 -0
- package/server/service/styling/tier1-tools.test.ts +300 -0
- package/server/service/subscribe-auth.test.ts +57 -0
- package/server/service/subscribe-auth.ts +34 -0
- package/server/service/types.ts +235 -0
- package/server/service/validation/board-model-schema.test.ts +778 -0
- package/server/service/validation/board-model-schema.ts +446 -0
- package/server/service/validation/tool-validation.ts +228 -0
- package/things-factory.config.js +1 -0
- package/translations/en.json +49 -0
- package/translations/ja.json +48 -0
- package/translations/ko.json +49 -0
- package/translations/ms.json +48 -0
- package/translations/zh.json +48 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { receiveRemoteMessage, reconcileSentLine, remoteMessageToLine } from './chat-echo-dedup';
|
|
2
|
+
const msg = (id, role = 'user', content = 'hi') => ({ id, role, content });
|
|
3
|
+
describe('remoteMessageToLine', () => {
|
|
4
|
+
it('필드 매핑 + relatedPatchId/toolUsagesJson 변환', () => {
|
|
5
|
+
const line = remoteMessageToLine({
|
|
6
|
+
id: 'm1',
|
|
7
|
+
role: 'assistant',
|
|
8
|
+
content: 'done',
|
|
9
|
+
relatedPatchId: 'p1',
|
|
10
|
+
toolUsagesJson: [{ name: 'x' }]
|
|
11
|
+
});
|
|
12
|
+
expect(line).toMatchObject({ id: 'm1', role: 'assistant', content: 'done', patchId: 'p1' });
|
|
13
|
+
expect(line.toolUsages).toEqual([{ name: 'x' }]);
|
|
14
|
+
});
|
|
15
|
+
it('toolUsagesJson 이 배열 아니면 undefined', () => {
|
|
16
|
+
expect(remoteMessageToLine({ id: 'm', role: 'user', content: 'x', toolUsagesJson: null }).toolUsages).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
it('creator → senderId/senderName (그룹챗 발신자 표시)', () => {
|
|
19
|
+
const line = remoteMessageToLine({ id: 'm', role: 'user', content: 'x', creator: { id: 'u9', name: 'Kim' } });
|
|
20
|
+
expect(line.senderId).toBe('u9');
|
|
21
|
+
expect(line.senderName).toBe('Kim');
|
|
22
|
+
});
|
|
23
|
+
it('creator 없으면 sender 필드 undefined', () => {
|
|
24
|
+
const line = remoteMessageToLine({ id: 'm', role: 'user', content: 'x', creator: null });
|
|
25
|
+
expect(line.senderId).toBeUndefined();
|
|
26
|
+
expect(line.senderName).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('receiveRemoteMessage — 방송 수신/에코 차단', () => {
|
|
30
|
+
it('신규 id → append + seen 등록', () => {
|
|
31
|
+
const res = receiveRemoteMessage([], new Set(), msg('m1'));
|
|
32
|
+
expect(res).not.toBeNull();
|
|
33
|
+
expect(res.lines).toHaveLength(1);
|
|
34
|
+
expect(res.lines[0].id).toBe('m1');
|
|
35
|
+
expect(res.seen.has('m1')).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
it('이미 seen 인 id → null (에코·중복 차단)', () => {
|
|
38
|
+
expect(receiveRemoteMessage([], new Set(['m1']), msg('m1'))).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
it('id 없는 메시지 → null', () => {
|
|
41
|
+
expect(receiveRemoteMessage([], new Set(), { role: 'user', content: 'x' })).toBeNull();
|
|
42
|
+
expect(receiveRemoteMessage([], new Set(), null)).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
it('불변 — 원본 배열/셋 미변경', () => {
|
|
45
|
+
const lines = [];
|
|
46
|
+
const seen = new Set();
|
|
47
|
+
receiveRemoteMessage(lines, seen, msg('m1'));
|
|
48
|
+
expect(lines).toHaveLength(0);
|
|
49
|
+
expect(seen.size).toBe(0);
|
|
50
|
+
});
|
|
51
|
+
it('다른 참여자 메시지 두 개 순차 append', () => {
|
|
52
|
+
let lines = [];
|
|
53
|
+
let seen = new Set();
|
|
54
|
+
({ lines, seen } = receiveRemoteMessage(lines, seen, msg('a')));
|
|
55
|
+
({ lines, seen } = receiveRemoteMessage(lines, seen, msg('b')));
|
|
56
|
+
expect(lines.map(l => l.id)).toEqual(['a', 'b']);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('reconcileSentLine — optimistic 라인 조정', () => {
|
|
60
|
+
const optimistic = () => [{ _localId: 'L1', role: 'user', content: 'typed' }];
|
|
61
|
+
it('정상(에코 전): serverId 부여 + seen 등록 + _localId 해제', () => {
|
|
62
|
+
const res = reconcileSentLine(optimistic(), new Set(), 'L1', 'srv1', {});
|
|
63
|
+
expect(res.lines).toHaveLength(1);
|
|
64
|
+
expect(res.lines[0].id).toBe('srv1');
|
|
65
|
+
expect(res.lines[0]._localId).toBeUndefined();
|
|
66
|
+
expect(res.seen.has('srv1')).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
it('레이스(에코 먼저): serverId 가 이미 seen → optimistic 중복 제거', () => {
|
|
69
|
+
// 에코가 먼저 도착해 같은 메시지가 이미 append + seen 됨
|
|
70
|
+
const lines = [
|
|
71
|
+
{ id: 'srv1', role: 'user', content: 'typed#enriched' }, // 방송 에코로 들어온 것
|
|
72
|
+
{ _localId: 'L1', role: 'user', content: 'typed' } // optimistic 중복
|
|
73
|
+
];
|
|
74
|
+
const res = reconcileSentLine(lines, new Set(['srv1']), 'L1', 'srv1', {});
|
|
75
|
+
expect(res.lines).toHaveLength(1);
|
|
76
|
+
expect(res.lines[0].id).toBe('srv1');
|
|
77
|
+
expect(res.lines.some(l => l._localId === 'L1')).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
it('patch 병합 (AI 라인: pending → 실제 응답)', () => {
|
|
80
|
+
const lines = [{ _localId: 'A1', role: 'assistant', content: '', pending: true }];
|
|
81
|
+
const res = reconcileSentLine(lines, new Set(), 'A1', 'srv2', {
|
|
82
|
+
content: 'reply',
|
|
83
|
+
pending: false,
|
|
84
|
+
patchId: 'p9'
|
|
85
|
+
});
|
|
86
|
+
expect(res.lines[0]).toMatchObject({ id: 'srv2', content: 'reply', pending: false, patchId: 'p9' });
|
|
87
|
+
});
|
|
88
|
+
it('serverId 없음(ad-hoc 세션): 라인 유지 + _localId 만 해제, seen 불변', () => {
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
const res = reconcileSentLine(optimistic(), seen, 'L1', undefined, {});
|
|
91
|
+
expect(res.lines[0]._localId).toBeUndefined();
|
|
92
|
+
expect(res.lines[0].id).toBeUndefined();
|
|
93
|
+
expect(res.seen.size).toBe(0);
|
|
94
|
+
});
|
|
95
|
+
it('해당 _localId 없으면 변화 없음', () => {
|
|
96
|
+
const lines = optimistic();
|
|
97
|
+
const res = reconcileSentLine(lines, new Set(), 'NOPE', 'srv1', {});
|
|
98
|
+
expect(res.lines).toBe(lines);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
//# sourceMappingURL=chat-echo-dedup.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-echo-dedup.test.js","sourceRoot":"","sources":["../../../client/components/chat-echo-dedup.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { receiveRemoteMessage, reconcileSentLine, remoteMessageToLine, type DedupLine } from './chat-echo-dedup'\n\nconst msg = (id: string, role = 'user', content = 'hi') => ({ id, role, content })\n\ndescribe('remoteMessageToLine', () => {\n it('필드 매핑 + relatedPatchId/toolUsagesJson 변환', () => {\n const line = remoteMessageToLine({\n id: 'm1',\n role: 'assistant',\n content: 'done',\n relatedPatchId: 'p1',\n toolUsagesJson: [{ name: 'x' }]\n })\n expect(line).toMatchObject({ id: 'm1', role: 'assistant', content: 'done', patchId: 'p1' })\n expect(line.toolUsages).toEqual([{ name: 'x' }])\n })\n\n it('toolUsagesJson 이 배열 아니면 undefined', () => {\n expect(remoteMessageToLine({ id: 'm', role: 'user', content: 'x', toolUsagesJson: null }).toolUsages).toBeUndefined()\n })\n\n it('creator → senderId/senderName (그룹챗 발신자 표시)', () => {\n const line = remoteMessageToLine({ id: 'm', role: 'user', content: 'x', creator: { id: 'u9', name: 'Kim' } })\n expect(line.senderId).toBe('u9')\n expect(line.senderName).toBe('Kim')\n })\n\n it('creator 없으면 sender 필드 undefined', () => {\n const line = remoteMessageToLine({ id: 'm', role: 'user', content: 'x', creator: null })\n expect(line.senderId).toBeUndefined()\n expect(line.senderName).toBeUndefined()\n })\n})\n\ndescribe('receiveRemoteMessage — 방송 수신/에코 차단', () => {\n it('신규 id → append + seen 등록', () => {\n const res = receiveRemoteMessage([], new Set(), msg('m1'))!\n expect(res).not.toBeNull()\n expect(res.lines).toHaveLength(1)\n expect(res.lines[0].id).toBe('m1')\n expect(res.seen.has('m1')).toBe(true)\n })\n\n it('이미 seen 인 id → null (에코·중복 차단)', () => {\n expect(receiveRemoteMessage([], new Set(['m1']), msg('m1'))).toBeNull()\n })\n\n it('id 없는 메시지 → null', () => {\n expect(receiveRemoteMessage([], new Set(), { role: 'user', content: 'x' } as any)).toBeNull()\n expect(receiveRemoteMessage([], new Set(), null)).toBeNull()\n })\n\n it('불변 — 원본 배열/셋 미변경', () => {\n const lines: DedupLine[] = []\n const seen = new Set<string>()\n receiveRemoteMessage(lines, seen, msg('m1'))\n expect(lines).toHaveLength(0)\n expect(seen.size).toBe(0)\n })\n\n it('다른 참여자 메시지 두 개 순차 append', () => {\n let lines: DedupLine[] = []\n let seen = new Set<string>()\n ;({ lines, seen } = receiveRemoteMessage(lines, seen, msg('a'))!)\n ;({ lines, seen } = receiveRemoteMessage(lines, seen, msg('b'))!)\n expect(lines.map(l => l.id)).toEqual(['a', 'b'])\n })\n})\n\ndescribe('reconcileSentLine — optimistic 라인 조정', () => {\n const optimistic = (): DedupLine[] => [{ _localId: 'L1', role: 'user', content: 'typed' }]\n\n it('정상(에코 전): serverId 부여 + seen 등록 + _localId 해제', () => {\n const res = reconcileSentLine(optimistic(), new Set(), 'L1', 'srv1', {})\n expect(res.lines).toHaveLength(1)\n expect(res.lines[0].id).toBe('srv1')\n expect(res.lines[0]._localId).toBeUndefined()\n expect(res.seen.has('srv1')).toBe(true)\n })\n\n it('레이스(에코 먼저): serverId 가 이미 seen → optimistic 중복 제거', () => {\n // 에코가 먼저 도착해 같은 메시지가 이미 append + seen 됨\n const lines: DedupLine[] = [\n { id: 'srv1', role: 'user', content: 'typed#enriched' }, // 방송 에코로 들어온 것\n { _localId: 'L1', role: 'user', content: 'typed' } // optimistic 중복\n ]\n const res = reconcileSentLine(lines, new Set(['srv1']), 'L1', 'srv1', {})\n expect(res.lines).toHaveLength(1)\n expect(res.lines[0].id).toBe('srv1')\n expect(res.lines.some(l => l._localId === 'L1')).toBe(false)\n })\n\n it('patch 병합 (AI 라인: pending → 실제 응답)', () => {\n const lines: DedupLine[] = [{ _localId: 'A1', role: 'assistant', content: '', pending: true }]\n const res = reconcileSentLine(lines, new Set(), 'A1', 'srv2', {\n content: 'reply',\n pending: false,\n patchId: 'p9'\n })\n expect(res.lines[0]).toMatchObject({ id: 'srv2', content: 'reply', pending: false, patchId: 'p9' })\n })\n\n it('serverId 없음(ad-hoc 세션): 라인 유지 + _localId 만 해제, seen 불변', () => {\n const seen = new Set<string>()\n const res = reconcileSentLine(optimistic(), seen, 'L1', undefined, {})\n expect(res.lines[0]._localId).toBeUndefined()\n expect(res.lines[0].id).toBeUndefined()\n expect(res.seen.size).toBe(0)\n })\n\n it('해당 _localId 없으면 변화 없음', () => {\n const lines = optimistic()\n const res = reconcileSentLine(lines, new Set(), 'NOPE', 'srv1', {})\n expect(res.lines).toBe(lines)\n })\n})\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* boardAIChat GraphQL mutation 의 input 객체 빌더.
|
|
3
|
+
*
|
|
4
|
+
* board-ai-chat.ts (Lit / Material Web 의존) 와 분리 — pure 함수만 두면 jest 의 node
|
|
5
|
+
* 환경에서도 import 가능. 회귀 테스트 (selectedRefids 누락 등) 가 단위 테스트로 잠긴다.
|
|
6
|
+
*/
|
|
7
|
+
export interface ChatMutationInputArgs {
|
|
8
|
+
sessionId?: string;
|
|
9
|
+
history: Array<{
|
|
10
|
+
role: string;
|
|
11
|
+
content: string;
|
|
12
|
+
}>;
|
|
13
|
+
liveBoard: any;
|
|
14
|
+
scopes?: string[];
|
|
15
|
+
knownTypes?: string[];
|
|
16
|
+
categories?: string[];
|
|
17
|
+
componentSchemas?: any;
|
|
18
|
+
/**
|
|
19
|
+
* 선택된 컴포넌트의 `refid` 배열 — things-scene universal numeric handle.
|
|
20
|
+
* id (데이터 바인딩 이름) 와는 다른 개념.
|
|
21
|
+
*/
|
|
22
|
+
selectedRefids: number[];
|
|
23
|
+
/**
|
|
24
|
+
* popup 에서 명시 선택한 #mention 매핑 — {token, refid}.
|
|
25
|
+
* 사용자 textarea 에는 #token 만 보이고, 정확한 refid 는 이 별도 채널로 server 에 전달.
|
|
26
|
+
* server 가 mention map 으로 LLM 에 정확한 refid 정보 첨부 (사용자 노출 0).
|
|
27
|
+
*/
|
|
28
|
+
mentions?: Array<{
|
|
29
|
+
token: string;
|
|
30
|
+
refid: number;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* GraphQL mutation 의 input 객체를 빌드.
|
|
35
|
+
*
|
|
36
|
+
* 핵심 보호 — `selectedRefids` 가 항상 포함되어야 한다. 누락이 사용자에게 보고된
|
|
37
|
+
* "AI 가 선택된 컴포넌트를 모른다" 의 root cause 였다.
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildChatMutationInput(args: ChatMutationInputArgs): Record<string, any>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* boardAIChat GraphQL mutation 의 input 객체 빌더.
|
|
3
|
+
*
|
|
4
|
+
* board-ai-chat.ts (Lit / Material Web 의존) 와 분리 — pure 함수만 두면 jest 의 node
|
|
5
|
+
* 환경에서도 import 가능. 회귀 테스트 (selectedRefids 누락 등) 가 단위 테스트로 잠긴다.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* GraphQL mutation 의 input 객체를 빌드.
|
|
9
|
+
*
|
|
10
|
+
* 핵심 보호 — `selectedRefids` 가 항상 포함되어야 한다. 누락이 사용자에게 보고된
|
|
11
|
+
* "AI 가 선택된 컴포넌트를 모른다" 의 root cause 였다.
|
|
12
|
+
*/
|
|
13
|
+
export function buildChatMutationInput(args) {
|
|
14
|
+
return {
|
|
15
|
+
sessionId: args.sessionId ?? null,
|
|
16
|
+
messages: args.history,
|
|
17
|
+
currentBoard: args.liveBoard ?? null,
|
|
18
|
+
scopes: args.scopes,
|
|
19
|
+
knownTypes: args.knownTypes,
|
|
20
|
+
categories: args.categories,
|
|
21
|
+
componentSchemas: args.componentSchemas,
|
|
22
|
+
selectedRefids: args.selectedRefids,
|
|
23
|
+
...(args.mentions && args.mentions.length > 0 && { mentions: args.mentions })
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=chat-input-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-input-builder.js","sourceRoot":"","sources":["../../../client/components/chat-input-builder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAuBH;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAA2B;IAChE,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI;QACjC,QAAQ,EAAE,IAAI,CAAC,OAAO;QACtB,YAAY,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI;QACpC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;QACvC,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;KAC9E,CAAA;AACH,CAAC","sourcesContent":["/**\n * boardAIChat GraphQL mutation 의 input 객체 빌더.\n *\n * board-ai-chat.ts (Lit / Material Web 의존) 와 분리 — pure 함수만 두면 jest 의 node\n * 환경에서도 import 가능. 회귀 테스트 (selectedRefids 누락 등) 가 단위 테스트로 잠긴다.\n */\n\nexport interface ChatMutationInputArgs {\n sessionId?: string\n history: Array<{ role: string; content: string }>\n liveBoard: any\n scopes?: string[]\n knownTypes?: string[]\n categories?: string[]\n componentSchemas?: any\n /**\n * 선택된 컴포넌트의 `refid` 배열 — things-scene universal numeric handle.\n * id (데이터 바인딩 이름) 와는 다른 개념.\n */\n selectedRefids: number[]\n /**\n * popup 에서 명시 선택한 #mention 매핑 — {token, refid}.\n * 사용자 textarea 에는 #token 만 보이고, 정확한 refid 는 이 별도 채널로 server 에 전달.\n * server 가 mention map 으로 LLM 에 정확한 refid 정보 첨부 (사용자 노출 0).\n */\n mentions?: Array<{ token: string; refid: number }>\n}\n\n/**\n * GraphQL mutation 의 input 객체를 빌드.\n *\n * 핵심 보호 — `selectedRefids` 가 항상 포함되어야 한다. 누락이 사용자에게 보고된\n * \"AI 가 선택된 컴포넌트를 모른다\" 의 root cause 였다.\n */\nexport function buildChatMutationInput(args: ChatMutationInputArgs): Record<string, any> {\n return {\n sessionId: args.sessionId ?? null,\n messages: args.history,\n currentBoard: args.liveBoard ?? null,\n scopes: args.scopes,\n knownTypes: args.knownTypes,\n categories: args.categories,\n componentSchemas: args.componentSchemas,\n selectedRefids: args.selectedRefids,\n ...(args.mentions && args.mentions.length > 0 && { mentions: args.mentions })\n }\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default 카탈로그 항목 (client-side hardcoded).
|
|
3
|
+
*
|
|
4
|
+
* 사용자 popup discoverability 용. server-side 카탈로그가 권위 source 이지만, 매번
|
|
5
|
+
* GraphQL 호출해 동적으로 가져오는 대신 default 항목을 client 에 캐시. 호스트 패키지
|
|
6
|
+
* (operato 등) 가 추가 prefab 등록 시 popup 에는 안 나타나지만, 사용자가 직접 이름을
|
|
7
|
+
* 타이핑하면 server-side resolver 가 정상 처리 (catalog-resolver.ts).
|
|
8
|
+
*
|
|
9
|
+
* Drift 경고 — 이 파일과 server-side 의 prefab-registry.ts / COLOR_PALETTES /
|
|
10
|
+
* LAYOUT_TEMPLATES 가 일치해야 한다. 새 default 추가 시 양쪽 동기 (assistant.test.ts
|
|
11
|
+
* 의 tool 카탈로그 회귀 테스트가 server-side drift 는 잡음, client 는 별도 검증
|
|
12
|
+
* 필요). 향후 GraphQL 'boardAICatalog' query 도입 시 이 파일 제거 가능.
|
|
13
|
+
*/
|
|
14
|
+
import type { CatalogEntry } from './mention-popup-helpers.js';
|
|
15
|
+
export declare const DEFAULT_CATALOG_ENTRIES: CatalogEntry[];
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const DEFAULT_CATALOG_ENTRIES = [
|
|
2
|
+
// ── Prefabs (3) ─────────────────────────────────────────
|
|
3
|
+
{
|
|
4
|
+
name: 'monitor-card',
|
|
5
|
+
kind: 'prefab',
|
|
6
|
+
description: 'KPI 모니터링 카드 — 라벨 + 큰 값 + 부제'
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: 'gauge-panel',
|
|
10
|
+
kind: 'prefab',
|
|
11
|
+
description: '원형 게이지 + 라벨 패널'
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'stat-tile',
|
|
15
|
+
kind: 'prefab',
|
|
16
|
+
description: '강조 컬러 KPI 타일 (큰 숫자)'
|
|
17
|
+
},
|
|
18
|
+
// ── Palettes (7) ────────────────────────────────────────
|
|
19
|
+
{
|
|
20
|
+
name: 'industrial-steel',
|
|
21
|
+
kind: 'palette',
|
|
22
|
+
description: '제조 / 공장 — 강철 + cyan 강조'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'financial-trader',
|
|
26
|
+
kind: 'palette',
|
|
27
|
+
description: '금융 / 트레이딩 — navy + gold'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'control-room-dark',
|
|
31
|
+
kind: 'palette',
|
|
32
|
+
description: 'NOC / 관제실 — pure black + 네온'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'medical-clean',
|
|
36
|
+
kind: 'palette',
|
|
37
|
+
description: '의료 / 헬스케어 — soft white + blue'
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'logistics-warm',
|
|
41
|
+
kind: 'palette',
|
|
42
|
+
description: '물류 / 운송 — warm earth + amber'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'neon-cyberpunk',
|
|
46
|
+
kind: 'palette',
|
|
47
|
+
description: 'SF / 사이버펑크 — black + magenta/cyan'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'paper-light',
|
|
51
|
+
kind: 'palette',
|
|
52
|
+
description: '보고서 / 프레젠테이션 — 종이 light'
|
|
53
|
+
},
|
|
54
|
+
// ── Layouts (6) ─────────────────────────────────────────
|
|
55
|
+
{
|
|
56
|
+
name: 'kpi-row',
|
|
57
|
+
kind: 'layout',
|
|
58
|
+
description: '상단 KPI 4-tile + 메인 영역'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: '3x3-grid',
|
|
62
|
+
kind: 'layout',
|
|
63
|
+
description: '균등 3×3 그리드 — 9개 동등 단위'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 't-shape-command',
|
|
67
|
+
kind: 'layout',
|
|
68
|
+
description: '관제실 — alarm bar + main + side'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'side-rail',
|
|
72
|
+
kind: 'layout',
|
|
73
|
+
description: '좌측 narrow + 메인 wide'
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'split-half',
|
|
77
|
+
kind: 'layout',
|
|
78
|
+
description: '50/50 가로 분할 — 비교'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'header-content-footer',
|
|
82
|
+
kind: 'layout',
|
|
83
|
+
description: '보고서 형식 — 3단'
|
|
84
|
+
}
|
|
85
|
+
];
|
|
86
|
+
//# sourceMappingURL=default-catalog-entries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-catalog-entries.js","sourceRoot":"","sources":["../../../client/components/default-catalog-entries.ts"],"names":[],"mappings":"AAeA,MAAM,CAAC,MAAM,uBAAuB,GAAmB;IACrD,2DAA2D;IAC3D;QACE,IAAI,EAAE,cAAc;QACpB,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,6BAA6B;KAC3C;IACD;QACE,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,gBAAgB;KAC9B;IACD;QACE,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,qBAAqB;KACnC;IACD,2DAA2D;IAC3D;QACE,IAAI,EAAE,kBAAkB;QACxB,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,wBAAwB;KACtC;IACD;QACE,IAAI,EAAE,kBAAkB;QACxB,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,yBAAyB;KACvC;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,6BAA6B;KAC3C;IACD;QACE,IAAI,EAAE,eAAe;QACrB,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,+BAA+B;KAC7C;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,8BAA8B;KAC5C;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,mCAAmC;KACjD;IACD;QACE,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,yBAAyB;KACvC;IACD,2DAA2D;IAC3D;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,uBAAuB;KACrC;IACD;QACE,IAAI,EAAE,UAAU;QAChB,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,uBAAuB;KACrC;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,+BAA+B;KAC7C;IACD;QACE,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,qBAAqB;KACnC;IACD;QACE,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,kBAAkB;KAChC;IACD;QACE,IAAI,EAAE,uBAAuB;QAC7B,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,aAAa;KAC3B;CACF,CAAA","sourcesContent":["/**\n * Default 카탈로그 항목 (client-side hardcoded).\n *\n * 사용자 popup discoverability 용. server-side 카탈로그가 권위 source 이지만, 매번\n * GraphQL 호출해 동적으로 가져오는 대신 default 항목을 client 에 캐시. 호스트 패키지\n * (operato 등) 가 추가 prefab 등록 시 popup 에는 안 나타나지만, 사용자가 직접 이름을\n * 타이핑하면 server-side resolver 가 정상 처리 (catalog-resolver.ts).\n *\n * Drift 경고 — 이 파일과 server-side 의 prefab-registry.ts / COLOR_PALETTES /\n * LAYOUT_TEMPLATES 가 일치해야 한다. 새 default 추가 시 양쪽 동기 (assistant.test.ts\n * 의 tool 카탈로그 회귀 테스트가 server-side drift 는 잡음, client 는 별도 검증\n * 필요). 향후 GraphQL 'boardAICatalog' query 도입 시 이 파일 제거 가능.\n */\nimport type { CatalogEntry } from './mention-popup-helpers.js'\n\nexport const DEFAULT_CATALOG_ENTRIES: CatalogEntry[] = [\n // ── Prefabs (3) ─────────────────────────────────────────\n {\n name: 'monitor-card',\n kind: 'prefab',\n description: 'KPI 모니터링 카드 — 라벨 + 큰 값 + 부제'\n },\n {\n name: 'gauge-panel',\n kind: 'prefab',\n description: '원형 게이지 + 라벨 패널'\n },\n {\n name: 'stat-tile',\n kind: 'prefab',\n description: '강조 컬러 KPI 타일 (큰 숫자)'\n },\n // ── Palettes (7) ────────────────────────────────────────\n {\n name: 'industrial-steel',\n kind: 'palette',\n description: '제조 / 공장 — 강철 + cyan 강조'\n },\n {\n name: 'financial-trader',\n kind: 'palette',\n description: '금융 / 트레이딩 — navy + gold'\n },\n {\n name: 'control-room-dark',\n kind: 'palette',\n description: 'NOC / 관제실 — pure black + 네온'\n },\n {\n name: 'medical-clean',\n kind: 'palette',\n description: '의료 / 헬스케어 — soft white + blue'\n },\n {\n name: 'logistics-warm',\n kind: 'palette',\n description: '물류 / 운송 — warm earth + amber'\n },\n {\n name: 'neon-cyberpunk',\n kind: 'palette',\n description: 'SF / 사이버펑크 — black + magenta/cyan'\n },\n {\n name: 'paper-light',\n kind: 'palette',\n description: '보고서 / 프레젠테이션 — 종이 light'\n },\n // ── Layouts (6) ─────────────────────────────────────────\n {\n name: 'kpi-row',\n kind: 'layout',\n description: '상단 KPI 4-tile + 메인 영역'\n },\n {\n name: '3x3-grid',\n kind: 'layout',\n description: '균등 3×3 그리드 — 9개 동등 단위'\n },\n {\n name: 't-shape-command',\n kind: 'layout',\n description: '관제실 — alarm bar + main + side'\n },\n {\n name: 'side-rail',\n kind: 'layout',\n description: '좌측 narrow + 메인 wide'\n },\n {\n name: 'split-half',\n kind: 'layout',\n description: '50/50 가로 분할 — 비교'\n },\n {\n name: 'header-content-footer',\n kind: 'layout',\n description: '보고서 형식 — 3단'\n }\n]\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* default-catalog-entries.ts 의 client-side hardcoded 카탈로그가 server-side 의
|
|
3
|
+
* prefab-registry / COLOR_PALETTES / LAYOUT_TEMPLATES 와 동기화되어 있는지 검증.
|
|
4
|
+
*
|
|
5
|
+
* 새 default 추가 / 이름 변경 시 server-side 만 바꾸고 client 누락하면 popup 에서
|
|
6
|
+
* 사용자 보지 못해 discoverability 무너짐 — 이 drift 테스트가 즉시 잡음.
|
|
7
|
+
*
|
|
8
|
+
* 향후 GraphQL 'boardAICatalog' query 도입으로 client 가 server 에서 동적 로드하면
|
|
9
|
+
* 이 파일과 default-catalog-entries.ts 모두 제거 가능.
|
|
10
|
+
*/
|
|
11
|
+
import { DEFAULT_CATALOG_ENTRIES } from './default-catalog-entries';
|
|
12
|
+
import { listBoardPrefabs } from '../../server/service/prefab-registry';
|
|
13
|
+
import { listColorPaletteNames, listLayoutTemplateNames } from '../../server/service/assistant';
|
|
14
|
+
describe('Default catalog entries — server/client drift 검증', () => {
|
|
15
|
+
test('client prefab 이름이 server prefab-registry 와 일치', () => {
|
|
16
|
+
const clientPrefabs = DEFAULT_CATALOG_ENTRIES.filter(e => e.kind === 'prefab')
|
|
17
|
+
.map(e => e.name)
|
|
18
|
+
.sort();
|
|
19
|
+
const serverPrefabs = listBoardPrefabs()
|
|
20
|
+
.map(p => p.name)
|
|
21
|
+
.sort();
|
|
22
|
+
expect(clientPrefabs).toEqual(serverPrefabs);
|
|
23
|
+
});
|
|
24
|
+
test('client palette 이름이 server COLOR_PALETTES 와 일치', () => {
|
|
25
|
+
const clientPalettes = DEFAULT_CATALOG_ENTRIES.filter(e => e.kind === 'palette')
|
|
26
|
+
.map(e => e.name)
|
|
27
|
+
.sort();
|
|
28
|
+
const serverPalettes = listColorPaletteNames().sort();
|
|
29
|
+
expect(clientPalettes).toEqual(serverPalettes);
|
|
30
|
+
});
|
|
31
|
+
test('client layout 이름이 server LAYOUT_TEMPLATES 와 일치', () => {
|
|
32
|
+
const clientLayouts = DEFAULT_CATALOG_ENTRIES.filter(e => e.kind === 'layout')
|
|
33
|
+
.map(e => e.name)
|
|
34
|
+
.sort();
|
|
35
|
+
const serverLayouts = listLayoutTemplateNames().sort();
|
|
36
|
+
expect(clientLayouts).toEqual(serverLayouts);
|
|
37
|
+
});
|
|
38
|
+
test('각 항목이 description 보유 (popup UX)', () => {
|
|
39
|
+
for (const e of DEFAULT_CATALOG_ENTRIES) {
|
|
40
|
+
expect(e.description).toBeTruthy();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
test('항목 이름 unique (kind 안에서)', () => {
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
for (const e of DEFAULT_CATALOG_ENTRIES) {
|
|
46
|
+
const key = `${e.kind}:${e.name}`;
|
|
47
|
+
expect(seen.has(key)).toBe(false);
|
|
48
|
+
seen.add(key);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=default-catalog-entries.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-catalog-entries.test.js","sourceRoot":"","sources":["../../../client/components/default-catalog-entries.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAA;AACvE,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,gCAAgC,CAAA;AAEvC,QAAQ,CAAC,kDAAkD,EAAE,GAAG,EAAE;IAChE,IAAI,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACzD,MAAM,aAAa,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;aAC3E,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAChB,IAAI,EAAE,CAAA;QACT,MAAM,aAAa,GAAG,gBAAgB,EAAE;aACrC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAChB,IAAI,EAAE,CAAA;QACT,MAAM,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAA;IAC9C,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACzD,MAAM,cAAc,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC;aAC7E,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAChB,IAAI,EAAE,CAAA;QACT,MAAM,cAAc,GAAG,qBAAqB,EAAE,CAAC,IAAI,EAAE,CAAA;QACrD,MAAM,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;IAChD,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,gDAAgD,EAAE,GAAG,EAAE;QAC1D,MAAM,aAAa,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;aAC3E,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAChB,IAAI,EAAE,CAAA;QACT,MAAM,aAAa,GAAG,uBAAuB,EAAE,CAAC,IAAI,EAAE,CAAA;QACtD,MAAM,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAA;IAC9C,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,iCAAiC,EAAE,GAAG,EAAE;QAC3C,KAAK,MAAM,CAAC,IAAI,uBAAuB,EAAE,CAAC;YACxC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,UAAU,EAAE,CAAA;QACpC,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACnC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;QAC9B,KAAK,MAAM,CAAC,IAAI,uBAAuB,EAAE,CAAC;YACxC,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;YACjC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACjC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACf,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["/**\n * default-catalog-entries.ts 의 client-side hardcoded 카탈로그가 server-side 의\n * prefab-registry / COLOR_PALETTES / LAYOUT_TEMPLATES 와 동기화되어 있는지 검증.\n *\n * 새 default 추가 / 이름 변경 시 server-side 만 바꾸고 client 누락하면 popup 에서\n * 사용자 보지 못해 discoverability 무너짐 — 이 drift 테스트가 즉시 잡음.\n *\n * 향후 GraphQL 'boardAICatalog' query 도입으로 client 가 server 에서 동적 로드하면\n * 이 파일과 default-catalog-entries.ts 모두 제거 가능.\n */\nimport { DEFAULT_CATALOG_ENTRIES } from './default-catalog-entries'\nimport { listBoardPrefabs } from '../../server/service/prefab-registry'\nimport {\n listColorPaletteNames,\n listLayoutTemplateNames\n} from '../../server/service/assistant'\n\ndescribe('Default catalog entries — server/client drift 검증', () => {\n test('client prefab 이름이 server prefab-registry 와 일치', () => {\n const clientPrefabs = DEFAULT_CATALOG_ENTRIES.filter(e => e.kind === 'prefab')\n .map(e => e.name)\n .sort()\n const serverPrefabs = listBoardPrefabs()\n .map(p => p.name)\n .sort()\n expect(clientPrefabs).toEqual(serverPrefabs)\n })\n\n test('client palette 이름이 server COLOR_PALETTES 와 일치', () => {\n const clientPalettes = DEFAULT_CATALOG_ENTRIES.filter(e => e.kind === 'palette')\n .map(e => e.name)\n .sort()\n const serverPalettes = listColorPaletteNames().sort()\n expect(clientPalettes).toEqual(serverPalettes)\n })\n\n test('client layout 이름이 server LAYOUT_TEMPLATES 와 일치', () => {\n const clientLayouts = DEFAULT_CATALOG_ENTRIES.filter(e => e.kind === 'layout')\n .map(e => e.name)\n .sort()\n const serverLayouts = listLayoutTemplateNames().sort()\n expect(clientLayouts).toEqual(serverLayouts)\n })\n\n test('각 항목이 description 보유 (popup UX)', () => {\n for (const e of DEFAULT_CATALOG_ENTRIES) {\n expect(e.description).toBeTruthy()\n }\n })\n\n test('항목 이름 unique (kind 안에서)', () => {\n const seen = new Set<string>()\n for (const e of DEFAULT_CATALOG_ENTRIES) {\n const key = `${e.kind}:${e.name}`\n expect(seen.has(key)).toBe(false)\n seen.add(key)\n }\n })\n})\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default slash templates — 사용자에게 빠르게 작성된 자연어 prompt 를 끼워주는 단축키.
|
|
3
|
+
*
|
|
4
|
+
* `/grid` → "선택한 컴포넌트들을 3x2 그리드로 정렬해줘" 같이 텍스트가 textarea 에
|
|
5
|
+
* 삽입됨. 사용자는 그대로 보내거나 숫자/카탈로그 이름을 수정해 보낼 수 있다.
|
|
6
|
+
*
|
|
7
|
+
* Slash 는 LLM 우회 명령이 아니다 — AI 가 받는 입력은 일반 자연어 prompt 와 동일.
|
|
8
|
+
* 단지 "이런 명령이 가능하구나" 의 discoverability 를 popup 으로 보여주는 역할.
|
|
9
|
+
*
|
|
10
|
+
* 수정 / 추가 정책:
|
|
11
|
+
* - 한국어 우선 (자연어가 한국어라야 한국 사용자에게 즉시 작동)
|
|
12
|
+
* - 카탈로그 이름 (@monitor-card 등) 가 들어간 템플릿은 server-side resolver 와
|
|
13
|
+
* 자동 호환 — drift 검증은 default-catalog-entries.test.ts 가 잡음
|
|
14
|
+
* - 너무 많이 만들지 말 것 (popup 이 과해짐). 가장 흔한 의도만.
|
|
15
|
+
*/
|
|
16
|
+
import type { SlashTemplate } from './mention-popup-helpers.js';
|
|
17
|
+
export declare const DEFAULT_SLASH_TEMPLATES: SlashTemplate[];
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export const DEFAULT_SLASH_TEMPLATES = [
|
|
2
|
+
{
|
|
3
|
+
name: 'grid',
|
|
4
|
+
description: '선택한 컴포넌트들을 그리드로 정렬',
|
|
5
|
+
template: '선택한 컴포넌트들을 3x2 그리드로 정렬해줘'
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
name: 'row',
|
|
9
|
+
description: '선택한 컴포넌트들을 한 줄로',
|
|
10
|
+
template: '선택한 컴포넌트들을 한 줄로 배치해줘'
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'column',
|
|
14
|
+
description: '선택한 컴포넌트들을 세로로 일렬',
|
|
15
|
+
template: '선택한 컴포넌트들을 세로로 일렬 배치해줘'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'palette',
|
|
19
|
+
description: '검증된 색 팔레트로 통일',
|
|
20
|
+
template: '@industrial-steel 팔레트로 보드 전체 통일해줘'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'atmosphere',
|
|
24
|
+
description: '보드 분위기 변경 (자연어 자유 작성)',
|
|
25
|
+
template: '보드 분위기를 어둡고 차분하게 바꿔줘 (atmosphere primer 활용)'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'dashboard',
|
|
29
|
+
description: 'KPI 대시보드 기본 구성',
|
|
30
|
+
template: '@kpi-row 레이아웃으로 @stat-tile 4개를 상단에 + 아래 메인 영역 구성해줘'
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'add-monitor',
|
|
34
|
+
description: '모니터링 카드 추가',
|
|
35
|
+
template: '@monitor-card 추가 (제목과 값은 적절히)'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'fit',
|
|
39
|
+
description: '보드 전체 보이게',
|
|
40
|
+
template: '보드 전체가 보이게 화면 맞춰줘'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'center',
|
|
44
|
+
description: '선택 컴포넌트 화면 가운데로',
|
|
45
|
+
template: '선택한 컴포넌트를 화면 가운데로 이동해줘'
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'inspect',
|
|
49
|
+
description: '선택 컴포넌트 정보 보기',
|
|
50
|
+
template: '현재 선택된 컴포넌트의 모든 속성을 보여줘'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'align',
|
|
54
|
+
description: '선택 컴포넌트 정렬 (좌/우/위/아래/가운데)',
|
|
55
|
+
template: '선택한 컴포넌트들을 왼쪽 기준으로 정렬해줘'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'distribute',
|
|
59
|
+
description: '선택 컴포넌트 간격 균등 분배',
|
|
60
|
+
template: '선택한 컴포넌트들을 가로로 간격 균등하게 분배해줘'
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'duplicate',
|
|
64
|
+
description: '선택 컴포넌트 복제',
|
|
65
|
+
template: '선택한 컴포넌트를 오른쪽에 같은 크기로 하나 더 복제해줘'
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'match-size',
|
|
69
|
+
description: '선택 컴포넌트 크기 통일',
|
|
70
|
+
template: '선택한 컴포넌트들을 첫 번째와 같은 크기로 맞춰줘'
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'z-front',
|
|
74
|
+
description: '선택 컴포넌트 맨 앞으로',
|
|
75
|
+
template: '선택한 컴포넌트를 z-order 맨 앞으로 가져와줘'
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'rotate',
|
|
79
|
+
description: '선택 컴포넌트 회전',
|
|
80
|
+
template: '선택한 컴포넌트를 시계방향으로 90도 회전해줘'
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'z-back',
|
|
84
|
+
description: '선택 컴포넌트 맨 뒤로',
|
|
85
|
+
template: '선택한 컴포넌트를 z-order 맨 뒤로 보내줘'
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'mirror',
|
|
89
|
+
description: '선택 컴포넌트 좌우 반전',
|
|
90
|
+
template: '선택한 컴포넌트를 좌우 대칭으로 반전해줘'
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'group',
|
|
94
|
+
description: '선택 컴포넌트 그룹화',
|
|
95
|
+
template: '선택한 컴포넌트들을 하나의 그룹으로 묶어줘'
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'rename-sequential',
|
|
99
|
+
description: '선택 컴포넌트들에 순번 라벨',
|
|
100
|
+
template: '선택한 컴포넌트들에 1, 2, 3 순번 라벨을 붙여줘'
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'bulk-set',
|
|
104
|
+
description: '선택 컴포넌트 속성 일괄 변경',
|
|
105
|
+
template: '선택한 컴포넌트들의 [속성]을 [값]으로 일괄 설정해줘'
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'cleanup',
|
|
109
|
+
description: '겹침/어색한 위치 자동 정리',
|
|
110
|
+
template: '겹쳐있거나 어색한 위치의 컴포넌트들을 자연스럽게 정리해줘'
|
|
111
|
+
}
|
|
112
|
+
];
|
|
113
|
+
//# sourceMappingURL=default-slash-templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-slash-templates.js","sourceRoot":"","sources":["../../../client/components/default-slash-templates.ts"],"names":[],"mappings":"AAiBA,MAAM,CAAC,MAAM,uBAAuB,GAAoB;IACtD;QACE,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,oBAAoB;QACjC,QAAQ,EAAE,0BAA0B;KACrC;IACD;QACE,IAAI,EAAE,KAAK;QACX,WAAW,EAAE,iBAAiB;QAC9B,QAAQ,EAAE,sBAAsB;KACjC;IACD;QACE,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,mBAAmB;QAChC,QAAQ,EAAE,wBAAwB;KACnC;IACD;QACE,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,eAAe;QAC5B,QAAQ,EAAE,mCAAmC;KAC9C;IACD;QACE,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,uBAAuB;QACpC,QAAQ,EAAE,6CAA6C;KACxD;IACD;QACE,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,gBAAgB;QAC7B,QAAQ,EAAE,oDAAoD;KAC/D;IACD;QACE,IAAI,EAAE,aAAa;QACnB,WAAW,EAAE,YAAY;QACzB,QAAQ,EAAE,+BAA+B;KAC1C;IACD;QACE,IAAI,EAAE,KAAK;QACX,WAAW,EAAE,WAAW;QACxB,QAAQ,EAAE,mBAAmB;KAC9B;IACD;QACE,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,iBAAiB;QAC9B,QAAQ,EAAE,wBAAwB;KACnC;IACD;QACE,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,eAAe;QAC5B,QAAQ,EAAE,yBAAyB;KACpC;IACD;QACE,IAAI,EAAE,OAAO;QACb,WAAW,EAAE,2BAA2B;QACxC,QAAQ,EAAE,yBAAyB;KACpC;IACD;QACE,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,kBAAkB;QAC/B,QAAQ,EAAE,6BAA6B;KACxC;IACD;QACE,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,YAAY;QACzB,QAAQ,EAAE,iCAAiC;KAC5C;IACD;QACE,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,eAAe;QAC5B,QAAQ,EAAE,6BAA6B;KACxC;IACD;QACE,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,eAAe;QAC5B,QAAQ,EAAE,8BAA8B;KACzC;IACD;QACE,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,YAAY;QACzB,QAAQ,EAAE,2BAA2B;KACtC;IACD;QACE,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,cAAc;QAC3B,QAAQ,EAAE,4BAA4B;KACvC;IACD;QACE,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,eAAe;QAC5B,QAAQ,EAAE,wBAAwB;KACnC;IACD;QACE,IAAI,EAAE,OAAO;QACb,WAAW,EAAE,aAAa;QAC1B,QAAQ,EAAE,yBAAyB;KACpC;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,WAAW,EAAE,iBAAiB;QAC9B,QAAQ,EAAE,+BAA+B;KAC1C;IACD;QACE,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,kBAAkB;QAC/B,QAAQ,EAAE,gCAAgC;KAC3C;IACD;QACE,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,iBAAiB;QAC9B,QAAQ,EAAE,iCAAiC;KAC5C;CACF,CAAA","sourcesContent":["/**\n * Default slash templates — 사용자에게 빠르게 작성된 자연어 prompt 를 끼워주는 단축키.\n *\n * `/grid` → \"선택한 컴포넌트들을 3x2 그리드로 정렬해줘\" 같이 텍스트가 textarea 에\n * 삽입됨. 사용자는 그대로 보내거나 숫자/카탈로그 이름을 수정해 보낼 수 있다.\n *\n * Slash 는 LLM 우회 명령이 아니다 — AI 가 받는 입력은 일반 자연어 prompt 와 동일.\n * 단지 \"이런 명령이 가능하구나\" 의 discoverability 를 popup 으로 보여주는 역할.\n *\n * 수정 / 추가 정책:\n * - 한국어 우선 (자연어가 한국어라야 한국 사용자에게 즉시 작동)\n * - 카탈로그 이름 (@monitor-card 등) 가 들어간 템플릿은 server-side resolver 와\n * 자동 호환 — drift 검증은 default-catalog-entries.test.ts 가 잡음\n * - 너무 많이 만들지 말 것 (popup 이 과해짐). 가장 흔한 의도만.\n */\nimport type { SlashTemplate } from './mention-popup-helpers.js'\n\nexport const DEFAULT_SLASH_TEMPLATES: SlashTemplate[] = [\n {\n name: 'grid',\n description: '선택한 컴포넌트들을 그리드로 정렬',\n template: '선택한 컴포넌트들을 3x2 그리드로 정렬해줘'\n },\n {\n name: 'row',\n description: '선택한 컴포넌트들을 한 줄로',\n template: '선택한 컴포넌트들을 한 줄로 배치해줘'\n },\n {\n name: 'column',\n description: '선택한 컴포넌트들을 세로로 일렬',\n template: '선택한 컴포넌트들을 세로로 일렬 배치해줘'\n },\n {\n name: 'palette',\n description: '검증된 색 팔레트로 통일',\n template: '@industrial-steel 팔레트로 보드 전체 통일해줘'\n },\n {\n name: 'atmosphere',\n description: '보드 분위기 변경 (자연어 자유 작성)',\n template: '보드 분위기를 어둡고 차분하게 바꿔줘 (atmosphere primer 활용)'\n },\n {\n name: 'dashboard',\n description: 'KPI 대시보드 기본 구성',\n template: '@kpi-row 레이아웃으로 @stat-tile 4개를 상단에 + 아래 메인 영역 구성해줘'\n },\n {\n name: 'add-monitor',\n description: '모니터링 카드 추가',\n template: '@monitor-card 추가 (제목과 값은 적절히)'\n },\n {\n name: 'fit',\n description: '보드 전체 보이게',\n template: '보드 전체가 보이게 화면 맞춰줘'\n },\n {\n name: 'center',\n description: '선택 컴포넌트 화면 가운데로',\n template: '선택한 컴포넌트를 화면 가운데로 이동해줘'\n },\n {\n name: 'inspect',\n description: '선택 컴포넌트 정보 보기',\n template: '현재 선택된 컴포넌트의 모든 속성을 보여줘'\n },\n {\n name: 'align',\n description: '선택 컴포넌트 정렬 (좌/우/위/아래/가운데)',\n template: '선택한 컴포넌트들을 왼쪽 기준으로 정렬해줘'\n },\n {\n name: 'distribute',\n description: '선택 컴포넌트 간격 균등 분배',\n template: '선택한 컴포넌트들을 가로로 간격 균등하게 분배해줘'\n },\n {\n name: 'duplicate',\n description: '선택 컴포넌트 복제',\n template: '선택한 컴포넌트를 오른쪽에 같은 크기로 하나 더 복제해줘'\n },\n {\n name: 'match-size',\n description: '선택 컴포넌트 크기 통일',\n template: '선택한 컴포넌트들을 첫 번째와 같은 크기로 맞춰줘'\n },\n {\n name: 'z-front',\n description: '선택 컴포넌트 맨 앞으로',\n template: '선택한 컴포넌트를 z-order 맨 앞으로 가져와줘'\n },\n {\n name: 'rotate',\n description: '선택 컴포넌트 회전',\n template: '선택한 컴포넌트를 시계방향으로 90도 회전해줘'\n },\n {\n name: 'z-back',\n description: '선택 컴포넌트 맨 뒤로',\n template: '선택한 컴포넌트를 z-order 맨 뒤로 보내줘'\n },\n {\n name: 'mirror',\n description: '선택 컴포넌트 좌우 반전',\n template: '선택한 컴포넌트를 좌우 대칭으로 반전해줘'\n },\n {\n name: 'group',\n description: '선택 컴포넌트 그룹화',\n template: '선택한 컴포넌트들을 하나의 그룹으로 묶어줘'\n },\n {\n name: 'rename-sequential',\n description: '선택 컴포넌트들에 순번 라벨',\n template: '선택한 컴포넌트들에 1, 2, 3 순번 라벨을 붙여줘'\n },\n {\n name: 'bulk-set',\n description: '선택 컴포넌트 속성 일괄 변경',\n template: '선택한 컴포넌트들의 [속성]을 [값]으로 일괄 설정해줘'\n },\n {\n name: 'cleanup',\n description: '겹침/어색한 위치 자동 정리',\n template: '겹쳐있거나 어색한 위치의 컴포넌트들을 자연스럽게 정리해줘'\n }\n]\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* default-slash-templates.ts 정합성 검증.
|
|
3
|
+
*
|
|
4
|
+
* Slash template 은 server-side counterpart 없이 client-only — drift 검증 대상이
|
|
5
|
+
* 아니지만 자체 정합성 (unique name / 비어있지 않은 template / @ 멘션이 카탈로그
|
|
6
|
+
* 항목과 호환) 을 검증.
|
|
7
|
+
*/
|
|
8
|
+
import { DEFAULT_SLASH_TEMPLATES } from './default-slash-templates';
|
|
9
|
+
import { DEFAULT_CATALOG_ENTRIES } from './default-catalog-entries';
|
|
10
|
+
describe('Default slash templates — 정합성', () => {
|
|
11
|
+
test('각 템플릿이 name + template 보유', () => {
|
|
12
|
+
for (const t of DEFAULT_SLASH_TEMPLATES) {
|
|
13
|
+
expect(typeof t.name).toBe('string');
|
|
14
|
+
expect(t.name.length).toBeGreaterThan(0);
|
|
15
|
+
expect(typeof t.template).toBe('string');
|
|
16
|
+
expect(t.template.length).toBeGreaterThan(0);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
test('이름 unique', () => {
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
for (const t of DEFAULT_SLASH_TEMPLATES) {
|
|
22
|
+
expect(seen.has(t.name)).toBe(false);
|
|
23
|
+
seen.add(t.name);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
test('description 권장 (popup UX)', () => {
|
|
27
|
+
for (const t of DEFAULT_SLASH_TEMPLATES) {
|
|
28
|
+
expect(t.description).toBeTruthy();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
test('template 안의 @멘션은 카탈로그에 등록된 이름만 사용', () => {
|
|
32
|
+
const catalogNames = new Set(DEFAULT_CATALOG_ENTRIES.map(e => e.name));
|
|
33
|
+
const pattern = /@([^\s@]+)/g;
|
|
34
|
+
for (const t of DEFAULT_SLASH_TEMPLATES) {
|
|
35
|
+
let m;
|
|
36
|
+
while ((m = pattern.exec(t.template)) !== null) {
|
|
37
|
+
const token = m[1];
|
|
38
|
+
// 알 수 없는 카탈로그 멘션이 들어가면 사용자가 선택해도 의도와 다른 결과
|
|
39
|
+
expect(catalogNames.has(token)).toBe(true);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
test('name 에는 # / @ / / 같은 trigger char 들어가면 안 됨', () => {
|
|
44
|
+
for (const t of DEFAULT_SLASH_TEMPLATES) {
|
|
45
|
+
expect(t.name).not.toContain('#');
|
|
46
|
+
expect(t.name).not.toContain('@');
|
|
47
|
+
expect(t.name).not.toContain('/');
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
//# sourceMappingURL=default-slash-templates.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-slash-templates.test.js","sourceRoot":"","sources":["../../../client/components/default-slash-templates.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAA;AACnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAA;AAEnE,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC7C,IAAI,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACrC,KAAK,MAAM,CAAC,IAAI,uBAAuB,EAAE,CAAC;YACxC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACpC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;YACxC,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACxC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;QAC9C,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;QACrB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;QAC9B,KAAK,MAAM,CAAC,IAAI,uBAAuB,EAAE,CAAC;YACxC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACpC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QAClB,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACrC,KAAK,MAAM,CAAC,IAAI,uBAAuB,EAAE,CAAC;YACxC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,UAAU,EAAE,CAAA;QACpC,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC7C,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;QACtE,MAAM,OAAO,GAAG,aAAa,CAAA;QAC7B,KAAK,MAAM,CAAC,IAAI,uBAAuB,EAAE,CAAC;YACxC,IAAI,CAAyB,CAAA;YAC7B,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;gBAC/C,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;gBAClB,2CAA2C;gBAC3C,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAC5C,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACtD,KAAK,MAAM,CAAC,IAAI,uBAAuB,EAAE,CAAC;YACxC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;YACjC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;YACjC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;QACnC,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["/**\n * default-slash-templates.ts 정합성 검증.\n *\n * Slash template 은 server-side counterpart 없이 client-only — drift 검증 대상이\n * 아니지만 자체 정합성 (unique name / 비어있지 않은 template / @ 멘션이 카탈로그\n * 항목과 호환) 을 검증.\n */\nimport { DEFAULT_SLASH_TEMPLATES } from './default-slash-templates'\nimport { DEFAULT_CATALOG_ENTRIES } from './default-catalog-entries'\n\ndescribe('Default slash templates — 정합성', () => {\n test('각 템플릿이 name + template 보유', () => {\n for (const t of DEFAULT_SLASH_TEMPLATES) {\n expect(typeof t.name).toBe('string')\n expect(t.name.length).toBeGreaterThan(0)\n expect(typeof t.template).toBe('string')\n expect(t.template.length).toBeGreaterThan(0)\n }\n })\n\n test('이름 unique', () => {\n const seen = new Set<string>()\n for (const t of DEFAULT_SLASH_TEMPLATES) {\n expect(seen.has(t.name)).toBe(false)\n seen.add(t.name)\n }\n })\n\n test('description 권장 (popup UX)', () => {\n for (const t of DEFAULT_SLASH_TEMPLATES) {\n expect(t.description).toBeTruthy()\n }\n })\n\n test('template 안의 @멘션은 카탈로그에 등록된 이름만 사용', () => {\n const catalogNames = new Set(DEFAULT_CATALOG_ENTRIES.map(e => e.name))\n const pattern = /@([^\\s@]+)/g\n for (const t of DEFAULT_SLASH_TEMPLATES) {\n let m: RegExpExecArray | null\n while ((m = pattern.exec(t.template)) !== null) {\n const token = m[1]\n // 알 수 없는 카탈로그 멘션이 들어가면 사용자가 선택해도 의도와 다른 결과\n expect(catalogNames.has(token)).toBe(true)\n }\n }\n })\n\n test('name 에는 # / @ / / 같은 trigger char 들어가면 안 됨', () => {\n for (const t of DEFAULT_SLASH_TEMPLATES) {\n expect(t.name).not.toContain('#')\n expect(t.name).not.toContain('@')\n expect(t.name).not.toContain('/')\n }\n })\n})\n"]}
|