@leviosa-ai/detail-page-editor 0.16.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +83 -0
- package/LICENSE.ko.md +71 -0
- package/components/cardnews/color-input.tsx +1 -0
- package/components/detail-page/ai-generate-panel.tsx +45 -9
- package/components/detail-page/annotation-canvas.tsx +19 -13
- package/components/detail-page/annotation-dialog.tsx +1 -0
- package/components/detail-page/bubble-tail-overlay.tsx +1 -0
- package/components/detail-page/canvas-context-menu.tsx +1 -0
- package/components/detail-page/canvas-insert-toolbar.tsx +1 -0
- package/components/detail-page/canvas-observer.tsx +1 -0
- package/components/detail-page/canvas-overlay-host.tsx +1 -0
- package/components/detail-page/canvas-selection-tools.tsx +1 -0
- package/components/detail-page/canvas-store-facade.ts +1 -0
- package/components/detail-page/chart-inspector.tsx +6 -0
- package/components/detail-page/confirm-delete-button.tsx +82 -0
- package/components/detail-page/design-reference-picker.tsx +48 -46
- package/components/detail-page/detail-page-basic-shapes.tsx +1 -0
- package/components/detail-page/detail-page-brand-gifs-panel.tsx +1 -0
- package/components/detail-page/detail-page-brand-panel-header.tsx +1 -0
- package/components/detail-page/detail-page-charts-panel.tsx +1 -0
- package/components/detail-page/detail-page-decorations-panel.tsx +1 -0
- package/components/detail-page/detail-page-download-dialog.tsx +118 -18
- package/components/detail-page/detail-page-editor.tsx +66 -15
- package/components/detail-page/detail-page-elements-panel.tsx +1 -0
- package/components/detail-page/detail-page-font-picker.tsx +1 -0
- package/components/detail-page/detail-page-frame-header.tsx +1 -0
- package/components/detail-page/detail-page-history-buttons.tsx +1 -0
- package/components/detail-page/detail-page-host-context.tsx +37 -2
- package/components/detail-page/detail-page-icons-panel.tsx +1 -0
- package/components/detail-page/detail-page-konva-json-preview.tsx +1 -0
- package/components/detail-page/detail-page-layers-panel.tsx +126 -21
- package/components/detail-page/detail-page-my-images-panel.tsx +25 -14
- package/components/detail-page/detail-page-my-shapes-panel.tsx +8 -6
- package/components/detail-page/detail-page-page-toolbar.tsx +1 -0
- package/components/detail-page/detail-page-pages-panel.tsx +13 -9
- package/components/detail-page/detail-page-pages-timeline.tsx +1 -0
- package/components/detail-page/detail-page-photos-panel.tsx +1 -0
- package/components/detail-page/detail-page-properties-panel.tsx +339 -1040
- package/components/detail-page/detail-page-qr-panel.tsx +1 -0
- package/components/detail-page/detail-page-references-panel.tsx +1 -0
- package/components/detail-page/detail-page-selection.ts +1 -0
- package/components/detail-page/detail-page-shapes-panel.tsx +1 -0
- package/components/detail-page/detail-page-sidebar-sections.tsx +1 -0
- package/components/detail-page/detail-page-tables-panel.tsx +1 -0
- package/components/detail-page/detail-page-text-panel.tsx +1 -0
- package/components/detail-page/detail-page-thumbnail-bus.ts +1 -0
- package/components/detail-page/edit-quota-ui.tsx +1 -0
- package/components/detail-page/editor-ai-context.tsx +1 -0
- package/components/detail-page/editor-history.ts +1 -0
- package/components/detail-page/editor-hotkeys.tsx +58 -9
- package/components/detail-page/element-recents-strip.tsx +1 -0
- package/components/detail-page/element-rects.ts +1 -0
- package/components/detail-page/fill-control.tsx +1 -0
- package/components/detail-page/find-replace-panel.tsx +6 -2
- package/components/detail-page/frame-drag-bus.ts +1 -0
- package/components/detail-page/frame-drag-grip.tsx +1 -0
- package/components/detail-page/frame-drag-layer.tsx +1 -0
- package/components/detail-page/generation-progress-fill.tsx +1 -0
- package/components/detail-page/gif-animator.tsx +1 -0
- package/components/detail-page/gif-effect-picker.tsx +1 -0
- package/components/detail-page/group-drill-in.tsx +1 -0
- package/components/detail-page/group-prompt-edit-panel.tsx +17 -8
- package/components/detail-page/hover-highlight-overlay.tsx +1 -0
- package/components/detail-page/hovered-layer.ts +1 -0
- package/components/detail-page/image-crop-overlay.tsx +1 -0
- package/components/detail-page/inspector-controls.tsx +107 -7
- package/components/detail-page/inspectors/gif-sections.tsx +912 -0
- package/components/detail-page/inspectors/shared.ts +74 -0
- package/components/detail-page/leviosa-canvas-workspace.tsx +96 -0
- package/components/detail-page/nested-selection-overlay.tsx +1 -0
- package/components/detail-page/panel-search-input.tsx +1 -0
- package/components/detail-page/prompt-edit-panel.tsx +4 -1
- package/components/detail-page/section-height-handle.tsx +1 -0
- package/components/detail-page/section-reauthor-controller.tsx +1 -0
- package/components/detail-page/selection-quick-toolbar.tsx +1 -0
- package/components/detail-page/spec-prompt-edit-panel.tsx +1 -0
- package/components/detail-page/spec-resize-absorber.tsx +1 -0
- package/components/detail-page/svg-prompt-edit-panel.tsx +1 -0
- package/components/detail-page/table-canvas-overlay.tsx +1 -0
- package/components/detail-page/table-inspector.tsx +25 -3
- package/components/detail-page/use-auto-save.ts +119 -47
- package/components/ui/button.tsx +1 -0
- package/components/ui/command.tsx +1 -0
- package/components/ui/dialog.tsx +1 -0
- package/components/ui/label.tsx +1 -0
- package/components/ui/select.tsx +1 -0
- package/components/ui/slider.tsx +1 -0
- package/components/ui/switch.tsx +1 -0
- package/components/ui/tooltip.tsx +1 -0
- package/i18n/en.json +127 -8
- package/i18n/index.ts +1 -0
- package/i18n/ko.json +127 -8
- package/index.ts +3 -0
- package/lib/cardnews/font-loader.ts +1 -0
- package/lib/detail-page/animation-sniff.ts +1 -0
- package/lib/detail-page/asset-bytes-url.ts +1 -0
- package/lib/detail-page/authoring-image-src.ts +1 -0
- package/lib/detail-page/basic-shapes.ts +1 -0
- package/lib/detail-page/brand-image-category.ts +1 -0
- package/lib/detail-page/canvas-menu.ts +1 -0
- package/lib/detail-page/canvas-paste.ts +77 -0
- package/lib/detail-page/chart/defaults.ts +1 -0
- package/lib/detail-page/chart/format.ts +1 -0
- package/lib/detail-page/chart/normalize.ts +1 -0
- package/lib/detail-page/chart/palette.ts +1 -0
- package/lib/detail-page/chart/paste.ts +1 -0
- package/lib/detail-page/chart/preview.ts +1 -0
- package/lib/detail-page/chart/render.ts +1 -0
- package/lib/detail-page/chart/renderers/bar-h.ts +1 -0
- package/lib/detail-page/chart/renderers/bar-v.ts +1 -0
- package/lib/detail-page/chart/renderers/donut.ts +1 -0
- package/lib/detail-page/chart/renderers/gauge.ts +1 -0
- package/lib/detail-page/chart/renderers/line.ts +1 -0
- package/lib/detail-page/chart/renderers/shared.ts +1 -0
- package/lib/detail-page/chart/renderers/stack.ts +1 -0
- package/lib/detail-page/chart/sync.ts +1 -0
- package/lib/detail-page/chart/types.ts +1 -0
- package/lib/detail-page/css-color.ts +1 -0
- package/lib/detail-page/data-gif-payload.ts +3 -0
- package/lib/detail-page/design-reference.ts +3 -2
- package/lib/detail-page/distribute.ts +1 -0
- package/lib/detail-page/editor-profile.ts +4 -3
- package/lib/detail-page/editor-routes.ts +1 -0
- package/lib/detail-page/element-recents.ts +1 -0
- package/lib/detail-page/export-platforms.ts +12 -0
- package/lib/detail-page/find-replace.ts +1 -0
- package/lib/detail-page/format-painter.ts +1 -0
- package/lib/detail-page/frame-pages.ts +1 -0
- package/lib/detail-page/gif-frames.ts +1 -0
- package/lib/detail-page/gif-timeline.ts +1 -0
- package/lib/detail-page/group-action.ts +33 -19
- package/lib/detail-page/hangul-roman.ts +1 -0
- package/lib/detail-page/icon-curated.ts +1 -0
- package/lib/detail-page/icon-fuzzy.ts +1 -0
- package/lib/detail-page/icon-keywords.ko.ts +1 -0
- package/lib/detail-page/icon-search.ts +1 -0
- package/lib/detail-page/icons.ts +1 -0
- package/lib/detail-page/image-credit.ts +1 -0
- package/lib/detail-page/image-crop.ts +1 -0
- package/lib/detail-page/image-data-uri.ts +1 -0
- package/lib/detail-page/insert-image.ts +1 -0
- package/lib/detail-page/insert-shape.ts +1 -0
- package/lib/detail-page/korean-dictionary.ts +1 -0
- package/lib/detail-page/layer-move.ts +90 -113
- package/lib/detail-page/new-product-draft-store.ts +1 -0
- package/lib/detail-page/new-product-draft.ts +1 -0
- package/lib/detail-page/personal-images-refresh.ts +1 -0
- package/lib/detail-page/product-source.ts +1 -0
- package/lib/detail-page/qr-code.ts +1 -0
- package/lib/detail-page/reference-image.ts +1 -0
- package/lib/detail-page/reference-upload.ts +1 -0
- package/lib/detail-page/replace-page.ts +1 -0
- package/lib/detail-page/replace-with-gif.ts +1 -0
- package/lib/detail-page/runtime-config.ts +1 -0
- package/lib/detail-page/section-height.ts +1 -0
- package/lib/detail-page/section-reauthor-bus.ts +1 -0
- package/lib/detail-page/selection-actions.ts +1 -0
- package/lib/detail-page/shape-to-image.ts +7 -4
- package/lib/detail-page/spec-group/clipboard.ts +1 -0
- package/lib/detail-page/spec-group/sync.ts +1 -0
- package/lib/detail-page/spec-group/text-metrics.ts +1 -0
- package/lib/detail-page/stock-photos.ts +1 -0
- package/lib/detail-page/svg-colors.ts +1 -0
- package/lib/detail-page/table/defaults.ts +1 -0
- package/lib/detail-page/table/edit.ts +1 -0
- package/lib/detail-page/table/harvest.ts +1 -0
- package/lib/detail-page/table/normalize.ts +1 -0
- package/lib/detail-page/table/paste.ts +1 -0
- package/lib/detail-page/table/preview.ts +1 -0
- package/lib/detail-page/table/render.ts +1 -0
- package/lib/detail-page/table/sync.ts +1 -0
- package/lib/detail-page/table/types.ts +1 -0
- package/lib/detail-page/text-gif-layout.ts +17 -11
- package/lib/detail-page/text-gif-payload.ts +1 -0
- package/lib/detail-page/text-presets.ts +1 -0
- package/lib/detail-page/use-fake-progress.ts +1 -0
- package/lib/detail-page/use-progressive-reveal.ts +1 -0
- package/lib/detail-page/z-order.ts +1 -0
- package/lib/detail-page-canvas/canvas-key.ts +2 -1
- package/lib/detail-page-canvas/custom-props-adapter.ts +1 -0
- package/lib/detail-page-canvas/editor-fonts.ts +1 -0
- package/lib/detail-page-canvas/export/ai.ts +30 -3
- package/lib/detail-page-canvas/export/color.ts +1 -0
- package/lib/detail-page-canvas/export/document-model.ts +10 -3
- package/lib/detail-page-canvas/export/export-files.ts +57 -10
- package/lib/detail-page-canvas/export/fit-budget.ts +1 -0
- package/lib/detail-page-canvas/export/font-subset.ts +1 -0
- package/lib/detail-page-canvas/export/frame.ts +83 -0
- package/lib/detail-page-canvas/export/gif-export.ts +1 -0
- package/lib/detail-page-canvas/export/gif-plan.ts +1 -0
- package/lib/detail-page-canvas/export/mp4-encode.ts +1 -0
- package/lib/detail-page-canvas/export/mp4-support.ts +1 -0
- package/lib/detail-page-canvas/export/pdf/geometry.ts +1 -0
- package/lib/detail-page-canvas/export/pdf/inline-svg.ts +1 -0
- package/lib/detail-page-canvas/export/pdf/resources.ts +1 -0
- package/lib/detail-page-canvas/export/pdf/text-ops.ts +1 -0
- package/lib/detail-page-canvas/export/pdf/writer.ts +1 -0
- package/lib/detail-page-canvas/export/psd.ts +94 -33
- package/lib/detail-page-canvas/export/raster.ts +36 -6
- package/lib/detail-page-canvas/export/svg.ts +98 -7
- package/lib/detail-page-canvas/export/text-layout.ts +36 -15
- package/lib/detail-page-canvas/font-catalog.ts +1 -0
- package/lib/detail-page-canvas/font-tags.ts +1 -0
- package/lib/detail-page-canvas/gif-web-fonts.ts +1 -0
- package/lib/detail-page-canvas/text-background-plan.ts +1 -0
- package/lib/utils.ts +1 -0
- package/package.json +4 -3
- package/server/icons.ts +1 -0
- package/server/route-kit.ts +1 -0
- package/server/stock-photos.ts +1 -0
- package/styles/canvas-bridge.css +1 -0
- package/styles/tokens.css +1 -0
- package/types/commerce.ts +1 -0
- package/types/detail-page-api.ts +1 -0
- package/types/detail-page-canvas.ts +6 -0
- package/types/fontkit.d.ts +1 -0
- package/types/gifenc.d.ts +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Copyright © 2026 주식회사레비오사에이아이. All rights reserved.
|
|
2
|
+
|
|
3
|
+
PROPRIETARY SOFTWARE — SOURCE VISIBLE; NOT OPEN SOURCE
|
|
4
|
+
|
|
5
|
+
This notice applies to the software, source code, documentation, and other
|
|
6
|
+
materials distributed in this repository or in packages published from it
|
|
7
|
+
(collectively, the "Software"), except for Third-Party Materials as described
|
|
8
|
+
below.
|
|
9
|
+
|
|
10
|
+
NO GENERAL LICENSE
|
|
11
|
+
|
|
12
|
+
The Software may be publicly accessible through services such as GitHub and
|
|
13
|
+
npm for distribution and operational convenience. Public accessibility does
|
|
14
|
+
not place the Software in the public domain and does not, by itself, grant a
|
|
15
|
+
license under the copyright or other intellectual property rights of
|
|
16
|
+
주식회사레비오사에이아이 ("Leviosa").
|
|
17
|
+
|
|
18
|
+
Except:
|
|
19
|
+
|
|
20
|
+
(a) as expressly authorized by a separate written agreement authorized by
|
|
21
|
+
Leviosa;
|
|
22
|
+
|
|
23
|
+
(b) for rights granted under the applicable terms of GitHub, npm, or another
|
|
24
|
+
distribution platform, solely to the extent and for the purposes provided
|
|
25
|
+
by those terms; or
|
|
26
|
+
|
|
27
|
+
(c) to the extent applicable law permits an act notwithstanding this notice,
|
|
28
|
+
|
|
29
|
+
no license or permission is granted, expressly, implicitly, by estoppel, or
|
|
30
|
+
otherwise, to reproduce, copy, modify, adapt, translate, merge, prepare
|
|
31
|
+
derivative works from, publish, distribute, sublicense, sell, transmit,
|
|
32
|
+
publicly display, publicly perform, or incorporate the Software, in whole or
|
|
33
|
+
in part, into any other product or service.
|
|
34
|
+
|
|
35
|
+
Leviosa does not intend public availability of the Software, documentation,
|
|
36
|
+
installation instructions, package metadata, or other related materials to
|
|
37
|
+
constitute an implied license beyond the rights expressly identified above.
|
|
38
|
+
|
|
39
|
+
THIRD-PARTY MATERIALS
|
|
40
|
+
|
|
41
|
+
The distribution may contain or bundle software, fonts, assets, or other
|
|
42
|
+
materials owned by third parties ("Third-Party Materials"). Third-Party
|
|
43
|
+
Materials are excluded from the proprietary license restrictions in this
|
|
44
|
+
notice to the extent they are governed by their own licenses.
|
|
45
|
+
|
|
46
|
+
Third-Party Materials remain subject to their applicable copyright notices,
|
|
47
|
+
license terms, and other accompanying notices. Nothing in this notice
|
|
48
|
+
restricts any right granted under an applicable third-party license. In the
|
|
49
|
+
event of a conflict between this notice and an applicable third-party license
|
|
50
|
+
with respect to Third-Party Materials, the third-party license controls with
|
|
51
|
+
respect to those materials.
|
|
52
|
+
|
|
53
|
+
NO WARRANTY
|
|
54
|
+
|
|
55
|
+
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, AND EXCEPT AS EXPRESSLY
|
|
56
|
+
PROVIDED IN A SEPARATE WRITTEN AGREEMENT, THE SOFTWARE IS PROVIDED "AS IS"
|
|
57
|
+
AND "AS AVAILABLE," WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED, OR
|
|
58
|
+
STATUTORY, INCLUDING WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY,
|
|
59
|
+
FITNESS FOR A PARTICULAR PURPOSE, TITLE, OR NON-INFRINGEMENT.
|
|
60
|
+
|
|
61
|
+
LIMITATION OF LIABILITY
|
|
62
|
+
|
|
63
|
+
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, AND EXCEPT AS EXPRESSLY
|
|
64
|
+
PROVIDED IN A SEPARATE WRITTEN AGREEMENT, LEVIOSA SHALL NOT BE LIABLE FOR
|
|
65
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, CONSEQUENTIAL, OR
|
|
66
|
+
OTHER DAMAGES OR LIABILITIES ARISING FROM OR IN CONNECTION WITH THE
|
|
67
|
+
SOFTWARE OR ITS USE.
|
|
68
|
+
|
|
69
|
+
NOTHING IN THIS NOTICE EXCLUDES OR LIMITS ANY LIABILITY TO THE EXTENT THAT
|
|
70
|
+
SUCH LIABILITY CANNOT LAWFULLY BE EXCLUDED OR LIMITED.
|
|
71
|
+
|
|
72
|
+
TRADEMARKS
|
|
73
|
+
|
|
74
|
+
No license or other right to use any trademark, service mark, trade name,
|
|
75
|
+
logo, or other source identifier of Leviosa is granted by this notice.
|
|
76
|
+
|
|
77
|
+
LICENSING INQUIRIES
|
|
78
|
+
|
|
79
|
+
For permission to use or license the Software, contact:
|
|
80
|
+
leviosaai2025@leviosa.ai.kr
|
|
81
|
+
|
|
82
|
+
A Korean convenience translation is provided in LICENSE.ko.md. The English
|
|
83
|
+
text of this notice controls.
|
package/LICENSE.ko.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
> 이 문서는 `LICENSE` 의 참고용 한국어 번역이다. 내용이 다르면 영문 `LICENSE` 가 우선한다.
|
|
2
|
+
|
|
3
|
+
저작권 © 2026 주식회사레비오사에이아이. 모든 권리 보유.
|
|
4
|
+
|
|
5
|
+
독점 소프트웨어 — 소스 열람 가능; 오픈소스 아님
|
|
6
|
+
|
|
7
|
+
본 고지는 아래에서 정하는 제3자 자료를 제외하고, 본 저장소 또는 본 저장소에서
|
|
8
|
+
배포되는 패키지에 포함된 소프트웨어, 소스 코드, 문서 및 그 밖의 자료(이하 통칭하여
|
|
9
|
+
"소프트웨어")에 적용됩니다.
|
|
10
|
+
|
|
11
|
+
## 일반적 라이선스의 부여 없음
|
|
12
|
+
|
|
13
|
+
소프트웨어는 배포 및 운영상의 편의를 위해 GitHub, npm 등의 서비스를 통하여
|
|
14
|
+
공개적으로 접근 가능할 수 있습니다. 이러한 공개적 접근 가능성은 소프트웨어를 퍼블릭
|
|
15
|
+
도메인에 귀속시키지 않으며, 그 자체로 주식회사레비오사에이아이(이하 "Leviosa")의
|
|
16
|
+
저작권 또는 그 밖의 지식재산권에 관한 라이선스를 부여하지 않습니다.
|
|
17
|
+
|
|
18
|
+
다음 각 호의 경우를 제외하고,
|
|
19
|
+
|
|
20
|
+
(a) Leviosa가 승인한 별도의 서면 계약에 따라 명시적으로 허용된 경우,
|
|
21
|
+
|
|
22
|
+
(b) GitHub, npm 또는 그 밖의 배포 플랫폼의 적용 가능한 이용약관에 따라 부여되는
|
|
23
|
+
권리의 범위 및 목적 내에서 이용하는 경우, 또는
|
|
24
|
+
|
|
25
|
+
(c) 본 고지에도 불구하고 적용 법률에 따라 허용되는 경우,
|
|
26
|
+
|
|
27
|
+
소프트웨어의 전부 또는 일부를 복제, 복사, 수정, 개작, 번역, 병합하거나, 2차적
|
|
28
|
+
저작물을 작성하거나, 출판, 배포, 재라이선스, 판매, 송신, 공개 전시, 공개 실행하거나
|
|
29
|
+
다른 제품 또는 서비스에 포함할 수 있는 어떠한 명시적 또는 묵시적 라이선스나 허락도
|
|
30
|
+
부여되지 않습니다. 금반언 등에 의한 라이선스 역시 부여되지 않습니다.
|
|
31
|
+
|
|
32
|
+
Leviosa는 소프트웨어의 공개적 접근 가능성, 문서, 설치 방법, 패키지 메타데이터 또는
|
|
33
|
+
관련 자료가 위에서 명시적으로 인정한 권리를 초과하는 묵시적 라이선스를 구성하도록
|
|
34
|
+
의도하지 않습니다.
|
|
35
|
+
|
|
36
|
+
## 제3자 자료
|
|
37
|
+
|
|
38
|
+
본 배포물에는 제3자가 소유하는 소프트웨어, 폰트, 자산 또는 그 밖의 자료(이하 "제3자
|
|
39
|
+
자료")가 포함되거나 번들될 수 있습니다. 제3자 자료가 자체 라이선스의 적용을 받는
|
|
40
|
+
범위에서는 본 고지의 독점 소프트웨어 제한사항이 해당 제3자 자료에 적용되지 않습니다.
|
|
41
|
+
|
|
42
|
+
제3자 자료에는 해당 제3자의 저작권 고지, 라이선스 조건 및 그 밖의 관련 고지가
|
|
43
|
+
적용됩니다. 본 고지의 어떠한 내용도 해당 제3자 라이선스가 부여하는 권리를 제한하지
|
|
44
|
+
않습니다. 제3자 자료에 관하여 본 고지와 해당 제3자 라이선스가 충돌하는 경우에는 해당
|
|
45
|
+
제3자 자료에 한하여 제3자 라이선스가 우선합니다.
|
|
46
|
+
|
|
47
|
+
## 보증의 부인
|
|
48
|
+
|
|
49
|
+
적용 법률이 허용하는 최대 범위에서, 그리고 별도의 서면 계약에서 명시적으로 달리 정한
|
|
50
|
+
경우를 제외하고, 소프트웨어는 상품성, 특정 목적에의 적합성, 권원 및 비침해성에 관한
|
|
51
|
+
보증을 포함하여 명시적, 묵시적 또는 법정의 어떠한 보증도 없이 "있는 그대로(AS IS)" 및
|
|
52
|
+
"이용 가능한 상태로(AS AVAILABLE)" 제공됩니다.
|
|
53
|
+
|
|
54
|
+
## 책임의 제한
|
|
55
|
+
|
|
56
|
+
적용 법률이 허용하는 최대 범위에서, 그리고 별도의 서면 계약에서 명시적으로 달리 정한
|
|
57
|
+
경우를 제외하고, Leviosa는 소프트웨어 또는 그 사용으로부터 발생하거나 이와 관련하여
|
|
58
|
+
발생하는 직접, 간접, 부수적, 특별, 징벌적, 결과적 손해 또는 그 밖의 손해나 책임에
|
|
59
|
+
대하여 책임을 부담하지 않습니다.
|
|
60
|
+
|
|
61
|
+
본 고지의 어떠한 내용도 적용 법률상 배제하거나 제한할 수 없는 책임을 배제하거나
|
|
62
|
+
제한하지 않습니다.
|
|
63
|
+
|
|
64
|
+
## 상표
|
|
65
|
+
|
|
66
|
+
본 고지는 Leviosa의 상표, 서비스표, 상호, 로고 또는 그 밖의 출처표시에 관한 어떠한
|
|
67
|
+
라이선스나 사용권도 부여하지 않습니다.
|
|
68
|
+
|
|
69
|
+
## 라이선스 문의
|
|
70
|
+
|
|
71
|
+
소프트웨어의 사용 또는 라이선스 허락에 관한 문의: leviosaai2025@leviosa.ai.kr
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
// Copyright © 2026 주식회사레비오사에이아이. All rights reserved. See LICENSE.
|
|
1
2
|
"use client";
|
|
2
3
|
|
|
3
|
-
import { useCallback, useMemo, useState, type ChangeEvent } from "react";
|
|
4
|
+
import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent } from "react";
|
|
4
5
|
import { ImagePlus, Info, Loader2, Pencil, Sparkles, X } from "lucide-react";
|
|
5
6
|
import { useTranslation } from "react-i18next";
|
|
6
7
|
|
|
@@ -69,6 +70,8 @@ export type GenerateImageInput = {
|
|
|
69
70
|
* 막혀도 이미지는 만들어졌는데 브랜드 버킷엔 아무것도 안 남았다.
|
|
70
71
|
*/
|
|
71
72
|
brandId?: string;
|
|
73
|
+
/** 사용자가 취소하면 끊긴다. 호스트는 요청에 그대로 넘기면 된다. */
|
|
74
|
+
signal?: AbortSignal;
|
|
72
75
|
};
|
|
73
76
|
|
|
74
77
|
export type GenerateImageFn = (input: GenerateImageInput) => Promise<string[]>;
|
|
@@ -80,6 +83,8 @@ export type GenerateGifInput = {
|
|
|
80
83
|
transparent: boolean;
|
|
81
84
|
/** 이미지와 같다. 저장 위치는 start 시점에 확정된다. */
|
|
82
85
|
brandId?: string;
|
|
86
|
+
/** 사용자가 취소하면 끊긴다. */
|
|
87
|
+
signal?: AbortSignal;
|
|
83
88
|
};
|
|
84
89
|
|
|
85
90
|
export type GenerateGifFn = (input: GenerateGifInput) => Promise<string[]>;
|
|
@@ -298,7 +303,10 @@ export function AiGeneratePanel({
|
|
|
298
303
|
onBuyCredits,
|
|
299
304
|
}: AiGeneratePanelProps) {
|
|
300
305
|
const { t } = useTranslation("branding");
|
|
301
|
-
const { brand } = useDetailPageHost();
|
|
306
|
+
const { brand, api } = useDetailPageHost();
|
|
307
|
+
// 진행 중 생성의 취소 손잡이. 패널이 닫혀도 끊는다.
|
|
308
|
+
const abortRef = useRef<AbortController | null>(null);
|
|
309
|
+
useEffect(() => () => abortRef.current?.abort(), []);
|
|
302
310
|
const [mode, setMode] = useState<"image" | "gif">(
|
|
303
311
|
// GIF 생성이 배선된 경우에만 gif 초기값을 존중(미배선이면 토글이 없어 갇힘).
|
|
304
312
|
initialMode === "gif" && onGenerateGif ? "gif" : "image",
|
|
@@ -356,7 +364,9 @@ export function AiGeneratePanel({
|
|
|
356
364
|
try {
|
|
357
365
|
setRefUrl(await uploadFile(file));
|
|
358
366
|
} catch (err) {
|
|
359
|
-
|
|
367
|
+
// 원문은 콘솔로만. 서버·네트워크 문구를 그대로 띄우면 번역도 안 되고 읽히지도 않는다.
|
|
368
|
+
console.error("Reference upload failed", err);
|
|
369
|
+
setError(t("detailPage.aiGenerate.uploadFailed"));
|
|
360
370
|
} finally {
|
|
361
371
|
setRefUploading(false);
|
|
362
372
|
}
|
|
@@ -383,6 +393,9 @@ export function AiGeneratePanel({
|
|
|
383
393
|
setLoading(true);
|
|
384
394
|
setError(null);
|
|
385
395
|
setInsufficient(false);
|
|
396
|
+
const controller = new AbortController();
|
|
397
|
+
abortRef.current = controller;
|
|
398
|
+
const { signal } = controller;
|
|
386
399
|
try {
|
|
387
400
|
const referenceImages = refUrl ? [refUrl] : [];
|
|
388
401
|
const brandId = brand.getStoredActiveBrandId() ?? undefined;
|
|
@@ -392,6 +405,7 @@ export function AiGeneratePanel({
|
|
|
392
405
|
referenceImages,
|
|
393
406
|
transparent,
|
|
394
407
|
brandId,
|
|
408
|
+
signal,
|
|
395
409
|
})
|
|
396
410
|
: await onGenerate!({
|
|
397
411
|
prompt: finalPrompt,
|
|
@@ -400,7 +414,10 @@ export function AiGeneratePanel({
|
|
|
400
414
|
tier: activeTier,
|
|
401
415
|
brandId,
|
|
402
416
|
annotatedImage,
|
|
417
|
+
signal,
|
|
403
418
|
});
|
|
419
|
+
// 취소했으면 늦게 온 결과를 넣지 않는다(호스트가 신호를 무시했어도).
|
|
420
|
+
if (signal.aborted) return;
|
|
404
421
|
const url = urls[0];
|
|
405
422
|
if (url) {
|
|
406
423
|
// GIF는 항상 페이지에 삽입(우측 이미지 교체 대상 아님) + 애니 태깅.
|
|
@@ -414,18 +431,28 @@ export function AiGeneratePanel({
|
|
|
414
431
|
: t("detailPage.aiGenerate.noImage"),
|
|
415
432
|
);
|
|
416
433
|
} catch (err) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
434
|
+
if (signal.aborted) return; // 사용자가 끊었다 — 오류가 아니다.
|
|
435
|
+
// 크레딧 부족은 호스트 판별기(402 본문)로 먼저 가리고, 생성기가 직접 붙인
|
|
436
|
+
// `insufficientCredits` 표시도 계속 받는다(우측 편집 경로가 그렇게 던진다).
|
|
437
|
+
const marked = (err as { insufficientCredits?: boolean } | null)?.insufficientCredits;
|
|
438
|
+
if (api.asInsufficientCreditsError(err) || marked) {
|
|
420
439
|
setInsufficient(true);
|
|
421
440
|
setError(t("detailPage.aiGenerate.insufficientCredits"));
|
|
422
441
|
} else {
|
|
423
|
-
|
|
442
|
+
console.error("AI generation failed", err);
|
|
443
|
+
setError(t("detailPage.aiGenerate.generateFailed"));
|
|
424
444
|
}
|
|
425
445
|
} finally {
|
|
426
|
-
|
|
446
|
+
if (abortRef.current === controller) abortRef.current = null;
|
|
447
|
+
if (!signal.aborted) setLoading(false);
|
|
427
448
|
}
|
|
428
|
-
}, [prompt, loading, blocked, isGif, gifMissingRef, transparent, onGenerate, onGenerateGif, refUrl, store, onResult, activeTier, t]);
|
|
449
|
+
}, [prompt, loading, blocked, isGif, gifMissingRef, transparent, onGenerate, onGenerateGif, refUrl, store, onResult, activeTier, t, api, brand]);
|
|
450
|
+
|
|
451
|
+
const cancelGenerate = useCallback(() => {
|
|
452
|
+
abortRef.current?.abort();
|
|
453
|
+
abortRef.current = null;
|
|
454
|
+
setLoading(false);
|
|
455
|
+
}, []);
|
|
429
456
|
|
|
430
457
|
const handleGenerate = useCallback(() => {
|
|
431
458
|
void runGenerate();
|
|
@@ -759,6 +786,15 @@ export function AiGeneratePanel({
|
|
|
759
786
|
) : null}
|
|
760
787
|
</span>
|
|
761
788
|
</button>
|
|
789
|
+
{loading ? (
|
|
790
|
+
<button
|
|
791
|
+
type="button"
|
|
792
|
+
onClick={cancelGenerate}
|
|
793
|
+
className="h-8 rounded-le-md border border-le-ink-200 text-xs font-le-medium text-le-ink-600 hover:bg-le-ink-50"
|
|
794
|
+
>
|
|
795
|
+
{t("detailPage.aiGenerate.cancel")}
|
|
796
|
+
</button>
|
|
797
|
+
) : null}
|
|
762
798
|
|
|
763
799
|
<AnnotationDialog
|
|
764
800
|
open={annotateOpen}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Copyright © 2026 주식회사레비오사에이아이. All rights reserved. See LICENSE.
|
|
1
2
|
"use client";
|
|
2
3
|
|
|
3
4
|
import {
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
Type,
|
|
19
20
|
Undo2,
|
|
20
21
|
} from "lucide-react";
|
|
22
|
+
import { useTranslation } from "react-i18next";
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* 그림으로 가리키는 편집 — 이미지·화면 위에 덧그리는 주석 캔버스.
|
|
@@ -119,7 +121,7 @@ function loadImage(src: string): Promise<HTMLImageElement> {
|
|
|
119
121
|
// 동일 출처 프록시를 타는 경우에도 캔버스 오염을 막으려면 명시해야 한다.
|
|
120
122
|
img.crossOrigin = "anonymous";
|
|
121
123
|
img.onload = () => resolve(img);
|
|
122
|
-
img.onerror = () => reject(new Error(
|
|
124
|
+
img.onerror = () => reject(new Error(`annotation image failed to load: ${src.slice(0, 64)}`));
|
|
123
125
|
img.src = src;
|
|
124
126
|
});
|
|
125
127
|
}
|
|
@@ -261,6 +263,10 @@ export const AnnotationCanvas = forwardRef<
|
|
|
261
263
|
{ imageUrl, maxEdge = 2048, onChange, labels },
|
|
262
264
|
ref,
|
|
263
265
|
) {
|
|
266
|
+
const { t } = useTranslation("branding");
|
|
267
|
+
// 라벨을 안 넘겨도 번역된 기본값이 나온다. 넘기면 그것이 이긴다.
|
|
268
|
+
const label = (key: Tool | "color" | "undo" | "redo" | "note") =>
|
|
269
|
+
labels?.[key] ?? t(`detailPage.annotate.tools.${key}`);
|
|
264
270
|
const wrapRef = useRef<HTMLDivElement>(null);
|
|
265
271
|
const imgRef = useRef<HTMLImageElement>(null);
|
|
266
272
|
const svgRef = useRef<SVGSVGElement>(null);
|
|
@@ -666,12 +672,12 @@ export const AnnotationCanvas = forwardRef<
|
|
|
666
672
|
const selectedBox = selected ? annotationBox(selected) : null;
|
|
667
673
|
|
|
668
674
|
const TOOLS: [Tool, string, React.ReactNode][] = [
|
|
669
|
-
["pen",
|
|
670
|
-
["rect",
|
|
671
|
-
["arrow",
|
|
672
|
-
["text",
|
|
673
|
-
["eraser",
|
|
674
|
-
["select",
|
|
675
|
+
["pen", label("pen"), <Pencil key="p" size={16} />],
|
|
676
|
+
["rect", label("rect"), <Square key="r" size={16} />],
|
|
677
|
+
["arrow", label("arrow"), <ArrowUpRight key="a" size={16} />],
|
|
678
|
+
["text", label("text"), <Type key="t" size={16} />],
|
|
679
|
+
["eraser", label("eraser"), <Eraser key="e" size={16} />],
|
|
680
|
+
["select", label("select"), <MousePointer2 key="s" size={16} />],
|
|
675
681
|
];
|
|
676
682
|
|
|
677
683
|
return (
|
|
@@ -765,7 +771,7 @@ export const AnnotationCanvas = forwardRef<
|
|
|
765
771
|
<input
|
|
766
772
|
autoFocus
|
|
767
773
|
value={textEditor.value}
|
|
768
|
-
placeholder={
|
|
774
|
+
placeholder={label("note")}
|
|
769
775
|
onChange={(e) =>
|
|
770
776
|
setTextEditor({ ...textEditor, value: e.target.value })
|
|
771
777
|
}
|
|
@@ -816,7 +822,7 @@ export const AnnotationCanvas = forwardRef<
|
|
|
816
822
|
<button
|
|
817
823
|
key={c}
|
|
818
824
|
type="button"
|
|
819
|
-
aria-label={`${
|
|
825
|
+
aria-label={`${label("color")} ${c}`}
|
|
820
826
|
aria-pressed={color === c}
|
|
821
827
|
onClick={() => setColor(c)}
|
|
822
828
|
className="flex h-8 w-6 items-center justify-center"
|
|
@@ -834,8 +840,8 @@ export const AnnotationCanvas = forwardRef<
|
|
|
834
840
|
<span className="mx-1 h-5 w-px bg-le-ink-200" />
|
|
835
841
|
<button
|
|
836
842
|
type="button"
|
|
837
|
-
title={
|
|
838
|
-
aria-label={
|
|
843
|
+
title={label("undo")}
|
|
844
|
+
aria-label={label("undo")}
|
|
839
845
|
disabled={!history.past.length}
|
|
840
846
|
onClick={undo}
|
|
841
847
|
className="flex h-8 w-8 items-center justify-center rounded-le-lg text-le-ink-500 transition-colors hover:bg-le-ink-100 hover:text-le-ink-900 disabled:cursor-not-allowed disabled:opacity-30"
|
|
@@ -844,8 +850,8 @@ export const AnnotationCanvas = forwardRef<
|
|
|
844
850
|
</button>
|
|
845
851
|
<button
|
|
846
852
|
type="button"
|
|
847
|
-
title={
|
|
848
|
-
aria-label={
|
|
853
|
+
title={label("redo")}
|
|
854
|
+
aria-label={label("redo")}
|
|
849
855
|
disabled={!history.future.length}
|
|
850
856
|
onClick={redo}
|
|
851
857
|
className="flex h-8 w-8 items-center justify-center rounded-le-lg text-le-ink-500 transition-colors hover:bg-le-ink-100 hover:text-le-ink-900 disabled:cursor-not-allowed disabled:opacity-30"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Copyright © 2026 주식회사레비오사에이아이. All rights reserved. See LICENSE.
|
|
1
2
|
"use client";
|
|
2
3
|
|
|
3
4
|
import { useEffect, useState } from "react";
|
|
@@ -10,6 +11,7 @@ import { SpecPromptEditPanel } from "./spec-prompt-edit-panel";
|
|
|
10
11
|
import type { SpecEditPrompting } from "./table-inspector";
|
|
11
12
|
import {
|
|
12
13
|
NumberField,
|
|
14
|
+
historyOf,
|
|
13
15
|
Section,
|
|
14
16
|
ToggleButton,
|
|
15
17
|
} from "./inspector-controls";
|
|
@@ -335,6 +337,7 @@ export const ChartInspector = observer(function ChartInspector({
|
|
|
335
337
|
{t("detailPage.chart.decimals")}
|
|
336
338
|
</span>
|
|
337
339
|
<NumberField
|
|
340
|
+
history={historyOf(store)}
|
|
338
341
|
value={spec.options.decimals}
|
|
339
342
|
min={0}
|
|
340
343
|
max={3}
|
|
@@ -388,6 +391,7 @@ export const ChartInspector = observer(function ChartInspector({
|
|
|
388
391
|
</button>
|
|
389
392
|
) : (
|
|
390
393
|
<NumberField
|
|
394
|
+
history={historyOf(store)}
|
|
391
395
|
value={spec.options.max}
|
|
392
396
|
min={1}
|
|
393
397
|
onChange={(max) => setOptions({ max })}
|
|
@@ -495,6 +499,7 @@ export const ChartInspector = observer(function ChartInspector({
|
|
|
495
499
|
{t("detailPage.chart.barSize")}
|
|
496
500
|
</span>
|
|
497
501
|
<NumberField
|
|
502
|
+
history={historyOf(store)}
|
|
498
503
|
value={spec.style.barSize}
|
|
499
504
|
min={1}
|
|
500
505
|
onChange={(barSize) => setStyle({ barSize })}
|
|
@@ -505,6 +510,7 @@ export const ChartInspector = observer(function ChartInspector({
|
|
|
505
510
|
{t("detailPage.chart.gap")}
|
|
506
511
|
</span>
|
|
507
512
|
<NumberField
|
|
513
|
+
history={historyOf(store)}
|
|
508
514
|
value={spec.style.gap}
|
|
509
515
|
min={0}
|
|
510
516
|
onChange={(gap) => setStyle({ gap })}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Copyright © 2026 주식회사레비오사에이아이. All rights reserved. See LICENSE.
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import { useState, type ReactNode } from "react";
|
|
5
|
+
import { useTranslation } from "react-i18next";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 지우기 전에 한 번 묻는 버튼.
|
|
9
|
+
*
|
|
10
|
+
* 서버 자산·화면처럼 되돌릴 수 없는(또는 되돌리기 번거로운) 것을 한 번 눌러 지우면
|
|
11
|
+
* 휴지통 아이콘을 스치기만 해도 사라진다. `window.confirm` 은 쓰지 않는다 — 앱 크롬
|
|
12
|
+
* 밖의 OS 대화상자가 뜨고, 호스트에 확인 대화상자 계약도 없다. 대신 그 자리에서
|
|
13
|
+
* «삭제? 확인 / 취소» 로 바뀐다.
|
|
14
|
+
*/
|
|
15
|
+
export function ConfirmDeleteButton({
|
|
16
|
+
label,
|
|
17
|
+
onConfirm,
|
|
18
|
+
disabled,
|
|
19
|
+
className,
|
|
20
|
+
confirmClassName = "",
|
|
21
|
+
children,
|
|
22
|
+
}: {
|
|
23
|
+
/** 휴지통 버튼의 이름(aria-label·title). */
|
|
24
|
+
label: string;
|
|
25
|
+
onConfirm: () => void;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
className?: string;
|
|
28
|
+
/** 묻는 동안의 자리. 평소 버튼이 호버에만 보이는 자리라면 여기서 늘 보이게 한다. */
|
|
29
|
+
confirmClassName?: string;
|
|
30
|
+
children: ReactNode;
|
|
31
|
+
}) {
|
|
32
|
+
const { t } = useTranslation("branding");
|
|
33
|
+
const [asking, setAsking] = useState(false);
|
|
34
|
+
|
|
35
|
+
if (!asking) {
|
|
36
|
+
return (
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
aria-label={label}
|
|
40
|
+
title={label}
|
|
41
|
+
disabled={disabled}
|
|
42
|
+
onClick={(event) => {
|
|
43
|
+
event.stopPropagation();
|
|
44
|
+
setAsking(true);
|
|
45
|
+
}}
|
|
46
|
+
className={className}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</button>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<span
|
|
55
|
+
role="group"
|
|
56
|
+
aria-label={label}
|
|
57
|
+
// 줄·카드 자체의 클릭(선택·삽입)으로 새지 않게 막는다.
|
|
58
|
+
onClick={(event) => event.stopPropagation()}
|
|
59
|
+
className={`flex items-center gap-1 rounded-le-md border border-le-ink-200 bg-le-surface px-1.5 py-0.5 text-[11px] shadow-sm ${confirmClassName}`}
|
|
60
|
+
>
|
|
61
|
+
<span className="text-le-ink-600">{t("detailPage.confirmDelete.ask")}</span>
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
autoFocus
|
|
65
|
+
onClick={() => {
|
|
66
|
+
setAsking(false);
|
|
67
|
+
onConfirm();
|
|
68
|
+
}}
|
|
69
|
+
className="font-le-semibold text-le-danger-600 hover:underline"
|
|
70
|
+
>
|
|
71
|
+
{t("detailPage.confirmDelete.confirm")}
|
|
72
|
+
</button>
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
onClick={() => setAsking(false)}
|
|
76
|
+
className="text-le-ink-500 hover:underline"
|
|
77
|
+
>
|
|
78
|
+
{t("detailPage.confirmDelete.cancel")}
|
|
79
|
+
</button>
|
|
80
|
+
</span>
|
|
81
|
+
);
|
|
82
|
+
}
|