mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-02 22:32:27 +08:00
Compare commits
151 Commits
feat/langf
...
feat/mcp-x
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3c9408dff | ||
|
|
958f9410df | ||
|
|
b8126bd98a | ||
|
|
378bef435e | ||
|
|
f087b54ee4 | ||
|
|
6bb33eeda2 | ||
|
|
a91bd9d1e8 | ||
|
|
81eb71e704 | ||
|
|
58b6b19526 | ||
|
|
f65ef548b2 | ||
|
|
741a00db89 | ||
|
|
bcc6684ecb | ||
|
|
a9415d24e7 | ||
|
|
439bdd4577 | ||
|
|
98b890bb06 | ||
|
|
f039e4a3c8 | ||
|
|
7857858074 | ||
|
|
f0919117eb | ||
|
|
cd76fa615e | ||
|
|
c527ce1520 | ||
|
|
44840d27b3 | ||
|
|
f175276872 | ||
|
|
09c556e4c3 | ||
|
|
ac1c2ce044 | ||
|
|
78a77e102d | ||
|
|
55821301dd | ||
|
|
f743219c03 | ||
|
|
ff34f0baf1 | ||
|
|
0851b32b67 | ||
|
|
2e24071539 | ||
|
|
66bd0e5493 | ||
|
|
b33e09be05 | ||
|
|
987dc9f026 | ||
|
|
6024443816 | ||
|
|
4b838fd6d5 | ||
|
|
e321ba7959 | ||
|
|
aa15519fba | ||
|
|
c2c65973f9 | ||
|
|
b5db980f69 | ||
|
|
c9b60bfdb2 | ||
|
|
f170bb41ae | ||
|
|
a0f163fe9e | ||
|
|
8fd3830b9d | ||
|
|
77a25d2543 | ||
|
|
b9da24dd6d | ||
|
|
97cc0a07dc | ||
|
|
c42efdc702 | ||
|
|
dd027f1856 | ||
|
|
869391a029 | ||
|
|
8b9336466f | ||
|
|
ee514efa9e | ||
|
|
e2757a34b7 | ||
|
|
c0347dd55d | ||
|
|
a047a6ff97 | ||
|
|
d2ba133eaf | ||
|
|
43e5993f47 | ||
|
|
9a954ccb44 | ||
|
|
9d4c89ec43 | ||
|
|
5da4ef67ec | ||
|
|
67b0adf211 | ||
|
|
65af353852 | ||
|
|
b3deb65584 | ||
|
|
626f3a76b5 | ||
|
|
97bb350dd6 | ||
|
|
97ab82e027 | ||
|
|
77cb10393b | ||
|
|
967d63c57e | ||
|
|
914e914423 | ||
|
|
f6cfcab45a | ||
|
|
95c5a75ca3 | ||
|
|
ac09f9f8f9 | ||
|
|
622829b903 | ||
|
|
728dda5267 | ||
|
|
b68b1b0f33 | ||
|
|
bd23aed93b | ||
|
|
95aa4b8a56 | ||
|
|
4070772733 | ||
|
|
c4aaa7c915 | ||
|
|
ecea8a6005 | ||
|
|
ebc622144b | ||
|
|
ee9267d54c | ||
|
|
f6682fe3ac | ||
|
|
03db4c8096 | ||
|
|
167f5ed36a | ||
|
|
cd8e0e2263 | ||
|
|
8c431ee6ed | ||
|
|
86420a42c6 | ||
|
|
0baf21fadb | ||
|
|
a54068fec2 | ||
|
|
e25fd367d5 | ||
|
|
3264244fe9 | ||
|
|
d8cdd049d1 | ||
|
|
b1bc1a6dc6 | ||
|
|
8b578a456e | ||
|
|
05d58025c4 | ||
|
|
4be64317b3 | ||
|
|
2fac6323f0 | ||
|
|
a415c46b66 | ||
|
|
3894abd9ed | ||
|
|
6965a54f48 | ||
|
|
46567cb0b8 | ||
|
|
9f77199272 | ||
|
|
77f2569a3b | ||
|
|
cbb92bd636 | ||
|
|
8d898d8adc | ||
|
|
1e0b1ed970 | ||
|
|
1d03d10ba8 | ||
|
|
e893bd60f9 | ||
|
|
9aaf9bf31f | ||
|
|
150eb1ff63 | ||
|
|
215a101f54 | ||
|
|
e00938d9d3 | ||
|
|
dd27d034e2 | ||
|
|
9e781005af | ||
|
|
fe1aa2747e | ||
|
|
7205896c8c | ||
|
|
4e32a094b1 | ||
|
|
96a1111654 | ||
|
|
3f35c52527 | ||
|
|
0af5229477 | ||
|
|
3fb349fb3e | ||
|
|
ed29e32ba3 | ||
|
|
4cd78dc561 | ||
|
|
e0c5d966e3 | ||
|
|
33471d5b3a | ||
|
|
3ef9908df7 | ||
|
|
57bfc9cef7 | ||
|
|
0543f71c43 | ||
|
|
970b88612d | ||
|
|
c805277a76 | ||
|
|
95160f5a21 | ||
|
|
b206e16c02 | ||
|
|
563b18e8ff | ||
|
|
2366255e8f | ||
|
|
255308f829 | ||
|
|
a9493c8877 | ||
|
|
a0c3db100a | ||
|
|
ff6f130f8a | ||
|
|
562751c913 | ||
|
|
95e8a9c0c0 | ||
|
|
d9568562f0 | ||
|
|
7b8bd8c621 | ||
|
|
46cbc3354c | ||
|
|
46d2d4e078 | ||
|
|
d8f2c85dab | ||
|
|
5f4d31e708 | ||
|
|
489b377063 | ||
|
|
3534cb13f7 | ||
|
|
9d9613a8d1 | ||
|
|
bed04c82f8 | ||
|
|
fa1b02ad78 |
@@ -1,6 +1,3 @@
|
||||
{
|
||||
"extends": [
|
||||
"next/core-web-vitals",
|
||||
"next/typescript"
|
||||
]
|
||||
"extends": ["next/core-web-vitals", "next/typescript"]
|
||||
}
|
||||
|
||||
35
.github/CONTRIBUTING.md
vendored
Normal file
35
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# Contributing
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
git clone https://github.com/YOUR_USERNAME/next-ai-draw-io.git
|
||||
cd next-ai-draw-io
|
||||
npm install
|
||||
cp env.example .env.local
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Code Style
|
||||
|
||||
We use [Biome](https://biomejs.dev/) for linting and formatting:
|
||||
|
||||
```bash
|
||||
npm run format # Format code
|
||||
npm run lint # Check lint errors
|
||||
npm run check # Run all checks (CI)
|
||||
```
|
||||
|
||||
Pre-commit hooks via Husky will run Biome automatically on staged files.
|
||||
|
||||
For a better experience, install the [Biome VS Code extension](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) for real-time linting and format-on-save.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
1. Create a feature branch
|
||||
2. Make changes and ensure `npm run check` passes
|
||||
3. Submit PR against `main` with a clear description
|
||||
|
||||
## Issues
|
||||
|
||||
Include steps to reproduce, expected vs actual behavior, and AI provider used.
|
||||
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug to help us improve
|
||||
title: '[Bug] '
|
||||
labels: bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
> **Note**: This template is just a guide. Feel free to ignore the format entirely - any feedback is welcome! Don't let the template stop you from sharing your thoughts.
|
||||
|
||||
## Bug Description
|
||||
A brief description of the issue.
|
||||
|
||||
## Steps to Reproduce
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll to '...'
|
||||
4. See error
|
||||
|
||||
## Expected Behavior
|
||||
What you expected to happen.
|
||||
|
||||
## Actual Behavior
|
||||
What actually happened.
|
||||
|
||||
## Screenshots
|
||||
If applicable, add screenshots to help explain the problem.
|
||||
|
||||
## Environment
|
||||
- OS: [e.g. Windows 11, macOS 14]
|
||||
- Browser: [e.g. Chrome 120, Safari 17]
|
||||
- Version: [e.g. 1.0.0]
|
||||
|
||||
## Additional Context
|
||||
Any other information about the problem.
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Discussions
|
||||
url: https://github.com/DayuanJiang/next-ai-draw-io/discussions
|
||||
about: Have questions or ideas? Feel free to start a discussion
|
||||
25
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest a new feature for this project
|
||||
title: '[Feature] '
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
> **Note**: This template is just a guide. Feel free to ignore the format entirely - any feedback is welcome! Don't let the template stop you from sharing your ideas.
|
||||
|
||||
## Feature Description
|
||||
A brief description of the feature you'd like.
|
||||
|
||||
## Problem Context
|
||||
Is this related to a problem? Please describe.
|
||||
e.g. I'm always frustrated when [...]
|
||||
|
||||
## Proposed Solution
|
||||
How you'd like this feature to work.
|
||||
|
||||
## Alternatives Considered
|
||||
Any alternative solutions or features you've considered.
|
||||
|
||||
## Additional Context
|
||||
Any other information or screenshots about the feature request.
|
||||
47
.github/workflows/auto-format.yml
vendored
Normal file
47
.github/workflows/auto-format.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Auto Format
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install Biome
|
||||
run: npm install --save-dev @biomejs/biome
|
||||
|
||||
- name: Run Biome format
|
||||
run: npx @biomejs/biome check --write --no-errors-on-unmatched .
|
||||
|
||||
- name: Check for changes
|
||||
id: changes
|
||||
run: |
|
||||
if git diff --quiet; then
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Commit changes
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add .
|
||||
git commit -m "style: auto-format with Biome"
|
||||
git push
|
||||
24
.github/workflows/docker-build.yml
vendored
24
.github/workflows/docker-build.yml
vendored
@@ -64,3 +64,27 @@ jobs:
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
# Push to AWS ECR for App Runner auto-deploy
|
||||
- name: Configure AWS credentials
|
||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ap-northeast-1
|
||||
|
||||
- name: Login to Amazon ECR
|
||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@v2
|
||||
|
||||
- name: Push to ECR (triggers App Runner auto-deploy)
|
||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
||||
env:
|
||||
REPO_LOWER: ${{ github.repository }}
|
||||
run: |
|
||||
REPO_LOWER=$(echo "$REPO_LOWER" | tr '[:upper:]' '[:lower:]')
|
||||
docker pull ghcr.io/${REPO_LOWER}:latest
|
||||
docker tag ghcr.io/${REPO_LOWER}:latest ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.ap-northeast-1.amazonaws.com/next-ai-draw-io:latest
|
||||
docker push ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.ap-northeast-1.amazonaws.com/next-ai-draw-io:latest
|
||||
|
||||
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -2,6 +2,8 @@
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
packages/*/node_modules
|
||||
packages/*/dist
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
@@ -40,4 +42,11 @@ yarn-error.log*
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
push-via-ec2.sh
|
||||
.claude/settings.local.json
|
||||
.claude/
|
||||
.playwright-mcp/
|
||||
# Cloudflare
|
||||
.dev.vars
|
||||
.open-next/
|
||||
.wrangler/
|
||||
.env*.local
|
||||
|
||||
|
||||
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
@@ -0,0 +1 @@
|
||||
npx lint-staged
|
||||
23
.vscode/settings.json
vendored
Normal file
23
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.biome": "explicit",
|
||||
"source.organizeImports.biome": "explicit"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,10 @@ COPY . .
|
||||
# Disable Next.js telemetry during build
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Build-time argument for self-hosted draw.io URL
|
||||
ARG NEXT_PUBLIC_DRAWIO_BASE_URL=https://embed.diagrams.net
|
||||
ENV NEXT_PUBLIC_DRAWIO_BASE_URL=${NEXT_PUBLIC_DRAWIO_BASE_URL}
|
||||
|
||||
# Build Next.js application (standalone mode)
|
||||
RUN npm run build
|
||||
|
||||
@@ -50,6 +54,6 @@ EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
# Start the application
|
||||
CMD ["node", "server.js"]
|
||||
# Start the application (HOSTNAME override needed for AWS App Runner)
|
||||
CMD ["sh", "-c", "HOSTNAME=0.0.0.0 exec node server.js"]
|
||||
|
||||
|
||||
190
LICENSE
Normal file
190
LICENSE
Normal file
@@ -0,0 +1,190 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2024 Dayuan Jiang
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
162
README.md
162
README.md
@@ -4,31 +4,45 @@
|
||||
|
||||
**AI-Powered Diagram Creation Tool - Chat, Draw, Visualize**
|
||||
|
||||
English | [中文](./README_CN.md) | [日本語](./README_JA.md)
|
||||
English | [中文](./docs/README_CN.md) | [日本語](./docs/README_JA.md)
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://nextjs.org/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://next-ai-drawio.jiang.jp/)
|
||||
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://nextjs.org/)
|
||||
[](https://react.dev/)
|
||||
[](https://github.com/sponsors/DayuanJiang)
|
||||
|
||||
[🚀 Live Demo](https://next-ai-drawio.jiang.jp/)
|
||||
[](https://next-ai-drawio.jiang.jp/)
|
||||
|
||||
</div>
|
||||
|
||||
A Next.js web application that integrates AI capabilities with draw.io diagrams. Create, modify, and enhance diagrams through natural language commands and AI-assisted visualization.
|
||||
|
||||
https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
|
||||
|
||||
## Features
|
||||
|
||||
- **LLM-Powered Diagram Creation**: Leverage Large Language Models to create and manipulate draw.io diagrams directly through natural language commands
|
||||
- **Image-Based Diagram Replication**: Upload existing diagrams or images and have the AI replicate and enhance them automatically
|
||||
- **Diagram History**: Comprehensive version control that tracks all changes, allowing you to view and restore previous versions of your diagrams before the AI editing.
|
||||
- **Interactive Chat Interface**: Communicate with AI to refine your diagrams in real-time
|
||||
- **AWS Architecture Diagram Support**: Specialized support for generating AWS architecture diagrams
|
||||
- **Animated Connectors**: Create dynamic and animated connectors between diagram elements for better visualization
|
||||
https://github.com/user-attachments/assets/9d60a3e8-4a1c-4b5e-acbb-26af2d3eabd1
|
||||
|
||||
## **Examples**
|
||||
|
||||
|
||||
## Table of Contents
|
||||
- [Next AI Draw.io ](#next-ai-drawio-)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Examples](#examples)
|
||||
- [Features](#features)
|
||||
- [MCP Server (Preview)](#mcp-server-preview)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Try it Online](#try-it-online)
|
||||
- [Run with Docker (Recommended)](#run-with-docker-recommended)
|
||||
- [Installation](#installation)
|
||||
- [Deployment](#deployment)
|
||||
- [Multi-Provider Support](#multi-provider-support)
|
||||
- [How It Works](#how-it-works)
|
||||
- [Project Structure](#project-structure)
|
||||
- [Support \& Contact](#support--contact)
|
||||
- [Star History](#star-history)
|
||||
|
||||
## Examples
|
||||
|
||||
Here are some example prompts and their generated diagrams:
|
||||
|
||||
@@ -68,31 +82,59 @@ Here are some example prompts and their generated diagrams:
|
||||
</table>
|
||||
</div>
|
||||
|
||||
## How It Works
|
||||
## Features
|
||||
|
||||
The application uses the following technologies:
|
||||
- **LLM-Powered Diagram Creation**: Leverage Large Language Models to create and manipulate draw.io diagrams directly through natural language commands
|
||||
- **Image-Based Diagram Replication**: Upload existing diagrams or images and have the AI replicate and enhance them automatically
|
||||
- **PDF & Text File Upload**: Upload PDF documents and text files to extract content and generate diagrams from existing documents
|
||||
- **AI Reasoning Display**: View the AI's thinking process for supported models (OpenAI o1/o3, Gemini, Claude, etc.)
|
||||
- **Diagram History**: Comprehensive version control that tracks all changes, allowing you to view and restore previous versions of your diagrams before the AI editing.
|
||||
- **Interactive Chat Interface**: Communicate with AI to refine your diagrams in real-time
|
||||
- **Cloud Architecture Diagram Support**: Specialized support for generating cloud architecture diagrams (AWS, GCP, Azure)
|
||||
- **Animated Connectors**: Create dynamic and animated connectors between diagram elements for better visualization
|
||||
|
||||
- **Next.js**: For the frontend framework and routing
|
||||
- **Vercel AI SDK** (`ai` + `@ai-sdk/*`): For streaming AI responses and multi-provider support
|
||||
- **react-drawio**: For diagram representation and manipulation
|
||||
## MCP Server (Preview)
|
||||
|
||||
Diagrams are represented as XML that can be rendered in draw.io. The AI processes your commands and generates or modifies this XML accordingly.
|
||||
> **Preview Feature**: This feature is experimental and may not stable.
|
||||
|
||||
## Multi-Provider Support
|
||||
Use Next AI Draw.io with AI agents like Claude Desktop, Cursor, and VS Code via MCP (Model Context Protocol).
|
||||
|
||||
- AWS Bedrock (default)
|
||||
- OpenAI / OpenAI-compatible APIs (via `OPENAI_BASE_URL`)
|
||||
- Anthropic
|
||||
- Google AI
|
||||
- Azure OpenAI
|
||||
- Ollama
|
||||
- OpenRouter
|
||||
- DeepSeek
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"drawio": {
|
||||
"command": "npx",
|
||||
"args": ["@next-ai-drawio/mcp-server@latest"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that `claude-sonnet-4-5` has trained on draw.io diagrams with AWS logos, so if you want to create AWS architecture diagrams, this is the best choice.
|
||||
### Claude Code CLI
|
||||
|
||||
```bash
|
||||
claude mcp add drawio -- npx @next-ai-drawio/mcp-server@latest
|
||||
```
|
||||
|
||||
Then ask Claude to create diagrams:
|
||||
> "Create a flowchart showing user authentication with login, MFA, and session management"
|
||||
|
||||
The diagram appears in your browser in real-time!
|
||||
|
||||
See the [MCP Server README](./packages/mcp-server/README.md) for VS Code, Cursor, and other client configurations.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Try it Online
|
||||
|
||||
No installation needed! Try the app directly on our demo site:
|
||||
|
||||
[](https://next-ai-drawio.jiang.jp/)
|
||||
|
||||
> Note: Due to high traffic, the demo site currently uses minimax-m2. For best results, we recommend self-hosting with Claude Sonnet 4.5 or Claude Opus 4.5.
|
||||
|
||||
> **Bring Your Own API Key**: You can use your own API key to bypass usage limits on the demo site. Click the Settings icon in the chat panel to configure your provider and API key. Your key is stored locally in your browser and is never stored on the server.
|
||||
|
||||
### Run with Docker (Recommended)
|
||||
|
||||
If you just want to run it locally, the best way is to use Docker.
|
||||
@@ -109,10 +151,20 @@ docker run -d -p 3000:3000 \
|
||||
ghcr.io/dayuanjiang/next-ai-draw-io:latest
|
||||
```
|
||||
|
||||
Or use an env file:
|
||||
|
||||
```bash
|
||||
cp env.example .env
|
||||
# Edit .env with your configuration
|
||||
docker run -d -p 3000:3000 --env-file .env ghcr.io/dayuanjiang/next-ai-draw-io:latest
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) in your browser.
|
||||
|
||||
Replace the environment variables with your preferred AI provider configuration. See [Multi-Provider Support](#multi-provider-support) for available options.
|
||||
|
||||
> **Offline Deployment:** If `embed.diagrams.net` is blocked, see [Offline Deployment](./docs/offline-deployment.md) for configuration options.
|
||||
|
||||
### Installation
|
||||
|
||||
1. Clone the repository:
|
||||
@@ -126,8 +178,6 @@ cd next-ai-draw-io
|
||||
|
||||
```bash
|
||||
npm install
|
||||
# or
|
||||
yarn install
|
||||
```
|
||||
|
||||
3. Configure your AI provider:
|
||||
@@ -140,11 +190,15 @@ cp env.example .env.local
|
||||
|
||||
Edit `.env.local` and configure your chosen provider:
|
||||
|
||||
- Set `AI_PROVIDER` to your chosen provider (bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek)
|
||||
- Set `AI_PROVIDER` to your chosen provider (bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow)
|
||||
- Set `AI_MODEL` to the specific model you want to use
|
||||
- Add the required API keys for your provider
|
||||
- `TEMPERATURE`: Optional temperature setting (e.g., `0` for deterministic output). Leave unset for models that don't support it (e.g., reasoning models).
|
||||
- `ACCESS_CODE_LIST`: Optional access password(s), can be comma-separated for multiple passwords.
|
||||
|
||||
See the [Multi-Provider Support](#multi-provider-support) section above for provider-specific configuration examples.
|
||||
> Warning: If you do not set `ACCESS_CODE_LIST`, anyone can access your deployed site directly, which may lead to rapid depletion of your token. It is recommended to set this option.
|
||||
|
||||
See the [Provider Configuration Guide](./docs/ai-providers.md) for detailed setup instructions for each provider.
|
||||
|
||||
4. Run the development server:
|
||||
|
||||
@@ -165,6 +219,38 @@ Or you can deploy by this button.
|
||||
|
||||
Be sure to **set the environment variables** in the Vercel dashboard as you did in your local `.env.local` file.
|
||||
|
||||
|
||||
## Multi-Provider Support
|
||||
|
||||
- AWS Bedrock (default)
|
||||
- OpenAI
|
||||
- Anthropic
|
||||
- Google AI
|
||||
- Azure OpenAI
|
||||
- Ollama
|
||||
- OpenRouter
|
||||
- DeepSeek
|
||||
- SiliconFlow
|
||||
|
||||
All providers except AWS Bedrock and OpenRouter support custom endpoints.
|
||||
|
||||
📖 **[Detailed Provider Configuration Guide](./docs/ai-providers.md)** - See setup instructions for each provider.
|
||||
|
||||
**Model Requirements**: This task requires strong model capabilities for generating long-form text with strict formatting constraints (draw.io XML). Recommended models include Claude Sonnet 4.5, GPT-5.1, Gemini 3 Pro, and DeepSeek V3.2/R1.
|
||||
|
||||
Note that `claude` series has trained on draw.io diagrams with cloud architecture logos like AWS, Azure, GCP. So if you want to create cloud architecture diagrams, this is the best choice.
|
||||
|
||||
|
||||
## How It Works
|
||||
|
||||
The application uses the following technologies:
|
||||
|
||||
- **Next.js**: For the frontend framework and routing
|
||||
- **Vercel AI SDK** (`ai` + `@ai-sdk/*`): For streaming AI responses and multi-provider support
|
||||
- **react-drawio**: For diagram representation and manipulation
|
||||
|
||||
Diagrams are represented as XML that can be rendered in draw.io. The AI processes your commands and generates or modifies this XML accordingly.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
@@ -184,14 +270,6 @@ lib/ # Utility functions and helpers
|
||||
public/ # Static assets including example images
|
||||
```
|
||||
|
||||
## TODOs
|
||||
|
||||
- [x] Allow the LLM to modify the XML instead of generating it from scratch everytime.
|
||||
- [x] Improve the smoothness of shape streaming updates.
|
||||
- [x] Add multiple AI provider support (OpenAI, Anthropic, Google, Azure, Ollama)
|
||||
- [x] Solve the bug that generation will fail for session that longer than 60s.
|
||||
- [ ] Add API config on the UI.
|
||||
|
||||
## Support & Contact
|
||||
|
||||
If you find this project useful, please consider [sponsoring](https://github.com/sponsors/DayuanJiang) to help me host the live demo site!
|
||||
|
||||
457
app/[lang]/about/cn/page.tsx
Normal file
457
app/[lang]/about/cn/page.tsx
Normal file
@@ -0,0 +1,457 @@
|
||||
import type { Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { FaGithub } from "react-icons/fa"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "关于 - Next AI Draw.io",
|
||||
description:
|
||||
"AI驱动的图表创建工具 - 对话、绘制、可视化。使用自然语言创建AWS、GCP和Azure架构图。",
|
||||
keywords: ["AI图表", "draw.io", "AWS架构", "GCP图表", "Azure图表", "LLM"],
|
||||
}
|
||||
|
||||
function formatNumber(num: number): string {
|
||||
if (num >= 1000) {
|
||||
return `${num / 1000}k`
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
export default function AboutCN() {
|
||||
const dailyRequestLimit = Number(process.env.DAILY_REQUEST_LIMIT) || 20
|
||||
const dailyTokenLimit = Number(process.env.DAILY_TOKEN_LIMIT) || 500000
|
||||
const tpmLimit = Number(process.env.TPM_LIMIT) || 50000
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Navigation */}
|
||||
<header className="bg-white border-b border-gray-200">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Link
|
||||
href="/"
|
||||
className="text-xl font-bold text-gray-900 hover:text-gray-700"
|
||||
>
|
||||
Next AI Draw.io
|
||||
</Link>
|
||||
<nav className="flex items-center gap-6 text-sm">
|
||||
<Link
|
||||
href="/"
|
||||
className="text-gray-600 hover:text-gray-900 transition-colors"
|
||||
>
|
||||
编辑器
|
||||
</Link>
|
||||
<Link
|
||||
href="/about/cn"
|
||||
className="text-blue-600 font-semibold"
|
||||
>
|
||||
关于
|
||||
</Link>
|
||||
<a
|
||||
href="https://github.com/DayuanJiang/next-ai-draw-io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-600 hover:text-gray-900 transition-colors"
|
||||
aria-label="在GitHub上查看"
|
||||
>
|
||||
<FaGithub className="w-5 h-5" />
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<article className="prose prose-lg max-w-none">
|
||||
{/* Title */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-2">
|
||||
Next AI Draw.io
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 font-medium">
|
||||
AI驱动的图表创建工具 - 对话、绘制、可视化
|
||||
</p>
|
||||
<div className="flex justify-center gap-4 mt-4 text-sm">
|
||||
<Link
|
||||
href="/about"
|
||||
className="text-gray-600 hover:text-blue-600"
|
||||
>
|
||||
English
|
||||
</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link
|
||||
href="/about/cn"
|
||||
className="text-blue-600 font-semibold"
|
||||
>
|
||||
中文
|
||||
</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link
|
||||
href="/about/ja"
|
||||
className="text-gray-600 hover:text-blue-600"
|
||||
>
|
||||
日本語
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative mb-8 rounded-2xl bg-gradient-to-br from-amber-50 via-orange-50 to-rose-50 p-[1px] shadow-lg">
|
||||
<div className="absolute inset-0 rounded-2xl bg-gradient-to-br from-amber-400 via-orange-400 to-rose-400 opacity-20" />
|
||||
<div className="relative rounded-2xl bg-white/80 backdrop-blur-sm p-6">
|
||||
{/* Header */}
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-bold text-gray-900 tracking-tight">
|
||||
模型变更与用量限制{" "}
|
||||
<span className="text-sm text-amber-600 font-medium italic font-normal">
|
||||
(或者说:我的钱包顶不住了)
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Story */}
|
||||
<div className="space-y-3 text-sm text-gray-700 leading-relaxed mb-5">
|
||||
<p>
|
||||
大家对这个项目的热情太高了——看来大家都真的很喜欢画图!但这也带来了一个幸福的烦恼:我们经常触发出上游
|
||||
AI 接口的频率限制
|
||||
(TPS/TPM)。一旦超限,系统就会暂停,导致请求失败。
|
||||
</p>
|
||||
<p>
|
||||
由于使用量过高,我已将模型从 Claude 更换为{" "}
|
||||
<span className="font-semibold text-amber-700">
|
||||
minimax-m2
|
||||
</span>
|
||||
,以降低成本。
|
||||
</p>
|
||||
<p>
|
||||
作为一个
|
||||
<span className="font-semibold text-amber-700">
|
||||
独立开发者
|
||||
</span>
|
||||
,目前的 API
|
||||
费用全是我自己在掏腰包(纯属为爱发电)。为了保证服务能细水长流,同时也为了避免我个人陷入财务危机,我还设置了以下临时用量限制:
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Limits Cards */}
|
||||
<div className="grid grid-cols-2 gap-3 mb-5">
|
||||
<div className="rounded-xl bg-gradient-to-br from-amber-100 to-orange-100 p-4 text-center">
|
||||
<div className="text-xs font-medium text-amber-700 uppercase tracking-wide mb-1">
|
||||
Token 用量
|
||||
</div>
|
||||
<div className="text-lg font-bold text-gray-900">
|
||||
{formatNumber(tpmLimit)}
|
||||
<span className="text-sm font-normal text-gray-600">
|
||||
/分钟
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-lg font-bold text-gray-900">
|
||||
{formatNumber(dailyTokenLimit)}
|
||||
<span className="text-sm font-normal text-gray-600">
|
||||
/天
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-xl bg-gradient-to-br from-amber-100 to-orange-100 p-4 text-center">
|
||||
<div className="text-xs font-medium text-amber-700 uppercase tracking-wide mb-1">
|
||||
每日请求数
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-gray-900">
|
||||
{dailyRequestLimit}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
次
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="flex items-center gap-3 my-5">
|
||||
<div className="flex-1 h-px bg-gradient-to-r from-transparent via-amber-300 to-transparent" />
|
||||
</div>
|
||||
|
||||
{/* Bring Your Own Key */}
|
||||
<div className="text-center mb-5">
|
||||
<h4 className="text-base font-bold text-gray-900 mb-2">
|
||||
使用自己的 API Key
|
||||
</h4>
|
||||
<p className="text-sm text-gray-600 mb-2 max-w-md mx-auto">
|
||||
您可以使用自己的 API Key
|
||||
来绕过这些限制。点击聊天面板中的设置图标即可配置您的
|
||||
Provider 和 API Key。
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 max-w-md mx-auto">
|
||||
您的 Key
|
||||
仅保存在浏览器本地,不会被存储在服务器上。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<div className="flex-1 h-px bg-gradient-to-r from-transparent via-amber-300 to-transparent" />
|
||||
</div>
|
||||
|
||||
{/* Sponsorship CTA */}
|
||||
<div className="text-center">
|
||||
<h4 className="text-base font-bold text-gray-900 mb-2">
|
||||
寻求赞助 (求大佬捞一把)
|
||||
</h4>
|
||||
<p className="text-sm text-gray-600 mb-4 max-w-md mx-auto">
|
||||
要想彻底解除这些限制,扩容后端是唯一的办法。我正在积极寻求
|
||||
AI API 提供商或云平台的赞助。
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 mb-4 max-w-md mx-auto">
|
||||
作为回报(无论是额度支持还是资金支持),我将在
|
||||
GitHub 仓库和 Live Demo
|
||||
网站的显眼位置展示贵公司的 Logo
|
||||
作为平台赞助商。
|
||||
</p>
|
||||
<a
|
||||
href="mailto:me@jiang.jp"
|
||||
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-full bg-gradient-to-r from-amber-500 to-orange-500 text-white font-medium text-sm shadow-md hover:shadow-lg hover:scale-105 transition-all duration-200"
|
||||
>
|
||||
联系我
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-700">
|
||||
一个集成了AI功能的Next.js网页应用,与draw.io图表无缝结合。通过自然语言命令和AI辅助可视化来创建、修改和增强图表。
|
||||
</p>
|
||||
|
||||
{/* Features */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">
|
||||
功能特性
|
||||
</h2>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-2">
|
||||
<li>
|
||||
<strong>LLM驱动的图表创建</strong>
|
||||
:利用大语言模型通过自然语言命令直接创建和操作draw.io图表
|
||||
</li>
|
||||
<li>
|
||||
<strong>基于图像的图表复制</strong>
|
||||
:上传现有图表或图像,让AI自动复制和增强
|
||||
</li>
|
||||
<li>
|
||||
<strong>图表历史记录</strong>
|
||||
:全面的版本控制,跟踪所有更改,允许您查看和恢复AI编辑前的图表版本
|
||||
</li>
|
||||
<li>
|
||||
<strong>交互式聊天界面</strong>
|
||||
:与AI实时对话来完善您的图表
|
||||
</li>
|
||||
<li>
|
||||
<strong>AWS架构图支持</strong>
|
||||
:专门支持生成AWS架构图
|
||||
</li>
|
||||
<li>
|
||||
<strong>动画连接器</strong>
|
||||
:在图表元素之间创建动态动画连接器,实现更好的可视化效果
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{/* Examples */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">
|
||||
示例
|
||||
</h2>
|
||||
<p className="text-gray-700 mb-6">
|
||||
以下是一些示例提示词及其生成的图表:
|
||||
</p>
|
||||
|
||||
<div className="space-y-8">
|
||||
{/* Animated Transformer */}
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
动画Transformer连接器
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
<strong>提示词:</strong> 给我一个带有
|
||||
<strong>动画连接器</strong>的Transformer架构图。
|
||||
</p>
|
||||
<Image
|
||||
src="/animated_connectors.svg"
|
||||
alt="带动画连接器的Transformer架构"
|
||||
width={480}
|
||||
height={360}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Cloud Architecture Grid */}
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
GCP架构图
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>提示词:</strong> 使用
|
||||
<strong>GCP图标</strong>
|
||||
生成一个GCP架构图。用户连接到托管在实例上的前端。
|
||||
</p>
|
||||
<Image
|
||||
src="/gcp_demo.svg"
|
||||
alt="GCP架构图"
|
||||
width={400}
|
||||
height={300}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
AWS架构图
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>提示词:</strong> 使用
|
||||
<strong>AWS图标</strong>
|
||||
生成一个AWS架构图。用户连接到托管在实例上的前端。
|
||||
</p>
|
||||
<Image
|
||||
src="/aws_demo.svg"
|
||||
alt="AWS架构图"
|
||||
width={400}
|
||||
height={300}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
Azure架构图
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>提示词:</strong> 使用
|
||||
<strong>Azure图标</strong>
|
||||
生成一个Azure架构图。用户连接到托管在实例上的前端。
|
||||
</p>
|
||||
<Image
|
||||
src="/azure_demo.svg"
|
||||
alt="Azure架构图"
|
||||
width={400}
|
||||
height={300}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
猫咪素描
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>提示词:</strong>{" "}
|
||||
给我画一只可爱的猫。
|
||||
</p>
|
||||
<Image
|
||||
src="/cat_demo.svg"
|
||||
alt="猫咪绘图"
|
||||
width={240}
|
||||
height={240}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* How It Works */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">
|
||||
工作原理
|
||||
</h2>
|
||||
<p className="text-gray-700 mb-4">本应用使用以下技术:</p>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-2">
|
||||
<li>
|
||||
<strong>Next.js</strong>:用于前端框架和路由
|
||||
</li>
|
||||
<li>
|
||||
<strong>Vercel AI SDK</strong>(<code>ai</code> +{" "}
|
||||
<code>@ai-sdk/*</code>
|
||||
):用于流式AI响应和多提供商支持
|
||||
</li>
|
||||
<li>
|
||||
<strong>react-drawio</strong>:用于图表表示和操作
|
||||
</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mt-4">
|
||||
图表以XML格式表示,可在draw.io中渲染。AI处理您的命令并相应地生成或修改此XML。
|
||||
</p>
|
||||
|
||||
{/* Multi-Provider Support */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">
|
||||
多提供商支持
|
||||
</h2>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-1">
|
||||
<li>AWS Bedrock(默认)</li>
|
||||
<li>
|
||||
OpenAI / OpenAI兼容API(通过{" "}
|
||||
<code>OPENAI_BASE_URL</code>)
|
||||
</li>
|
||||
<li>Anthropic</li>
|
||||
<li>Google AI</li>
|
||||
<li>Azure OpenAI</li>
|
||||
<li>Ollama</li>
|
||||
<li>OpenRouter</li>
|
||||
<li>DeepSeek</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mt-4">
|
||||
注意:<code>claude-sonnet-4-5</code>{" "}
|
||||
已在带有AWS标志的draw.io图表上进行训练,因此如果您想创建AWS架构图,这是最佳选择。
|
||||
</p>
|
||||
|
||||
{/* Support */}
|
||||
<div className="flex items-center gap-4 mt-10 mb-4">
|
||||
<h2 className="text-2xl font-semibold text-gray-900">
|
||||
支持与联系
|
||||
</h2>
|
||||
<iframe
|
||||
src="https://github.com/sponsors/DayuanJiang/button"
|
||||
title="Sponsor DayuanJiang"
|
||||
height="32"
|
||||
width="114"
|
||||
style={{ border: 0, borderRadius: 6 }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-gray-700">
|
||||
如果您觉得这个项目有用,请考虑{" "}
|
||||
<a
|
||||
href="https://github.com/sponsors/DayuanJiang"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:underline"
|
||||
>
|
||||
赞助
|
||||
</a>{" "}
|
||||
来帮助托管在线演示站点!
|
||||
</p>
|
||||
<p className="text-gray-700 mt-2">
|
||||
如需支持或咨询,请在{" "}
|
||||
<a
|
||||
href="https://github.com/DayuanJiang/next-ai-draw-io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:underline"
|
||||
>
|
||||
GitHub仓库
|
||||
</a>{" "}
|
||||
上提交issue或联系:me[at]jiang.jp
|
||||
</p>
|
||||
|
||||
{/* CTA */}
|
||||
<div className="mt-12 text-center">
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-block bg-blue-600 text-white px-8 py-3 rounded-lg font-semibold hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
打开编辑器
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-white border-t border-gray-200 mt-16">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<p className="text-center text-gray-600 text-sm">
|
||||
Next AI Draw.io - 开源AI驱动的图表生成器
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
472
app/[lang]/about/ja/page.tsx
Normal file
472
app/[lang]/about/ja/page.tsx
Normal file
@@ -0,0 +1,472 @@
|
||||
import type { Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { FaGithub } from "react-icons/fa"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "概要 - Next AI Draw.io",
|
||||
description:
|
||||
"AI搭載のダイアグラム作成ツール - チャット、描画、可視化。自然言語でAWS、GCP、Azureアーキテクチャ図を作成。",
|
||||
keywords: [
|
||||
"AIダイアグラム",
|
||||
"draw.io",
|
||||
"AWSアーキテクチャ",
|
||||
"GCPダイアグラム",
|
||||
"Azureダイアグラム",
|
||||
"LLM",
|
||||
],
|
||||
}
|
||||
|
||||
function formatNumber(num: number): string {
|
||||
if (num >= 1000) {
|
||||
return `${num / 1000}k`
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
export default function AboutJA() {
|
||||
const dailyRequestLimit = Number(process.env.DAILY_REQUEST_LIMIT) || 20
|
||||
const dailyTokenLimit = Number(process.env.DAILY_TOKEN_LIMIT) || 500000
|
||||
const tpmLimit = Number(process.env.TPM_LIMIT) || 50000
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Navigation */}
|
||||
<header className="bg-white border-b border-gray-200">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Link
|
||||
href="/"
|
||||
className="text-xl font-bold text-gray-900 hover:text-gray-700"
|
||||
>
|
||||
Next AI Draw.io
|
||||
</Link>
|
||||
<nav className="flex items-center gap-6 text-sm">
|
||||
<Link
|
||||
href="/"
|
||||
className="text-gray-600 hover:text-gray-900 transition-colors"
|
||||
>
|
||||
エディタ
|
||||
</Link>
|
||||
<Link
|
||||
href="/about/ja"
|
||||
className="text-blue-600 font-semibold"
|
||||
>
|
||||
概要
|
||||
</Link>
|
||||
<a
|
||||
href="https://github.com/DayuanJiang/next-ai-draw-io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-600 hover:text-gray-900 transition-colors"
|
||||
aria-label="GitHubで見る"
|
||||
>
|
||||
<FaGithub className="w-5 h-5" />
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<article className="prose prose-lg max-w-none">
|
||||
{/* Title */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-2">
|
||||
Next AI Draw.io
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 font-medium">
|
||||
AI搭載のダイアグラム作成ツール -
|
||||
チャット、描画、可視化
|
||||
</p>
|
||||
<div className="flex justify-center gap-4 mt-4 text-sm">
|
||||
<Link
|
||||
href="/about"
|
||||
className="text-gray-600 hover:text-blue-600"
|
||||
>
|
||||
English
|
||||
</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link
|
||||
href="/about/cn"
|
||||
className="text-gray-600 hover:text-blue-600"
|
||||
>
|
||||
中文
|
||||
</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link
|
||||
href="/about/ja"
|
||||
className="text-blue-600 font-semibold"
|
||||
>
|
||||
日本語
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative mb-8 rounded-2xl bg-gradient-to-br from-amber-50 via-orange-50 to-rose-50 p-[1px] shadow-lg">
|
||||
<div className="absolute inset-0 rounded-2xl bg-gradient-to-br from-amber-400 via-orange-400 to-rose-400 opacity-20" />
|
||||
<div className="relative rounded-2xl bg-white/80 backdrop-blur-sm p-6">
|
||||
{/* Header */}
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-bold text-gray-900 tracking-tight">
|
||||
モデル変更と利用制限について{" "}
|
||||
<span className="text-sm text-amber-600 font-medium italic font-normal">
|
||||
(別名:お財布が悲鳴を上げています)
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Story */}
|
||||
<div className="space-y-3 text-sm text-gray-700 leading-relaxed mb-5">
|
||||
<p>
|
||||
予想以上の反響をいただき、ありがとうございます!皆様にダイアグラム作成を楽しんでいただいているのは嬉しい限りですが、その熱量により
|
||||
AI API のレート制限 (TPS/TPM)
|
||||
に頻繁に引っかかってしまっています。制限に達するとシステムが一時停止し、エラーが発生してしまいます。
|
||||
</p>
|
||||
<p>
|
||||
利用量の増加に伴い、コスト削減のためモデルを
|
||||
Claude から{" "}
|
||||
<span className="font-semibold text-amber-700">
|
||||
minimax-m2
|
||||
</span>{" "}
|
||||
に変更しました。
|
||||
</p>
|
||||
<p>
|
||||
私は現在、
|
||||
<span className="font-semibold text-amber-700">
|
||||
個人開発者
|
||||
</span>
|
||||
として API
|
||||
費用を全額自腹で負担しています。サービスを継続し、かつ私自身が借金を背負わないようにするため(笑)、一時的に以下の利用制限も設けさせていただきました。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Limits Cards */}
|
||||
<div className="grid grid-cols-2 gap-3 mb-5">
|
||||
<div className="rounded-xl bg-gradient-to-br from-amber-100 to-orange-100 p-4 text-center">
|
||||
<div className="text-xs font-medium text-amber-700 uppercase tracking-wide mb-1">
|
||||
トークン使用量
|
||||
</div>
|
||||
<div className="text-lg font-bold text-gray-900">
|
||||
{formatNumber(tpmLimit)}
|
||||
<span className="text-sm font-normal text-gray-600">
|
||||
/分
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-lg font-bold text-gray-900">
|
||||
{formatNumber(dailyTokenLimit)}
|
||||
<span className="text-sm font-normal text-gray-600">
|
||||
/日
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-xl bg-gradient-to-br from-amber-100 to-orange-100 p-4 text-center">
|
||||
<div className="text-xs font-medium text-amber-700 uppercase tracking-wide mb-1">
|
||||
1日のリクエスト数
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-gray-900">
|
||||
{dailyRequestLimit}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
回
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="flex items-center gap-3 my-5">
|
||||
<div className="flex-1 h-px bg-gradient-to-r from-transparent via-amber-300 to-transparent" />
|
||||
</div>
|
||||
|
||||
{/* Bring Your Own Key */}
|
||||
<div className="text-center mb-5">
|
||||
<h4 className="text-base font-bold text-gray-900 mb-2">
|
||||
自分のAPIキーを使用
|
||||
</h4>
|
||||
<p className="text-sm text-gray-600 mb-2 max-w-md mx-auto">
|
||||
自分のAPIキーを使用することで、これらの制限を回避できます。チャットパネルの設定アイコンをクリックして、プロバイダーとAPIキーを設定してください。
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 max-w-md mx-auto">
|
||||
キーはブラウザのローカルに保存され、サーバーには保存されません。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<div className="flex-1 h-px bg-gradient-to-r from-transparent via-amber-300 to-transparent" />
|
||||
</div>
|
||||
|
||||
{/* Sponsorship CTA */}
|
||||
<div className="text-center">
|
||||
<h4 className="text-base font-bold text-gray-900 mb-2">
|
||||
スポンサー募集
|
||||
</h4>
|
||||
<p className="text-sm text-gray-600 mb-4 max-w-md mx-auto">
|
||||
これらの制限を取り払い、バックエンドをスケールさせるには皆様の支援が必要です。現在、AI
|
||||
API
|
||||
プロバイダー様やクラウドプラットフォーム様からのスポンサー支援を積極的に募集しています。
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 mb-4 max-w-md mx-auto">
|
||||
ご支援(クレジット提供や資金援助)をいただける場合、GitHub
|
||||
リポジトリおよびデモサイトにて、プラットフォームスポンサーとして貴社を大々的にご紹介させていただきます。
|
||||
</p>
|
||||
<a
|
||||
href="mailto:me@jiang.jp"
|
||||
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-full bg-gradient-to-r from-amber-500 to-orange-500 text-white font-medium text-sm shadow-md hover:shadow-lg hover:scale-105 transition-all duration-200"
|
||||
>
|
||||
お問い合わせ
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-700">
|
||||
AI機能とdraw.ioダイアグラムを統合したNext.jsウェブアプリケーションです。自然言語コマンドとAI支援の可視化により、ダイアグラムを作成、修正、強化できます。
|
||||
</p>
|
||||
|
||||
{/* Features */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">
|
||||
機能
|
||||
</h2>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-2">
|
||||
<li>
|
||||
<strong>LLM搭載のダイアグラム作成</strong>
|
||||
:大規模言語モデルを活用して、自然言語コマンドで直接draw.ioダイアグラムを作成・操作
|
||||
</li>
|
||||
<li>
|
||||
<strong>画像ベースのダイアグラム複製</strong>
|
||||
:既存のダイアグラムや画像をアップロードし、AIが自動的に複製・強化
|
||||
</li>
|
||||
<li>
|
||||
<strong>ダイアグラム履歴</strong>
|
||||
:すべての変更を追跡する包括的なバージョン管理。AI編集前のダイアグラムの以前のバージョンを表示・復元可能
|
||||
</li>
|
||||
<li>
|
||||
<strong>
|
||||
インタラクティブなチャットインターフェース
|
||||
</strong>
|
||||
:AIとリアルタイムでコミュニケーションしてダイアグラムを改善
|
||||
</li>
|
||||
<li>
|
||||
<strong>
|
||||
AWSアーキテクチャダイアグラムサポート
|
||||
</strong>
|
||||
:AWSアーキテクチャダイアグラムの生成を専門的にサポート
|
||||
</li>
|
||||
<li>
|
||||
<strong>アニメーションコネクタ</strong>
|
||||
:より良い可視化のためにダイアグラム要素間に動的でアニメーション化されたコネクタを作成
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{/* Examples */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">
|
||||
例
|
||||
</h2>
|
||||
<p className="text-gray-700 mb-6">
|
||||
以下はいくつかのプロンプト例と生成されたダイアグラムです:
|
||||
</p>
|
||||
|
||||
<div className="space-y-8">
|
||||
{/* Animated Transformer */}
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
アニメーションTransformerコネクタ
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
<strong>プロンプト:</strong>{" "}
|
||||
<strong>アニメーションコネクタ</strong>
|
||||
付きのTransformerアーキテクチャ図を作成してください。
|
||||
</p>
|
||||
<Image
|
||||
src="/animated_connectors.svg"
|
||||
alt="アニメーションコネクタ付きTransformerアーキテクチャ"
|
||||
width={480}
|
||||
height={360}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Cloud Architecture Grid */}
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
GCPアーキテクチャ図
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>プロンプト:</strong>{" "}
|
||||
<strong>GCPアイコン</strong>
|
||||
を使用してGCPアーキテクチャ図を生成してください。ユーザーがインスタンス上でホストされているフロントエンドに接続します。
|
||||
</p>
|
||||
<Image
|
||||
src="/gcp_demo.svg"
|
||||
alt="GCPアーキテクチャ図"
|
||||
width={400}
|
||||
height={300}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
AWSアーキテクチャ図
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>プロンプト:</strong>{" "}
|
||||
<strong>AWSアイコン</strong>
|
||||
を使用してAWSアーキテクチャ図を生成してください。ユーザーがインスタンス上でホストされているフロントエンドに接続します。
|
||||
</p>
|
||||
<Image
|
||||
src="/aws_demo.svg"
|
||||
alt="AWSアーキテクチャ図"
|
||||
width={400}
|
||||
height={300}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
Azureアーキテクチャ図
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>プロンプト:</strong>{" "}
|
||||
<strong>Azureアイコン</strong>
|
||||
を使用してAzureアーキテクチャ図を生成してください。ユーザーがインスタンス上でホストされているフロントエンドに接続します。
|
||||
</p>
|
||||
<Image
|
||||
src="/azure_demo.svg"
|
||||
alt="Azureアーキテクチャ図"
|
||||
width={400}
|
||||
height={300}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
猫のスケッチ
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>プロンプト:</strong>{" "}
|
||||
かわいい猫を描いてください。
|
||||
</p>
|
||||
<Image
|
||||
src="/cat_demo.svg"
|
||||
alt="猫の絵"
|
||||
width={240}
|
||||
height={240}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* How It Works */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">
|
||||
仕組み
|
||||
</h2>
|
||||
<p className="text-gray-700 mb-4">
|
||||
本アプリケーションは以下の技術を使用しています:
|
||||
</p>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-2">
|
||||
<li>
|
||||
<strong>Next.js</strong>
|
||||
:フロントエンドフレームワークとルーティング
|
||||
</li>
|
||||
<li>
|
||||
<strong>Vercel AI SDK</strong>(<code>ai</code> +{" "}
|
||||
<code>@ai-sdk/*</code>
|
||||
):ストリーミングAIレスポンスとマルチプロバイダーサポート
|
||||
</li>
|
||||
<li>
|
||||
<strong>react-drawio</strong>
|
||||
:ダイアグラムの表現と操作
|
||||
</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mt-4">
|
||||
ダイアグラムはdraw.ioでレンダリングできるXMLとして表現されます。AIがコマンドを処理し、それに応じてこのXMLを生成または変更します。
|
||||
</p>
|
||||
|
||||
{/* Multi-Provider Support */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">
|
||||
マルチプロバイダーサポート
|
||||
</h2>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-1">
|
||||
<li>AWS Bedrock(デフォルト)</li>
|
||||
<li>
|
||||
OpenAI / OpenAI互換API(<code>OPENAI_BASE_URL</code>
|
||||
経由)
|
||||
</li>
|
||||
<li>Anthropic</li>
|
||||
<li>Google AI</li>
|
||||
<li>Azure OpenAI</li>
|
||||
<li>Ollama</li>
|
||||
<li>OpenRouter</li>
|
||||
<li>DeepSeek</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mt-4">
|
||||
注:<code>claude-sonnet-4-5</code>
|
||||
はAWSロゴ付きのdraw.ioダイアグラムで学習されているため、AWSアーキテクチャダイアグラムを作成したい場合は最適な選択です。
|
||||
</p>
|
||||
|
||||
{/* Support */}
|
||||
<div className="flex items-center gap-4 mt-10 mb-4">
|
||||
<h2 className="text-2xl font-semibold text-gray-900">
|
||||
サポート&お問い合わせ
|
||||
</h2>
|
||||
<iframe
|
||||
src="https://github.com/sponsors/DayuanJiang/button"
|
||||
title="Sponsor DayuanJiang"
|
||||
height="32"
|
||||
width="114"
|
||||
style={{ border: 0, borderRadius: 6 }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-gray-700">
|
||||
このプロジェクトが役に立ったら、ライブデモサイトのホスティングを支援するために{" "}
|
||||
<a
|
||||
href="https://github.com/sponsors/DayuanJiang"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:underline"
|
||||
>
|
||||
スポンサー
|
||||
</a>{" "}
|
||||
をご検討ください!
|
||||
</p>
|
||||
<p className="text-gray-700 mt-2">
|
||||
サポートやお問い合わせについては、{" "}
|
||||
<a
|
||||
href="https://github.com/DayuanJiang/next-ai-draw-io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:underline"
|
||||
>
|
||||
GitHubリポジトリ
|
||||
</a>{" "}
|
||||
でissueを開くか、ご連絡ください:me[at]jiang.jp
|
||||
</p>
|
||||
|
||||
{/* CTA */}
|
||||
<div className="mt-12 text-center">
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-block bg-blue-600 text-white px-8 py-3 rounded-lg font-semibold hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
エディタを開く
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-white border-t border-gray-200 mt-16">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<p className="text-center text-gray-600 text-sm">
|
||||
Next AI Draw.io -
|
||||
オープンソースAI搭載ダイアグラムジェネレーター
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
500
app/[lang]/about/page.tsx
Normal file
500
app/[lang]/about/page.tsx
Normal file
@@ -0,0 +1,500 @@
|
||||
import type { Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { FaGithub } from "react-icons/fa"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "About - Next AI Draw.io",
|
||||
description:
|
||||
"AI-Powered Diagram Creation Tool - Chat, Draw, Visualize. Create AWS, GCP, and Azure architecture diagrams with natural language.",
|
||||
keywords: [
|
||||
"AI diagram",
|
||||
"draw.io",
|
||||
"AWS architecture",
|
||||
"GCP diagram",
|
||||
"Azure diagram",
|
||||
"LLM",
|
||||
],
|
||||
}
|
||||
|
||||
function formatNumber(num: number): string {
|
||||
if (num >= 1000) {
|
||||
return `${num / 1000}k`
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
export default function About() {
|
||||
const dailyRequestLimit = Number(process.env.DAILY_REQUEST_LIMIT) || 20
|
||||
const dailyTokenLimit = Number(process.env.DAILY_TOKEN_LIMIT) || 500000
|
||||
const tpmLimit = Number(process.env.TPM_LIMIT) || 50000
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Navigation */}
|
||||
<header className="bg-white border-b border-gray-200">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Link
|
||||
href="/"
|
||||
className="text-xl font-bold text-gray-900 hover:text-gray-700"
|
||||
>
|
||||
Next AI Draw.io
|
||||
</Link>
|
||||
<nav className="flex items-center gap-6 text-sm">
|
||||
<Link
|
||||
href="/"
|
||||
className="text-gray-600 hover:text-gray-900 transition-colors"
|
||||
>
|
||||
Editor
|
||||
</Link>
|
||||
<Link
|
||||
href="/about"
|
||||
className="text-blue-600 font-semibold"
|
||||
>
|
||||
About
|
||||
</Link>
|
||||
<a
|
||||
href="https://github.com/DayuanJiang/next-ai-draw-io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-600 hover:text-gray-900 transition-colors"
|
||||
aria-label="View on GitHub"
|
||||
>
|
||||
<FaGithub className="w-5 h-5" />
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<article className="prose prose-lg max-w-none">
|
||||
{/* Title */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-2">
|
||||
Next AI Draw.io
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 font-medium">
|
||||
AI-Powered Diagram Creation Tool - Chat, Draw,
|
||||
Visualize
|
||||
</p>
|
||||
<div className="flex justify-center gap-4 mt-4 text-sm">
|
||||
<Link
|
||||
href="/about"
|
||||
className="text-blue-600 font-semibold"
|
||||
>
|
||||
English
|
||||
</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link
|
||||
href="/about/cn"
|
||||
className="text-gray-600 hover:text-blue-600"
|
||||
>
|
||||
中文
|
||||
</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link
|
||||
href="/about/ja"
|
||||
className="text-gray-600 hover:text-blue-600"
|
||||
>
|
||||
日本語
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative mb-8 rounded-2xl bg-gradient-to-br from-amber-50 via-orange-50 to-rose-50 p-[1px] shadow-lg">
|
||||
<div className="absolute inset-0 rounded-2xl bg-gradient-to-br from-amber-400 via-orange-400 to-rose-400 opacity-20" />
|
||||
<div className="relative rounded-2xl bg-white/80 backdrop-blur-sm p-6">
|
||||
{/* Header */}
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-bold text-gray-900 tracking-tight">
|
||||
Model Change & Usage Limits{" "}
|
||||
<span className="text-sm text-amber-600 font-medium italic font-normal">
|
||||
(Or: Why My Wallet is Crying)
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Story */}
|
||||
<div className="space-y-3 text-sm text-gray-700 leading-relaxed mb-5">
|
||||
<p>
|
||||
The response to this project has been
|
||||
incredible—you all love making diagrams!
|
||||
However, this enthusiasm means we are
|
||||
frequently hitting the AI API rate limits
|
||||
(TPS/TPM). When this happens, the system
|
||||
pauses, leading to failed requests.
|
||||
</p>
|
||||
<p>
|
||||
Due to the high usage, I have changed the
|
||||
model from Claude to{" "}
|
||||
<span className="font-semibold text-amber-700">
|
||||
minimax-m2
|
||||
</span>
|
||||
, which is more cost-effective.
|
||||
</p>
|
||||
<p>
|
||||
As an{" "}
|
||||
<span className="font-semibold text-amber-700">
|
||||
indie developer
|
||||
</span>
|
||||
, I am currently footing the entire API
|
||||
bill. To keep the lights on and ensure the
|
||||
service remains available to everyone
|
||||
without sending me into debt, I have also
|
||||
implemented the following temporary caps:
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Limits Cards */}
|
||||
<div className="grid grid-cols-2 gap-3 mb-5">
|
||||
<div className="rounded-xl bg-gradient-to-br from-amber-100 to-orange-100 p-4 text-center">
|
||||
<div className="text-xs font-medium text-amber-700 uppercase tracking-wide mb-1">
|
||||
Token Usage
|
||||
</div>
|
||||
<div className="text-lg font-bold text-gray-900">
|
||||
{formatNumber(tpmLimit)}
|
||||
<span className="text-sm font-normal text-gray-600">
|
||||
/min
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-lg font-bold text-gray-900">
|
||||
{formatNumber(dailyTokenLimit)}
|
||||
<span className="text-sm font-normal text-gray-600">
|
||||
/day
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-xl bg-gradient-to-br from-amber-100 to-orange-100 p-4 text-center">
|
||||
<div className="text-xs font-medium text-amber-700 uppercase tracking-wide mb-1">
|
||||
Daily Requests
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-gray-900">
|
||||
{dailyRequestLimit}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
requests
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="flex items-center gap-3 my-5">
|
||||
<div className="flex-1 h-px bg-gradient-to-r from-transparent via-amber-300 to-transparent" />
|
||||
</div>
|
||||
|
||||
{/* Bring Your Own Key */}
|
||||
<div className="text-center mb-5">
|
||||
<h4 className="text-base font-bold text-gray-900 mb-2">
|
||||
Bring Your Own API Key
|
||||
</h4>
|
||||
<p className="text-sm text-gray-600 mb-2 max-w-md mx-auto">
|
||||
You can use your own API key to bypass these
|
||||
limits. Click the Settings icon in the chat
|
||||
panel to configure your provider and API
|
||||
key.
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 max-w-md mx-auto">
|
||||
Your key is stored locally in your browser
|
||||
and is never stored on the server.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<div className="flex-1 h-px bg-gradient-to-r from-transparent via-amber-300 to-transparent" />
|
||||
</div>
|
||||
|
||||
{/* Sponsorship CTA */}
|
||||
<div className="text-center">
|
||||
<h4 className="text-base font-bold text-gray-900 mb-2">
|
||||
Call for Sponsorship
|
||||
</h4>
|
||||
<p className="text-sm text-gray-600 mb-4 max-w-md mx-auto">
|
||||
Scaling the backend is the only way to
|
||||
remove these limits. I am actively seeking
|
||||
sponsorship from AI API providers or Cloud
|
||||
Platforms.
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 mb-4 max-w-md mx-auto">
|
||||
In return for support (credits or funding),
|
||||
I will prominently feature your company as a
|
||||
platform sponsor on both the GitHub
|
||||
repository and the live demo site.
|
||||
</p>
|
||||
<a
|
||||
href="mailto:me@jiang.jp"
|
||||
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-full bg-gradient-to-r from-amber-500 to-orange-500 text-white font-medium text-sm shadow-md hover:shadow-lg hover:scale-105 transition-all duration-200"
|
||||
>
|
||||
Contact Me
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-700">
|
||||
A Next.js web application that integrates AI
|
||||
capabilities with draw.io diagrams. Create, modify, and
|
||||
enhance diagrams through natural language commands and
|
||||
AI-assisted visualization.
|
||||
</p>
|
||||
|
||||
{/* Features */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">
|
||||
Features
|
||||
</h2>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-2">
|
||||
<li>
|
||||
<strong>LLM-Powered Diagram Creation</strong>:
|
||||
Leverage Large Language Models to create and
|
||||
manipulate draw.io diagrams directly through natural
|
||||
language commands
|
||||
</li>
|
||||
<li>
|
||||
<strong>Image-Based Diagram Replication</strong>:
|
||||
Upload existing diagrams or images and have the AI
|
||||
replicate and enhance them automatically
|
||||
</li>
|
||||
<li>
|
||||
<strong>Diagram History</strong>: Comprehensive
|
||||
version control that tracks all changes, allowing
|
||||
you to view and restore previous versions of your
|
||||
diagrams before the AI editing
|
||||
</li>
|
||||
<li>
|
||||
<strong>Interactive Chat Interface</strong>:
|
||||
Communicate with AI to refine your diagrams in
|
||||
real-time
|
||||
</li>
|
||||
<li>
|
||||
<strong>AWS Architecture Diagram Support</strong>:
|
||||
Specialized support for generating AWS architecture
|
||||
diagrams
|
||||
</li>
|
||||
<li>
|
||||
<strong>Animated Connectors</strong>: Create dynamic
|
||||
and animated connectors between diagram elements for
|
||||
better visualization
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{/* Examples */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">
|
||||
Examples
|
||||
</h2>
|
||||
<p className="text-gray-700 mb-6">
|
||||
Here are some example prompts and their generated
|
||||
diagrams:
|
||||
</p>
|
||||
|
||||
<div className="space-y-8">
|
||||
{/* Animated Transformer */}
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
Animated Transformer Connectors
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
<strong>Prompt:</strong> Give me an{" "}
|
||||
<strong>animated connector</strong> diagram of
|
||||
transformer's architecture.
|
||||
</p>
|
||||
<Image
|
||||
src="/animated_connectors.svg"
|
||||
alt="Transformer Architecture with Animated Connectors"
|
||||
width={480}
|
||||
height={360}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Cloud Architecture Grid */}
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
GCP Architecture Diagram
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>Prompt:</strong> Generate a GCP
|
||||
architecture diagram with{" "}
|
||||
<strong>GCP icons</strong>. Users connect to
|
||||
a frontend hosted on an instance.
|
||||
</p>
|
||||
<Image
|
||||
src="/gcp_demo.svg"
|
||||
alt="GCP Architecture Diagram"
|
||||
width={400}
|
||||
height={300}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
AWS Architecture Diagram
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>Prompt:</strong> Generate an AWS
|
||||
architecture diagram with{" "}
|
||||
<strong>AWS icons</strong>. Users connect to
|
||||
a frontend hosted on an instance.
|
||||
</p>
|
||||
<Image
|
||||
src="/aws_demo.svg"
|
||||
alt="AWS Architecture Diagram"
|
||||
width={400}
|
||||
height={300}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
Azure Architecture Diagram
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>Prompt:</strong> Generate an Azure
|
||||
architecture diagram with{" "}
|
||||
<strong>Azure icons</strong>. Users connect
|
||||
to a frontend hosted on an instance.
|
||||
</p>
|
||||
<Image
|
||||
src="/azure_demo.svg"
|
||||
alt="Azure Architecture Diagram"
|
||||
width={400}
|
||||
height={300}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
Cat Sketch
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>Prompt:</strong> Draw a cute cat for
|
||||
me.
|
||||
</p>
|
||||
<Image
|
||||
src="/cat_demo.svg"
|
||||
alt="Cat Drawing"
|
||||
width={240}
|
||||
height={240}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* How It Works */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">
|
||||
How It Works
|
||||
</h2>
|
||||
<p className="text-gray-700 mb-4">
|
||||
The application uses the following technologies:
|
||||
</p>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-2">
|
||||
<li>
|
||||
<strong>Next.js</strong>: For the frontend framework
|
||||
and routing
|
||||
</li>
|
||||
<li>
|
||||
<strong>Vercel AI SDK</strong> (<code>ai</code> +{" "}
|
||||
<code>@ai-sdk/*</code>): For streaming AI responses
|
||||
and multi-provider support
|
||||
</li>
|
||||
<li>
|
||||
<strong>react-drawio</strong>: For diagram
|
||||
representation and manipulation
|
||||
</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mt-4">
|
||||
Diagrams are represented as XML that can be rendered in
|
||||
draw.io. The AI processes your commands and generates or
|
||||
modifies this XML accordingly.
|
||||
</p>
|
||||
|
||||
{/* Multi-Provider Support */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">
|
||||
Multi-Provider Support
|
||||
</h2>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-1">
|
||||
<li>AWS Bedrock (default)</li>
|
||||
<li>
|
||||
OpenAI / OpenAI-compatible APIs (via{" "}
|
||||
<code>OPENAI_BASE_URL</code>)
|
||||
</li>
|
||||
<li>Anthropic</li>
|
||||
<li>Google AI</li>
|
||||
<li>Azure OpenAI</li>
|
||||
<li>Ollama</li>
|
||||
<li>OpenRouter</li>
|
||||
<li>DeepSeek</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mt-4">
|
||||
Note that <code>claude-sonnet-4-5</code> has trained on
|
||||
draw.io diagrams with AWS logos, so if you want to
|
||||
create AWS architecture diagrams, this is the best
|
||||
choice.
|
||||
</p>
|
||||
|
||||
{/* Support */}
|
||||
<div className="flex items-center gap-4 mt-10 mb-4">
|
||||
<h2 className="text-2xl font-semibold text-gray-900">
|
||||
Support & Contact
|
||||
</h2>
|
||||
<iframe
|
||||
src="https://github.com/sponsors/DayuanJiang/button"
|
||||
title="Sponsor DayuanJiang"
|
||||
height="32"
|
||||
width="114"
|
||||
style={{ border: 0, borderRadius: 6 }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-gray-700">
|
||||
If you find this project useful, please consider{" "}
|
||||
<a
|
||||
href="https://github.com/sponsors/DayuanJiang"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:underline"
|
||||
>
|
||||
sponsoring
|
||||
</a>{" "}
|
||||
to help host the live demo site!
|
||||
</p>
|
||||
<p className="text-gray-700 mt-2">
|
||||
For support or inquiries, please open an issue on the{" "}
|
||||
<a
|
||||
href="https://github.com/DayuanJiang/next-ai-draw-io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:underline"
|
||||
>
|
||||
GitHub repository
|
||||
</a>{" "}
|
||||
or contact: me[at]jiang.jp
|
||||
</p>
|
||||
|
||||
{/* CTA */}
|
||||
<div className="mt-12 text-center">
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-block bg-blue-600 text-white px-8 py-3 rounded-lg font-semibold hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Open Editor
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-white border-t border-gray-200 mt-16">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<p className="text-center text-gray-600 text-sm">
|
||||
Next AI Draw.io - Open Source AI-Powered Diagram
|
||||
Generator
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
172
app/[lang]/layout.tsx
Normal file
172
app/[lang]/layout.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
import { GoogleAnalytics } from "@next/third-parties/google"
|
||||
import type { Metadata, Viewport } from "next"
|
||||
import { JetBrains_Mono, Plus_Jakarta_Sans } from "next/font/google"
|
||||
import { notFound } from "next/navigation"
|
||||
import { DiagramProvider } from "@/contexts/diagram-context"
|
||||
import { DictionaryProvider } from "@/hooks/use-dictionary"
|
||||
import type { Locale } from "@/lib/i18n/config"
|
||||
import { i18n } from "@/lib/i18n/config"
|
||||
import { getDictionary, hasLocale } from "@/lib/i18n/dictionaries"
|
||||
|
||||
import "../globals.css"
|
||||
|
||||
const plusJakarta = Plus_Jakarta_Sans({
|
||||
variable: "--font-sans",
|
||||
subsets: ["latin"],
|
||||
weight: ["400", "500", "600", "700"],
|
||||
})
|
||||
|
||||
const jetbrainsMono = JetBrains_Mono({
|
||||
variable: "--font-mono",
|
||||
subsets: ["latin"],
|
||||
weight: ["400", "500"],
|
||||
})
|
||||
|
||||
export const viewport: Viewport = {
|
||||
width: "device-width",
|
||||
initialScale: 1,
|
||||
maximumScale: 1,
|
||||
userScalable: false,
|
||||
}
|
||||
|
||||
// Generate static params for all locales
|
||||
export async function generateStaticParams() {
|
||||
return i18n.locales.map((locale) => ({ lang: locale }))
|
||||
}
|
||||
|
||||
// Generate metadata per locale
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lang: string }>
|
||||
}): Promise<Metadata> {
|
||||
const { lang: rawLang } = await params
|
||||
const lang = (rawLang in { en: 1, zh: 1, ja: 1 } ? rawLang : "en") as Locale
|
||||
|
||||
// Default to English metadata
|
||||
const titles: Record<Locale, string> = {
|
||||
en: "Next AI Draw.io - AI-Powered Diagram Generator",
|
||||
zh: "Next AI Draw.io - AI powered diagram generator",
|
||||
ja: "Next AI Draw.io - AI-powered diagram generator",
|
||||
}
|
||||
|
||||
const descriptions: Record<Locale, string> = {
|
||||
en: "Create AWS architecture diagrams, flowcharts, and technical diagrams using AI. Free online tool integrating draw.io with AI assistance for professional diagram creation.",
|
||||
zh: "Use AI to create AWS architecture diagrams, flowcharts, and technical diagrams. Free online tool integrated with draw.io and AI assistance for professional diagram creation.",
|
||||
ja: "Create AWS architecture diagrams, flowcharts, and technical diagrams using AI. Create professional diagrams with a free online tool that integrates draw.io with an AI assistant.",
|
||||
}
|
||||
|
||||
return {
|
||||
title: titles[lang],
|
||||
description: descriptions[lang],
|
||||
keywords: [
|
||||
"AI diagram generator",
|
||||
"AWS architecture",
|
||||
"flowchart creator",
|
||||
"draw.io",
|
||||
"AI drawing tool",
|
||||
"technical diagrams",
|
||||
"diagram automation",
|
||||
"free diagram generator",
|
||||
"online diagram maker",
|
||||
],
|
||||
authors: [{ name: "Next AI Draw.io" }],
|
||||
creator: "Next AI Draw.io",
|
||||
publisher: "Next AI Draw.io",
|
||||
metadataBase: new URL("https://next-ai-drawio.jiang.jp"),
|
||||
openGraph: {
|
||||
title: titles[lang],
|
||||
description: descriptions[lang],
|
||||
type: "website",
|
||||
url: "https://next-ai-drawio.jiang.jp",
|
||||
siteName: "Next AI Draw.io",
|
||||
locale: lang === "zh" ? "zh_CN" : lang === "ja" ? "ja_JP" : "en_US",
|
||||
images: [
|
||||
{
|
||||
url: "/architecture.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "Next AI Draw.io - AI-powered diagram creation tool",
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: titles[lang],
|
||||
description: descriptions[lang],
|
||||
images: ["/architecture.png"],
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
"max-video-preview": -1,
|
||||
"max-image-preview": "large",
|
||||
"max-snippet": -1,
|
||||
},
|
||||
},
|
||||
icons: {
|
||||
icon: "/favicon.ico",
|
||||
},
|
||||
alternates: {
|
||||
languages: {
|
||||
en: "/en",
|
||||
zh: "/zh",
|
||||
ja: "/ja",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
params: Promise<{ lang: string }>
|
||||
}>) {
|
||||
const { lang } = await params
|
||||
if (!hasLocale(lang)) notFound()
|
||||
const validLang = lang as Locale
|
||||
const dictionary = await getDictionary(validLang)
|
||||
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
name: "Next AI Draw.io",
|
||||
applicationCategory: "DesignApplication",
|
||||
operatingSystem: "Web Browser",
|
||||
description:
|
||||
"AI-powered diagram generator with targeted XML editing capabilities that integrates with draw.io for creating AWS architecture diagrams, flowcharts, and technical diagrams. Features diagram history, multi-provider AI support, and real-time collaboration.",
|
||||
url: "https://next-ai-drawio.jiang.jp",
|
||||
inLanguage: validLang,
|
||||
offers: {
|
||||
"@type": "Offer",
|
||||
price: "0",
|
||||
priceCurrency: "USD",
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<html lang={validLang} suppressHydrationWarning>
|
||||
<head>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
</head>
|
||||
<body
|
||||
className={`${plusJakarta.variable} ${jetbrainsMono.variable} antialiased`}
|
||||
>
|
||||
<DictionaryProvider dictionary={dictionary}>
|
||||
<DiagramProvider>{children}</DiagramProvider>
|
||||
</DictionaryProvider>
|
||||
</body>
|
||||
{process.env.NEXT_PUBLIC_GA_ID && (
|
||||
<GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_ID} />
|
||||
)}
|
||||
</html>
|
||||
)
|
||||
}
|
||||
249
app/[lang]/page.tsx
Normal file
249
app/[lang]/page.tsx
Normal file
@@ -0,0 +1,249 @@
|
||||
"use client"
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import { DrawIoEmbed } from "react-drawio"
|
||||
import type { ImperativePanelHandle } from "react-resizable-panels"
|
||||
import ChatPanel from "@/components/chat-panel"
|
||||
import { STORAGE_CLOSE_PROTECTION_KEY } from "@/components/settings-dialog"
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/components/ui/resizable"
|
||||
import { useDiagram } from "@/contexts/diagram-context"
|
||||
|
||||
const drawioBaseUrl =
|
||||
process.env.NEXT_PUBLIC_DRAWIO_BASE_URL || "https://embed.diagrams.net"
|
||||
|
||||
export default function Home() {
|
||||
const {
|
||||
drawioRef,
|
||||
handleDiagramExport,
|
||||
onDrawioLoad,
|
||||
resetDrawioReady,
|
||||
saveDiagramToStorage,
|
||||
showSaveDialog,
|
||||
setShowSaveDialog,
|
||||
} = useDiagram()
|
||||
const [isMobile, setIsMobile] = useState(false)
|
||||
const [isChatVisible, setIsChatVisible] = useState(true)
|
||||
const [drawioUi, setDrawioUi] = useState<"min" | "sketch">("min")
|
||||
const [darkMode, setDarkMode] = useState(false)
|
||||
const [isLoaded, setIsLoaded] = useState(false)
|
||||
const [closeProtection, setCloseProtection] = useState(false)
|
||||
|
||||
const chatPanelRef = useRef<ImperativePanelHandle>(null)
|
||||
const isSavingRef = useRef(false)
|
||||
const mouseOverDrawioRef = useRef(false)
|
||||
const isMobileRef = useRef(false)
|
||||
|
||||
// Reset saving flag when dialog closes (with delay to ignore lingering save events from draw.io)
|
||||
useEffect(() => {
|
||||
if (!showSaveDialog) {
|
||||
const timeout = setTimeout(() => {
|
||||
isSavingRef.current = false
|
||||
}, 1000)
|
||||
return () => clearTimeout(timeout)
|
||||
}
|
||||
}, [showSaveDialog])
|
||||
|
||||
// Handle save from draw.io's built-in save button
|
||||
// Note: draw.io sends save events for various reasons (focus changes, etc.)
|
||||
// We use mouse position to determine if the user is interacting with draw.io
|
||||
const handleDrawioSave = useCallback(() => {
|
||||
if (!mouseOverDrawioRef.current) return
|
||||
if (isSavingRef.current) return
|
||||
isSavingRef.current = true
|
||||
setShowSaveDialog(true)
|
||||
}, [setShowSaveDialog])
|
||||
|
||||
// Load preferences from localStorage after mount
|
||||
useEffect(() => {
|
||||
const savedUi = localStorage.getItem("drawio-theme")
|
||||
if (savedUi === "min" || savedUi === "sketch") {
|
||||
setDrawioUi(savedUi)
|
||||
}
|
||||
|
||||
const savedDarkMode = localStorage.getItem("next-ai-draw-io-dark-mode")
|
||||
if (savedDarkMode !== null) {
|
||||
const isDark = savedDarkMode === "true"
|
||||
setDarkMode(isDark)
|
||||
document.documentElement.classList.toggle("dark", isDark)
|
||||
} else {
|
||||
const prefersDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)",
|
||||
).matches
|
||||
setDarkMode(prefersDark)
|
||||
document.documentElement.classList.toggle("dark", prefersDark)
|
||||
}
|
||||
|
||||
const savedCloseProtection = localStorage.getItem(
|
||||
STORAGE_CLOSE_PROTECTION_KEY,
|
||||
)
|
||||
if (savedCloseProtection === "true") {
|
||||
setCloseProtection(true)
|
||||
}
|
||||
|
||||
setIsLoaded(true)
|
||||
}, [])
|
||||
|
||||
const handleDarkModeChange = async () => {
|
||||
await saveDiagramToStorage()
|
||||
const newValue = !darkMode
|
||||
setDarkMode(newValue)
|
||||
localStorage.setItem("next-ai-draw-io-dark-mode", String(newValue))
|
||||
document.documentElement.classList.toggle("dark", newValue)
|
||||
resetDrawioReady()
|
||||
}
|
||||
|
||||
const handleDrawioUiChange = async () => {
|
||||
await saveDiagramToStorage()
|
||||
const newUi = drawioUi === "min" ? "sketch" : "min"
|
||||
localStorage.setItem("drawio-theme", newUi)
|
||||
setDrawioUi(newUi)
|
||||
resetDrawioReady()
|
||||
}
|
||||
|
||||
// Check mobile - save diagram and reset draw.io before crossing breakpoint
|
||||
const isInitialRenderRef = useRef(true)
|
||||
useEffect(() => {
|
||||
const checkMobile = () => {
|
||||
const newIsMobile = window.innerWidth < 768
|
||||
if (
|
||||
!isInitialRenderRef.current &&
|
||||
newIsMobile !== isMobileRef.current
|
||||
) {
|
||||
saveDiagramToStorage().catch(() => {})
|
||||
resetDrawioReady()
|
||||
}
|
||||
isMobileRef.current = newIsMobile
|
||||
isInitialRenderRef.current = false
|
||||
setIsMobile(newIsMobile)
|
||||
}
|
||||
|
||||
checkMobile()
|
||||
window.addEventListener("resize", checkMobile)
|
||||
return () => window.removeEventListener("resize", checkMobile)
|
||||
}, [saveDiagramToStorage, resetDrawioReady])
|
||||
|
||||
const toggleChatPanel = () => {
|
||||
const panel = chatPanelRef.current
|
||||
if (panel) {
|
||||
if (panel.isCollapsed()) {
|
||||
panel.expand()
|
||||
setIsChatVisible(true)
|
||||
} else {
|
||||
panel.collapse()
|
||||
setIsChatVisible(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard shortcut for toggling chat panel
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === "b") {
|
||||
event.preventDefault()
|
||||
toggleChatPanel()
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown)
|
||||
return () => window.removeEventListener("keydown", handleKeyDown)
|
||||
}, [])
|
||||
|
||||
// Show confirmation dialog when user tries to leave the page
|
||||
useEffect(() => {
|
||||
if (!closeProtection) return
|
||||
|
||||
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||
event.preventDefault()
|
||||
return ""
|
||||
}
|
||||
|
||||
window.addEventListener("beforeunload", handleBeforeUnload)
|
||||
return () =>
|
||||
window.removeEventListener("beforeunload", handleBeforeUnload)
|
||||
}, [closeProtection])
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-background relative overflow-hidden">
|
||||
<ResizablePanelGroup
|
||||
id="main-panel-group"
|
||||
direction={isMobile ? "vertical" : "horizontal"}
|
||||
className="h-full"
|
||||
>
|
||||
<ResizablePanel
|
||||
id="drawio-panel"
|
||||
defaultSize={isMobile ? 50 : 67}
|
||||
minSize={20}
|
||||
>
|
||||
<div
|
||||
className={`h-full relative ${
|
||||
isMobile ? "p-1" : "p-2"
|
||||
}`}
|
||||
onMouseEnter={() => {
|
||||
mouseOverDrawioRef.current = true
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
mouseOverDrawioRef.current = false
|
||||
}}
|
||||
>
|
||||
<div className="h-full rounded-xl overflow-hidden shadow-soft-lg border border-border/30">
|
||||
{isLoaded ? (
|
||||
<DrawIoEmbed
|
||||
key={`${drawioUi}-${darkMode}`}
|
||||
ref={drawioRef}
|
||||
onExport={handleDiagramExport}
|
||||
onLoad={onDrawioLoad}
|
||||
onSave={handleDrawioSave}
|
||||
baseUrl={drawioBaseUrl}
|
||||
urlParameters={{
|
||||
ui: drawioUi,
|
||||
spin: true,
|
||||
libraries: false,
|
||||
saveAndExit: false,
|
||||
noExitBtn: true,
|
||||
dark: darkMode,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full w-full flex items-center justify-center bg-background">
|
||||
<div className="animate-spin h-8 w-8 border-4 border-primary border-t-transparent rounded-full" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
|
||||
<ResizableHandle withHandle />
|
||||
|
||||
{/* Chat Panel */}
|
||||
<ResizablePanel
|
||||
key={isMobile ? "mobile" : "desktop"}
|
||||
id="chat-panel"
|
||||
ref={chatPanelRef}
|
||||
defaultSize={isMobile ? 50 : 33}
|
||||
minSize={isMobile ? 20 : 15}
|
||||
maxSize={isMobile ? 80 : 50}
|
||||
collapsible={!isMobile}
|
||||
collapsedSize={isMobile ? 0 : 3}
|
||||
onCollapse={() => setIsChatVisible(false)}
|
||||
onExpand={() => setIsChatVisible(true)}
|
||||
>
|
||||
<div className={`h-full ${isMobile ? "p-1" : "py-2 pr-2"}`}>
|
||||
<ChatPanel
|
||||
isVisible={isChatVisible}
|
||||
onToggleVisibility={toggleChatPanel}
|
||||
drawioUi={drawioUi}
|
||||
onToggleDrawioUi={handleDrawioUiChange}
|
||||
darkMode={darkMode}
|
||||
onToggleDarkMode={handleDarkModeChange}
|
||||
isMobile={isMobile}
|
||||
onCloseProtectionChange={setCloseProtection}
|
||||
/>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { FaGithub } from "react-icons/fa";
|
||||
import Image from "next/image";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "关于 - Next AI Draw.io",
|
||||
description: "AI驱动的图表创建工具 - 对话、绘制、可视化。使用自然语言创建AWS、GCP和Azure架构图。",
|
||||
keywords: ["AI图表", "draw.io", "AWS架构", "GCP图表", "Azure图表", "LLM"],
|
||||
};
|
||||
|
||||
export default function AboutCN() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Navigation */}
|
||||
<header className="bg-white border-b border-gray-200">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Link href="/" className="text-xl font-bold text-gray-900 hover:text-gray-700">
|
||||
Next AI Draw.io
|
||||
</Link>
|
||||
<nav className="flex items-center gap-6 text-sm">
|
||||
<Link href="/" className="text-gray-600 hover:text-gray-900 transition-colors">
|
||||
编辑器
|
||||
</Link>
|
||||
<Link href="/about/cn" className="text-blue-600 font-semibold">
|
||||
关于
|
||||
</Link>
|
||||
<a
|
||||
href="https://github.com/DayuanJiang/next-ai-draw-io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-600 hover:text-gray-900 transition-colors"
|
||||
aria-label="在GitHub上查看"
|
||||
>
|
||||
<FaGithub className="w-5 h-5" />
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<article className="prose prose-lg max-w-none">
|
||||
{/* Title */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-2">Next AI Draw.io</h1>
|
||||
<p className="text-xl text-gray-600 font-medium">
|
||||
AI驱动的图表创建工具 - 对话、绘制、可视化
|
||||
</p>
|
||||
<div className="flex justify-center gap-4 mt-4 text-sm">
|
||||
<Link href="/about" className="text-gray-600 hover:text-blue-600">English</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link href="/about/cn" className="text-blue-600 font-semibold">中文</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link href="/about/ja" className="text-gray-600 hover:text-blue-600">日本語</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 mb-6">
|
||||
<p className="text-amber-800">
|
||||
本应用设计运行于 Claude Opus 4.5 以获得最佳性能。但由于流量超出预期,运行顶级模型的成本变得难以承受。为避免服务中断并控制成本,我已将后端切换至 Claude Haiku 4.5。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-700">
|
||||
一个集成了AI功能的Next.js网页应用,与draw.io图表无缝结合。通过自然语言命令和AI辅助可视化来创建、修改和增强图表。
|
||||
</p>
|
||||
|
||||
{/* Features */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">功能特性</h2>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-2">
|
||||
<li><strong>LLM驱动的图表创建</strong>:利用大语言模型通过自然语言命令直接创建和操作draw.io图表</li>
|
||||
<li><strong>基于图像的图表复制</strong>:上传现有图表或图像,让AI自动复制和增强</li>
|
||||
<li><strong>图表历史记录</strong>:全面的版本控制,跟踪所有更改,允许您查看和恢复AI编辑前的图表版本</li>
|
||||
<li><strong>交互式聊天界面</strong>:与AI实时对话来完善您的图表</li>
|
||||
<li><strong>AWS架构图支持</strong>:专门支持生成AWS架构图</li>
|
||||
<li><strong>动画连接器</strong>:在图表元素之间创建动态动画连接器,实现更好的可视化效果</li>
|
||||
</ul>
|
||||
|
||||
{/* Examples */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">示例</h2>
|
||||
<p className="text-gray-700 mb-6">以下是一些示例提示词及其生成的图表:</p>
|
||||
|
||||
<div className="space-y-8">
|
||||
{/* Animated Transformer */}
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">动画Transformer连接器</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
<strong>提示词:</strong> 给我一个带有<strong>动画连接器</strong>的Transformer架构图。
|
||||
</p>
|
||||
<Image src="/animated_connectors.svg" alt="带动画连接器的Transformer架构" width={480} height={360} className="mx-auto" />
|
||||
</div>
|
||||
|
||||
{/* Cloud Architecture Grid */}
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">GCP架构图</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>提示词:</strong> 使用<strong>GCP图标</strong>生成一个GCP架构图。用户连接到托管在实例上的前端。
|
||||
</p>
|
||||
<Image src="/gcp_demo.svg" alt="GCP架构图" width={400} height={300} className="mx-auto" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">AWS架构图</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>提示词:</strong> 使用<strong>AWS图标</strong>生成一个AWS架构图。用户连接到托管在实例上的前端。
|
||||
</p>
|
||||
<Image src="/aws_demo.svg" alt="AWS架构图" width={400} height={300} className="mx-auto" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">Azure架构图</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>提示词:</strong> 使用<strong>Azure图标</strong>生成一个Azure架构图。用户连接到托管在实例上的前端。
|
||||
</p>
|
||||
<Image src="/azure_demo.svg" alt="Azure架构图" width={400} height={300} className="mx-auto" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">猫咪素描</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>提示词:</strong> 给我画一只可爱的猫。
|
||||
</p>
|
||||
<Image src="/cat_demo.svg" alt="猫咪绘图" width={240} height={240} className="mx-auto" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* How It Works */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">工作原理</h2>
|
||||
<p className="text-gray-700 mb-4">本应用使用以下技术:</p>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-2">
|
||||
<li><strong>Next.js</strong>:用于前端框架和路由</li>
|
||||
<li><strong>Vercel AI SDK</strong>(<code>ai</code> + <code>@ai-sdk/*</code>):用于流式AI响应和多提供商支持</li>
|
||||
<li><strong>react-drawio</strong>:用于图表表示和操作</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mt-4">
|
||||
图表以XML格式表示,可在draw.io中渲染。AI处理您的命令并相应地生成或修改此XML。
|
||||
</p>
|
||||
|
||||
{/* Multi-Provider Support */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">多提供商支持</h2>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-1">
|
||||
<li>AWS Bedrock(默认)</li>
|
||||
<li>OpenAI / OpenAI兼容API(通过 <code>OPENAI_BASE_URL</code>)</li>
|
||||
<li>Anthropic</li>
|
||||
<li>Google AI</li>
|
||||
<li>Azure OpenAI</li>
|
||||
<li>Ollama</li>
|
||||
<li>OpenRouter</li>
|
||||
<li>DeepSeek</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mt-4">
|
||||
注意:<code>claude-sonnet-4-5</code> 已在带有AWS标志的draw.io图表上进行训练,因此如果您想创建AWS架构图,这是最佳选择。
|
||||
</p>
|
||||
|
||||
{/* Support */}
|
||||
<div className="flex items-center gap-4 mt-10 mb-4">
|
||||
<h2 className="text-2xl font-semibold text-gray-900">支持与联系</h2>
|
||||
<iframe
|
||||
src="https://github.com/sponsors/DayuanJiang/button"
|
||||
title="Sponsor DayuanJiang"
|
||||
height="32"
|
||||
width="114"
|
||||
style={{ border: 0, borderRadius: 6 }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-gray-700">
|
||||
如果您觉得这个项目有用,请考虑{" "}
|
||||
<a href="https://github.com/sponsors/DayuanJiang" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">
|
||||
赞助
|
||||
</a>{" "}
|
||||
来帮助托管在线演示站点!
|
||||
</p>
|
||||
<p className="text-gray-700 mt-2">
|
||||
如需支持或咨询,请在{" "}
|
||||
<a href="https://github.com/DayuanJiang/next-ai-draw-io" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">
|
||||
GitHub仓库
|
||||
</a>{" "}
|
||||
上提交issue或联系:me[at]jiang.jp
|
||||
</p>
|
||||
|
||||
{/* CTA */}
|
||||
<div className="mt-12 text-center">
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-block bg-blue-600 text-white px-8 py-3 rounded-lg font-semibold hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
打开编辑器
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-white border-t border-gray-200 mt-16">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<p className="text-center text-gray-600 text-sm">
|
||||
Next AI Draw.io - 开源AI驱动的图表生成器
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { FaGithub } from "react-icons/fa";
|
||||
import Image from "next/image";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "概要 - Next AI Draw.io",
|
||||
description: "AI搭載のダイアグラム作成ツール - チャット、描画、可視化。自然言語でAWS、GCP、Azureアーキテクチャ図を作成。",
|
||||
keywords: ["AIダイアグラム", "draw.io", "AWSアーキテクチャ", "GCPダイアグラム", "Azureダイアグラム", "LLM"],
|
||||
};
|
||||
|
||||
export default function AboutJA() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Navigation */}
|
||||
<header className="bg-white border-b border-gray-200">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Link href="/" className="text-xl font-bold text-gray-900 hover:text-gray-700">
|
||||
Next AI Draw.io
|
||||
</Link>
|
||||
<nav className="flex items-center gap-6 text-sm">
|
||||
<Link href="/" className="text-gray-600 hover:text-gray-900 transition-colors">
|
||||
エディタ
|
||||
</Link>
|
||||
<Link href="/about/ja" className="text-blue-600 font-semibold">
|
||||
概要
|
||||
</Link>
|
||||
<a
|
||||
href="https://github.com/DayuanJiang/next-ai-draw-io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-600 hover:text-gray-900 transition-colors"
|
||||
aria-label="GitHubで見る"
|
||||
>
|
||||
<FaGithub className="w-5 h-5" />
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<article className="prose prose-lg max-w-none">
|
||||
{/* Title */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-2">Next AI Draw.io</h1>
|
||||
<p className="text-xl text-gray-600 font-medium">
|
||||
AI搭載のダイアグラム作成ツール - チャット、描画、可視化
|
||||
</p>
|
||||
<div className="flex justify-center gap-4 mt-4 text-sm">
|
||||
<Link href="/about" className="text-gray-600 hover:text-blue-600">English</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link href="/about/cn" className="text-gray-600 hover:text-blue-600">中文</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link href="/about/ja" className="text-blue-600 font-semibold">日本語</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 mb-6">
|
||||
<p className="text-amber-800">
|
||||
本アプリは最高のパフォーマンスを発揮するため、Claude Opus 4.5 で動作するよう設計されています。しかし、予想以上のトラフィックにより、最上位モデルの運用コストが負担となっています。サービスの中断を避け、コストを管理するため、バックエンドを Claude Haiku 4.5 に切り替えました。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-700">
|
||||
AI機能とdraw.ioダイアグラムを統合したNext.jsウェブアプリケーションです。自然言語コマンドとAI支援の可視化により、ダイアグラムを作成、修正、強化できます。
|
||||
</p>
|
||||
|
||||
{/* Features */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">機能</h2>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-2">
|
||||
<li><strong>LLM搭載のダイアグラム作成</strong>:大規模言語モデルを活用して、自然言語コマンドで直接draw.ioダイアグラムを作成・操作</li>
|
||||
<li><strong>画像ベースのダイアグラム複製</strong>:既存のダイアグラムや画像をアップロードし、AIが自動的に複製・強化</li>
|
||||
<li><strong>ダイアグラム履歴</strong>:すべての変更を追跡する包括的なバージョン管理。AI編集前のダイアグラムの以前のバージョンを表示・復元可能</li>
|
||||
<li><strong>インタラクティブなチャットインターフェース</strong>:AIとリアルタイムでコミュニケーションしてダイアグラムを改善</li>
|
||||
<li><strong>AWSアーキテクチャダイアグラムサポート</strong>:AWSアーキテクチャダイアグラムの生成を専門的にサポート</li>
|
||||
<li><strong>アニメーションコネクタ</strong>:より良い可視化のためにダイアグラム要素間に動的でアニメーション化されたコネクタを作成</li>
|
||||
</ul>
|
||||
|
||||
{/* Examples */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">例</h2>
|
||||
<p className="text-gray-700 mb-6">以下はいくつかのプロンプト例と生成されたダイアグラムです:</p>
|
||||
|
||||
<div className="space-y-8">
|
||||
{/* Animated Transformer */}
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">アニメーションTransformerコネクタ</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
<strong>プロンプト:</strong> <strong>アニメーションコネクタ</strong>付きのTransformerアーキテクチャ図を作成してください。
|
||||
</p>
|
||||
<Image src="/animated_connectors.svg" alt="アニメーションコネクタ付きTransformerアーキテクチャ" width={480} height={360} className="mx-auto" />
|
||||
</div>
|
||||
|
||||
{/* Cloud Architecture Grid */}
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">GCPアーキテクチャ図</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>プロンプト:</strong> <strong>GCPアイコン</strong>を使用してGCPアーキテクチャ図を生成してください。ユーザーがインスタンス上でホストされているフロントエンドに接続します。
|
||||
</p>
|
||||
<Image src="/gcp_demo.svg" alt="GCPアーキテクチャ図" width={400} height={300} className="mx-auto" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">AWSアーキテクチャ図</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>プロンプト:</strong> <strong>AWSアイコン</strong>を使用してAWSアーキテクチャ図を生成してください。ユーザーがインスタンス上でホストされているフロントエンドに接続します。
|
||||
</p>
|
||||
<Image src="/aws_demo.svg" alt="AWSアーキテクチャ図" width={400} height={300} className="mx-auto" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">Azureアーキテクチャ図</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>プロンプト:</strong> <strong>Azureアイコン</strong>を使用してAzureアーキテクチャ図を生成してください。ユーザーがインスタンス上でホストされているフロントエンドに接続します。
|
||||
</p>
|
||||
<Image src="/azure_demo.svg" alt="Azureアーキテクチャ図" width={400} height={300} className="mx-auto" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">猫のスケッチ</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>プロンプト:</strong> かわいい猫を描いてください。
|
||||
</p>
|
||||
<Image src="/cat_demo.svg" alt="猫の絵" width={240} height={240} className="mx-auto" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* How It Works */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">仕組み</h2>
|
||||
<p className="text-gray-700 mb-4">本アプリケーションは以下の技術を使用しています:</p>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-2">
|
||||
<li><strong>Next.js</strong>:フロントエンドフレームワークとルーティング</li>
|
||||
<li><strong>Vercel AI SDK</strong>(<code>ai</code> + <code>@ai-sdk/*</code>):ストリーミングAIレスポンスとマルチプロバイダーサポート</li>
|
||||
<li><strong>react-drawio</strong>:ダイアグラムの表現と操作</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mt-4">
|
||||
ダイアグラムはdraw.ioでレンダリングできるXMLとして表現されます。AIがコマンドを処理し、それに応じてこのXMLを生成または変更します。
|
||||
</p>
|
||||
|
||||
{/* Multi-Provider Support */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">マルチプロバイダーサポート</h2>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-1">
|
||||
<li>AWS Bedrock(デフォルト)</li>
|
||||
<li>OpenAI / OpenAI互換API(<code>OPENAI_BASE_URL</code>経由)</li>
|
||||
<li>Anthropic</li>
|
||||
<li>Google AI</li>
|
||||
<li>Azure OpenAI</li>
|
||||
<li>Ollama</li>
|
||||
<li>OpenRouter</li>
|
||||
<li>DeepSeek</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mt-4">
|
||||
注:<code>claude-sonnet-4-5</code>はAWSロゴ付きのdraw.ioダイアグラムで学習されているため、AWSアーキテクチャダイアグラムを作成したい場合は最適な選択です。
|
||||
</p>
|
||||
|
||||
{/* Support */}
|
||||
<div className="flex items-center gap-4 mt-10 mb-4">
|
||||
<h2 className="text-2xl font-semibold text-gray-900">サポート&お問い合わせ</h2>
|
||||
<iframe
|
||||
src="https://github.com/sponsors/DayuanJiang/button"
|
||||
title="Sponsor DayuanJiang"
|
||||
height="32"
|
||||
width="114"
|
||||
style={{ border: 0, borderRadius: 6 }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-gray-700">
|
||||
このプロジェクトが役に立ったら、ライブデモサイトのホスティングを支援するために{" "}
|
||||
<a href="https://github.com/sponsors/DayuanJiang" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">
|
||||
スポンサー
|
||||
</a>{" "}
|
||||
をご検討ください!
|
||||
</p>
|
||||
<p className="text-gray-700 mt-2">
|
||||
サポートやお問い合わせについては、{" "}
|
||||
<a href="https://github.com/DayuanJiang/next-ai-draw-io" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">
|
||||
GitHubリポジトリ
|
||||
</a>{" "}
|
||||
でissueを開くか、ご連絡ください:me[at]jiang.jp
|
||||
</p>
|
||||
|
||||
{/* CTA */}
|
||||
<div className="mt-12 text-center">
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-block bg-blue-600 text-white px-8 py-3 rounded-lg font-semibold hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
エディタを開く
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-white border-t border-gray-200 mt-16">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<p className="text-center text-gray-600 text-sm">
|
||||
Next AI Draw.io - オープンソースAI搭載ダイアグラムジェネレーター
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { FaGithub } from "react-icons/fa";
|
||||
import Image from "next/image";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "About - Next AI Draw.io",
|
||||
description: "AI-Powered Diagram Creation Tool - Chat, Draw, Visualize. Create AWS, GCP, and Azure architecture diagrams with natural language.",
|
||||
keywords: ["AI diagram", "draw.io", "AWS architecture", "GCP diagram", "Azure diagram", "LLM"],
|
||||
};
|
||||
|
||||
export default function About() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Navigation */}
|
||||
<header className="bg-white border-b border-gray-200">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Link href="/" className="text-xl font-bold text-gray-900 hover:text-gray-700">
|
||||
Next AI Draw.io
|
||||
</Link>
|
||||
<nav className="flex items-center gap-6 text-sm">
|
||||
<Link href="/" className="text-gray-600 hover:text-gray-900 transition-colors">
|
||||
Editor
|
||||
</Link>
|
||||
<Link href="/about" className="text-blue-600 font-semibold">
|
||||
About
|
||||
</Link>
|
||||
<a
|
||||
href="https://github.com/DayuanJiang/next-ai-draw-io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-600 hover:text-gray-900 transition-colors"
|
||||
aria-label="View on GitHub"
|
||||
>
|
||||
<FaGithub className="w-5 h-5" />
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<article className="prose prose-lg max-w-none">
|
||||
{/* Title */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-2">Next AI Draw.io</h1>
|
||||
<p className="text-xl text-gray-600 font-medium">
|
||||
AI-Powered Diagram Creation Tool - Chat, Draw, Visualize
|
||||
</p>
|
||||
<div className="flex justify-center gap-4 mt-4 text-sm">
|
||||
<Link href="/about" className="text-blue-600 font-semibold">English</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link href="/about/cn" className="text-gray-600 hover:text-blue-600">中文</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link href="/about/ja" className="text-gray-600 hover:text-blue-600">日本語</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 mb-6">
|
||||
<p className="text-amber-800">
|
||||
This app is designed to run on Claude Opus 4.5 for best performance. However, due to higher-than-expected traffic, running the top-tier model has become cost-prohibitive. To avoid service interruptions and manage costs, I have switched the backend to Claude Haiku 4.5.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-700">
|
||||
A Next.js web application that integrates AI capabilities with draw.io diagrams.
|
||||
Create, modify, and enhance diagrams through natural language commands and AI-assisted visualization.
|
||||
</p>
|
||||
|
||||
{/* Features */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">Features</h2>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-2">
|
||||
<li><strong>LLM-Powered Diagram Creation</strong>: Leverage Large Language Models to create and manipulate draw.io diagrams directly through natural language commands</li>
|
||||
<li><strong>Image-Based Diagram Replication</strong>: Upload existing diagrams or images and have the AI replicate and enhance them automatically</li>
|
||||
<li><strong>Diagram History</strong>: Comprehensive version control that tracks all changes, allowing you to view and restore previous versions of your diagrams before the AI editing</li>
|
||||
<li><strong>Interactive Chat Interface</strong>: Communicate with AI to refine your diagrams in real-time</li>
|
||||
<li><strong>AWS Architecture Diagram Support</strong>: Specialized support for generating AWS architecture diagrams</li>
|
||||
<li><strong>Animated Connectors</strong>: Create dynamic and animated connectors between diagram elements for better visualization</li>
|
||||
</ul>
|
||||
|
||||
{/* Examples */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">Examples</h2>
|
||||
<p className="text-gray-700 mb-6">Here are some example prompts and their generated diagrams:</p>
|
||||
|
||||
<div className="space-y-8">
|
||||
{/* Animated Transformer */}
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">Animated Transformer Connectors</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
<strong>Prompt:</strong> Give me an <strong>animated connector</strong> diagram of transformer's architecture.
|
||||
</p>
|
||||
<Image src="/animated_connectors.svg" alt="Transformer Architecture with Animated Connectors" width={480} height={360} className="mx-auto" />
|
||||
</div>
|
||||
|
||||
{/* Cloud Architecture Grid */}
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">GCP Architecture Diagram</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>Prompt:</strong> Generate a GCP architecture diagram with <strong>GCP icons</strong>. Users connect to a frontend hosted on an instance.
|
||||
</p>
|
||||
<Image src="/gcp_demo.svg" alt="GCP Architecture Diagram" width={400} height={300} className="mx-auto" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">AWS Architecture Diagram</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>Prompt:</strong> Generate an AWS architecture diagram with <strong>AWS icons</strong>. Users connect to a frontend hosted on an instance.
|
||||
</p>
|
||||
<Image src="/aws_demo.svg" alt="AWS Architecture Diagram" width={400} height={300} className="mx-auto" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">Azure Architecture Diagram</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>Prompt:</strong> Generate an Azure architecture diagram with <strong>Azure icons</strong>. Users connect to a frontend hosted on an instance.
|
||||
</p>
|
||||
<Image src="/azure_demo.svg" alt="Azure Architecture Diagram" width={400} height={300} className="mx-auto" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">Cat Sketch</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<strong>Prompt:</strong> Draw a cute cat for me.
|
||||
</p>
|
||||
<Image src="/cat_demo.svg" alt="Cat Drawing" width={240} height={240} className="mx-auto" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* How It Works */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">How It Works</h2>
|
||||
<p className="text-gray-700 mb-4">The application uses the following technologies:</p>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-2">
|
||||
<li><strong>Next.js</strong>: For the frontend framework and routing</li>
|
||||
<li><strong>Vercel AI SDK</strong> (<code>ai</code> + <code>@ai-sdk/*</code>): For streaming AI responses and multi-provider support</li>
|
||||
<li><strong>react-drawio</strong>: For diagram representation and manipulation</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mt-4">
|
||||
Diagrams are represented as XML that can be rendered in draw.io. The AI processes your commands and generates or modifies this XML accordingly.
|
||||
</p>
|
||||
|
||||
{/* Multi-Provider Support */}
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mt-10 mb-4">Multi-Provider Support</h2>
|
||||
<ul className="list-disc pl-6 text-gray-700 space-y-1">
|
||||
<li>AWS Bedrock (default)</li>
|
||||
<li>OpenAI / OpenAI-compatible APIs (via <code>OPENAI_BASE_URL</code>)</li>
|
||||
<li>Anthropic</li>
|
||||
<li>Google AI</li>
|
||||
<li>Azure OpenAI</li>
|
||||
<li>Ollama</li>
|
||||
<li>OpenRouter</li>
|
||||
<li>DeepSeek</li>
|
||||
</ul>
|
||||
<p className="text-gray-700 mt-4">
|
||||
Note that <code>claude-sonnet-4-5</code> has trained on draw.io diagrams with AWS logos, so if you want to create AWS architecture diagrams, this is the best choice.
|
||||
</p>
|
||||
|
||||
{/* Support */}
|
||||
<div className="flex items-center gap-4 mt-10 mb-4">
|
||||
<h2 className="text-2xl font-semibold text-gray-900">Support & Contact</h2>
|
||||
<iframe
|
||||
src="https://github.com/sponsors/DayuanJiang/button"
|
||||
title="Sponsor DayuanJiang"
|
||||
height="32"
|
||||
width="114"
|
||||
style={{ border: 0, borderRadius: 6 }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-gray-700">
|
||||
If you find this project useful, please consider{" "}
|
||||
<a href="https://github.com/sponsors/DayuanJiang" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">
|
||||
sponsoring
|
||||
</a>{" "}
|
||||
to help host the live demo site!
|
||||
</p>
|
||||
<p className="text-gray-700 mt-2">
|
||||
For support or inquiries, please open an issue on the{" "}
|
||||
<a href="https://github.com/DayuanJiang/next-ai-draw-io" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">
|
||||
GitHub repository
|
||||
</a>{" "}
|
||||
or contact: me[at]jiang.jp
|
||||
</p>
|
||||
|
||||
{/* CTA */}
|
||||
<div className="mt-12 text-center">
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-block bg-blue-600 text-white px-8 py-3 rounded-lg font-semibold hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Open Editor
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-white border-t border-gray-200 mt-16">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<p className="text-center text-gray-600 text-sm">
|
||||
Next AI Draw.io - Open Source AI-Powered Diagram Generator
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -81,16 +81,15 @@ Contains the actual diagram data.
|
||||
|
||||
## Root Cell Container: `<root>`
|
||||
|
||||
Contains all the cells in the diagram.
|
||||
Contains all the cells in the diagram. **Note:** When generating diagrams, you only need to provide the mxCell elements - the root container and root cells (id="0", id="1") are added automatically.
|
||||
|
||||
**Example:**
|
||||
**Internal structure (auto-generated):**
|
||||
|
||||
```xml
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
|
||||
<!-- Other cells go here -->
|
||||
<mxCell id="0"/> <!-- Auto-added -->
|
||||
<mxCell id="1" parent="0"/> <!-- Auto-added -->
|
||||
<!-- Your mxCell elements go here (start from id="2") -->
|
||||
</root>
|
||||
```
|
||||
|
||||
@@ -203,15 +202,15 @@ Draw.io files contain two special cells that are always present:
|
||||
1. **Root Cell** (id = "0"): The parent of all cells
|
||||
2. **Default Parent Cell** (id = "1", parent = "0"): The default layer and parent for most cells
|
||||
|
||||
## Tips for Manually Creating Draw.io XML
|
||||
## Tips for Creating Draw.io XML
|
||||
|
||||
1. Start with the basic structure (`mxfile`, `diagram`, `mxGraphModel`, `root`)
|
||||
2. Always include the two special cells (id = "0" and id = "1")
|
||||
1. **Generate ONLY mxCell elements** - wrapper tags and root cells (id="0", id="1") are added automatically
|
||||
2. Start IDs from "2" (id="0" and id="1" are reserved for root cells)
|
||||
3. Assign unique and sequential IDs to all cells
|
||||
4. Define parent relationships correctly
|
||||
4. Define parent relationships correctly (use parent="1" for top-level shapes)
|
||||
5. Use `mxGeometry` elements to position shapes
|
||||
6. For connectors, specify `source` and `target` attributes
|
||||
7. **CRITICAL: All mxCell elements must be DIRECT children of `<root>`. NEVER nest mxCell inside another mxCell.**
|
||||
7. **CRITICAL: All mxCell elements must be siblings. NEVER nest mxCell inside another mxCell.**
|
||||
|
||||
## Common Patterns
|
||||
|
||||
|
||||
10
app/api/config/route.ts
Normal file
10
app/api/config/route.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json({
|
||||
accessCodeRequired: !!process.env.ACCESS_CODE_LIST,
|
||||
dailyRequestLimit: Number(process.env.DAILY_REQUEST_LIMIT) || 0,
|
||||
dailyTokenLimit: Number(process.env.DAILY_TOKEN_LIMIT) || 0,
|
||||
tpmLimit: Number(process.env.TPM_LIMIT) || 0,
|
||||
})
|
||||
}
|
||||
112
app/api/log-feedback/route.ts
Normal file
112
app/api/log-feedback/route.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { randomUUID } from "crypto"
|
||||
import { z } from "zod"
|
||||
import { getLangfuseClient } from "@/lib/langfuse"
|
||||
|
||||
const feedbackSchema = z.object({
|
||||
messageId: z.string().min(1).max(200),
|
||||
feedback: z.enum(["good", "bad"]),
|
||||
sessionId: z.string().min(1).max(200).optional(),
|
||||
})
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const langfuse = getLangfuseClient()
|
||||
if (!langfuse) {
|
||||
return Response.json({ success: true, logged: false })
|
||||
}
|
||||
|
||||
// Validate input
|
||||
let data
|
||||
try {
|
||||
data = feedbackSchema.parse(await req.json())
|
||||
} catch {
|
||||
return Response.json(
|
||||
{ success: false, error: "Invalid input" },
|
||||
{ status: 400 },
|
||||
)
|
||||
}
|
||||
|
||||
const { messageId, feedback, sessionId } = data
|
||||
|
||||
// Get user IP for tracking
|
||||
const forwardedFor = req.headers.get("x-forwarded-for")
|
||||
const userId = forwardedFor?.split(",")[0]?.trim() || "anonymous"
|
||||
|
||||
try {
|
||||
// Find the most recent chat trace for this session to attach the score to
|
||||
const tracesResponse = await langfuse.api.trace.list({
|
||||
sessionId,
|
||||
limit: 1,
|
||||
})
|
||||
|
||||
const traces = tracesResponse.data || []
|
||||
const latestTrace = traces[0]
|
||||
|
||||
if (!latestTrace) {
|
||||
// No trace found for this session - create a standalone feedback trace
|
||||
const traceId = randomUUID()
|
||||
const timestamp = new Date().toISOString()
|
||||
|
||||
await langfuse.api.ingestion.batch({
|
||||
batch: [
|
||||
{
|
||||
type: "trace-create",
|
||||
id: randomUUID(),
|
||||
timestamp,
|
||||
body: {
|
||||
id: traceId,
|
||||
name: "user-feedback",
|
||||
sessionId,
|
||||
userId,
|
||||
input: { messageId, feedback },
|
||||
metadata: {
|
||||
source: "feedback-button",
|
||||
note: "standalone - no chat trace found",
|
||||
},
|
||||
timestamp,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "score-create",
|
||||
id: randomUUID(),
|
||||
timestamp,
|
||||
body: {
|
||||
id: randomUUID(),
|
||||
traceId,
|
||||
name: "user-feedback",
|
||||
value: feedback === "good" ? 1 : 0,
|
||||
comment: `User gave ${feedback} feedback`,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
} else {
|
||||
// Attach score to the existing chat trace
|
||||
const timestamp = new Date().toISOString()
|
||||
|
||||
await langfuse.api.ingestion.batch({
|
||||
batch: [
|
||||
{
|
||||
type: "score-create",
|
||||
id: randomUUID(),
|
||||
timestamp,
|
||||
body: {
|
||||
id: randomUUID(),
|
||||
traceId: latestTrace.id,
|
||||
name: "user-feedback",
|
||||
value: feedback === "good" ? 1 : 0,
|
||||
comment: `User gave ${feedback} feedback`,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
return Response.json({ success: true, logged: true })
|
||||
} catch (error) {
|
||||
console.error("Langfuse feedback error:", error)
|
||||
return Response.json(
|
||||
{ success: false, error: "Failed to log feedback" },
|
||||
{ status: 500 },
|
||||
)
|
||||
}
|
||||
}
|
||||
71
app/api/log-save/route.ts
Normal file
71
app/api/log-save/route.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { randomUUID } from "crypto"
|
||||
import { z } from "zod"
|
||||
import { getLangfuseClient } from "@/lib/langfuse"
|
||||
|
||||
const saveSchema = z.object({
|
||||
filename: z.string().min(1).max(255),
|
||||
format: z.enum(["drawio", "png", "svg"]),
|
||||
sessionId: z.string().min(1).max(200).optional(),
|
||||
})
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const langfuse = getLangfuseClient()
|
||||
if (!langfuse) {
|
||||
return Response.json({ success: true, logged: false })
|
||||
}
|
||||
|
||||
// Validate input
|
||||
let data
|
||||
try {
|
||||
data = saveSchema.parse(await req.json())
|
||||
} catch {
|
||||
return Response.json(
|
||||
{ success: false, error: "Invalid input" },
|
||||
{ status: 400 },
|
||||
)
|
||||
}
|
||||
|
||||
const { filename, format, sessionId } = data
|
||||
|
||||
try {
|
||||
const timestamp = new Date().toISOString()
|
||||
|
||||
// Find the most recent chat trace for this session to attach the save flag
|
||||
const tracesResponse = await langfuse.api.trace.list({
|
||||
sessionId,
|
||||
limit: 1,
|
||||
})
|
||||
|
||||
const traces = tracesResponse.data || []
|
||||
const latestTrace = traces[0]
|
||||
|
||||
if (latestTrace) {
|
||||
// Add a score to the existing trace to flag that user saved
|
||||
await langfuse.api.ingestion.batch({
|
||||
batch: [
|
||||
{
|
||||
type: "score-create",
|
||||
id: randomUUID(),
|
||||
timestamp,
|
||||
body: {
|
||||
id: randomUUID(),
|
||||
traceId: latestTrace.id,
|
||||
name: "diagram-saved",
|
||||
value: 1,
|
||||
comment: `User saved diagram as ${filename}.${format}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
// If no trace found, skip logging (user hasn't chatted yet)
|
||||
|
||||
return Response.json({ success: true, logged: !!latestTrace })
|
||||
} catch (error) {
|
||||
console.error("Langfuse save error:", error)
|
||||
return Response.json(
|
||||
{ success: false, error: "Failed to log save" },
|
||||
{ status: 500 },
|
||||
)
|
||||
}
|
||||
}
|
||||
32
app/api/verify-access-code/route.ts
Normal file
32
app/api/verify-access-code/route.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export async function POST(req: Request) {
|
||||
const accessCodes =
|
||||
process.env.ACCESS_CODE_LIST?.split(",")
|
||||
.map((code) => code.trim())
|
||||
.filter(Boolean) || []
|
||||
|
||||
// If no access codes configured, verification always passes
|
||||
if (accessCodes.length === 0) {
|
||||
return Response.json({
|
||||
valid: true,
|
||||
message: "No access code required",
|
||||
})
|
||||
}
|
||||
|
||||
const accessCodeHeader = req.headers.get("x-access-code")
|
||||
|
||||
if (!accessCodeHeader) {
|
||||
return Response.json(
|
||||
{ valid: false, message: "Access code is required" },
|
||||
{ status: 401 },
|
||||
)
|
||||
}
|
||||
|
||||
if (!accessCodes.includes(accessCodeHeader)) {
|
||||
return Response.json(
|
||||
{ valid: false, message: "Invalid access code" },
|
||||
{ status: 401 },
|
||||
)
|
||||
}
|
||||
|
||||
return Response.json({ valid: true, message: "Access code is valid" })
|
||||
}
|
||||
353
app/globals.css
353
app/globals.css
@@ -1,248 +1,259 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@plugin "tailwindcss-animate";
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-mono);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-mono);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.75rem;
|
||||
--radius: 0.75rem;
|
||||
|
||||
/* Clean Light Modern Palette */
|
||||
--background: oklch(0.985 0.002 240);
|
||||
--foreground: oklch(0.23 0.02 260);
|
||||
/* Clean Light Modern Palette */
|
||||
--background: oklch(0.985 0.002 240);
|
||||
--foreground: oklch(0.23 0.02 260);
|
||||
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.23 0.02 260);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.23 0.02 260);
|
||||
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.23 0.02 260);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.23 0.02 260);
|
||||
|
||||
/* Dark primary - slightly lighter */
|
||||
--primary: oklch(0.35 0.01 260);
|
||||
--primary-foreground: oklch(0.99 0 0);
|
||||
/* Dark primary - slightly lighter */
|
||||
--primary: oklch(0.35 0.01 260);
|
||||
--primary-foreground: oklch(0.99 0 0);
|
||||
|
||||
/* Warm gray secondary */
|
||||
--secondary: oklch(0.96 0.005 260);
|
||||
--secondary-foreground: oklch(0.35 0.02 260);
|
||||
/* Warm gray secondary */
|
||||
--secondary: oklch(0.96 0.005 260);
|
||||
--secondary-foreground: oklch(0.35 0.02 260);
|
||||
|
||||
/* Light muted tones */
|
||||
--muted: oklch(0.965 0.005 260);
|
||||
--muted-foreground: oklch(0.50 0.02 260);
|
||||
/* Light muted tones */
|
||||
--muted: oklch(0.965 0.005 260);
|
||||
--muted-foreground: oklch(0.5 0.02 260);
|
||||
|
||||
/* Soft lavender accent */
|
||||
--accent: oklch(0.94 0.03 280);
|
||||
--accent-foreground: oklch(0.35 0.08 270);
|
||||
/* Soft lavender accent */
|
||||
--accent: oklch(0.94 0.03 280);
|
||||
--accent-foreground: oklch(0.35 0.08 270);
|
||||
|
||||
/* Coral destructive */
|
||||
--destructive: oklch(0.60 0.20 25);
|
||||
/* Coral destructive */
|
||||
--destructive: oklch(0.6 0.2 25);
|
||||
|
||||
/* Subtle borders */
|
||||
--border: oklch(0.92 0.01 260);
|
||||
--input: oklch(0.94 0.01 260);
|
||||
--ring: oklch(0.25 0.01 260);
|
||||
/* Subtle borders */
|
||||
--border: oklch(0.92 0.01 260);
|
||||
--input: oklch(0.94 0.01 260);
|
||||
--ring: oklch(0.25 0.01 260);
|
||||
|
||||
/* Chart colors - harmonious palette */
|
||||
--chart-1: oklch(0.55 0.18 265);
|
||||
--chart-2: oklch(0.65 0.15 170);
|
||||
--chart-3: oklch(0.70 0.18 45);
|
||||
--chart-4: oklch(0.60 0.20 330);
|
||||
--chart-5: oklch(0.50 0.15 200);
|
||||
/* Chart colors - harmonious palette */
|
||||
--chart-1: oklch(0.55 0.18 265);
|
||||
--chart-2: oklch(0.65 0.15 170);
|
||||
--chart-3: oklch(0.7 0.18 45);
|
||||
--chart-4: oklch(0.6 0.2 330);
|
||||
--chart-5: oklch(0.5 0.15 200);
|
||||
|
||||
/* Sidebar */
|
||||
--sidebar: oklch(0.99 0.002 260);
|
||||
--sidebar-foreground: oklch(0.23 0.02 260);
|
||||
--sidebar-primary: oklch(0.55 0.18 265);
|
||||
--sidebar-primary-foreground: oklch(0.99 0 0);
|
||||
--sidebar-accent: oklch(0.96 0.02 270);
|
||||
--sidebar-accent-foreground: oklch(0.35 0.05 265);
|
||||
--sidebar-border: oklch(0.93 0.01 260);
|
||||
--sidebar-ring: oklch(0.55 0.18 265);
|
||||
/* Sidebar */
|
||||
--sidebar: oklch(0.99 0.002 260);
|
||||
--sidebar-foreground: oklch(0.23 0.02 260);
|
||||
--sidebar-primary: oklch(0.55 0.18 265);
|
||||
--sidebar-primary-foreground: oklch(0.99 0 0);
|
||||
--sidebar-accent: oklch(0.96 0.02 270);
|
||||
--sidebar-accent-foreground: oklch(0.35 0.05 265);
|
||||
--sidebar-border: oklch(0.93 0.01 260);
|
||||
--sidebar-ring: oklch(0.55 0.18 265);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.15 0.015 260);
|
||||
--foreground: oklch(0.95 0.01 260);
|
||||
--background: oklch(0.15 0.015 260);
|
||||
--foreground: oklch(0.95 0.01 260);
|
||||
|
||||
--card: oklch(0.20 0.015 260);
|
||||
--card-foreground: oklch(0.95 0.01 260);
|
||||
--card: oklch(0.2 0.015 260);
|
||||
--card-foreground: oklch(0.95 0.01 260);
|
||||
|
||||
--popover: oklch(0.20 0.015 260);
|
||||
--popover-foreground: oklch(0.95 0.01 260);
|
||||
--popover: oklch(0.2 0.015 260);
|
||||
--popover-foreground: oklch(0.95 0.01 260);
|
||||
|
||||
--primary: oklch(0.70 0.16 265);
|
||||
--primary-foreground: oklch(0.15 0.02 260);
|
||||
--primary: oklch(0.7 0.16 265);
|
||||
--primary-foreground: oklch(0.15 0.02 260);
|
||||
|
||||
--secondary: oklch(0.25 0.015 260);
|
||||
--secondary-foreground: oklch(0.90 0.01 260);
|
||||
--secondary: oklch(0.25 0.015 260);
|
||||
--secondary-foreground: oklch(0.9 0.01 260);
|
||||
|
||||
--muted: oklch(0.25 0.015 260);
|
||||
--muted-foreground: oklch(0.65 0.02 260);
|
||||
--muted: oklch(0.25 0.015 260);
|
||||
--muted-foreground: oklch(0.65 0.02 260);
|
||||
|
||||
--accent: oklch(0.30 0.04 280);
|
||||
--accent-foreground: oklch(0.90 0.03 270);
|
||||
--accent: oklch(0.3 0.04 280);
|
||||
--accent-foreground: oklch(0.9 0.03 270);
|
||||
|
||||
--destructive: oklch(0.65 0.22 25);
|
||||
--destructive: oklch(0.65 0.22 25);
|
||||
|
||||
--border: oklch(0.28 0.015 260);
|
||||
--input: oklch(0.25 0.015 260);
|
||||
--ring: oklch(0.70 0.16 265);
|
||||
--border: oklch(0.28 0.015 260);
|
||||
--input: oklch(0.25 0.015 260);
|
||||
--ring: oklch(0.7 0.16 265);
|
||||
|
||||
--chart-1: oklch(0.70 0.16 265);
|
||||
--chart-2: oklch(0.70 0.13 170);
|
||||
--chart-3: oklch(0.75 0.16 45);
|
||||
--chart-4: oklch(0.70 0.18 330);
|
||||
--chart-5: oklch(0.60 0.13 200);
|
||||
--chart-1: oklch(0.7 0.16 265);
|
||||
--chart-2: oklch(0.7 0.13 170);
|
||||
--chart-3: oklch(0.75 0.16 45);
|
||||
--chart-4: oklch(0.7 0.18 330);
|
||||
--chart-5: oklch(0.6 0.13 200);
|
||||
|
||||
--sidebar: oklch(0.18 0.015 260);
|
||||
--sidebar-foreground: oklch(0.95 0.01 260);
|
||||
--sidebar-primary: oklch(0.70 0.16 265);
|
||||
--sidebar-primary-foreground: oklch(0.15 0.02 260);
|
||||
--sidebar-accent: oklch(0.25 0.03 270);
|
||||
--sidebar-accent-foreground: oklch(0.90 0.02 265);
|
||||
--sidebar-border: oklch(0.28 0.015 260);
|
||||
--sidebar-ring: oklch(0.70 0.16 265);
|
||||
--sidebar: oklch(0.18 0.015 260);
|
||||
--sidebar-foreground: oklch(0.95 0.01 260);
|
||||
--sidebar-primary: oklch(0.7 0.16 265);
|
||||
--sidebar-primary-foreground: oklch(0.15 0.02 260);
|
||||
--sidebar-accent: oklch(0.25 0.03 270);
|
||||
--sidebar-accent-foreground: oklch(0.9 0.02 265);
|
||||
--sidebar-border: oklch(0.28 0.015 260);
|
||||
--sidebar-ring: oklch(0.7 0.16 265);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground font-sans;
|
||||
}
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground font-sans;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fix for Radix ScrollArea viewport horizontal overflow */
|
||||
[data-slot="scroll-area-viewport"] > div {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
@layer utilities {
|
||||
.scrollbar-thin {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: oklch(0.85 0.01 260) transparent;
|
||||
}
|
||||
.scrollbar-thin {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: oklch(0.85 0.01 260) transparent;
|
||||
}
|
||||
|
||||
.scrollbar-thin::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.scrollbar-thin::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.scrollbar-thin::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.scrollbar-thin::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb {
|
||||
background-color: oklch(0.85 0.01 260);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb {
|
||||
background-color: oklch(0.85 0.01 260);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
||||
background-color: oklch(0.75 0.01 260);
|
||||
}
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
||||
background-color: oklch(0.75 0.01 260);
|
||||
}
|
||||
}
|
||||
|
||||
/* Smooth page transitions */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(16px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(16px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.3s ease-out forwards;
|
||||
animation: fadeIn 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-slide-in-right {
|
||||
animation: slideInRight 0.3s ease-out forwards;
|
||||
animation: slideInRight 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
/* Message bubble animations */
|
||||
@keyframes messageIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(12px) scale(0.98);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(12px) scale(0.98);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-message-in {
|
||||
animation: messageIn 0.25s ease-out forwards;
|
||||
animation: messageIn 0.25s ease-out forwards;
|
||||
}
|
||||
|
||||
/* Subtle floating shadow for cards */
|
||||
.shadow-soft {
|
||||
box-shadow:
|
||||
0 1px 2px oklch(0.23 0.02 260 / 0.04),
|
||||
0 4px 12px oklch(0.23 0.02 260 / 0.06),
|
||||
0 8px 24px oklch(0.23 0.02 260 / 0.04);
|
||||
box-shadow:
|
||||
0 1px 2px oklch(0.23 0.02 260 / 0.04),
|
||||
0 4px 12px oklch(0.23 0.02 260 / 0.06),
|
||||
0 8px 24px oklch(0.23 0.02 260 / 0.04);
|
||||
}
|
||||
|
||||
.shadow-soft-lg {
|
||||
box-shadow:
|
||||
0 2px 4px oklch(0.23 0.02 260 / 0.04),
|
||||
0 8px 20px oklch(0.23 0.02 260 / 0.08),
|
||||
0 16px 40px oklch(0.23 0.02 260 / 0.06);
|
||||
box-shadow:
|
||||
0 2px 4px oklch(0.23 0.02 260 / 0.04),
|
||||
0 8px 20px oklch(0.23 0.02 260 / 0.08),
|
||||
0 16px 40px oklch(0.23 0.02 260 / 0.06);
|
||||
}
|
||||
|
||||
/* Gradient text utility */
|
||||
.text-gradient-primary {
|
||||
background: linear-gradient(135deg, oklch(0.55 0.18 265), oklch(0.60 0.20 290));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
oklch(0.55 0.18 265),
|
||||
oklch(0.6 0.2 290)
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
107
app/layout.tsx
107
app/layout.tsx
@@ -1,107 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Plus_Jakarta_Sans, JetBrains_Mono } from "next/font/google";
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
import { GoogleAnalytics } from "@next/third-parties/google";
|
||||
import { DiagramProvider } from "@/contexts/diagram-context";
|
||||
|
||||
import "./globals.css";
|
||||
|
||||
const plusJakarta = Plus_Jakarta_Sans({
|
||||
variable: "--font-sans",
|
||||
subsets: ["latin"],
|
||||
weight: ["400", "500", "600", "700"],
|
||||
});
|
||||
|
||||
const jetbrainsMono = JetBrains_Mono({
|
||||
variable: "--font-mono",
|
||||
subsets: ["latin"],
|
||||
weight: ["400", "500"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Next AI Draw.io - AI-Powered Diagram Generator",
|
||||
description: "Create AWS architecture diagrams, flowcharts, and technical diagrams using AI. Free online tool integrating draw.io with AI assistance for professional diagram creation.",
|
||||
keywords: ["AI diagram generator", "AWS architecture", "flowchart creator", "draw.io", "AI drawing tool", "technical diagrams", "diagram automation", "free diagram generator", "online diagram maker"],
|
||||
authors: [{ name: "Next AI Draw.io" }],
|
||||
creator: "Next AI Draw.io",
|
||||
publisher: "Next AI Draw.io",
|
||||
metadataBase: new URL("https://next-ai-drawio.jiang.jp"),
|
||||
openGraph: {
|
||||
title: "Next AI Draw.io - AI Diagram Generator",
|
||||
description: "Create professional diagrams with AI assistance. Supports AWS architecture, flowcharts, and more.",
|
||||
type: "website",
|
||||
url: "https://next-ai-drawio.jiang.jp",
|
||||
siteName: "Next AI Draw.io",
|
||||
locale: "en_US",
|
||||
images: [
|
||||
{
|
||||
url: "/architecture.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "Next AI Draw.io - AI-powered diagram creation tool",
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Next AI Draw.io - AI Diagram Generator",
|
||||
description: "Create professional diagrams with AI assistance. Free, no login required.",
|
||||
images: ["/architecture.png"],
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
"max-video-preview": -1,
|
||||
"max-image-preview": "large",
|
||||
"max-snippet": -1,
|
||||
},
|
||||
},
|
||||
icons: {
|
||||
icon: "/favicon.ico",
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'Next AI Draw.io',
|
||||
applicationCategory: 'DesignApplication',
|
||||
operatingSystem: 'Web Browser',
|
||||
description: 'AI-powered diagram generator with targeted XML editing capabilities that integrates with draw.io for creating AWS architecture diagrams, flowcharts, and technical diagrams. Features diagram history, multi-provider AI support, and real-time collaboration.',
|
||||
url: 'https://next-ai-drawio.jiang.jp',
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
price: '0',
|
||||
priceCurrency: 'USD',
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
</head>
|
||||
<body
|
||||
className={`${plusJakarta.variable} ${jetbrainsMono.variable} antialiased`}
|
||||
>
|
||||
<DiagramProvider>{children}</DiagramProvider>
|
||||
|
||||
<Analytics />
|
||||
</body>
|
||||
{process.env.NEXT_PUBLIC_GA_ID && (
|
||||
<GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_ID} />
|
||||
)}
|
||||
</html>
|
||||
);
|
||||
}
|
||||
28
app/manifest.ts
Normal file
28
app/manifest.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { MetadataRoute } from "next"
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
name: "Next AI Draw.io",
|
||||
short_name: "AIDraw.io",
|
||||
description:
|
||||
"Create AWS architecture diagrams, flowcharts, and technical diagrams using AI. Free online tool integrating draw.io with AI assistance for professional diagram creation.",
|
||||
start_url: "/",
|
||||
display: "standalone",
|
||||
background_color: "#f9fafb",
|
||||
theme_color: "#171d26",
|
||||
icons: [
|
||||
{
|
||||
src: "/favicon-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
purpose: "any",
|
||||
},
|
||||
{
|
||||
src: "/favicon-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "any",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
85
app/page.tsx
85
app/page.tsx
@@ -1,85 +0,0 @@
|
||||
"use client";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { DrawIoEmbed } from "react-drawio";
|
||||
import ChatPanel from "@/components/chat-panel";
|
||||
import { useDiagram } from "@/contexts/diagram-context";
|
||||
import { Monitor } from "lucide-react";
|
||||
|
||||
export default function Home() {
|
||||
const { drawioRef, handleDiagramExport } = useDiagram();
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const [isChatVisible, setIsChatVisible] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const checkMobile = () => {
|
||||
setIsMobile(window.innerWidth < 768);
|
||||
};
|
||||
|
||||
checkMobile();
|
||||
window.addEventListener("resize", checkMobile);
|
||||
return () => window.removeEventListener("resize", checkMobile);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 'b') {
|
||||
event.preventDefault();
|
||||
setIsChatVisible((prev) => !prev);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-background relative overflow-hidden">
|
||||
{/* Mobile warning overlay */}
|
||||
{isMobile && (
|
||||
<div className="absolute inset-0 z-50 flex items-center justify-center bg-background">
|
||||
<div className="text-center p-8 max-w-sm mx-auto animate-fade-in">
|
||||
<div className="w-16 h-16 rounded-2xl bg-primary/10 flex items-center justify-center mx-auto mb-6">
|
||||
<Monitor className="w-8 h-8 text-primary" />
|
||||
</div>
|
||||
<h1 className="text-xl font-semibold text-foreground mb-3">
|
||||
Desktop Required
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
This application works best on desktop or laptop devices. Please open it on a larger screen for the full experience.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Draw.io Canvas */}
|
||||
<div
|
||||
className={`${isChatVisible ? 'w-2/3' : 'w-full'} h-full relative transition-all duration-300 ease-out`}
|
||||
>
|
||||
<div className="absolute inset-2 rounded-xl overflow-hidden shadow-soft-lg border border-border/30 bg-white">
|
||||
<DrawIoEmbed
|
||||
ref={drawioRef}
|
||||
onExport={handleDiagramExport}
|
||||
urlParameters={{
|
||||
spin: true,
|
||||
libraries: false,
|
||||
saveAndExit: false,
|
||||
noExitBtn: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chat Panel */}
|
||||
<div
|
||||
className={`${isChatVisible ? 'w-1/3' : 'w-12'} h-full transition-all duration-300 ease-out`}
|
||||
>
|
||||
<div className="h-full py-2 pr-2">
|
||||
<ChatPanel
|
||||
isVisible={isChatVisible}
|
||||
onToggleVisibility={() => setIsChatVisible(!isChatVisible)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import { MetadataRoute } from 'next'
|
||||
import type { MetadataRoute } from "next"
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: {
|
||||
userAgent: '*',
|
||||
allow: '/',
|
||||
disallow: '/api/',
|
||||
userAgent: "*",
|
||||
allow: "/",
|
||||
disallow: "/api/",
|
||||
},
|
||||
sitemap: 'https://next-ai-drawio.jiang.jp/sitemap.xml',
|
||||
sitemap: "https://next-ai-drawio.jiang.jp/sitemap.xml",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { MetadataRoute } from 'next'
|
||||
import type { MetadataRoute } from "next"
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return [
|
||||
{
|
||||
url: 'https://next-ai-drawio.jiang.jp',
|
||||
url: "https://next-ai-drawio.jiang.jp",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'weekly',
|
||||
changeFrequency: "weekly",
|
||||
priority: 1,
|
||||
},
|
||||
{
|
||||
url: 'https://next-ai-drawio.jiang.jp/about',
|
||||
url: "https://next-ai-drawio.jiang.jp/about",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
},
|
||||
]
|
||||
|
||||
83
biome.json
Normal file
83
biome.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.3.8/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 4
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"complexity": {
|
||||
"noImportantStyles": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off",
|
||||
"noArrayIndexKey": "off",
|
||||
"noImplicitAnyLet": "off",
|
||||
"noAssignInExpressions": "off"
|
||||
},
|
||||
"a11y": {
|
||||
"useButtonType": "off",
|
||||
"noAutofocus": "off",
|
||||
"noStaticElementInteractions": "off",
|
||||
"useKeyWithClickEvents": "off",
|
||||
"noLabelWithoutControl": "off",
|
||||
"noNoninteractiveTabindex": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"useExhaustiveDependencies": "off"
|
||||
},
|
||||
"style": {
|
||||
"useNodejsImportProtocol": "off",
|
||||
"useTemplate": "off"
|
||||
},
|
||||
"security": {
|
||||
"noDangerouslySetInnerHtml": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double",
|
||||
"semicolons": "asNeeded"
|
||||
}
|
||||
},
|
||||
"css": {
|
||||
"parser": {
|
||||
"cssModules": true,
|
||||
"tailwindDirectives": true
|
||||
}
|
||||
},
|
||||
"assist": {
|
||||
"enabled": true,
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "on"
|
||||
}
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"includes": ["components/ui/**"],
|
||||
"formatter": {
|
||||
"enabled": false
|
||||
},
|
||||
"linter": {
|
||||
"enabled": false
|
||||
},
|
||||
"assist": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
|
||||
186
components/ai-elements/reasoning.tsx
Normal file
186
components/ai-elements/reasoning.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
"use client"
|
||||
|
||||
import { useControllableState } from "@radix-ui/react-use-controllable-state"
|
||||
import { BrainIcon, ChevronDownIcon } from "lucide-react"
|
||||
import type { ComponentProps, ReactNode } from "react"
|
||||
import { createContext, memo, useContext, useEffect, useState } from "react"
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Shimmer } from "./shimmer"
|
||||
|
||||
type ReasoningContextValue = {
|
||||
isStreaming: boolean
|
||||
isOpen: boolean
|
||||
setIsOpen: (open: boolean) => void
|
||||
duration: number | undefined
|
||||
}
|
||||
|
||||
const ReasoningContext = createContext<ReasoningContextValue | null>(null)
|
||||
|
||||
export const useReasoning = () => {
|
||||
const context = useContext(ReasoningContext)
|
||||
if (!context) {
|
||||
throw new Error("Reasoning components must be used within Reasoning")
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
export type ReasoningProps = ComponentProps<typeof Collapsible> & {
|
||||
isStreaming?: boolean
|
||||
open?: boolean
|
||||
defaultOpen?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
duration?: number
|
||||
}
|
||||
|
||||
const AUTO_CLOSE_DELAY = 1000
|
||||
const MS_IN_S = 1000
|
||||
|
||||
export const Reasoning = memo(
|
||||
({
|
||||
className,
|
||||
isStreaming = false,
|
||||
open,
|
||||
defaultOpen = true,
|
||||
onOpenChange,
|
||||
duration: durationProp,
|
||||
children,
|
||||
...props
|
||||
}: ReasoningProps) => {
|
||||
const [isOpen, setIsOpen] = useControllableState({
|
||||
prop: open,
|
||||
defaultProp: defaultOpen,
|
||||
onChange: onOpenChange,
|
||||
})
|
||||
const [duration, setDuration] = useControllableState({
|
||||
prop: durationProp,
|
||||
defaultProp: undefined,
|
||||
})
|
||||
|
||||
const [hasAutoClosed, setHasAutoClosed] = useState(false)
|
||||
const [startTime, setStartTime] = useState<number | null>(null)
|
||||
|
||||
// Track duration when streaming starts and ends
|
||||
useEffect(() => {
|
||||
if (isStreaming) {
|
||||
if (startTime === null) {
|
||||
setStartTime(Date.now())
|
||||
}
|
||||
} else if (startTime !== null) {
|
||||
setDuration(Math.ceil((Date.now() - startTime) / MS_IN_S))
|
||||
setStartTime(null)
|
||||
}
|
||||
}, [isStreaming, startTime, setDuration])
|
||||
|
||||
// Auto-open when streaming starts, auto-close when streaming ends (once only)
|
||||
useEffect(() => {
|
||||
if (defaultOpen && !isStreaming && isOpen && !hasAutoClosed) {
|
||||
// Add a small delay before closing to allow user to see the content
|
||||
const timer = setTimeout(() => {
|
||||
setIsOpen(false)
|
||||
setHasAutoClosed(true)
|
||||
}, AUTO_CLOSE_DELAY)
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [isStreaming, isOpen, defaultOpen, setIsOpen, hasAutoClosed])
|
||||
|
||||
const handleOpenChange = (newOpen: boolean) => {
|
||||
setIsOpen(newOpen)
|
||||
}
|
||||
|
||||
return (
|
||||
<ReasoningContext.Provider
|
||||
value={{ isStreaming, isOpen, setIsOpen, duration }}
|
||||
>
|
||||
<Collapsible
|
||||
className={cn("not-prose mb-4", className)}
|
||||
onOpenChange={handleOpenChange}
|
||||
open={isOpen}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Collapsible>
|
||||
</ReasoningContext.Provider>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export type ReasoningTriggerProps = ComponentProps<
|
||||
typeof CollapsibleTrigger
|
||||
> & {
|
||||
getThinkingMessage?: (isStreaming: boolean, duration?: number) => ReactNode
|
||||
}
|
||||
|
||||
const defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => {
|
||||
if (isStreaming || duration === 0) {
|
||||
return <Shimmer duration={1}>Thinking...</Shimmer>
|
||||
}
|
||||
if (duration === undefined) {
|
||||
return <p>Thought for a few seconds</p>
|
||||
}
|
||||
return <p>Thought for {duration} seconds</p>
|
||||
}
|
||||
|
||||
export const ReasoningTrigger = memo(
|
||||
({
|
||||
className,
|
||||
children,
|
||||
getThinkingMessage = defaultGetThinkingMessage,
|
||||
...props
|
||||
}: ReasoningTriggerProps) => {
|
||||
const { isStreaming, isOpen, duration } = useReasoning()
|
||||
|
||||
return (
|
||||
<CollapsibleTrigger
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children ?? (
|
||||
<>
|
||||
<BrainIcon className="size-4" />
|
||||
{getThinkingMessage(isStreaming, duration)}
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
"size-4 transition-transform",
|
||||
isOpen ? "rotate-180" : "rotate-0",
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</CollapsibleTrigger>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export type ReasoningContentProps = ComponentProps<
|
||||
typeof CollapsibleContent
|
||||
> & {
|
||||
children: string
|
||||
}
|
||||
|
||||
export const ReasoningContent = memo(
|
||||
({ className, children, ...props }: ReasoningContentProps) => (
|
||||
<CollapsibleContent
|
||||
className={cn(
|
||||
"mt-4 text-sm",
|
||||
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-muted-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="whitespace-pre-wrap">{children}</div>
|
||||
</CollapsibleContent>
|
||||
),
|
||||
)
|
||||
|
||||
Reasoning.displayName = "Reasoning"
|
||||
ReasoningTrigger.displayName = "ReasoningTrigger"
|
||||
ReasoningContent.displayName = "ReasoningContent"
|
||||
64
components/ai-elements/shimmer.tsx
Normal file
64
components/ai-elements/shimmer.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
"use client"
|
||||
|
||||
import { motion } from "motion/react"
|
||||
import {
|
||||
type CSSProperties,
|
||||
type ElementType,
|
||||
type JSX,
|
||||
memo,
|
||||
useMemo,
|
||||
} from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export type TextShimmerProps = {
|
||||
children: string
|
||||
as?: ElementType
|
||||
className?: string
|
||||
duration?: number
|
||||
spread?: number
|
||||
}
|
||||
|
||||
const ShimmerComponent = ({
|
||||
children,
|
||||
as: Component = "p",
|
||||
className,
|
||||
duration = 2,
|
||||
spread = 2,
|
||||
}: TextShimmerProps) => {
|
||||
const MotionComponent = motion.create(
|
||||
Component as keyof JSX.IntrinsicElements,
|
||||
)
|
||||
|
||||
const dynamicSpread = useMemo(
|
||||
() => (children?.length ?? 0) * spread,
|
||||
[children, spread],
|
||||
)
|
||||
|
||||
return (
|
||||
<MotionComponent
|
||||
animate={{ backgroundPosition: "0% center" }}
|
||||
className={cn(
|
||||
"relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
|
||||
"[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]",
|
||||
className,
|
||||
)}
|
||||
initial={{ backgroundPosition: "100% center" }}
|
||||
style={
|
||||
{
|
||||
"--spread": `${dynamicSpread}px`,
|
||||
backgroundImage:
|
||||
"var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))",
|
||||
} as CSSProperties
|
||||
}
|
||||
transition={{
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
duration,
|
||||
ease: "linear",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MotionComponent>
|
||||
)
|
||||
}
|
||||
|
||||
export const Shimmer = memo(ShimmerComponent)
|
||||
@@ -1,19 +1,19 @@
|
||||
import React from "react";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
import type React from "react"
|
||||
import { Button, type buttonVariants } from "@/components/ui/button"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { type VariantProps } from "class-variance-authority";
|
||||
} from "@/components/ui/tooltip"
|
||||
|
||||
interface ButtonWithTooltipProps
|
||||
extends React.ComponentProps<"button">,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
tooltipContent: string;
|
||||
children: React.ReactNode;
|
||||
asChild?: boolean;
|
||||
tooltipContent: string
|
||||
children: React.ReactNode
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
export function ButtonWithTooltip({
|
||||
@@ -27,8 +27,10 @@ export function ButtonWithTooltip({
|
||||
<TooltipTrigger asChild>
|
||||
<Button {...buttonProps}>{children}</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{tooltipContent}</TooltipContent>
|
||||
<TooltipContent className="max-w-xs text-wrap">
|
||||
{tooltipContent}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,124 +1,218 @@
|
||||
"use client";
|
||||
"use client"
|
||||
|
||||
import { Zap, Cloud, GitBranch, Palette } from "lucide-react";
|
||||
import {
|
||||
Cloud,
|
||||
FileText,
|
||||
GitBranch,
|
||||
Palette,
|
||||
Terminal,
|
||||
Zap,
|
||||
} from "lucide-react"
|
||||
import { useDictionary } from "@/hooks/use-dictionary"
|
||||
|
||||
interface ExampleCardProps {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
onClick: () => void;
|
||||
icon: React.ReactNode
|
||||
title: string
|
||||
description: string
|
||||
onClick: () => void
|
||||
isNew?: boolean
|
||||
}
|
||||
|
||||
function ExampleCard({ icon, title, description, onClick }: ExampleCardProps) {
|
||||
function ExampleCard({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
onClick,
|
||||
isNew,
|
||||
}: ExampleCardProps) {
|
||||
const dict = useDictionary()
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className="group w-full text-left p-4 rounded-xl border border-border/60 bg-card hover:bg-accent/50 hover:border-primary/30 transition-all duration-200 hover:shadow-sm"
|
||||
className={`group w-full text-left p-4 rounded-xl border bg-card hover:bg-accent/50 hover:border-primary/30 transition-all duration-200 hover:shadow-sm ${
|
||||
isNew
|
||||
? "border-primary/40 ring-1 ring-primary/20"
|
||||
: "border-border/60"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="w-9 h-9 rounded-lg bg-primary/10 flex items-center justify-center shrink-0 group-hover:bg-primary/15 transition-colors">
|
||||
<div
|
||||
className={`w-9 h-9 rounded-lg flex items-center justify-center shrink-0 transition-colors ${
|
||||
isNew
|
||||
? "bg-primary/20 group-hover:bg-primary/25"
|
||||
: "bg-primary/10 group-hover:bg-primary/15"
|
||||
}`}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<h3 className="text-sm font-medium text-foreground group-hover:text-primary transition-colors">
|
||||
{title}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-sm font-medium text-foreground group-hover:text-primary transition-colors">
|
||||
{title}
|
||||
</h3>
|
||||
{isNew && (
|
||||
<span className="px-1.5 py-0.5 text-[10px] font-semibold bg-primary text-primary-foreground rounded">
|
||||
{dict.common.new}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-0.5 line-clamp-2">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default function ExamplePanel({
|
||||
setInput,
|
||||
setFiles,
|
||||
}: {
|
||||
setInput: (input: string) => void;
|
||||
setFiles: (files: File[]) => void;
|
||||
setInput: (input: string) => void
|
||||
setFiles: (files: File[]) => void
|
||||
}) {
|
||||
const dict = useDictionary()
|
||||
|
||||
const handleReplicateFlowchart = async () => {
|
||||
setInput("Replicate this flowchart.");
|
||||
setInput("Replicate this flowchart.")
|
||||
|
||||
try {
|
||||
const response = await fetch("/example.png");
|
||||
const blob = await response.blob();
|
||||
const file = new File([blob], "example.png", { type: "image/png" });
|
||||
setFiles([file]);
|
||||
const response = await fetch("/example.png")
|
||||
const blob = await response.blob()
|
||||
const file = new File([blob], "example.png", { type: "image/png" })
|
||||
setFiles([file])
|
||||
} catch (error) {
|
||||
console.error("Error loading example image:", error);
|
||||
console.error(dict.errors.failedToLoadExample, error)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleReplicateArchitecture = async () => {
|
||||
setInput("Replicate this in aws style");
|
||||
setInput("Replicate this in aws style")
|
||||
|
||||
try {
|
||||
const response = await fetch("/architecture.png");
|
||||
const blob = await response.blob();
|
||||
const response = await fetch("/architecture.png")
|
||||
const blob = await response.blob()
|
||||
const file = new File([blob], "architecture.png", {
|
||||
type: "image/png",
|
||||
});
|
||||
setFiles([file]);
|
||||
})
|
||||
setFiles([file])
|
||||
} catch (error) {
|
||||
console.error("Error loading architecture image:", error);
|
||||
console.error(dict.errors.failedToLoadExample, error)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handlePdfExample = async () => {
|
||||
setInput("Summarize this paper as a diagram")
|
||||
|
||||
try {
|
||||
const response = await fetch("/chain-of-thought.txt")
|
||||
const blob = await response.blob()
|
||||
const file = new File([blob], "chain-of-thought.txt", {
|
||||
type: "text/plain",
|
||||
})
|
||||
setFiles([file])
|
||||
} catch (error) {
|
||||
console.error(dict.errors.failedToLoadExample, error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="py-6 px-2 animate-fade-in">
|
||||
{/* MCP Server Notice */}
|
||||
<a
|
||||
href="https://github.com/DayuanJiang/next-ai-draw-io/tree/main/packages/mcp-server"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block mb-4 p-3 rounded-xl bg-gradient-to-r from-purple-500/10 to-blue-500/10 border border-purple-500/20 hover:border-purple-500/40 transition-colors group"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-purple-500/20 flex items-center justify-center shrink-0">
|
||||
<Terminal className="w-4 h-4 text-purple-500" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-foreground group-hover:text-purple-500 transition-colors">
|
||||
{dict.examples.mcpServer}
|
||||
</span>
|
||||
<span className="px-1.5 py-0.5 text-[10px] font-semibold bg-purple-500 text-white rounded">
|
||||
{dict.examples.preview}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{dict.examples.mcpDescription}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{/* Welcome section */}
|
||||
<div className="text-center mb-6">
|
||||
<h2 className="text-lg font-semibold text-foreground mb-2">
|
||||
Create diagrams with AI
|
||||
{dict.examples.title}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground max-w-xs mx-auto">
|
||||
Describe what you want to create or upload an image to replicate
|
||||
{dict.examples.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Examples grid */}
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wider px-1">
|
||||
Quick Examples
|
||||
{dict.examples.quickExamples}
|
||||
</p>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<ExampleCard
|
||||
icon={<FileText className="w-4 h-4 text-primary" />}
|
||||
title={dict.examples.paperToDiagram}
|
||||
description={dict.examples.paperDescription}
|
||||
onClick={handlePdfExample}
|
||||
isNew
|
||||
/>
|
||||
|
||||
<ExampleCard
|
||||
icon={<Zap className="w-4 h-4 text-primary" />}
|
||||
title="Animated Diagram"
|
||||
description="Draw a transformer architecture with animated connectors"
|
||||
onClick={() => setInput("Give me a **animated connector** diagram of transformer's architecture")}
|
||||
title={dict.examples.animatedDiagram}
|
||||
description={dict.examples.animatedDescription}
|
||||
onClick={() => {
|
||||
setInput(
|
||||
"Give me a **animated connector** diagram of transformer's architecture",
|
||||
)
|
||||
setFiles([])
|
||||
}}
|
||||
/>
|
||||
|
||||
<ExampleCard
|
||||
icon={<Cloud className="w-4 h-4 text-primary" />}
|
||||
title="AWS Architecture"
|
||||
description="Create a cloud architecture diagram with AWS icons"
|
||||
title={dict.examples.awsArchitecture}
|
||||
description={dict.examples.awsDescription}
|
||||
onClick={handleReplicateArchitecture}
|
||||
/>
|
||||
|
||||
<ExampleCard
|
||||
icon={<GitBranch className="w-4 h-4 text-primary" />}
|
||||
title="Replicate Flowchart"
|
||||
description="Upload and replicate an existing flowchart"
|
||||
title={dict.examples.replicateFlowchart}
|
||||
description={dict.examples.replicateDescription}
|
||||
onClick={handleReplicateFlowchart}
|
||||
/>
|
||||
|
||||
<ExampleCard
|
||||
icon={<Palette className="w-4 h-4 text-primary" />}
|
||||
title="Creative Drawing"
|
||||
description="Draw something fun and creative"
|
||||
onClick={() => setInput("Draw a cat for me")}
|
||||
title={dict.examples.creativeDrawing}
|
||||
description={dict.examples.creativeDescription}
|
||||
onClick={() => {
|
||||
setInput("Draw a cat for me")
|
||||
setFiles([])
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="text-[11px] text-muted-foreground/60 text-center mt-4">
|
||||
Examples are cached for instant response
|
||||
{dict.examples.cachedNote}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,34 +1,161 @@
|
||||
"use client";
|
||||
"use client"
|
||||
|
||||
import React, { useCallback, useRef, useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { ResetWarningModal } from "@/components/reset-warning-modal";
|
||||
import { SaveDialog } from "@/components/save-dialog";
|
||||
import {
|
||||
Download,
|
||||
History,
|
||||
Image as ImageIcon,
|
||||
Loader2,
|
||||
Send,
|
||||
Trash2,
|
||||
Image as ImageIcon,
|
||||
History,
|
||||
Download,
|
||||
Paperclip,
|
||||
} from "lucide-react";
|
||||
import { ButtonWithTooltip } from "@/components/button-with-tooltip";
|
||||
import { FilePreviewList } from "./file-preview-list";
|
||||
import { useDiagram } from "@/contexts/diagram-context";
|
||||
import { HistoryDialog } from "@/components/history-dialog";
|
||||
} from "lucide-react"
|
||||
import type React from "react"
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import { toast } from "sonner"
|
||||
import { ButtonWithTooltip } from "@/components/button-with-tooltip"
|
||||
import { ErrorToast } from "@/components/error-toast"
|
||||
import { HistoryDialog } from "@/components/history-dialog"
|
||||
import { ResetWarningModal } from "@/components/reset-warning-modal"
|
||||
import { SaveDialog } from "@/components/save-dialog"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip"
|
||||
import { useDiagram } from "@/contexts/diagram-context"
|
||||
import { useDictionary } from "@/hooks/use-dictionary"
|
||||
import { formatMessage } from "@/lib/i18n/utils"
|
||||
import { isPdfFile, isTextFile } from "@/lib/pdf-utils"
|
||||
import { FilePreviewList } from "./file-preview-list"
|
||||
|
||||
const MAX_IMAGE_SIZE = 2 * 1024 * 1024 // 2MB
|
||||
const MAX_FILES = 5
|
||||
|
||||
function isValidFileType(file: File): boolean {
|
||||
return file.type.startsWith("image/") || isPdfFile(file) || isTextFile(file)
|
||||
}
|
||||
|
||||
function formatFileSize(bytes: number): string {
|
||||
const mb = bytes / 1024 / 1024
|
||||
if (mb < 0.01) return `${(bytes / 1024).toFixed(0)}KB`
|
||||
return `${mb.toFixed(2)}MB`
|
||||
}
|
||||
|
||||
function showErrorToast(message: React.ReactNode) {
|
||||
toast.custom(
|
||||
(t) => (
|
||||
<ErrorToast message={message} onDismiss={() => toast.dismiss(t)} />
|
||||
),
|
||||
{ duration: 5000 },
|
||||
)
|
||||
}
|
||||
|
||||
interface ValidationResult {
|
||||
validFiles: File[]
|
||||
errors: string[]
|
||||
}
|
||||
|
||||
function validateFiles(
|
||||
newFiles: File[],
|
||||
existingCount: number,
|
||||
dict: any,
|
||||
): ValidationResult {
|
||||
const errors: string[] = []
|
||||
const validFiles: File[] = []
|
||||
|
||||
const availableSlots = MAX_FILES - existingCount
|
||||
|
||||
if (availableSlots <= 0) {
|
||||
errors.push(formatMessage(dict.errors.maxFiles, { max: MAX_FILES }))
|
||||
return { validFiles, errors }
|
||||
}
|
||||
|
||||
for (const file of newFiles) {
|
||||
if (validFiles.length >= availableSlots) {
|
||||
errors.push(
|
||||
formatMessage(dict.errors.onlyMoreAllowed, {
|
||||
slots: availableSlots,
|
||||
}),
|
||||
)
|
||||
break
|
||||
}
|
||||
if (!isValidFileType(file)) {
|
||||
errors.push(
|
||||
formatMessage(dict.errors.unsupportedType, { name: file.name }),
|
||||
)
|
||||
continue
|
||||
}
|
||||
// Only check size for images (PDFs/text files are extracted client-side, so file size doesn't matter)
|
||||
const isExtractedFile = isPdfFile(file) || isTextFile(file)
|
||||
if (!isExtractedFile && file.size > MAX_IMAGE_SIZE) {
|
||||
const maxSizeMB = MAX_IMAGE_SIZE / 1024 / 1024
|
||||
errors.push(
|
||||
formatMessage(dict.errors.fileExceeds, {
|
||||
name: file.name,
|
||||
size: formatFileSize(file.size),
|
||||
max: maxSizeMB,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
validFiles.push(file)
|
||||
}
|
||||
}
|
||||
|
||||
return { validFiles, errors }
|
||||
}
|
||||
|
||||
function showValidationErrors(errors: string[], dict: any) {
|
||||
if (errors.length === 0) return
|
||||
|
||||
if (errors.length === 1) {
|
||||
showErrorToast(
|
||||
<span className="text-muted-foreground">{errors[0]}</span>,
|
||||
)
|
||||
} else {
|
||||
showErrorToast(
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">
|
||||
{formatMessage(dict.errors.filesRejected, {
|
||||
count: errors.length,
|
||||
})}
|
||||
</span>
|
||||
<ul className="text-muted-foreground text-xs list-disc list-inside">
|
||||
{errors.slice(0, 3).map((err) => (
|
||||
<li key={err}>{err}</li>
|
||||
))}
|
||||
{errors.length > 3 && (
|
||||
<li>
|
||||
{formatMessage(dict.errors.andMore, {
|
||||
count: errors.length - 3,
|
||||
})}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
interface ChatInputProps {
|
||||
input: string;
|
||||
status: "submitted" | "streaming" | "ready" | "error";
|
||||
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
|
||||
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
onClearChat: () => void;
|
||||
files?: File[];
|
||||
onFileChange?: (files: File[]) => void;
|
||||
showHistory?: boolean;
|
||||
onToggleHistory?: (show: boolean) => void;
|
||||
input: string
|
||||
status: "submitted" | "streaming" | "ready" | "error"
|
||||
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void
|
||||
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||
onClearChat: () => void
|
||||
files?: File[]
|
||||
onFileChange?: (files: File[]) => void
|
||||
pdfData?: Map<
|
||||
File,
|
||||
{ text: string; charCount: number; isExtracting: boolean }
|
||||
>
|
||||
showHistory?: boolean
|
||||
onToggleHistory?: (show: boolean) => void
|
||||
sessionId?: string
|
||||
error?: Error | null
|
||||
minimalStyle?: boolean
|
||||
onMinimalStyleChange?: (value: boolean) => void
|
||||
}
|
||||
|
||||
export function ChatInput({
|
||||
@@ -39,126 +166,158 @@ export function ChatInput({
|
||||
onClearChat,
|
||||
files = [],
|
||||
onFileChange = () => {},
|
||||
pdfData = new Map(),
|
||||
showHistory = false,
|
||||
onToggleHistory = () => {},
|
||||
sessionId,
|
||||
error = null,
|
||||
minimalStyle = false,
|
||||
onMinimalStyleChange = () => {},
|
||||
}: ChatInputProps) {
|
||||
const { diagramHistory, saveDiagramToFile } = useDiagram();
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [showClearDialog, setShowClearDialog] = useState(false);
|
||||
const [showSaveDialog, setShowSaveDialog] = useState(false);
|
||||
|
||||
const isDisabled = status === "streaming" || status === "submitted";
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[ChatInput] Status changed to:', status, '| Input disabled:', isDisabled);
|
||||
}, [status, isDisabled]);
|
||||
const dict = useDictionary()
|
||||
const {
|
||||
diagramHistory,
|
||||
saveDiagramToFile,
|
||||
showSaveDialog,
|
||||
setShowSaveDialog,
|
||||
} = useDiagram()
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [showClearDialog, setShowClearDialog] = useState(false)
|
||||
// Allow retry when there's an error (even if status is still "streaming" or "submitted")
|
||||
const isDisabled =
|
||||
(status === "streaming" || status === "submitted") && !error
|
||||
|
||||
const adjustTextareaHeight = useCallback(() => {
|
||||
const textarea = textareaRef.current;
|
||||
const textarea = textareaRef.current
|
||||
if (textarea) {
|
||||
textarea.style.height = "auto";
|
||||
textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`;
|
||||
textarea.style.height = "auto"
|
||||
textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`
|
||||
}
|
||||
}, []);
|
||||
|
||||
}, [])
|
||||
// Handle programmatic input changes (e.g., setInput("") after form submission)
|
||||
useEffect(() => {
|
||||
adjustTextareaHeight();
|
||||
}, [input, adjustTextareaHeight]);
|
||||
adjustTextareaHeight()
|
||||
}, [input, adjustTextareaHeight])
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
onChange(e)
|
||||
adjustTextareaHeight()
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
const form = e.currentTarget.closest("form");
|
||||
e.preventDefault()
|
||||
const form = e.currentTarget.closest("form")
|
||||
if (form && input.trim() && !isDisabled) {
|
||||
form.requestSubmit();
|
||||
form.requestSubmit()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handlePaste = async (e: React.ClipboardEvent) => {
|
||||
if (isDisabled) return;
|
||||
if (isDisabled) return
|
||||
|
||||
const items = e.clipboardData.items;
|
||||
const items = e.clipboardData.items
|
||||
const imageItems = Array.from(items).filter((item) =>
|
||||
item.type.startsWith("image/")
|
||||
);
|
||||
item.type.startsWith("image/"),
|
||||
)
|
||||
|
||||
if (imageItems.length > 0) {
|
||||
const imageFiles = await Promise.all(
|
||||
imageItems.map(async (item) => {
|
||||
const file = item.getAsFile();
|
||||
if (!file) return null;
|
||||
return new File(
|
||||
[file],
|
||||
`pasted-image-${Date.now()}.${file.type.split("/")[1]}`,
|
||||
{
|
||||
type: file.type,
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
const imageFiles = (
|
||||
await Promise.all(
|
||||
imageItems.map(async (item, index) => {
|
||||
const file = item.getAsFile()
|
||||
if (!file) return null
|
||||
return new File(
|
||||
[file],
|
||||
`pasted-image-${Date.now()}-${index}.${file.type.split("/")[1]}`,
|
||||
{ type: file.type },
|
||||
)
|
||||
}),
|
||||
)
|
||||
).filter((f): f is File => f !== null)
|
||||
|
||||
const validFiles = imageFiles.filter(
|
||||
(file): file is File => file !== null
|
||||
);
|
||||
const { validFiles, errors } = validateFiles(
|
||||
imageFiles,
|
||||
files.length,
|
||||
dict,
|
||||
)
|
||||
showValidationErrors(errors, dict)
|
||||
if (validFiles.length > 0) {
|
||||
onFileChange([...files, ...validFiles]);
|
||||
onFileChange([...files, ...validFiles])
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newFiles = Array.from(e.target.files || []);
|
||||
onFileChange([...files, ...newFiles]);
|
||||
};
|
||||
const newFiles = Array.from(e.target.files || [])
|
||||
const { validFiles, errors } = validateFiles(
|
||||
newFiles,
|
||||
files.length,
|
||||
dict,
|
||||
)
|
||||
showValidationErrors(errors, dict)
|
||||
if (validFiles.length > 0) {
|
||||
onFileChange([...files, ...validFiles])
|
||||
}
|
||||
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = ""
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemoveFile = (fileToRemove: File) => {
|
||||
onFileChange(files.filter((file) => file !== fileToRemove));
|
||||
onFileChange(files.filter((file) => file !== fileToRemove))
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = "";
|
||||
fileInputRef.current.value = ""
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const triggerFileInput = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
fileInputRef.current?.click()
|
||||
}
|
||||
|
||||
const handleDragOver = (e: React.DragEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(true);
|
||||
};
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setIsDragging(true)
|
||||
}
|
||||
|
||||
const handleDragLeave = (e: React.DragEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(false);
|
||||
};
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setIsDragging(false)
|
||||
}
|
||||
|
||||
const handleDrop = (e: React.DragEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(false);
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setIsDragging(false)
|
||||
|
||||
if (isDisabled) return;
|
||||
if (isDisabled) return
|
||||
|
||||
const droppedFiles = e.dataTransfer.files;
|
||||
const droppedFiles = e.dataTransfer.files
|
||||
const supportedFiles = Array.from(droppedFiles).filter((file) =>
|
||||
isValidFileType(file),
|
||||
)
|
||||
|
||||
const imageFiles = Array.from(droppedFiles).filter((file) =>
|
||||
file.type.startsWith("image/")
|
||||
);
|
||||
|
||||
if (imageFiles.length > 0) {
|
||||
onFileChange([...files, ...imageFiles]);
|
||||
const { validFiles, errors } = validateFiles(
|
||||
supportedFiles,
|
||||
files.length,
|
||||
dict,
|
||||
)
|
||||
showValidationErrors(errors, dict)
|
||||
if (validFiles.length > 0) {
|
||||
onFileChange([...files, ...validFiles])
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleClear = () => {
|
||||
onClearChat();
|
||||
setShowClearDialog(false);
|
||||
};
|
||||
onClearChat()
|
||||
setShowClearDialog(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
@@ -175,34 +334,34 @@ export function ChatInput({
|
||||
{/* File previews */}
|
||||
{files.length > 0 && (
|
||||
<div className="mb-3">
|
||||
<FilePreviewList files={files} onRemoveFile={handleRemoveFile} />
|
||||
<FilePreviewList
|
||||
files={files}
|
||||
onRemoveFile={handleRemoveFile}
|
||||
pdfData={pdfData}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Input container */}
|
||||
<div className="relative rounded-2xl border border-border bg-background shadow-sm focus-within:ring-2 focus-within:ring-primary/20 focus-within:border-primary/50 transition-all duration-200">
|
||||
<Textarea
|
||||
ref={textareaRef}
|
||||
value={input}
|
||||
onChange={onChange}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onPaste={handlePaste}
|
||||
placeholder="Describe your diagram or paste an image..."
|
||||
placeholder={dict.chat.placeholder}
|
||||
disabled={isDisabled}
|
||||
aria-label="Chat input"
|
||||
className="min-h-[60px] max-h-[200px] resize-none border-0 bg-transparent px-4 py-3 text-sm focus-visible:ring-0 focus-visible:ring-offset-0 placeholder:text-muted-foreground/60"
|
||||
/>
|
||||
|
||||
{/* Action bar */}
|
||||
<div className="flex items-center justify-between px-3 py-2 border-t border-border/50">
|
||||
{/* Left actions */}
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex items-center gap-1 overflow-x-hidden">
|
||||
<ButtonWithTooltip
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowClearDialog(true)}
|
||||
tooltipContent="Clear conversation"
|
||||
tooltipContent={dict.chat.clearConversation}
|
||||
className="h-8 w-8 p-0 text-muted-foreground hover:text-destructive hover:bg-destructive/10"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
@@ -218,17 +377,44 @@ export function ChatInput({
|
||||
showHistory={showHistory}
|
||||
onToggleHistory={onToggleHistory}
|
||||
/>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Switch
|
||||
id="minimal-style"
|
||||
checked={minimalStyle}
|
||||
onCheckedChange={onMinimalStyleChange}
|
||||
className="scale-75"
|
||||
/>
|
||||
<label
|
||||
htmlFor="minimal-style"
|
||||
className={`text-xs cursor-pointer select-none ${
|
||||
minimalStyle
|
||||
? "text-primary font-medium"
|
||||
: "text-muted-foreground"
|
||||
}`}
|
||||
>
|
||||
{minimalStyle
|
||||
? dict.chat.minimalStyle
|
||||
: dict.chat.styledMode}
|
||||
</label>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
{dict.chat.minimalTooltip}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{/* Right actions */}
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex items-center gap-1 overflow-hidden justify-end">
|
||||
<ButtonWithTooltip
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onToggleHistory(true)}
|
||||
disabled={isDisabled || diagramHistory.length === 0}
|
||||
tooltipContent="Diagram history"
|
||||
tooltipContent={dict.chat.diagramHistory}
|
||||
className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<History className="h-4 w-4" />
|
||||
@@ -240,7 +426,7 @@ export function ChatInput({
|
||||
size="sm"
|
||||
onClick={() => setShowSaveDialog(true)}
|
||||
disabled={isDisabled}
|
||||
tooltipContent="Save diagram"
|
||||
tooltipContent={dict.chat.saveDiagram}
|
||||
className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
@@ -249,8 +435,12 @@ export function ChatInput({
|
||||
<SaveDialog
|
||||
open={showSaveDialog}
|
||||
onOpenChange={setShowSaveDialog}
|
||||
onSave={saveDiagramToFile}
|
||||
defaultFilename={`diagram-${new Date().toISOString().slice(0, 10)}`}
|
||||
onSave={(filename, format) =>
|
||||
saveDiagramToFile(filename, format, sessionId)
|
||||
}
|
||||
defaultFilename={`diagram-${new Date()
|
||||
.toISOString()
|
||||
.slice(0, 10)}`}
|
||||
/>
|
||||
|
||||
<ButtonWithTooltip
|
||||
@@ -259,7 +449,7 @@ export function ChatInput({
|
||||
size="sm"
|
||||
onClick={triggerFileInput}
|
||||
disabled={isDisabled}
|
||||
tooltipContent="Upload image"
|
||||
tooltipContent={dict.chat.uploadFile}
|
||||
className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<ImageIcon className="h-4 w-4" />
|
||||
@@ -270,7 +460,7 @@ export function ChatInput({
|
||||
ref={fileInputRef}
|
||||
className="hidden"
|
||||
onChange={handleFileChange}
|
||||
accept="image/*"
|
||||
accept="image/*,.pdf,application/pdf,text/*,.md,.markdown,.json,.csv,.xml,.yaml,.yml,.toml"
|
||||
multiple
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
@@ -282,21 +472,22 @@ export function ChatInput({
|
||||
disabled={isDisabled || !input.trim()}
|
||||
size="sm"
|
||||
className="h-8 px-4 rounded-xl font-medium shadow-sm"
|
||||
aria-label={isDisabled ? "Sending..." : "Send message"}
|
||||
aria-label={
|
||||
isDisabled ? dict.chat.sending : dict.chat.send
|
||||
}
|
||||
>
|
||||
{isDisabled ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<>
|
||||
<Send className="h-4 w-4 mr-1.5" />
|
||||
Send
|
||||
{dict.chat.send}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,29 @@
|
||||
"use client";
|
||||
"use client"
|
||||
|
||||
import { Highlight, themes } from "prism-react-renderer";
|
||||
import { Highlight, themes } from "prism-react-renderer"
|
||||
|
||||
interface CodeBlockProps {
|
||||
code: string;
|
||||
language?: "xml" | "json";
|
||||
code: string
|
||||
language?: "xml" | "json"
|
||||
}
|
||||
|
||||
export function CodeBlock({ code, language = "xml" }: CodeBlockProps) {
|
||||
return (
|
||||
<div className="overflow-hidden w-full">
|
||||
<Highlight theme={themes.github} code={code} language={language}>
|
||||
{({ className, style, tokens, getLineProps, getTokenProps }) => (
|
||||
{({
|
||||
className: _className,
|
||||
style,
|
||||
tokens,
|
||||
getLineProps,
|
||||
getTokenProps,
|
||||
}) => (
|
||||
<pre
|
||||
className="text-[11px] leading-relaxed overflow-x-auto overflow-y-auto max-h-48 scrollbar-thin break-all"
|
||||
style={{
|
||||
...style,
|
||||
fontFamily: "var(--font-mono), ui-monospace, monospace",
|
||||
fontFamily:
|
||||
"var(--font-mono), ui-monospace, monospace",
|
||||
backgroundColor: "transparent",
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
@@ -25,9 +32,16 @@ export function CodeBlock({ code, language = "xml" }: CodeBlockProps) {
|
||||
}}
|
||||
>
|
||||
{tokens.map((line, i) => (
|
||||
<div key={i} {...getLineProps({ line })} style={{ wordBreak: "break-all" }}>
|
||||
<div
|
||||
key={i}
|
||||
{...getLineProps({ line })}
|
||||
style={{ wordBreak: "break-all" }}
|
||||
>
|
||||
{line.map((token, key) => (
|
||||
<span key={key} {...getTokenProps({ token })} />
|
||||
<span
|
||||
key={key}
|
||||
{...getTokenProps({ token })}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
@@ -35,5 +49,5 @@ export function CodeBlock({ code, language = "xml" }: CodeBlockProps) {
|
||||
)}
|
||||
</Highlight>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
44
components/error-toast.tsx
Normal file
44
components/error-toast.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
interface ErrorToastProps {
|
||||
message: React.ReactNode
|
||||
onDismiss: () => void
|
||||
}
|
||||
|
||||
export function ErrorToast({ message, onDismiss }: ErrorToastProps) {
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter" || e.key === " " || e.key === "Escape") {
|
||||
e.preventDefault()
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
role="alert"
|
||||
aria-live="polite"
|
||||
tabIndex={0}
|
||||
onClick={onDismiss}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="flex items-center gap-3 bg-card border border-border/50 px-4 py-3 rounded-xl shadow-sm cursor-pointer hover:bg-muted/50 focus:outline-none focus:ring-2 focus:ring-primary/50 transition-colors"
|
||||
>
|
||||
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-destructive/10 flex-shrink-0">
|
||||
<svg
|
||||
className="w-4 h-4 text-destructive"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-sm text-foreground">{message}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,49 +1,140 @@
|
||||
"use client";
|
||||
"use client"
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { X } from "lucide-react";
|
||||
import { FileCode, FileText, Loader2, X } from "lucide-react"
|
||||
import Image from "next/image"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { useDictionary } from "@/hooks/use-dictionary"
|
||||
import { isPdfFile, isTextFile } from "@/lib/pdf-utils"
|
||||
|
||||
interface FilePreviewListProps {
|
||||
files: File[];
|
||||
onRemoveFile: (fileToRemove: File) => void;
|
||||
function formatCharCount(count: number): string {
|
||||
if (count >= 1000) {
|
||||
return `${(count / 1000).toFixed(1)}k`
|
||||
}
|
||||
return String(count)
|
||||
}
|
||||
|
||||
export function FilePreviewList({ files, onRemoveFile }: FilePreviewListProps) {
|
||||
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
||||
interface FilePreviewListProps {
|
||||
files: File[]
|
||||
onRemoveFile: (fileToRemove: File) => void
|
||||
pdfData?: Map<
|
||||
File,
|
||||
{ text: string; charCount: number; isExtracting: boolean }
|
||||
>
|
||||
}
|
||||
|
||||
// Cleanup object URLs on unmount
|
||||
export function FilePreviewList({
|
||||
files,
|
||||
onRemoveFile,
|
||||
pdfData = new Map(),
|
||||
}: FilePreviewListProps) {
|
||||
const dict = useDictionary()
|
||||
const [selectedImage, setSelectedImage] = useState<string | null>(null)
|
||||
const [imageUrls, setImageUrls] = useState<Map<File, string>>(new Map())
|
||||
const imageUrlsRef = useRef<Map<File, string>>(new Map())
|
||||
// Create and cleanup object URLs when files change
|
||||
useEffect(() => {
|
||||
const objectUrls = files
|
||||
.filter((file) => file.type.startsWith("image/"))
|
||||
.map((file) => URL.createObjectURL(file));
|
||||
const currentUrls = imageUrlsRef.current
|
||||
const newUrls = new Map<File, string>()
|
||||
|
||||
files.forEach((file) => {
|
||||
if (file.type.startsWith("image/")) {
|
||||
// Reuse existing URL if file is already tracked
|
||||
const existingUrl = currentUrls.get(file)
|
||||
if (existingUrl) {
|
||||
newUrls.set(file, existingUrl)
|
||||
} else {
|
||||
newUrls.set(file, URL.createObjectURL(file))
|
||||
}
|
||||
}
|
||||
})
|
||||
// Revoke URLs for files that are no longer in the list
|
||||
currentUrls.forEach((url, file) => {
|
||||
if (!newUrls.has(file)) {
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
})
|
||||
|
||||
imageUrlsRef.current = newUrls
|
||||
setImageUrls(newUrls)
|
||||
}, [files])
|
||||
// Cleanup all URLs on unmount only
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
objectUrls.forEach(URL.revokeObjectURL);
|
||||
};
|
||||
}, [files]);
|
||||
imageUrlsRef.current.forEach((url) => {
|
||||
URL.revokeObjectURL(url)
|
||||
})
|
||||
// Clear the ref so StrictMode remount creates fresh URLs
|
||||
imageUrlsRef.current = new Map()
|
||||
}
|
||||
}, [])
|
||||
// Clear selected image if its URL was revoked
|
||||
useEffect(() => {
|
||||
if (
|
||||
selectedImage &&
|
||||
!Array.from(imageUrls.values()).includes(selectedImage)
|
||||
) {
|
||||
setSelectedImage(null)
|
||||
}
|
||||
}, [imageUrls, selectedImage])
|
||||
|
||||
if (files.length === 0) return null;
|
||||
if (files.length === 0) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-wrap gap-2 mt-2 p-2 bg-muted/50 rounded-md">
|
||||
{files.map((file, index) => {
|
||||
const imageUrl = file.type.startsWith("image/") ? URL.createObjectURL(file) : null;
|
||||
const imageUrl = imageUrls.get(file) || null
|
||||
const pdfInfo = pdfData.get(file)
|
||||
return (
|
||||
<div key={file.name + index} className="relative group">
|
||||
<div
|
||||
className="w-20 h-20 border rounded-md overflow-hidden bg-muted cursor-pointer"
|
||||
onClick={() => imageUrl && setSelectedImage(imageUrl)}
|
||||
className={`w-20 h-20 border rounded-md overflow-hidden bg-muted ${
|
||||
file.type.startsWith("image/") && imageUrl
|
||||
? "cursor-pointer"
|
||||
: ""
|
||||
}`}
|
||||
onClick={() =>
|
||||
file.type.startsWith("image/") &&
|
||||
imageUrl &&
|
||||
setSelectedImage(imageUrl)
|
||||
}
|
||||
>
|
||||
{file.type.startsWith("image/") ? (
|
||||
{file.type.startsWith("image/") && imageUrl ? (
|
||||
<Image
|
||||
src={imageUrl!}
|
||||
src={imageUrl}
|
||||
alt={file.name}
|
||||
width={80}
|
||||
height={80}
|
||||
className="object-cover w-full h-full"
|
||||
unoptimized
|
||||
/>
|
||||
) : isPdfFile(file) || isTextFile(file) ? (
|
||||
<div className="flex flex-col items-center justify-center h-full p-1">
|
||||
{pdfInfo?.isExtracting ? (
|
||||
<Loader2 className="h-6 w-6 text-blue-500 mb-1 animate-spin" />
|
||||
) : isPdfFile(file) ? (
|
||||
<FileText className="h-6 w-6 text-red-500 mb-1" />
|
||||
) : (
|
||||
<FileCode className="h-6 w-6 text-blue-500 mb-1" />
|
||||
)}
|
||||
<span className="text-xs text-center truncate w-full px-1">
|
||||
{file.name.length > 10
|
||||
? `${file.name.slice(0, 7)}...`
|
||||
: file.name}
|
||||
</span>
|
||||
{pdfInfo?.isExtracting ? (
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{dict.file.reading}
|
||||
</span>
|
||||
) : pdfInfo?.charCount ? (
|
||||
<span className="text-[10px] text-green-600 font-medium">
|
||||
{formatCharCount(
|
||||
pdfInfo.charCount,
|
||||
)}{" "}
|
||||
{dict.file.chars}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full text-xs text-center p-1">
|
||||
{file.name}
|
||||
@@ -54,15 +145,14 @@ export function FilePreviewList({ files, onRemoveFile }: FilePreviewListProps) {
|
||||
type="button"
|
||||
onClick={() => onRemoveFile(file)}
|
||||
className="absolute -top-2 -right-2 bg-destructive rounded-full p-1 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
aria-label="Remove file"
|
||||
aria-label={dict.file.removeFile}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Image Modal/Lightbox */}
|
||||
{selectedImage && (
|
||||
<div
|
||||
@@ -72,7 +162,7 @@ export function FilePreviewList({ files, onRemoveFile }: FilePreviewListProps) {
|
||||
<button
|
||||
className="absolute top-4 right-4 z-10 bg-white rounded-full p-2 hover:bg-gray-200 transition-colors"
|
||||
onClick={() => setSelectedImage(null)}
|
||||
aria-label="Close"
|
||||
aria-label={dict.common.close}
|
||||
>
|
||||
<X className="h-6 w-6" />
|
||||
</button>
|
||||
@@ -84,10 +174,11 @@ export function FilePreviewList({ files, onRemoveFile }: FilePreviewListProps) {
|
||||
height={900}
|
||||
className="object-contain max-w-full max-h-[90vh] w-auto h-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"use client";
|
||||
"use client"
|
||||
|
||||
import { useState } from "react";
|
||||
import Image from "next/image"
|
||||
import { useState } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -8,51 +10,50 @@ import {
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Image from "next/image";
|
||||
import { useDiagram } from "@/contexts/diagram-context";
|
||||
} from "@/components/ui/dialog"
|
||||
import { useDiagram } from "@/contexts/diagram-context"
|
||||
import { useDictionary } from "@/hooks/use-dictionary"
|
||||
import { formatMessage } from "@/lib/i18n/utils"
|
||||
|
||||
interface HistoryDialogProps {
|
||||
showHistory: boolean;
|
||||
onToggleHistory: (show: boolean) => void;
|
||||
showHistory: boolean
|
||||
onToggleHistory: (show: boolean) => void
|
||||
}
|
||||
|
||||
export function HistoryDialog({
|
||||
showHistory,
|
||||
onToggleHistory,
|
||||
}: HistoryDialogProps) {
|
||||
const { loadDiagram: onDisplayChart, diagramHistory } = useDiagram();
|
||||
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
|
||||
const dict = useDictionary()
|
||||
const { loadDiagram: onDisplayChart, diagramHistory } = useDiagram()
|
||||
const [selectedIndex, setSelectedIndex] = useState<number | null>(null)
|
||||
|
||||
const handleClose = () => {
|
||||
setSelectedIndex(null);
|
||||
onToggleHistory(false);
|
||||
};
|
||||
setSelectedIndex(null)
|
||||
onToggleHistory(false)
|
||||
}
|
||||
|
||||
const handleConfirmRestore = () => {
|
||||
if (selectedIndex !== null) {
|
||||
onDisplayChart(diagramHistory[selectedIndex].xml);
|
||||
handleClose();
|
||||
// Skip validation for trusted history snapshots
|
||||
onDisplayChart(diagramHistory[selectedIndex].xml, true)
|
||||
handleClose()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={showHistory} onOpenChange={onToggleHistory}>
|
||||
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Diagram History</DialogTitle>
|
||||
<DialogTitle>{dict.history.title}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Here saved each diagram before AI modification.
|
||||
<br />
|
||||
Click on a diagram to restore it
|
||||
{dict.history.description}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{diagramHistory.length === 0 ? (
|
||||
<div className="text-center p-4 text-gray-500">
|
||||
No history available yet. Send messages to create
|
||||
diagram history.
|
||||
{dict.history.noHistory}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 py-4">
|
||||
@@ -69,14 +70,14 @@ export function HistoryDialog({
|
||||
<div className="aspect-video bg-white rounded overflow-hidden flex items-center justify-center">
|
||||
<Image
|
||||
src={item.svg}
|
||||
alt={`Diagram version ${index + 1}`}
|
||||
alt={`${dict.history.version} ${index + 1}`}
|
||||
width={200}
|
||||
height={100}
|
||||
className="object-contain w-full h-full p-1"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs text-center mt-1 text-gray-500">
|
||||
Version {index + 1}
|
||||
{dict.history.version} {index + 1}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -87,28 +88,27 @@ export function HistoryDialog({
|
||||
{selectedIndex !== null ? (
|
||||
<>
|
||||
<div className="flex-1 text-sm text-muted-foreground">
|
||||
Restore to Version {selectedIndex + 1}?
|
||||
{formatMessage(dict.history.restoreTo, {
|
||||
version: selectedIndex + 1,
|
||||
})}
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setSelectedIndex(null)}
|
||||
>
|
||||
Cancel
|
||||
{dict.common.cancel}
|
||||
</Button>
|
||||
<Button onClick={handleConfirmRestore}>
|
||||
Confirm
|
||||
{dict.common.confirm}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleClose}
|
||||
>
|
||||
Close
|
||||
<Button variant="outline" onClick={handleClose}>
|
||||
{dict.common.close}
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
108
components/language-toggle.tsx
Normal file
108
components/language-toggle.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
"use client"
|
||||
|
||||
import { Globe } from "lucide-react"
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
||||
import { Suspense, useEffect, useRef, useState } from "react"
|
||||
import { i18n, type Locale } from "@/lib/i18n/config"
|
||||
|
||||
const LABELS: Record<string, string> = {
|
||||
en: "EN",
|
||||
zh: "中文",
|
||||
ja: "日本語",
|
||||
}
|
||||
|
||||
function LanguageToggleInner({ className = "" }: { className?: string }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname() || "/"
|
||||
const search = useSearchParams()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [value, setValue] = useState<Locale>(i18n.defaultLocale)
|
||||
const ref = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const seg = pathname.split("/").filter(Boolean)
|
||||
const first = seg[0]
|
||||
if (first && i18n.locales.includes(first as Locale))
|
||||
setValue(first as Locale)
|
||||
else setValue(i18n.defaultLocale)
|
||||
}, [pathname])
|
||||
|
||||
useEffect(() => {
|
||||
function onDoc(e: MouseEvent) {
|
||||
if (!ref.current) return
|
||||
if (!ref.current.contains(e.target as Node)) setOpen(false)
|
||||
}
|
||||
if (open) document.addEventListener("mousedown", onDoc)
|
||||
return () => document.removeEventListener("mousedown", onDoc)
|
||||
}, [open])
|
||||
|
||||
const changeLocale = (lang: string) => {
|
||||
const parts = pathname.split("/")
|
||||
if (parts.length > 1 && i18n.locales.includes(parts[1] as Locale)) {
|
||||
parts[1] = lang
|
||||
} else {
|
||||
parts.splice(1, 0, lang)
|
||||
}
|
||||
const newPath = parts.join("/") || "/"
|
||||
const searchStr = search?.toString() ? `?${search.toString()}` : ""
|
||||
setOpen(false)
|
||||
router.push(newPath + searchStr)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`relative inline-flex ${className}`} ref={ref}>
|
||||
<button
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={open}
|
||||
onClick={() => setOpen((s) => !s)}
|
||||
className="p-2 rounded-full hover:bg-accent/20 transition-colors text-muted-foreground"
|
||||
aria-label="Change language"
|
||||
>
|
||||
<Globe className="w-5 h-5" />
|
||||
</button>
|
||||
{open && (
|
||||
<div className="absolute right-0 top-full mt-2 w-40 bg-popover dark:bg-popover text-popover-foreground rounded-xl shadow-md border border-border/30 overflow-hidden z-50">
|
||||
<div className="grid gap-0 divide-y divide-border/30">
|
||||
{i18n.locales.map((loc) => (
|
||||
<button
|
||||
key={loc}
|
||||
onClick={() => changeLocale(loc)}
|
||||
className={`flex items-center gap-2 px-4 py-2 text-sm w-full text-left hover:bg-accent/10 transition-colors ${value === loc ? "bg-accent/10 font-semibold" : ""}`}
|
||||
>
|
||||
<span className="flex-1">
|
||||
{LABELS[loc] ?? loc}
|
||||
</span>
|
||||
{value === loc && (
|
||||
<span className="text-xs opacity-70">
|
||||
✓
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function LanguageToggle({
|
||||
className = "",
|
||||
}: {
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<button
|
||||
className="p-2 rounded-full text-muted-foreground opacity-50"
|
||||
disabled
|
||||
>
|
||||
<Globe className="w-5 h-5" />
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<LanguageToggleInner className={className} />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
104
components/quota-limit-toast.tsx
Normal file
104
components/quota-limit-toast.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
"use client"
|
||||
|
||||
import { Coffee, X } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import type React from "react"
|
||||
import { FaGithub } from "react-icons/fa"
|
||||
import { useDictionary } from "@/hooks/use-dictionary"
|
||||
import { formatMessage } from "@/lib/i18n/utils"
|
||||
|
||||
interface QuotaLimitToastProps {
|
||||
type?: "request" | "token"
|
||||
used: number
|
||||
limit: number
|
||||
onDismiss: () => void
|
||||
}
|
||||
|
||||
export function QuotaLimitToast({
|
||||
type = "request",
|
||||
used,
|
||||
limit,
|
||||
onDismiss,
|
||||
}: QuotaLimitToastProps) {
|
||||
const dict = useDictionary()
|
||||
const isTokenLimit = type === "token"
|
||||
const formatNumber = (n: number) =>
|
||||
n >= 1000 ? `${(n / 1000).toFixed(1)}k` : n.toString()
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault()
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
role="alert"
|
||||
aria-live="polite"
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="relative w-[400px] overflow-hidden rounded-xl border border-border/50 bg-card p-5 shadow-soft animate-message-in"
|
||||
>
|
||||
{/* Close button */}
|
||||
<button
|
||||
onClick={onDismiss}
|
||||
className="absolute right-3 top-3 p-1.5 rounded-full text-muted-foreground/60 hover:text-foreground hover:bg-muted transition-colors"
|
||||
aria-label="Dismiss"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
{/* Title row with icon */}
|
||||
<div className="flex items-center gap-2.5 mb-3 pr-6">
|
||||
<div className="flex-shrink-0 w-8 h-8 rounded-lg bg-accent flex items-center justify-center">
|
||||
<Coffee
|
||||
className="w-4 h-4 text-accent-foreground"
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</div>
|
||||
<h3 className="font-semibold text-foreground text-sm">
|
||||
{isTokenLimit
|
||||
? dict.quota.tokenLimit
|
||||
: dict.quota.dailyLimit}
|
||||
</h3>
|
||||
<span className="px-2 py-0.5 text-xs font-medium rounded-md bg-muted text-muted-foreground">
|
||||
{formatMessage(dict.quota.usedOf, {
|
||||
used: formatNumber(used),
|
||||
limit: formatNumber(limit),
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
{/* Message */}
|
||||
<div className="text-sm text-muted-foreground leading-relaxed mb-4 space-y-2">
|
||||
<p>
|
||||
{isTokenLimit
|
||||
? dict.quota.messageToken
|
||||
: dict.quota.messageApi}
|
||||
</p>
|
||||
<p dangerouslySetInnerHTML={{ __html: dict.quota.tip }} />
|
||||
<p>{dict.quota.reset}</p>
|
||||
</div>{" "}
|
||||
{/* Action buttons */}
|
||||
<div className="flex items-center gap-2">
|
||||
<a
|
||||
href="https://github.com/DayuanJiang/next-ai-draw-io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
<FaGithub className="w-3.5 h-3.5" />
|
||||
{dict.quota.selfHost}
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/sponsors/DayuanJiang"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border border-border text-foreground hover:bg-muted transition-colors"
|
||||
>
|
||||
<Coffee className="w-3.5 h-3.5" />
|
||||
{dict.quota.sponsor}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -8,12 +8,13 @@ import {
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
} from "@/components/ui/dialog"
|
||||
import { useDictionary } from "@/hooks/use-dictionary"
|
||||
|
||||
interface ResetWarningModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onClear: () => void;
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
onClear: () => void
|
||||
}
|
||||
|
||||
export function ResetWarningModal({
|
||||
@@ -21,14 +22,15 @@ export function ResetWarningModal({
|
||||
onOpenChange,
|
||||
onClear,
|
||||
}: ResetWarningModalProps) {
|
||||
const dict = useDictionary()
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Clear Everything?</DialogTitle>
|
||||
<DialogTitle>{dict.dialogs.clearTitle}</DialogTitle>
|
||||
<DialogDescription>
|
||||
This will clear the current conversation and reset the
|
||||
diagram. This action cannot be undone.
|
||||
{dict.dialogs.clearDescription}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
@@ -36,13 +38,13 @@ export function ResetWarningModal({
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
Cancel
|
||||
{dict.common.cancel}
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={onClear}>
|
||||
Clear Everything
|
||||
{dict.dialogs.clearEverything}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
"use client";
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useEffect, useState } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { useDictionary } from "@/hooks/use-dictionary"
|
||||
|
||||
export type ExportFormat = "drawio" | "png" | "svg"
|
||||
|
||||
interface SaveDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSave: (filename: string) => void;
|
||||
defaultFilename: string;
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
onSave: (filename: string, format: ExportFormat) => void
|
||||
defaultFilename: string
|
||||
}
|
||||
|
||||
export function SaveDialog({
|
||||
@@ -24,57 +35,112 @@ export function SaveDialog({
|
||||
onSave,
|
||||
defaultFilename,
|
||||
}: SaveDialogProps) {
|
||||
const [filename, setFilename] = useState(defaultFilename);
|
||||
const dict = useDictionary()
|
||||
const [filename, setFilename] = useState(defaultFilename)
|
||||
const [format, setFormat] = useState<ExportFormat>("drawio")
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setFilename(defaultFilename);
|
||||
setFilename(defaultFilename)
|
||||
}
|
||||
}, [open, defaultFilename]);
|
||||
}, [open, defaultFilename])
|
||||
|
||||
const handleSave = () => {
|
||||
const finalFilename = filename.trim() || defaultFilename;
|
||||
onSave(finalFilename);
|
||||
onOpenChange(false);
|
||||
};
|
||||
const finalFilename = filename.trim() || defaultFilename
|
||||
onSave(finalFilename, format)
|
||||
onOpenChange(false)
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
handleSave();
|
||||
e.preventDefault()
|
||||
handleSave()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const FORMAT_OPTIONS = [
|
||||
{
|
||||
value: "drawio" as const,
|
||||
label: dict.save.formats.drawio,
|
||||
extension: ".drawio",
|
||||
},
|
||||
{
|
||||
value: "png" as const,
|
||||
label: dict.save.formats.png,
|
||||
extension: ".png",
|
||||
},
|
||||
{
|
||||
value: "svg" as const,
|
||||
label: dict.save.formats.svg,
|
||||
extension: ".svg",
|
||||
},
|
||||
]
|
||||
|
||||
const currentFormat = FORMAT_OPTIONS.find((f) => f.value === format)
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Save Diagram</DialogTitle>
|
||||
<DialogTitle>{dict.save.title}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{dict.save.description}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Filename</label>
|
||||
<div className="flex items-stretch">
|
||||
<Input
|
||||
value={filename}
|
||||
onChange={(e) => setFilename(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Enter filename"
|
||||
autoFocus
|
||||
onFocus={(e) => e.target.select()}
|
||||
className="rounded-r-none border-r-0 focus-visible:z-10"
|
||||
/>
|
||||
<span className="inline-flex items-center px-3 rounded-r-md border border-l-0 border-input bg-muted text-sm text-muted-foreground font-mono">
|
||||
.drawio
|
||||
</span>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
{dict.save.format}
|
||||
</label>
|
||||
<Select
|
||||
value={format}
|
||||
onValueChange={(v) => setFormat(v as ExportFormat)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{FORMAT_OPTIONS.map((opt) => (
|
||||
<SelectItem
|
||||
key={opt.value}
|
||||
value={opt.value}
|
||||
>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
{dict.save.filename}
|
||||
</label>
|
||||
<div className="flex items-stretch">
|
||||
<Input
|
||||
value={filename}
|
||||
onChange={(e) => setFilename(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={dict.save.filenamePlaceholder}
|
||||
autoFocus
|
||||
onFocus={(e) => e.target.select()}
|
||||
className="rounded-r-none border-r-0 focus-visible:z-10"
|
||||
/>
|
||||
<span className="inline-flex items-center px-3 rounded-r-md border border-l-0 border-input bg-muted text-sm text-muted-foreground font-mono">
|
||||
{currentFormat?.extension || ".drawio"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
{dict.common.cancel}
|
||||
</Button>
|
||||
<Button onClick={handleSave}>Save</Button>
|
||||
<Button onClick={handleSave}>{dict.common.save}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
458
components/settings-dialog.tsx
Normal file
458
components/settings-dialog.tsx
Normal file
@@ -0,0 +1,458 @@
|
||||
"use client"
|
||||
|
||||
import { Moon, Sun } from "lucide-react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { useDictionary } from "@/hooks/use-dictionary"
|
||||
|
||||
interface SettingsDialogProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
onCloseProtectionChange?: (enabled: boolean) => void
|
||||
drawioUi: "min" | "sketch"
|
||||
onToggleDrawioUi: () => void
|
||||
darkMode: boolean
|
||||
onToggleDarkMode: () => void
|
||||
}
|
||||
|
||||
export const STORAGE_ACCESS_CODE_KEY = "next-ai-draw-io-access-code"
|
||||
export const STORAGE_CLOSE_PROTECTION_KEY = "next-ai-draw-io-close-protection"
|
||||
const STORAGE_ACCESS_CODE_REQUIRED_KEY = "next-ai-draw-io-access-code-required"
|
||||
export const STORAGE_AI_PROVIDER_KEY = "next-ai-draw-io-ai-provider"
|
||||
export const STORAGE_AI_BASE_URL_KEY = "next-ai-draw-io-ai-base-url"
|
||||
export const STORAGE_AI_API_KEY_KEY = "next-ai-draw-io-ai-api-key"
|
||||
export const STORAGE_AI_MODEL_KEY = "next-ai-draw-io-ai-model"
|
||||
|
||||
function getStoredAccessCodeRequired(): boolean | null {
|
||||
if (typeof window === "undefined") return null
|
||||
const stored = localStorage.getItem(STORAGE_ACCESS_CODE_REQUIRED_KEY)
|
||||
if (stored === null) return null
|
||||
return stored === "true"
|
||||
}
|
||||
|
||||
export function SettingsDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
onCloseProtectionChange,
|
||||
drawioUi,
|
||||
onToggleDrawioUi,
|
||||
darkMode,
|
||||
onToggleDarkMode,
|
||||
}: SettingsDialogProps) {
|
||||
const dict = useDictionary()
|
||||
const [accessCode, setAccessCode] = useState("")
|
||||
const [closeProtection, setCloseProtection] = useState(true)
|
||||
const [isVerifying, setIsVerifying] = useState(false)
|
||||
const [error, setError] = useState("")
|
||||
const [accessCodeRequired, setAccessCodeRequired] = useState(
|
||||
() => getStoredAccessCodeRequired() ?? false,
|
||||
)
|
||||
const [provider, setProvider] = useState("")
|
||||
const [baseUrl, setBaseUrl] = useState("")
|
||||
const [apiKey, setApiKey] = useState("")
|
||||
const [modelId, setModelId] = useState("")
|
||||
|
||||
useEffect(() => {
|
||||
// Only fetch if not cached in localStorage
|
||||
if (getStoredAccessCodeRequired() !== null) return
|
||||
|
||||
fetch("/api/config")
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
return res.json()
|
||||
})
|
||||
.then((data) => {
|
||||
const required = data?.accessCodeRequired === true
|
||||
localStorage.setItem(
|
||||
STORAGE_ACCESS_CODE_REQUIRED_KEY,
|
||||
String(required),
|
||||
)
|
||||
setAccessCodeRequired(required)
|
||||
})
|
||||
.catch(() => {
|
||||
// Don't cache on error - allow retry on next mount
|
||||
setAccessCodeRequired(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
const storedCode =
|
||||
localStorage.getItem(STORAGE_ACCESS_CODE_KEY) || ""
|
||||
setAccessCode(storedCode)
|
||||
|
||||
const storedCloseProtection = localStorage.getItem(
|
||||
STORAGE_CLOSE_PROTECTION_KEY,
|
||||
)
|
||||
// Default to true if not set
|
||||
setCloseProtection(storedCloseProtection !== "false")
|
||||
|
||||
// Load AI provider settings
|
||||
setProvider(localStorage.getItem(STORAGE_AI_PROVIDER_KEY) || "")
|
||||
setBaseUrl(localStorage.getItem(STORAGE_AI_BASE_URL_KEY) || "")
|
||||
setApiKey(localStorage.getItem(STORAGE_AI_API_KEY_KEY) || "")
|
||||
setModelId(localStorage.getItem(STORAGE_AI_MODEL_KEY) || "")
|
||||
|
||||
setError("")
|
||||
}
|
||||
}, [open])
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!accessCodeRequired) return
|
||||
|
||||
setError("")
|
||||
setIsVerifying(true)
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/verify-access-code", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"x-access-code": accessCode.trim(),
|
||||
},
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.valid) {
|
||||
setError(data.message || dict.errors.invalidAccessCode)
|
||||
return
|
||||
}
|
||||
|
||||
localStorage.setItem(STORAGE_ACCESS_CODE_KEY, accessCode.trim())
|
||||
onOpenChange(false)
|
||||
} catch {
|
||||
setError(dict.errors.networkError)
|
||||
} finally {
|
||||
setIsVerifying(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault()
|
||||
handleSave()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{dict.settings.title}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{dict.settings.description}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-2">
|
||||
{accessCodeRequired && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="access-code">
|
||||
{dict.settings.accessCode}
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="access-code"
|
||||
type="password"
|
||||
value={accessCode}
|
||||
onChange={(e) =>
|
||||
setAccessCode(e.target.value)
|
||||
}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={
|
||||
dict.settings.accessCodePlaceholder
|
||||
}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isVerifying || !accessCode.trim()}
|
||||
>
|
||||
{isVerifying ? "..." : dict.common.save}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-[0.8rem] text-muted-foreground">
|
||||
{dict.settings.accessCodeDescription}
|
||||
</p>
|
||||
{error && (
|
||||
<p className="text-[0.8rem] text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<Label>{dict.settings.aiProvider}</Label>
|
||||
<p className="text-[0.8rem] text-muted-foreground">
|
||||
{dict.settings.aiProviderDescription}
|
||||
</p>
|
||||
<div className="space-y-3 pt-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ai-provider">
|
||||
{dict.settings.provider}
|
||||
</Label>
|
||||
<Select
|
||||
value={provider || "default"}
|
||||
onValueChange={(value) => {
|
||||
const actualValue =
|
||||
value === "default" ? "" : value
|
||||
setProvider(actualValue)
|
||||
localStorage.setItem(
|
||||
STORAGE_AI_PROVIDER_KEY,
|
||||
actualValue,
|
||||
)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger id="ai-provider">
|
||||
<SelectValue
|
||||
placeholder={
|
||||
dict.settings.useServerDefault
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="default">
|
||||
{dict.settings.useServerDefault}
|
||||
</SelectItem>
|
||||
<SelectItem value="openai">
|
||||
{dict.providers.openai}
|
||||
</SelectItem>
|
||||
<SelectItem value="anthropic">
|
||||
{dict.providers.anthropic}
|
||||
</SelectItem>
|
||||
<SelectItem value="google">
|
||||
{dict.providers.google}
|
||||
</SelectItem>
|
||||
<SelectItem value="azure">
|
||||
{dict.providers.azure}
|
||||
</SelectItem>
|
||||
<SelectItem value="openrouter">
|
||||
{dict.providers.openrouter}
|
||||
</SelectItem>
|
||||
<SelectItem value="deepseek">
|
||||
{dict.providers.deepseek}
|
||||
</SelectItem>
|
||||
<SelectItem value="siliconflow">
|
||||
{dict.providers.siliconflow}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{provider && provider !== "default" && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ai-model">
|
||||
{dict.settings.modelId}
|
||||
</Label>
|
||||
<Input
|
||||
id="ai-model"
|
||||
value={modelId}
|
||||
onChange={(e) => {
|
||||
setModelId(e.target.value)
|
||||
localStorage.setItem(
|
||||
STORAGE_AI_MODEL_KEY,
|
||||
e.target.value,
|
||||
)
|
||||
}}
|
||||
placeholder={
|
||||
provider === "openai"
|
||||
? "e.g., gpt-4o"
|
||||
: provider === "anthropic"
|
||||
? "e.g., claude-sonnet-4-5"
|
||||
: provider === "google"
|
||||
? "e.g., gemini-2.0-flash-exp"
|
||||
: provider ===
|
||||
"deepseek"
|
||||
? "e.g., deepseek-chat"
|
||||
: dict.settings
|
||||
.modelId
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ai-api-key">
|
||||
{dict.settings.apiKey}
|
||||
</Label>
|
||||
<Input
|
||||
id="ai-api-key"
|
||||
type="password"
|
||||
value={apiKey}
|
||||
onChange={(e) => {
|
||||
setApiKey(e.target.value)
|
||||
localStorage.setItem(
|
||||
STORAGE_AI_API_KEY_KEY,
|
||||
e.target.value,
|
||||
)
|
||||
}}
|
||||
placeholder={
|
||||
dict.settings.apiKeyPlaceholder
|
||||
}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<p className="text-[0.8rem] text-muted-foreground">
|
||||
{dict.settings.overrides}{" "}
|
||||
{provider === "openai"
|
||||
? "OPENAI_API_KEY"
|
||||
: provider === "anthropic"
|
||||
? "ANTHROPIC_API_KEY"
|
||||
: provider === "google"
|
||||
? "GOOGLE_GENERATIVE_AI_API_KEY"
|
||||
: provider === "azure"
|
||||
? "AZURE_API_KEY"
|
||||
: provider ===
|
||||
"openrouter"
|
||||
? "OPENROUTER_API_KEY"
|
||||
: provider ===
|
||||
"deepseek"
|
||||
? "DEEPSEEK_API_KEY"
|
||||
: provider ===
|
||||
"siliconflow"
|
||||
? "SILICONFLOW_API_KEY"
|
||||
: "server API key"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ai-base-url">
|
||||
{dict.settings.baseUrl}
|
||||
</Label>
|
||||
<Input
|
||||
id="ai-base-url"
|
||||
value={baseUrl}
|
||||
onChange={(e) => {
|
||||
setBaseUrl(e.target.value)
|
||||
localStorage.setItem(
|
||||
STORAGE_AI_BASE_URL_KEY,
|
||||
e.target.value,
|
||||
)
|
||||
}}
|
||||
placeholder={
|
||||
provider === "anthropic"
|
||||
? "https://api.anthropic.com/v1"
|
||||
: provider === "siliconflow"
|
||||
? "https://api.siliconflow.com/v1"
|
||||
: dict.settings
|
||||
.customEndpoint
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
localStorage.removeItem(
|
||||
STORAGE_AI_PROVIDER_KEY,
|
||||
)
|
||||
localStorage.removeItem(
|
||||
STORAGE_AI_BASE_URL_KEY,
|
||||
)
|
||||
localStorage.removeItem(
|
||||
STORAGE_AI_API_KEY_KEY,
|
||||
)
|
||||
localStorage.removeItem(
|
||||
STORAGE_AI_MODEL_KEY,
|
||||
)
|
||||
setProvider("")
|
||||
setBaseUrl("")
|
||||
setApiKey("")
|
||||
setModelId("")
|
||||
}}
|
||||
>
|
||||
{dict.settings.clearSettings}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="theme-toggle">
|
||||
{dict.settings.theme}
|
||||
</Label>
|
||||
<p className="text-[0.8rem] text-muted-foreground">
|
||||
{dict.settings.themeDescription}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
id="theme-toggle"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={onToggleDarkMode}
|
||||
>
|
||||
{darkMode ? (
|
||||
<Sun className="h-4 w-4" />
|
||||
) : (
|
||||
<Moon className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="drawio-ui">
|
||||
{dict.settings.drawioStyle}
|
||||
</Label>
|
||||
<p className="text-[0.8rem] text-muted-foreground">
|
||||
{dict.settings.drawioStyleDescription}{" "}
|
||||
{drawioUi === "min"
|
||||
? dict.settings.minimal
|
||||
: dict.settings.sketch}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
id="drawio-ui"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onToggleDrawioUi}
|
||||
>
|
||||
{dict.settings.switchTo}{" "}
|
||||
{drawioUi === "min"
|
||||
? dict.settings.sketch
|
||||
: dict.settings.minimal}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="close-protection">
|
||||
{dict.settings.closeProtection}
|
||||
</Label>
|
||||
<p className="text-[0.8rem] text-muted-foreground">
|
||||
{dict.settings.closeProtectionDescription}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="close-protection"
|
||||
checked={closeProtection}
|
||||
onCheckedChange={(checked) => {
|
||||
setCloseProtection(checked)
|
||||
localStorage.setItem(
|
||||
STORAGE_CLOSE_PROTECTION_KEY,
|
||||
checked.toString(),
|
||||
)
|
||||
onCloseProtectionChange?.(checked)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -10,7 +10,7 @@ const buttonVariants = cva(
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
||||
"bg-primary text-primary-foreground shadow-xs hover:brightness-75",
|
||||
destructive:
|
||||
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
|
||||
33
components/ui/collapsible.tsx
Normal file
33
components/ui/collapsible.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
"use client"
|
||||
|
||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
||||
|
||||
function Collapsible({
|
||||
...props
|
||||
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
||||
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
|
||||
}
|
||||
|
||||
function CollapsibleTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
|
||||
return (
|
||||
<CollapsiblePrimitive.CollapsibleTrigger
|
||||
data-slot="collapsible-trigger"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CollapsibleContent({
|
||||
...props
|
||||
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
|
||||
return (
|
||||
<CollapsiblePrimitive.CollapsibleContent
|
||||
data-slot="collapsible-content"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
||||
24
components/ui/label.tsx
Normal file
24
components/ui/label.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Label({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||
return (
|
||||
<LabelPrimitive.Root
|
||||
data-slot="label"
|
||||
className={cn(
|
||||
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Label }
|
||||
56
components/ui/resizable.tsx
Normal file
56
components/ui/resizable.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { GripVerticalIcon } from "lucide-react"
|
||||
import * as ResizablePrimitive from "react-resizable-panels"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function ResizablePanelGroup({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) {
|
||||
return (
|
||||
<ResizablePrimitive.PanelGroup
|
||||
data-slot="resizable-panel-group"
|
||||
className={cn(
|
||||
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ResizablePanel({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
|
||||
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
|
||||
}
|
||||
|
||||
function ResizableHandle({
|
||||
withHandle,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
|
||||
withHandle?: boolean
|
||||
}) {
|
||||
return (
|
||||
<ResizablePrimitive.PanelResizeHandle
|
||||
data-slot="resizable-handle"
|
||||
className={cn(
|
||||
"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{withHandle && (
|
||||
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
|
||||
<GripVerticalIcon className="size-2.5" />
|
||||
</div>
|
||||
)}
|
||||
</ResizablePrimitive.PanelResizeHandle>
|
||||
)
|
||||
}
|
||||
|
||||
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
|
||||
@@ -18,7 +18,7 @@ function ScrollArea({
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport
|
||||
data-slot="scroll-area-viewport"
|
||||
className="ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] focus-visible:ring-4 focus-visible:outline-1"
|
||||
className="ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] focus-visible:ring-4 focus-visible:outline-1 !overflow-x-hidden"
|
||||
>
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
|
||||
187
components/ui/select.tsx
Normal file
187
components/ui/select.tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Select({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||
return <SelectPrimitive.Root data-slot="select" {...props} />
|
||||
}
|
||||
|
||||
function SelectGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />
|
||||
}
|
||||
|
||||
function SelectValue({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
||||
}
|
||||
|
||||
function SelectTrigger({
|
||||
className,
|
||||
size = "default",
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||
size?: "sm" | "default"
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Trigger
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDownIcon className="size-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectContent({
|
||||
className,
|
||||
children,
|
||||
position = "popper",
|
||||
align = "center",
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
||||
return (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
data-slot="select-content"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
)}
|
||||
position={position}
|
||||
align={align}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectLabel({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
||||
return (
|
||||
<SelectPrimitive.Label
|
||||
data-slot="select-label"
|
||||
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
||||
return (
|
||||
<SelectPrimitive.Item
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute right-2 flex size-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
||||
return (
|
||||
<SelectPrimitive.Separator
|
||||
data-slot="select-separator"
|
||||
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectScrollUpButton({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
data-slot="select-scroll-up-button"
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectScrollDownButton({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
data-slot="select-scroll-down-button"
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectScrollDownButton,
|
||||
SelectScrollUpButton,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
}
|
||||
31
components/ui/switch.tsx
Normal file
31
components/ui/switch.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SwitchPrimitive from "@radix-ui/react-switch"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Switch({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
|
||||
return (
|
||||
<SwitchPrimitive.Root
|
||||
data-slot="switch"
|
||||
className={cn(
|
||||
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SwitchPrimitive.Thumb
|
||||
data-slot="switch-thumb"
|
||||
className={cn(
|
||||
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export { Switch }
|
||||
@@ -1,130 +1,343 @@
|
||||
"use client";
|
||||
"use client"
|
||||
|
||||
import React, { createContext, useContext, useRef, useState } from "react";
|
||||
import type { DrawIoEmbedRef } from "react-drawio";
|
||||
import { extractDiagramXML } from "../lib/utils";
|
||||
import type React from "react"
|
||||
import { createContext, useContext, useEffect, useRef, useState } from "react"
|
||||
import type { DrawIoEmbedRef } from "react-drawio"
|
||||
import { STORAGE_DIAGRAM_XML_KEY } from "@/components/chat-panel"
|
||||
import type { ExportFormat } from "@/components/save-dialog"
|
||||
import { extractDiagramXML, validateAndFixXml } from "../lib/utils"
|
||||
|
||||
interface DiagramContextType {
|
||||
chartXML: string;
|
||||
latestSvg: string;
|
||||
diagramHistory: { svg: string; xml: string }[];
|
||||
loadDiagram: (chart: string) => void;
|
||||
handleExport: () => void;
|
||||
handleExportWithoutHistory: () => void;
|
||||
resolverRef: React.Ref<((value: string) => void) | null>;
|
||||
drawioRef: React.Ref<DrawIoEmbedRef | null>;
|
||||
handleDiagramExport: (data: any) => void;
|
||||
clearDiagram: () => void;
|
||||
saveDiagramToFile: (filename: string) => void;
|
||||
chartXML: string
|
||||
latestSvg: string
|
||||
diagramHistory: { svg: string; xml: string }[]
|
||||
loadDiagram: (chart: string, skipValidation?: boolean) => string | null
|
||||
handleExport: () => void
|
||||
handleExportWithoutHistory: () => void
|
||||
resolverRef: React.Ref<((value: string) => void) | null>
|
||||
drawioRef: React.Ref<DrawIoEmbedRef | null>
|
||||
handleDiagramExport: (data: any) => void
|
||||
clearDiagram: () => void
|
||||
saveDiagramToFile: (
|
||||
filename: string,
|
||||
format: ExportFormat,
|
||||
sessionId?: string,
|
||||
) => void
|
||||
saveDiagramToStorage: () => Promise<void>
|
||||
isDrawioReady: boolean
|
||||
onDrawioLoad: () => void
|
||||
resetDrawioReady: () => void
|
||||
showSaveDialog: boolean
|
||||
setShowSaveDialog: (show: boolean) => void
|
||||
}
|
||||
|
||||
const DiagramContext = createContext<DiagramContextType | undefined>(undefined);
|
||||
const DiagramContext = createContext<DiagramContextType | undefined>(undefined)
|
||||
|
||||
export function DiagramProvider({ children }: { children: React.ReactNode }) {
|
||||
const [chartXML, setChartXML] = useState<string>("");
|
||||
const [latestSvg, setLatestSvg] = useState<string>("");
|
||||
const [chartXML, setChartXML] = useState<string>("")
|
||||
const [latestSvg, setLatestSvg] = useState<string>("")
|
||||
const [diagramHistory, setDiagramHistory] = useState<
|
||||
{ svg: string; xml: string }[]
|
||||
>([]);
|
||||
const drawioRef = useRef<DrawIoEmbedRef | null>(null);
|
||||
const resolverRef = useRef<((value: string) => void) | null>(null);
|
||||
>([])
|
||||
const [isDrawioReady, setIsDrawioReady] = useState(false)
|
||||
const [canSaveDiagram, setCanSaveDiagram] = useState(false)
|
||||
const [showSaveDialog, setShowSaveDialog] = useState(false)
|
||||
const hasCalledOnLoadRef = useRef(false)
|
||||
const drawioRef = useRef<DrawIoEmbedRef | null>(null)
|
||||
const resolverRef = useRef<((value: string) => void) | null>(null)
|
||||
// Track if we're expecting an export for history (user-initiated)
|
||||
const expectHistoryExportRef = useRef<boolean>(false);
|
||||
// Track if we're expecting an export for file save
|
||||
const saveResolverRef = useRef<((xml: string) => void) | null>(null);
|
||||
const expectHistoryExportRef = useRef<boolean>(false)
|
||||
// Track if diagram has been restored from localStorage
|
||||
const hasDiagramRestoredRef = useRef<boolean>(false)
|
||||
|
||||
const onDrawioLoad = () => {
|
||||
// Only set ready state once to prevent infinite loops
|
||||
if (hasCalledOnLoadRef.current) return
|
||||
hasCalledOnLoadRef.current = true
|
||||
// console.log("[DiagramContext] DrawIO loaded, setting ready state")
|
||||
setIsDrawioReady(true)
|
||||
}
|
||||
|
||||
const resetDrawioReady = () => {
|
||||
// console.log("[DiagramContext] Resetting DrawIO ready state")
|
||||
hasCalledOnLoadRef.current = false
|
||||
setIsDrawioReady(false)
|
||||
}
|
||||
|
||||
// Restore diagram XML when DrawIO becomes ready
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- loadDiagram uses refs internally and is stable
|
||||
useEffect(() => {
|
||||
// Reset restore flag when DrawIO is not ready (e.g., theme/UI change remounts it)
|
||||
if (!isDrawioReady) {
|
||||
hasDiagramRestoredRef.current = false
|
||||
setCanSaveDiagram(false)
|
||||
return
|
||||
}
|
||||
if (hasDiagramRestoredRef.current) return
|
||||
hasDiagramRestoredRef.current = true
|
||||
|
||||
try {
|
||||
const savedDiagramXml = localStorage.getItem(
|
||||
STORAGE_DIAGRAM_XML_KEY,
|
||||
)
|
||||
if (savedDiagramXml) {
|
||||
// Skip validation for trusted saved diagrams
|
||||
loadDiagram(savedDiagramXml, true)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to restore diagram from localStorage:", error)
|
||||
}
|
||||
|
||||
// Allow saving after restore is complete
|
||||
setTimeout(() => {
|
||||
setCanSaveDiagram(true)
|
||||
}, 500)
|
||||
}, [isDrawioReady])
|
||||
|
||||
// Save diagram XML to localStorage whenever it changes (debounced)
|
||||
useEffect(() => {
|
||||
if (!canSaveDiagram) return
|
||||
if (!chartXML || chartXML.length <= 300) return
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
localStorage.setItem(STORAGE_DIAGRAM_XML_KEY, chartXML)
|
||||
}, 1000)
|
||||
|
||||
return () => clearTimeout(timeoutId)
|
||||
}, [chartXML, canSaveDiagram])
|
||||
|
||||
// Track if we're expecting an export for file save (stores raw export data)
|
||||
const saveResolverRef = useRef<{
|
||||
resolver: ((data: string) => void) | null
|
||||
format: ExportFormat | null
|
||||
}>({ resolver: null, format: null })
|
||||
|
||||
const handleExport = () => {
|
||||
if (drawioRef.current) {
|
||||
// Mark that this export should be saved to history
|
||||
expectHistoryExportRef.current = true;
|
||||
expectHistoryExportRef.current = true
|
||||
drawioRef.current.exportDiagram({
|
||||
format: "xmlsvg",
|
||||
});
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleExportWithoutHistory = () => {
|
||||
if (drawioRef.current) {
|
||||
// Export without saving to history (for edit_diagram fetching current state)
|
||||
drawioRef.current.exportDiagram({
|
||||
format: "xmlsvg",
|
||||
});
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Save current diagram to localStorage (used before theme/UI changes)
|
||||
const saveDiagramToStorage = async (): Promise<void> => {
|
||||
if (!drawioRef.current) return
|
||||
|
||||
try {
|
||||
const currentXml = await Promise.race([
|
||||
new Promise<string>((resolve) => {
|
||||
resolverRef.current = resolve
|
||||
drawioRef.current?.exportDiagram({ format: "xmlsvg" })
|
||||
}),
|
||||
new Promise<string>((_, reject) =>
|
||||
setTimeout(() => reject(new Error("Export timeout")), 2000),
|
||||
),
|
||||
])
|
||||
|
||||
// Only save if diagram has meaningful content (not empty template)
|
||||
if (currentXml && currentXml.length > 300) {
|
||||
localStorage.setItem(STORAGE_DIAGRAM_XML_KEY, currentXml)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save diagram to storage:", error)
|
||||
}
|
||||
}
|
||||
|
||||
const loadDiagram = (
|
||||
chart: string,
|
||||
skipValidation?: boolean,
|
||||
): string | null => {
|
||||
let xmlToLoad = chart
|
||||
|
||||
// Validate XML structure before loading (unless skipped for internal use)
|
||||
if (!skipValidation) {
|
||||
const validation = validateAndFixXml(chart)
|
||||
if (!validation.valid) {
|
||||
console.warn(
|
||||
"[loadDiagram] Validation error:",
|
||||
validation.error,
|
||||
)
|
||||
return validation.error
|
||||
}
|
||||
// Use fixed XML if auto-fix was applied
|
||||
if (validation.fixed) {
|
||||
console.log(
|
||||
"[loadDiagram] Auto-fixed XML issues:",
|
||||
validation.fixes,
|
||||
)
|
||||
xmlToLoad = validation.fixed
|
||||
}
|
||||
}
|
||||
|
||||
// Keep chartXML in sync even when diagrams are injected (e.g., display_diagram tool)
|
||||
setChartXML(xmlToLoad)
|
||||
|
||||
const loadDiagram = (chart: string) => {
|
||||
if (drawioRef.current) {
|
||||
drawioRef.current.load({
|
||||
xml: chart,
|
||||
});
|
||||
xml: xmlToLoad,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const handleDiagramExport = (data: any) => {
|
||||
const extractedXML = extractDiagramXML(data.data);
|
||||
setChartXML(extractedXML);
|
||||
setLatestSvg(data.data);
|
||||
// Handle save to file if requested (process raw data before extraction)
|
||||
if (saveResolverRef.current.resolver) {
|
||||
const format = saveResolverRef.current.format
|
||||
saveResolverRef.current.resolver(data.data)
|
||||
saveResolverRef.current = { resolver: null, format: null }
|
||||
// For non-xmlsvg formats, skip XML extraction as it will fail
|
||||
// Only drawio (which uses xmlsvg internally) has the content attribute
|
||||
if (format === "png" || format === "svg") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const extractedXML = extractDiagramXML(data.data)
|
||||
setChartXML(extractedXML)
|
||||
setLatestSvg(data.data)
|
||||
|
||||
// Only add to history if this was a user-initiated export
|
||||
// Limit to 20 entries to prevent memory leaks during long sessions
|
||||
const MAX_HISTORY_SIZE = 20
|
||||
if (expectHistoryExportRef.current) {
|
||||
setDiagramHistory((prev) => [
|
||||
...prev,
|
||||
{
|
||||
svg: data.data,
|
||||
xml: extractedXML,
|
||||
},
|
||||
]);
|
||||
expectHistoryExportRef.current = false;
|
||||
setDiagramHistory((prev) => {
|
||||
const newHistory = [
|
||||
...prev,
|
||||
{
|
||||
svg: data.data,
|
||||
xml: extractedXML,
|
||||
},
|
||||
]
|
||||
// Keep only the last MAX_HISTORY_SIZE entries (circular buffer)
|
||||
return newHistory.slice(-MAX_HISTORY_SIZE)
|
||||
})
|
||||
expectHistoryExportRef.current = false
|
||||
}
|
||||
|
||||
if (resolverRef.current) {
|
||||
resolverRef.current(extractedXML);
|
||||
resolverRef.current = null;
|
||||
resolverRef.current(extractedXML)
|
||||
resolverRef.current = null
|
||||
}
|
||||
|
||||
// Handle save to file if requested
|
||||
if (saveResolverRef.current) {
|
||||
saveResolverRef.current(extractedXML);
|
||||
saveResolverRef.current = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const clearDiagram = () => {
|
||||
const emptyDiagram = `<mxfile><diagram name="Page-1" id="page-1"><mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel></diagram></mxfile>`;
|
||||
loadDiagram(emptyDiagram);
|
||||
setChartXML(emptyDiagram);
|
||||
setLatestSvg("");
|
||||
setDiagramHistory([]);
|
||||
};
|
||||
const emptyDiagram = `<mxfile><diagram name="Page-1" id="page-1"><mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel></diagram></mxfile>`
|
||||
// Skip validation for trusted internal template (loadDiagram also sets chartXML)
|
||||
loadDiagram(emptyDiagram, true)
|
||||
setLatestSvg("")
|
||||
setDiagramHistory([])
|
||||
}
|
||||
|
||||
const saveDiagramToFile = (filename: string) => {
|
||||
const saveDiagramToFile = (
|
||||
filename: string,
|
||||
format: ExportFormat,
|
||||
sessionId?: string,
|
||||
) => {
|
||||
if (!drawioRef.current) {
|
||||
console.warn("Draw.io editor not ready");
|
||||
return;
|
||||
console.warn("Draw.io editor not ready")
|
||||
return
|
||||
}
|
||||
|
||||
// Export diagram and save when export completes
|
||||
drawioRef.current.exportDiagram({ format: "xmlsvg" });
|
||||
saveResolverRef.current = (xml: string) => {
|
||||
// Wrap in proper .drawio format
|
||||
let fileContent = xml;
|
||||
if (!xml.includes("<mxfile")) {
|
||||
fileContent = `<mxfile><diagram name="Page-1" id="page-1">${xml}</diagram></mxfile>`;
|
||||
}
|
||||
// Map format to draw.io export format
|
||||
const drawioFormat = format === "drawio" ? "xmlsvg" : format
|
||||
|
||||
const blob = new Blob([fileContent], { type: "application/xml" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
// Add .drawio extension if not present
|
||||
a.download = filename.endsWith(".drawio") ? filename : `${filename}.drawio`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
// Delay URL revocation to ensure download completes
|
||||
setTimeout(() => URL.revokeObjectURL(url), 100);
|
||||
};
|
||||
};
|
||||
// Set up the resolver before triggering export
|
||||
saveResolverRef.current = {
|
||||
resolver: (exportData: string) => {
|
||||
let fileContent: string | Blob
|
||||
let mimeType: string
|
||||
let extension: string
|
||||
|
||||
if (format === "drawio") {
|
||||
// Extract XML from SVG for .drawio format
|
||||
const xml = extractDiagramXML(exportData)
|
||||
let xmlContent = xml
|
||||
if (!xml.includes("<mxfile")) {
|
||||
xmlContent = `<mxfile><diagram name="Page-1" id="page-1">${xml}</diagram></mxfile>`
|
||||
}
|
||||
fileContent = xmlContent
|
||||
mimeType = "application/xml"
|
||||
extension = ".drawio"
|
||||
|
||||
// Save to localStorage when user manually saves
|
||||
localStorage.setItem(STORAGE_DIAGRAM_XML_KEY, xmlContent)
|
||||
} else if (format === "png") {
|
||||
// PNG data comes as base64 data URL
|
||||
fileContent = exportData
|
||||
mimeType = "image/png"
|
||||
extension = ".png"
|
||||
} else {
|
||||
// SVG format
|
||||
fileContent = exportData
|
||||
mimeType = "image/svg+xml"
|
||||
extension = ".svg"
|
||||
}
|
||||
|
||||
// Log save event to Langfuse (flags the trace)
|
||||
logSaveToLangfuse(filename, format, sessionId)
|
||||
|
||||
// Handle download
|
||||
let url: string
|
||||
if (
|
||||
typeof fileContent === "string" &&
|
||||
fileContent.startsWith("data:")
|
||||
) {
|
||||
// Already a data URL (PNG)
|
||||
url = fileContent
|
||||
} else {
|
||||
const blob = new Blob([fileContent], { type: mimeType })
|
||||
url = URL.createObjectURL(blob)
|
||||
}
|
||||
|
||||
const a = document.createElement("a")
|
||||
a.href = url
|
||||
a.download = `${filename}${extension}`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
|
||||
// Delay URL revocation to ensure download completes
|
||||
if (!url.startsWith("data:")) {
|
||||
setTimeout(() => URL.revokeObjectURL(url), 100)
|
||||
}
|
||||
},
|
||||
format,
|
||||
}
|
||||
|
||||
// Export diagram - callback will be handled in handleDiagramExport
|
||||
drawioRef.current.exportDiagram({ format: drawioFormat })
|
||||
}
|
||||
|
||||
// Log save event to Langfuse (just flags the trace, doesn't send content)
|
||||
const logSaveToLangfuse = async (
|
||||
filename: string,
|
||||
format: string,
|
||||
sessionId?: string,
|
||||
) => {
|
||||
try {
|
||||
await fetch("/api/log-save", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ filename, format, sessionId }),
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn("Failed to log save to Langfuse:", error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<DiagramContext.Provider
|
||||
@@ -140,17 +353,23 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
|
||||
handleDiagramExport,
|
||||
clearDiagram,
|
||||
saveDiagramToFile,
|
||||
saveDiagramToStorage,
|
||||
isDrawioReady,
|
||||
onDrawioLoad,
|
||||
resetDrawioReady,
|
||||
showSaveDialog,
|
||||
setShowSaveDialog,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</DiagramContext.Provider>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export function useDiagram() {
|
||||
const context = useContext(DiagramContext);
|
||||
const context = useContext(DiagramContext)
|
||||
if (context === undefined) {
|
||||
throw new Error("useDiagram must be used within a DiagramProvider");
|
||||
throw new Error("useDiagram must be used within a DiagramProvider")
|
||||
}
|
||||
return context;
|
||||
return context
|
||||
}
|
||||
|
||||
12
docker-compose.yml
Normal file
12
docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
services:
|
||||
drawio:
|
||||
image: jgraph/drawio:latest
|
||||
ports: ["8080:8080"]
|
||||
next-ai-draw-io:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
- NEXT_PUBLIC_DRAWIO_BASE_URL=http://localhost:8080
|
||||
ports: ["3000:3000"]
|
||||
env_file: .env
|
||||
depends_on: [drawio]
|
||||
@@ -4,14 +4,16 @@
|
||||
|
||||
**AI驱动的图表创建工具 - 对话、绘制、可视化**
|
||||
|
||||
[English](./README.md) | 中文 | [日本語](./README_JA.md)
|
||||
[English](../README.md) | 中文 | [日本語](./README_JA.md)
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://nextjs.org/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://next-ai-drawio.jiang.jp/)
|
||||
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://nextjs.org/)
|
||||
[](https://react.dev/)
|
||||
[](https://github.com/sponsors/DayuanJiang)
|
||||
|
||||
[🚀 在线演示](https://next-ai-drawio.jiang.jp/)
|
||||
[](https://next-ai-drawio.jiang.jp/)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -19,16 +21,24 @@
|
||||
|
||||
https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
|
||||
|
||||
## 功能特性
|
||||
## 目录
|
||||
- [Next AI Draw.io](#next-ai-drawio)
|
||||
- [目录](#目录)
|
||||
- [示例](#示例)
|
||||
- [功能特性](#功能特性)
|
||||
- [MCP服务器(预览)](#mcp服务器预览)
|
||||
- [快速开始](#快速开始)
|
||||
- [在线试用](#在线试用)
|
||||
- [使用Docker运行(推荐)](#使用docker运行推荐)
|
||||
- [安装](#安装)
|
||||
- [部署](#部署)
|
||||
- [多提供商支持](#多提供商支持)
|
||||
- [工作原理](#工作原理)
|
||||
- [项目结构](#项目结构)
|
||||
- [支持与联系](#支持与联系)
|
||||
- [Star历史](#star历史)
|
||||
|
||||
- **LLM驱动的图表创建**:利用大语言模型通过自然语言命令直接创建和操作draw.io图表
|
||||
- **基于图像的图表复制**:上传现有图表或图像,让AI自动复制和增强
|
||||
- **图表历史记录**:全面的版本控制,跟踪所有更改,允许您查看和恢复AI编辑前的图表版本
|
||||
- **交互式聊天界面**:与AI实时对话来完善您的图表
|
||||
- **AWS架构图支持**:专门支持生成AWS架构图
|
||||
- **动画连接器**:在图表元素之间创建动态动画连接器,实现更好的可视化效果
|
||||
|
||||
## **示例**
|
||||
## 示例
|
||||
|
||||
以下是一些示例提示词及其生成的图表:
|
||||
|
||||
@@ -38,61 +48,89 @@ https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
|
||||
<td colspan="2" valign="top" align="center">
|
||||
<strong>动画Transformer连接器</strong><br />
|
||||
<p><strong>提示词:</strong> 给我一个带有**动画连接器**的Transformer架构图。</p>
|
||||
<img src="./public/animated_connectors.svg" alt="带动画连接器的Transformer架构" width="480" />
|
||||
<img src="../public/animated_connectors.svg" alt="带动画连接器的Transformer架构" width="480" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" valign="top">
|
||||
<strong>GCP架构图</strong><br />
|
||||
<p><strong>提示词:</strong> 使用**GCP图标**生成一个GCP架构图。在这个图中,用户连接到托管在实例上的前端。</p>
|
||||
<img src="./public/gcp_demo.svg" alt="GCP架构图" width="480" />
|
||||
<img src="../public/gcp_demo.svg" alt="GCP架构图" width="480" />
|
||||
</td>
|
||||
<td width="50%" valign="top">
|
||||
<strong>AWS架构图</strong><br />
|
||||
<p><strong>提示词:</strong> 使用**AWS图标**生成一个AWS架构图。在这个图中,用户连接到托管在实例上的前端。</p>
|
||||
<img src="./public/aws_demo.svg" alt="AWS架构图" width="480" />
|
||||
<img src="../public/aws_demo.svg" alt="AWS架构图" width="480" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" valign="top">
|
||||
<strong>Azure架构图</strong><br />
|
||||
<p><strong>提示词:</strong> 使用**Azure图标**生成一个Azure架构图。在这个图中,用户连接到托管在实例上的前端。</p>
|
||||
<img src="./public/azure_demo.svg" alt="Azure架构图" width="480" />
|
||||
<img src="../public/azure_demo.svg" alt="Azure架构图" width="480" />
|
||||
</td>
|
||||
<td width="50%" valign="top">
|
||||
<strong>猫咪素描</strong><br />
|
||||
<p><strong>提示词:</strong> 给我画一只可爱的猫。</p>
|
||||
<img src="./public/cat_demo.svg" alt="猫咪绘图" width="240" />
|
||||
<img src="../public/cat_demo.svg" alt="猫咪绘图" width="240" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
## 工作原理
|
||||
## 功能特性
|
||||
|
||||
本应用使用以下技术:
|
||||
- **LLM驱动的图表创建**:利用大语言模型通过自然语言命令直接创建和操作draw.io图表
|
||||
- **基于图像的图表复制**:上传现有图表或图像,让AI自动复制和增强
|
||||
- **PDF和文本文件上传**:上传PDF文档和文本文件,提取内容并从现有文档生成图表
|
||||
- **AI推理过程显示**:查看支持模型的AI思考过程(OpenAI o1/o3、Gemini、Claude等)
|
||||
- **图表历史记录**:全面的版本控制,跟踪所有更改,允许您查看和恢复AI编辑前的图表版本
|
||||
- **交互式聊天界面**:与AI实时对话来完善您的图表
|
||||
- **云架构图支持**:专门支持生成云架构图(AWS、GCP、Azure)
|
||||
- **动画连接器**:在图表元素之间创建动态动画连接器,实现更好的可视化效果
|
||||
|
||||
- **Next.js**:用于前端框架和路由
|
||||
- **Vercel AI SDK**(`ai` + `@ai-sdk/*`):用于流式AI响应和多提供商支持
|
||||
- **react-drawio**:用于图表表示和操作
|
||||
## MCP服务器(预览)
|
||||
|
||||
图表以XML格式表示,可在draw.io中渲染。AI处理您的命令并相应地生成或修改此XML。
|
||||
> **预览功能**:此功能为实验性功能,可能会有变化。
|
||||
|
||||
## 多提供商支持
|
||||
通过MCP(模型上下文协议)在Claude Desktop、Cursor和VS Code等AI代理中使用Next AI Draw.io。
|
||||
|
||||
- AWS Bedrock(默认)
|
||||
- OpenAI / OpenAI兼容API(通过 `OPENAI_BASE_URL`)
|
||||
- Anthropic
|
||||
- Google AI
|
||||
- Azure OpenAI
|
||||
- Ollama
|
||||
- OpenRouter
|
||||
- DeepSeek
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"drawio": {
|
||||
"command": "npx",
|
||||
"args": ["@next-ai-drawio/mcp-server@latest"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意:`claude-sonnet-4-5` 已在带有AWS标志的draw.io图表上进行训练,因此如果您想创建AWS架构图,这是最佳选择。
|
||||
### Claude Code CLI
|
||||
|
||||
```bash
|
||||
claude mcp add drawio -- npx @next-ai-drawio/mcp-server@latest
|
||||
```
|
||||
|
||||
然后让Claude创建图表:
|
||||
> "创建一个展示用户认证流程的流程图,包含登录、MFA和会话管理"
|
||||
|
||||
图表会实时显示在浏览器中!
|
||||
|
||||
详情请参阅[MCP服务器README](../packages/mcp-server/README.md),了解VS Code、Cursor等客户端配置。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 在线试用
|
||||
|
||||
无需安装!直接在我们的演示站点试用:
|
||||
|
||||
[](https://next-ai-drawio.jiang.jp/)
|
||||
|
||||
> 注意:由于访问量较大,演示站点目前使用 minimax-m2 模型。如需获得最佳效果,建议使用 Claude Sonnet 4.5 或 Claude Opus 4.5 自行部署。
|
||||
|
||||
> **使用自己的 API Key**:您可以使用自己的 API Key 来绕过演示站点的用量限制。点击聊天面板中的设置图标即可配置您的 Provider 和 API Key。您的 Key 仅保存在浏览器本地,不会被存储在服务器上。
|
||||
|
||||
### 使用Docker运行(推荐)
|
||||
|
||||
如果您只想在本地运行,最好的方式是使用Docker。
|
||||
@@ -109,10 +147,20 @@ docker run -d -p 3000:3000 \
|
||||
ghcr.io/dayuanjiang/next-ai-draw-io:latest
|
||||
```
|
||||
|
||||
或者使用 env 文件:
|
||||
|
||||
```bash
|
||||
cp env.example .env
|
||||
# 编辑 .env 填写您的配置
|
||||
docker run -d -p 3000:3000 --env-file .env ghcr.io/dayuanjiang/next-ai-draw-io:latest
|
||||
```
|
||||
|
||||
在浏览器中打开 [http://localhost:3000](http://localhost:3000)。
|
||||
|
||||
请根据您首选的AI提供商配置替换环境变量。可用选项请参阅[多提供商支持](#多提供商支持)。
|
||||
|
||||
> **离线部署:** 如果 `embed.diagrams.net` 被屏蔽,请参阅 [离线部署指南](./offline-deployment.md) 了解配置选项。
|
||||
|
||||
### 安装
|
||||
|
||||
1. 克隆仓库:
|
||||
@@ -126,8 +174,6 @@ cd next-ai-draw-io
|
||||
|
||||
```bash
|
||||
npm install
|
||||
# 或
|
||||
yarn install
|
||||
```
|
||||
|
||||
3. 配置您的AI提供商:
|
||||
@@ -140,11 +186,15 @@ cp env.example .env.local
|
||||
|
||||
编辑 `.env.local` 并配置您选择的提供商:
|
||||
|
||||
- 将 `AI_PROVIDER` 设置为您选择的提供商(bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek)
|
||||
- 将 `AI_PROVIDER` 设置为您选择的提供商(bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow)
|
||||
- 将 `AI_MODEL` 设置为您要使用的特定模型
|
||||
- 添加您的提供商所需的API密钥
|
||||
- `TEMPERATURE`:可选的温度设置(例如 `0` 表示确定性输出)。对于不支持此参数的模型(如推理模型),请不要设置。
|
||||
- `ACCESS_CODE_LIST` 访问密码,可选,可以使用逗号隔开多个密码。
|
||||
|
||||
请参阅上面的[多提供商支持](#多提供商支持)部分了解特定提供商的配置示例。
|
||||
> 警告:如果不填写 `ACCESS_CODE_LIST`,则任何人都可以直接使用你部署后的网站,可能会导致你的 token 被急速消耗完毕,建议填写此选项。
|
||||
|
||||
详细设置说明请参阅[提供商配置指南](./ai-providers.md)。
|
||||
|
||||
4. 运行开发服务器:
|
||||
|
||||
@@ -165,6 +215,38 @@ npm run dev
|
||||
|
||||
请确保在Vercel控制台中**设置环境变量**,就像您在本地 `.env.local` 文件中所做的那样。
|
||||
|
||||
|
||||
## 多提供商支持
|
||||
|
||||
- AWS Bedrock(默认)
|
||||
- OpenAI
|
||||
- Anthropic
|
||||
- Google AI
|
||||
- Azure OpenAI
|
||||
- Ollama
|
||||
- OpenRouter
|
||||
- DeepSeek
|
||||
- SiliconFlow
|
||||
|
||||
除AWS Bedrock和OpenRouter外,所有提供商都支持自定义端点。
|
||||
|
||||
📖 **[详细的提供商配置指南](./ai-providers.md)** - 查看各提供商的设置说明。
|
||||
|
||||
**模型要求**:此任务需要强大的模型能力,因为它涉及生成具有严格格式约束的长文本(draw.io XML)。推荐使用Claude Sonnet 4.5、GPT-4o、Gemini 2.0和DeepSeek V3/R1。
|
||||
|
||||
注意:`claude-sonnet-4-5` 已在带有AWS标志的draw.io图表上进行训练,因此如果您想创建AWS架构图,这是最佳选择。
|
||||
|
||||
|
||||
## 工作原理
|
||||
|
||||
本应用使用以下技术:
|
||||
|
||||
- **Next.js**:用于前端框架和路由
|
||||
- **Vercel AI SDK**(`ai` + `@ai-sdk/*`):用于流式AI响应和多提供商支持
|
||||
- **react-drawio**:用于图表表示和操作
|
||||
|
||||
图表以XML格式表示,可在draw.io中渲染。AI处理您的命令并相应地生成或修改此XML。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
@@ -184,14 +266,6 @@ lib/ # 工具函数和辅助程序
|
||||
public/ # 静态资源包括示例图片
|
||||
```
|
||||
|
||||
## 待办事项
|
||||
|
||||
- [x] 允许LLM修改XML而不是每次从头生成
|
||||
- [x] 提高形状流式更新的流畅度
|
||||
- [x] 添加多AI提供商支持(OpenAI, Anthropic, Google, Azure, Ollama)
|
||||
- [x] 解决超过60秒的会话生成失败的bug
|
||||
- [ ] 在UI上添加API配置
|
||||
|
||||
## 支持与联系
|
||||
|
||||
如果您觉得这个项目有用,请考虑[赞助](https://github.com/sponsors/DayuanJiang)来帮助我托管在线演示站点!
|
||||
@@ -4,14 +4,16 @@
|
||||
|
||||
**AI搭載のダイアグラム作成ツール - チャット、描画、可視化**
|
||||
|
||||
[English](./README.md) | [中文](./README_CN.md) | 日本語
|
||||
[English](../README.md) | [中文](./README_CN.md) | 日本語
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://nextjs.org/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://next-ai-drawio.jiang.jp/)
|
||||
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://nextjs.org/)
|
||||
[](https://react.dev/)
|
||||
[](https://github.com/sponsors/DayuanJiang)
|
||||
|
||||
[🚀 ライブデモ](https://next-ai-drawio.jiang.jp/)
|
||||
[](https://next-ai-drawio.jiang.jp/)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -19,16 +21,24 @@ AI機能とdraw.ioダイアグラムを統合したNext.jsウェブアプリケ
|
||||
|
||||
https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
|
||||
|
||||
## 機能
|
||||
## 目次
|
||||
- [Next AI Draw.io](#next-ai-drawio)
|
||||
- [目次](#目次)
|
||||
- [例](#例)
|
||||
- [機能](#機能)
|
||||
- [MCPサーバー(プレビュー)](#mcpサーバープレビュー)
|
||||
- [はじめに](#はじめに)
|
||||
- [オンラインで試す](#オンラインで試す)
|
||||
- [Dockerで実行(推奨)](#dockerで実行推奨)
|
||||
- [インストール](#インストール)
|
||||
- [デプロイ](#デプロイ)
|
||||
- [マルチプロバイダーサポート](#マルチプロバイダーサポート)
|
||||
- [仕組み](#仕組み)
|
||||
- [プロジェクト構造](#プロジェクト構造)
|
||||
- [サポート&お問い合わせ](#サポートお問い合わせ)
|
||||
- [スター履歴](#スター履歴)
|
||||
|
||||
- **LLM搭載のダイアグラム作成**:大規模言語モデルを活用して、自然言語コマンドで直接draw.ioダイアグラムを作成・操作
|
||||
- **画像ベースのダイアグラム複製**:既存のダイアグラムや画像をアップロードし、AIが自動的に複製・強化
|
||||
- **ダイアグラム履歴**:すべての変更を追跡する包括的なバージョン管理。AI編集前のダイアグラムの以前のバージョンを表示・復元可能
|
||||
- **インタラクティブなチャットインターフェース**:AIとリアルタイムでコミュニケーションしてダイアグラムを改善
|
||||
- **AWSアーキテクチャダイアグラムサポート**:AWSアーキテクチャダイアグラムの生成を専門的にサポート
|
||||
- **アニメーションコネクタ**:より良い可視化のためにダイアグラム要素間に動的でアニメーション化されたコネクタを作成
|
||||
|
||||
## **例**
|
||||
## 例
|
||||
|
||||
以下はいくつかのプロンプト例と生成されたダイアグラムです:
|
||||
|
||||
@@ -38,61 +48,89 @@ https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
|
||||
<td colspan="2" valign="top" align="center">
|
||||
<strong>アニメーションTransformerコネクタ</strong><br />
|
||||
<p><strong>プロンプト:</strong> **アニメーションコネクタ**付きのTransformerアーキテクチャ図を作成してください。</p>
|
||||
<img src="./public/animated_connectors.svg" alt="アニメーションコネクタ付きTransformerアーキテクチャ" width="480" />
|
||||
<img src="../public/animated_connectors.svg" alt="アニメーションコネクタ付きTransformerアーキテクチャ" width="480" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" valign="top">
|
||||
<strong>GCPアーキテクチャ図</strong><br />
|
||||
<p><strong>プロンプト:</strong> **GCPアイコン**を使用してGCPアーキテクチャ図を生成してください。この図では、ユーザーがインスタンス上でホストされているフロントエンドに接続します。</p>
|
||||
<img src="./public/gcp_demo.svg" alt="GCPアーキテクチャ図" width="480" />
|
||||
<img src="../public/gcp_demo.svg" alt="GCPアーキテクチャ図" width="480" />
|
||||
</td>
|
||||
<td width="50%" valign="top">
|
||||
<strong>AWSアーキテクチャ図</strong><br />
|
||||
<p><strong>プロンプト:</strong> **AWSアイコン**を使用してAWSアーキテクチャ図を生成してください。この図では、ユーザーがインスタンス上でホストされているフロントエンドに接続します。</p>
|
||||
<img src="./public/aws_demo.svg" alt="AWSアーキテクチャ図" width="480" />
|
||||
<img src="../public/aws_demo.svg" alt="AWSアーキテクチャ図" width="480" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" valign="top">
|
||||
<strong>Azureアーキテクチャ図</strong><br />
|
||||
<p><strong>プロンプト:</strong> **Azureアイコン**を使用してAzureアーキテクチャ図を生成してください。この図では、ユーザーがインスタンス上でホストされているフロントエンドに接続します。</p>
|
||||
<img src="./public/azure_demo.svg" alt="Azureアーキテクチャ図" width="480" />
|
||||
<img src="../public/azure_demo.svg" alt="Azureアーキテクチャ図" width="480" />
|
||||
</td>
|
||||
<td width="50%" valign="top">
|
||||
<strong>猫のスケッチ</strong><br />
|
||||
<p><strong>プロンプト:</strong> かわいい猫を描いてください。</p>
|
||||
<img src="./public/cat_demo.svg" alt="猫の絵" width="240" />
|
||||
<img src="../public/cat_demo.svg" alt="猫の絵" width="240" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
## 仕組み
|
||||
## 機能
|
||||
|
||||
本アプリケーションは以下の技術を使用しています:
|
||||
- **LLM搭載のダイアグラム作成**:大規模言語モデルを活用して、自然言語コマンドで直接draw.ioダイアグラムを作成・操作
|
||||
- **画像ベースのダイアグラム複製**:既存のダイアグラムや画像をアップロードし、AIが自動的に複製・強化
|
||||
- **PDFとテキストファイルのアップロード**:PDFドキュメントやテキストファイルをアップロードして、既存のドキュメントからコンテンツを抽出し、ダイアグラムを生成
|
||||
- **AI推論プロセス表示**:サポートされているモデル(OpenAI o1/o3、Gemini、Claudeなど)のAIの思考プロセスを表示
|
||||
- **ダイアグラム履歴**:すべての変更を追跡する包括的なバージョン管理。AI編集前のダイアグラムの以前のバージョンを表示・復元可能
|
||||
- **インタラクティブなチャットインターフェース**:AIとリアルタイムでコミュニケーションしてダイアグラムを改善
|
||||
- **クラウドアーキテクチャダイアグラムサポート**:クラウドアーキテクチャダイアグラムの生成を専門的にサポート(AWS、GCP、Azure)
|
||||
- **アニメーションコネクタ**:より良い可視化のためにダイアグラム要素間に動的でアニメーション化されたコネクタを作成
|
||||
|
||||
- **Next.js**:フロントエンドフレームワークとルーティング
|
||||
- **Vercel AI SDK**(`ai` + `@ai-sdk/*`):ストリーミングAIレスポンスとマルチプロバイダーサポート
|
||||
- **react-drawio**:ダイアグラムの表現と操作
|
||||
## MCPサーバー(プレビュー)
|
||||
|
||||
ダイアグラムはdraw.ioでレンダリングできるXMLとして表現されます。AIがコマンドを処理し、それに応じてこのXMLを生成または変更します。
|
||||
> **プレビュー機能**:この機能は実験的であり、変更される可能性があります。
|
||||
|
||||
## マルチプロバイダーサポート
|
||||
MCP(Model Context Protocol)を介して、Claude Desktop、Cursor、VS CodeなどのAIエージェントでNext AI Draw.ioを使用できます。
|
||||
|
||||
- AWS Bedrock(デフォルト)
|
||||
- OpenAI / OpenAI互換API(`OPENAI_BASE_URL`経由)
|
||||
- Anthropic
|
||||
- Google AI
|
||||
- Azure OpenAI
|
||||
- Ollama
|
||||
- OpenRouter
|
||||
- DeepSeek
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"drawio": {
|
||||
"command": "npx",
|
||||
"args": ["@next-ai-drawio/mcp-server@latest"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注:`claude-sonnet-4-5`はAWSロゴ付きのdraw.ioダイアグラムで学習されているため、AWSアーキテクチャダイアグラムを作成したい場合は最適な選択です。
|
||||
### Claude Code CLI
|
||||
|
||||
```bash
|
||||
claude mcp add drawio -- npx @next-ai-drawio/mcp-server@latest
|
||||
```
|
||||
|
||||
Claudeにダイアグラムの作成を依頼:
|
||||
> 「ログイン、MFA、セッション管理を含むユーザー認証のフローチャートを作成してください」
|
||||
|
||||
ダイアグラムがリアルタイムでブラウザに表示されます!
|
||||
|
||||
詳細は[MCPサーバーREADME](../packages/mcp-server/README.md)をご覧ください(VS Code、Cursorなどのクライアント設定も含む)。
|
||||
|
||||
## はじめに
|
||||
|
||||
### オンラインで試す
|
||||
|
||||
インストール不要!デモサイトで直接お試しください:
|
||||
|
||||
[](https://next-ai-drawio.jiang.jp/)
|
||||
|
||||
> 注意:アクセス数が多いため、デモサイトでは現在 minimax-m2 モデルを使用しています。最高の結果を得るには、Claude Sonnet 4.5 または Claude Opus 4.5 でのセルフホスティングをお勧めします。
|
||||
|
||||
> **自分のAPIキーを使用**:自分のAPIキーを使用することで、デモサイトの利用制限を回避できます。チャットパネルの設定アイコンをクリックして、プロバイダーとAPIキーを設定してください。キーはブラウザのローカルに保存され、サーバーには保存されません。
|
||||
|
||||
### Dockerで実行(推奨)
|
||||
|
||||
ローカルで実行したいだけなら、Dockerを使用するのが最も簡単です。
|
||||
@@ -109,10 +147,20 @@ docker run -d -p 3000:3000 \
|
||||
ghcr.io/dayuanjiang/next-ai-draw-io:latest
|
||||
```
|
||||
|
||||
または env ファイルを使用:
|
||||
|
||||
```bash
|
||||
cp env.example .env
|
||||
# .env を編集して設定を入力
|
||||
docker run -d -p 3000:3000 --env-file .env ghcr.io/dayuanjiang/next-ai-draw-io:latest
|
||||
```
|
||||
|
||||
ブラウザで [http://localhost:3000](http://localhost:3000) を開いてください。
|
||||
|
||||
環境変数はお好みのAIプロバイダー設定に置き換えてください。利用可能なオプションについては[マルチプロバイダーサポート](#マルチプロバイダーサポート)を参照してください。
|
||||
|
||||
> **オフラインデプロイ:** `embed.diagrams.net` がブロックされている場合は、[オフラインデプロイガイド](./offline-deployment.md) で設定オプションをご確認ください。
|
||||
|
||||
### インストール
|
||||
|
||||
1. リポジトリをクローン:
|
||||
@@ -126,8 +174,6 @@ cd next-ai-draw-io
|
||||
|
||||
```bash
|
||||
npm install
|
||||
# または
|
||||
yarn install
|
||||
```
|
||||
|
||||
3. AIプロバイダーを設定:
|
||||
@@ -140,11 +186,15 @@ cp env.example .env.local
|
||||
|
||||
`.env.local`を編集して選択したプロバイダーを設定:
|
||||
|
||||
- `AI_PROVIDER`を選択したプロバイダーに設定(bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek)
|
||||
- `AI_PROVIDER`を選択したプロバイダーに設定(bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow)
|
||||
- `AI_MODEL`を使用する特定のモデルに設定
|
||||
- プロバイダーに必要なAPIキーを追加
|
||||
- `TEMPERATURE`:オプションの温度設定(例:`0`で決定論的な出力)。温度をサポートしないモデル(推論モデルなど)では設定しないでください。
|
||||
- `ACCESS_CODE_LIST` アクセスパスワード(オプション)。カンマ区切りで複数のパスワードを指定できます。
|
||||
|
||||
プロバイダー固有の設定例については、上記の[マルチプロバイダーサポート](#マルチプロバイダーサポート)セクションを参照してください。
|
||||
> 警告:`ACCESS_CODE_LIST`を設定しない場合、誰でもデプロイされたサイトに直接アクセスできるため、トークンが急速に消費される可能性があります。このオプションを設定することをお勧めします。
|
||||
|
||||
詳細な設定手順については[プロバイダー設定ガイド](./ai-providers.md)を参照してください。
|
||||
|
||||
4. 開発サーバーを起動:
|
||||
|
||||
@@ -165,6 +215,38 @@ Next.jsアプリをデプロイする最も簡単な方法は、Next.jsの作成
|
||||
|
||||
ローカルの`.env.local`ファイルと同様に、Vercelダッシュボードで**環境変数を設定**してください。
|
||||
|
||||
|
||||
## マルチプロバイダーサポート
|
||||
|
||||
- AWS Bedrock(デフォルト)
|
||||
- OpenAI
|
||||
- Anthropic
|
||||
- Google AI
|
||||
- Azure OpenAI
|
||||
- Ollama
|
||||
- OpenRouter
|
||||
- DeepSeek
|
||||
- SiliconFlow
|
||||
|
||||
AWS BedrockとOpenRouter以外のすべてのプロバイダーはカスタムエンドポイントをサポートしています。
|
||||
|
||||
📖 **[詳細なプロバイダー設定ガイド](./ai-providers.md)** - 各プロバイダーの設定手順をご覧ください。
|
||||
|
||||
**モデル要件**:このタスクは厳密なフォーマット制約(draw.io XML)を持つ長文テキスト生成を伴うため、強力なモデル機能が必要です。Claude Sonnet 4.5、GPT-4o、Gemini 2.0、DeepSeek V3/R1を推奨します。
|
||||
|
||||
注:`claude-sonnet-4-5`はAWSロゴ付きのdraw.ioダイアグラムで学習されているため、AWSアーキテクチャダイアグラムを作成したい場合は最適な選択です。
|
||||
|
||||
|
||||
## 仕組み
|
||||
|
||||
本アプリケーションは以下の技術を使用しています:
|
||||
|
||||
- **Next.js**:フロントエンドフレームワークとルーティング
|
||||
- **Vercel AI SDK**(`ai` + `@ai-sdk/*`):ストリーミングAIレスポンスとマルチプロバイダーサポート
|
||||
- **react-drawio**:ダイアグラムの表現と操作
|
||||
|
||||
ダイアグラムはdraw.ioでレンダリングできるXMLとして表現されます。AIがコマンドを処理し、それに応じてこのXMLを生成または変更します。
|
||||
|
||||
## プロジェクト構造
|
||||
|
||||
```
|
||||
@@ -184,14 +266,6 @@ lib/ # ユーティリティ関数とヘルパー
|
||||
public/ # サンプル画像を含む静的アセット
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] LLMが毎回ゼロから生成する代わりにXMLを修正できるようにする
|
||||
- [x] シェイプストリーミング更新の滑らかさを改善
|
||||
- [x] 複数のAIプロバイダーサポートを追加(OpenAI, Anthropic, Google, Azure, Ollama)
|
||||
- [x] 60秒以上のセッションで生成が失敗するバグを解決
|
||||
- [ ] UIにAPI設定を追加
|
||||
|
||||
## サポート&お問い合わせ
|
||||
|
||||
このプロジェクトが役に立ったら、ライブデモサイトのホスティングを支援するために[スポンサー](https://github.com/sponsors/DayuanJiang)をご検討ください!
|
||||
214
docs/ai-providers.md
Normal file
214
docs/ai-providers.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# AI Provider Configuration
|
||||
|
||||
This guide explains how to configure different AI model providers for next-ai-draw-io.
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Copy `.env.example` to `.env.local`
|
||||
2. Set your API key for your chosen provider
|
||||
3. Set `AI_MODEL` to your desired model
|
||||
4. Run `npm run dev`
|
||||
|
||||
## Supported Providers
|
||||
|
||||
### Google Gemini
|
||||
|
||||
```bash
|
||||
GOOGLE_GENERATIVE_AI_API_KEY=your_api_key
|
||||
AI_MODEL=gemini-2.0-flash
|
||||
```
|
||||
|
||||
Optional custom endpoint:
|
||||
|
||||
```bash
|
||||
GOOGLE_BASE_URL=https://your-custom-endpoint
|
||||
```
|
||||
|
||||
### OpenAI
|
||||
|
||||
```bash
|
||||
OPENAI_API_KEY=your_api_key
|
||||
AI_MODEL=gpt-4o
|
||||
```
|
||||
|
||||
Optional custom endpoint (for OpenAI-compatible services):
|
||||
|
||||
```bash
|
||||
OPENAI_BASE_URL=https://your-custom-endpoint/v1
|
||||
```
|
||||
|
||||
### Anthropic
|
||||
|
||||
```bash
|
||||
ANTHROPIC_API_KEY=your_api_key
|
||||
AI_MODEL=claude-sonnet-4-5-20250514
|
||||
```
|
||||
|
||||
Optional custom endpoint:
|
||||
|
||||
```bash
|
||||
ANTHROPIC_BASE_URL=https://your-custom-endpoint
|
||||
```
|
||||
|
||||
### DeepSeek
|
||||
|
||||
```bash
|
||||
DEEPSEEK_API_KEY=your_api_key
|
||||
AI_MODEL=deepseek-chat
|
||||
```
|
||||
|
||||
Optional custom endpoint:
|
||||
|
||||
```bash
|
||||
DEEPSEEK_BASE_URL=https://your-custom-endpoint
|
||||
```
|
||||
|
||||
### SiliconFlow (OpenAI-compatible)
|
||||
|
||||
```bash
|
||||
SILICONFLOW_API_KEY=your_api_key
|
||||
AI_MODEL=deepseek-ai/DeepSeek-V3 # example; use any SiliconFlow model id
|
||||
```
|
||||
|
||||
Optional custom endpoint (defaults to the recommended domain):
|
||||
|
||||
```bash
|
||||
SILICONFLOW_BASE_URL=https://api.siliconflow.com/v1 # or https://api.siliconflow.cn/v1
|
||||
```
|
||||
|
||||
### Azure OpenAI
|
||||
|
||||
```bash
|
||||
AZURE_API_KEY=your_api_key
|
||||
AZURE_RESOURCE_NAME=your-resource-name # Required: your Azure resource name
|
||||
AI_MODEL=your-deployment-name
|
||||
```
|
||||
|
||||
Or use a custom endpoint instead of resource name:
|
||||
|
||||
```bash
|
||||
AZURE_API_KEY=your_api_key
|
||||
AZURE_BASE_URL=https://your-resource.openai.azure.com # Alternative to AZURE_RESOURCE_NAME
|
||||
AI_MODEL=your-deployment-name
|
||||
```
|
||||
|
||||
Optional reasoning configuration:
|
||||
|
||||
```bash
|
||||
AZURE_REASONING_EFFORT=low # Optional: low, medium, high
|
||||
AZURE_REASONING_SUMMARY=detailed # Optional: none, brief, detailed
|
||||
```
|
||||
|
||||
### AWS Bedrock
|
||||
|
||||
```bash
|
||||
AWS_REGION=us-west-2
|
||||
AWS_ACCESS_KEY_ID=your_access_key_id
|
||||
AWS_SECRET_ACCESS_KEY=your_secret_access_key
|
||||
AI_MODEL=anthropic.claude-sonnet-4-5-20250514-v1:0
|
||||
```
|
||||
|
||||
Note: On AWS (Lambda, EC2 with IAM role), credentials are automatically obtained from the IAM role.
|
||||
|
||||
### OpenRouter
|
||||
|
||||
```bash
|
||||
OPENROUTER_API_KEY=your_api_key
|
||||
AI_MODEL=anthropic/claude-sonnet-4
|
||||
```
|
||||
|
||||
Optional custom endpoint:
|
||||
|
||||
```bash
|
||||
OPENROUTER_BASE_URL=https://your-custom-endpoint
|
||||
```
|
||||
|
||||
### Ollama (Local)
|
||||
|
||||
```bash
|
||||
AI_PROVIDER=ollama
|
||||
AI_MODEL=llama3.2
|
||||
```
|
||||
|
||||
Optional custom URL:
|
||||
|
||||
```bash
|
||||
OLLAMA_BASE_URL=http://localhost:11434
|
||||
```
|
||||
|
||||
### Vercel AI Gateway
|
||||
|
||||
Vercel AI Gateway provides unified access to multiple AI providers through a single API key. This simplifies authentication and allows you to switch between providers without managing multiple API keys.
|
||||
|
||||
**Basic Usage (Vercel-hosted Gateway):**
|
||||
|
||||
```bash
|
||||
AI_GATEWAY_API_KEY=your_gateway_api_key
|
||||
AI_MODEL=openai/gpt-4o
|
||||
```
|
||||
|
||||
**Custom Gateway URL (for local development or self-hosted Gateway):**
|
||||
|
||||
```bash
|
||||
AI_GATEWAY_API_KEY=your_custom_api_key
|
||||
AI_GATEWAY_BASE_URL=https://your-custom-gateway.com/v1/ai
|
||||
AI_MODEL=openai/gpt-4o
|
||||
```
|
||||
|
||||
Model format uses `provider/model` syntax:
|
||||
|
||||
- `openai/gpt-4o` - OpenAI GPT-4o
|
||||
- `anthropic/claude-sonnet-4-5` - Anthropic Claude Sonnet 4.5
|
||||
- `google/gemini-2.0-flash` - Google Gemini 2.0 Flash
|
||||
|
||||
**Configuration notes:**
|
||||
|
||||
- If `AI_GATEWAY_BASE_URL` is not set, the default Vercel Gateway URL (`https://ai-gateway.vercel.sh/v1/ai`) is used
|
||||
- Custom base URL is useful for:
|
||||
- Local development with a custom Gateway instance
|
||||
- Self-hosted AI Gateway deployments
|
||||
- Enterprise proxy configurations
|
||||
- When using a custom base URL, you must also provide `AI_GATEWAY_API_KEY`
|
||||
|
||||
Get your API key from the [Vercel AI Gateway dashboard](https://vercel.com/ai-gateway).
|
||||
|
||||
## Auto-Detection
|
||||
|
||||
If you only configure **one** provider's API key, the system will automatically detect and use that provider. No need to set `AI_PROVIDER`.
|
||||
|
||||
If you configure **multiple** API keys, you must explicitly set `AI_PROVIDER`:
|
||||
|
||||
```bash
|
||||
AI_PROVIDER=google # or: openai, anthropic, deepseek, siliconflow, azure, bedrock, openrouter, ollama, gateway
|
||||
```
|
||||
|
||||
## Model Capability Requirements
|
||||
|
||||
This task requires exceptionally strong model capabilities, as it involves generating long-form text with strict formatting constraints (draw.io XML).
|
||||
|
||||
**Recommended models**:
|
||||
|
||||
- Claude Sonnet 4.5 / Opus 4.5
|
||||
|
||||
**Note on Ollama**: While Ollama is supported as a provider, it's generally not practical for this use case unless you're running high-capability models like DeepSeek R1 or Qwen3-235B locally.
|
||||
|
||||
## Temperature Setting
|
||||
|
||||
You can optionally configure the temperature via environment variable:
|
||||
|
||||
```bash
|
||||
TEMPERATURE=0 # More deterministic output (recommended for diagrams)
|
||||
```
|
||||
|
||||
**Important**: Leave `TEMPERATURE` unset for models that don't support temperature settings, such as:
|
||||
- GPT-5.1 and other reasoning models
|
||||
- Some specialized models
|
||||
|
||||
When unset, the model uses its default behavior.
|
||||
|
||||
## Recommendations
|
||||
|
||||
- **Best experience**: Use models with vision support (GPT-4o, Claude, Gemini) for image-to-diagram features
|
||||
- **Budget-friendly**: DeepSeek offers competitive pricing
|
||||
- **Privacy**: Use Ollama for fully local, offline operation (requires powerful hardware)
|
||||
- **Flexibility**: OpenRouter provides access to many models through a single API
|
||||
39
docs/offline-deployment.md
Normal file
39
docs/offline-deployment.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Offline Deployment
|
||||
|
||||
Deploy Next AI Draw.io offline by self-hosting draw.io to replace `embed.diagrams.net`.
|
||||
|
||||
**Note:** `NEXT_PUBLIC_DRAWIO_BASE_URL` is a **build-time** variable. Changing it requires rebuilding the Docker image.
|
||||
|
||||
## Docker Compose Setup
|
||||
|
||||
1. Clone the repository and define API keys in `.env`.
|
||||
2. Create `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
drawio:
|
||||
image: jgraph/drawio:latest
|
||||
ports: ["8080:8080"]
|
||||
next-ai-draw-io:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
- NEXT_PUBLIC_DRAWIO_BASE_URL=http://localhost:8080
|
||||
ports: ["3000:3000"]
|
||||
env_file: .env
|
||||
depends_on: [drawio]
|
||||
```
|
||||
|
||||
3. Run `docker compose up -d` and open `http://localhost:3000`.
|
||||
|
||||
## Configuration & Critical Warning
|
||||
|
||||
**The `NEXT_PUBLIC_DRAWIO_BASE_URL` must be accessible from the user's browser.**
|
||||
|
||||
| Scenario | URL Value |
|
||||
|----------|-----------|
|
||||
| Localhost | `http://localhost:8080` |
|
||||
| Remote/Server | `http://YOUR_SERVER_IP:8080` or `https://drawio.your-domain.com` |
|
||||
|
||||
**Do NOT use** internal Docker aliases like `http://drawio:8080`; the browser cannot resolve them.
|
||||
|
||||
78
docs/shape-libraries/README.md
Normal file
78
docs/shape-libraries/README.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Draw.io Shape Libraries
|
||||
|
||||
Reference: `style="shape=mxgraph.<library>.<shape_name>"`
|
||||
|
||||
## Cloud Providers
|
||||
|
||||
| Library | Shapes | Prefix | Description | File |
|
||||
|---------|--------|--------|-------------|------|
|
||||
| aws4 | 1031 | `mxgraph.aws4` | Amazon Web Services (2025) - EC2, S3, Lambda, RDS, etc. | [aws4.md](./aws4.md) |
|
||||
| azure2 | 608 | `img/lib/azure2/` | Microsoft Azure (2024) - VMs, Storage, AI, Networking, etc. | [azure2.md](./azure2.md) |
|
||||
| gcp2 | 297 | `mxgraph.gcp2` | Google Cloud Platform - Compute Engine, BigQuery, GKE, etc. | [gcp2.md](./gcp2.md) |
|
||||
| alibaba_cloud | 273 | `mxgraph.alibaba_cloud` | Alibaba Cloud - ECS, OSS, RDS, SLB, VPC, etc. | [alibaba_cloud.md](./alibaba_cloud.md) |
|
||||
| openstack | 18 | `mxgraph.openstack` | OpenStack cloud platform icons | [openstack.md](./openstack.md) |
|
||||
| digitalocean | 74 | `mxgraph.digitalocean` | DigitalOcean - Droplets, Spaces, Kubernetes, etc. | [digitalocean.md](./digitalocean.md) |
|
||||
| salesforce | 96 | `mxgraph.salesforce` | Salesforce platform icons | [salesforce.md](./salesforce.md) |
|
||||
|
||||
## Networking & Infrastructure
|
||||
|
||||
| Library | Shapes | Prefix | Description | File |
|
||||
|---------|--------|--------|-------------|------|
|
||||
| cisco19 | 232 | `mxgraph.cisco19` | Cisco network equipment - routers, switches, firewalls | [cisco19.md](./cisco19.md) |
|
||||
| network | 58 | `mxgraph.networks` | General network diagram symbols | [network.md](./network.md) |
|
||||
| arista | 45 | `mxgraph.arista` | Arista network switches and equipment | [arista.md](./arista.md) |
|
||||
| kubernetes | 40 | `mxgraph.kubernetes` | Kubernetes - pods, services, deployments, nodes | [kubernetes.md](./kubernetes.md) |
|
||||
| vvd | 93 | `mxgraph.vvd` | VMware Validated Design icons | [vvd.md](./vvd.md) |
|
||||
| rack | 11 | `mxgraph.rack` | Server rack and data center equipment | [rack.md](./rack.md) |
|
||||
|
||||
## Business Process
|
||||
|
||||
| Library | Shapes | Prefix | Description | File |
|
||||
|---------|--------|--------|-------------|------|
|
||||
| bpmn | 39 | `mxgraph.bpmn` | Business Process Model and Notation - events, gateways, tasks | [bpmn.md](./bpmn.md) |
|
||||
| eip | 36 | `mxgraph.eip` | Enterprise Integration Patterns - messaging, routing | [eip.md](./eip.md) |
|
||||
| lean_mapping | 13 | `mxgraph.lean_mapping` | Lean/Value Stream Mapping symbols | [lean_mapping.md](./lean_mapping.md) |
|
||||
|
||||
## General Diagrams
|
||||
|
||||
| Library | Shapes | Prefix | Description | File |
|
||||
|---------|--------|--------|-------------|------|
|
||||
| flowchart | 34 | `mxgraph.flowchart` | Standard flowchart symbols - process, decision, data | [flowchart.md](./flowchart.md) |
|
||||
| basic | 30 | `mxgraph.basic` | Basic shapes - stars, banners, callouts, hearts | [basic.md](./basic.md) |
|
||||
| arrows2 | 34 | `mxgraph.arrows2` | Arrow shapes and connectors | [arrows2.md](./arrows2.md) |
|
||||
| infographic | 29 | `mxgraph.infographic` | Infographic elements - charts, icons, badges | [infographic.md](./infographic.md) |
|
||||
| sitemap | 50 | `mxgraph.sitemap` | Website sitemap icons - pages, forms, navigation | [sitemap.md](./sitemap.md) |
|
||||
|
||||
## UI/Mockups
|
||||
|
||||
| Library | Shapes | Prefix | Description | File |
|
||||
|---------|--------|--------|-------------|------|
|
||||
| android | 17 | `mxgraph.android` | Android UI mockup components | [android.md](./android.md) |
|
||||
|
||||
## Enterprise Software
|
||||
|
||||
| Library | Shapes | Prefix | Description | File |
|
||||
|---------|--------|--------|-------------|------|
|
||||
| citrix | 97 | `mxgraph.citrix` | Citrix virtualization - XenApp, XenDesktop, NetScaler | [citrix.md](./citrix.md) |
|
||||
| sap | 98 | `mxgraph.sap` | SAP enterprise software icons | [sap.md](./sap.md) |
|
||||
| mscae | 73 | `mxgraph.mscae` | Microsoft Cloud and Enterprise symbols | [mscae.md](./mscae.md) |
|
||||
| atlassian | 26 | `mxgraph.atlassian` | Atlassian - Jira, Confluence issue types | [atlassian.md](./atlassian.md) |
|
||||
|
||||
## Engineering
|
||||
|
||||
| Library | Shapes | Prefix | Description | File |
|
||||
|---------|--------|--------|-------------|------|
|
||||
| fluidpower | 246 | `mxgraph.fluid_power` | Hydraulic/pneumatic engineering symbols | [fluidpower.md](./fluidpower.md) |
|
||||
| electrical | 50 | `mxgraph.electrical` | Electrical circuit symbols - resistors, capacitors | [electrical.md](./electrical.md) |
|
||||
| pid | 18 | `mxgraph.pid2` | Piping and Instrumentation Diagram symbols | [pid.md](./pid.md) |
|
||||
| cabinets | 53 | `mxgraph.cabinets` | Electrical cabinet components - breakers, terminals | [cabinets.md](./cabinets.md) |
|
||||
| floorplan | 44 | `mxgraph.floorplan` | Floor plan furniture and fixtures | [floorplan.md](./floorplan.md) |
|
||||
|
||||
## Icons & Graphics
|
||||
|
||||
| Library | Shapes | Prefix | Description | File |
|
||||
|---------|--------|--------|-------------|------|
|
||||
| webicons | 176 | `mxgraph.webicons` | Web/social media logos - GitHub, Twitter, AWS, etc. | [webicons.md](./webicons.md) |
|
||||
| un-ocha-icons | 242 | `mxgraph.un-ocha-icons` | UN OCHA humanitarian icons | [un-ocha-icons.md](./un-ocha-icons.md) |
|
||||
|
||||
**Total: 33 libraries, 4,281 shapes**
|
||||
328
docs/shape-libraries/alibaba_cloud.md
Normal file
328
docs/shape-libraries/alibaba_cloud.md
Normal file
@@ -0,0 +1,328 @@
|
||||
# alibaba_cloud
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.alibaba_cloud`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.alibaba_cloud.{shape};fillColor=#FF6A00;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Shapes (311)
|
||||
|
||||
- `abap_business_application_platform`
|
||||
- `acms_application_configuration_manangement`
|
||||
- `acr_cloud_container_registry`
|
||||
- `actiontrail`
|
||||
- `adam_advanced_database_and_application_migration`
|
||||
- `adb_analyticdb_for_mysql`
|
||||
- `address_purification`
|
||||
- `afs_fraud_service`
|
||||
- `agw_aligateway`
|
||||
- `ahas_application_high_availability_service`
|
||||
- `airec_artificial_intelligence_recommendation`
|
||||
- `alb_application_load_balancer_01`
|
||||
- `alb_application_load_balancer_02`
|
||||
- `alibaba_cloud_logo`
|
||||
- `alibaba_cloud_logo_chinese`
|
||||
- `alibaba_cloud_logo_english`
|
||||
- `alimail`
|
||||
- `alimt_machine_translation`
|
||||
- `aliyun_linux`
|
||||
- `amqp_advanced_message_queuing_protocol`
|
||||
- `amscloudapp`
|
||||
- `analyticdb_for_postgresql`
|
||||
- `antibot`
|
||||
- `apigateway`
|
||||
- `apsara_file_storage_for_hdfs`
|
||||
- `apsaravideo_vod`
|
||||
- `arms_application_real-time_monitoring_service`
|
||||
- `ask_ack_container_service_for_kubernetes`
|
||||
- `asm_service_mesh`
|
||||
- `assettech`
|
||||
- `avds_vulnerability_db_scanning`
|
||||
- `baas_blockchain_as_a_service`
|
||||
- `bandwidth_bag`
|
||||
- `bastionhost`
|
||||
- `batchcompute`
|
||||
- `bccluster`
|
||||
- `beebot`
|
||||
- `beian`
|
||||
- `bizdevops`
|
||||
- `bizworks`
|
||||
- `bpstudio`
|
||||
- `cas_ssl_central_authentication_service`
|
||||
- `cassandra_wide-column_database_01`
|
||||
- `cassandra_wide-column_database_02`
|
||||
- `ccc_cloud_call_center`
|
||||
- `ccn_cloud_connect_network`
|
||||
- `ccs_customer_service_01`
|
||||
- `ccs_customer_service_02`
|
||||
- `cddc_cloud_database_dedicated_cluster`
|
||||
- `cdn_content_distribution_network`
|
||||
- `cdp_cloudera_cdp`
|
||||
- `cdt_cloud_datatransfer`
|
||||
- `cen_cloud_enterprise_network`
|
||||
- `cfw_cloud_firewall`
|
||||
- `cityvisual`
|
||||
- `clb_classic_load_balancer_01`
|
||||
- `clb_classic_load_balancer_02`
|
||||
- `clickhouse`
|
||||
- `cloud_auth`
|
||||
- `cloud_config`
|
||||
- `cloud_display`
|
||||
- `cloud_governance_center`
|
||||
- `cloud_security_center`
|
||||
- `cloud_shield`
|
||||
- `cloudap`
|
||||
- `cloudbox`
|
||||
- `clouddesktop`
|
||||
- `clouddev`
|
||||
- `cloudphoto`
|
||||
- `cloudproc`
|
||||
- `cloudshell`
|
||||
- `cmn_cloud_managed_network`
|
||||
- `cmp_cloud_mobile_push`
|
||||
- `cms_cloud_monitor_service`
|
||||
- `codepipeline`
|
||||
- `codestore`
|
||||
- `companyreg`
|
||||
- `computenest`
|
||||
- `content_security`
|
||||
- `coo`
|
||||
- `cpns_cell_phone_number_service`
|
||||
- `csas_cloud_security_access_service`
|
||||
- `cvc_cloud_video_conferencing`
|
||||
- `cwh_cloud_web_hosting`
|
||||
- `das_database_autonomy_service`
|
||||
- `databot`
|
||||
- `datahub`
|
||||
- `dataphin`
|
||||
- `dataquotient`
|
||||
- `datav`
|
||||
- `dataworks_dataide`
|
||||
- `dbaudit`
|
||||
- `dbes_database_expert_service`
|
||||
- `dbfs_database_file_system`
|
||||
- `dbs_database_backup`
|
||||
- `dcdn_dynamic_route_for_cdn`
|
||||
- `ddh_dedicated_host`
|
||||
- `ddos-bgp`
|
||||
- `ddos-dip`
|
||||
- `ddos-pro`
|
||||
- `ddos_protection`
|
||||
- `devops`
|
||||
- `dg_database_gateway`
|
||||
- `directmail`
|
||||
- `disk_block_storage`
|
||||
- `dlf_data_lake_formation`
|
||||
- `dms_data_management_service`
|
||||
- `dns_domain_name_system`
|
||||
- `dns_privatezone_01`
|
||||
- `dns_privatezone_02`
|
||||
- `domain`
|
||||
- `domain_and_website`
|
||||
- `drds_distribute_relational_database_service`
|
||||
- `dsi_data_security_insurance`
|
||||
- `dts_data_transmission_service`
|
||||
- `e-mapreduce`
|
||||
- `eais_elastic_accelerated_computing_instances`
|
||||
- `eci_elastic_container_instance`
|
||||
- `ecs_elastic_compute_service`
|
||||
- `edas_enterprise_distributed_application_service`
|
||||
- `ehpc_elastic_high_performance_computing`
|
||||
- `eip_elastic_ip_address`
|
||||
- `elastic_web_hosting`
|
||||
- `elasticsearch`
|
||||
- `emas_enterprise_mobile_application_studio`
|
||||
- `energyexpert`
|
||||
- `ens_edge_node_service`
|
||||
- `enterprise_website`
|
||||
- `eprofile`
|
||||
- `esign`
|
||||
- `ess_elastic_scaling_service`
|
||||
- `eventbridge`
|
||||
- `express_connect`
|
||||
- `face_recognition`
|
||||
- `fc_function_compute`
|
||||
- `flow_service`
|
||||
- `flowbag`
|
||||
- `fnf_serverless_function_flow`
|
||||
- `fpga_field_programmable_gate_array`
|
||||
- `fraud_detection`
|
||||
- `ga_global_accelerator`
|
||||
- `gameshield`
|
||||
- `gdb_graph_database`
|
||||
- `graphanalytics`
|
||||
- `graphcompute`
|
||||
- `gtm_global_traffic_manager`
|
||||
- `gts_global_transaction_service`
|
||||
- `gws_graphic_workstation`
|
||||
- `havip_high-availability_virtual_ip_address`
|
||||
- `hbase`
|
||||
- `hbr_hybrid_backup_recovery`
|
||||
- `hcs-hgw_hybrid_cloud_storage_array`
|
||||
- `hcs-mgw_hybrid_cloud_storage_datatransport`
|
||||
- `hcs-sgw_hybrid_cloud_storage_gateway`
|
||||
- `hdr_hybrid_disaster_recovery`
|
||||
- `hologres`
|
||||
- `holowatcher`
|
||||
- `hsm_hardware_security_module`
|
||||
- `httpdns`
|
||||
- `idrsservice`
|
||||
- `image_recognition`
|
||||
- `imagesearch`
|
||||
- `imarketing`
|
||||
- `imm_intelligent_media_management`
|
||||
- `imp_intelligent_media_production`
|
||||
- `imp_low_code_video_factory`
|
||||
- `indvi_industrial_visual_intelligence`
|
||||
- `intelligent_advisor`
|
||||
- `iot_internet_of_things_platform`
|
||||
- `iot_wireless_connection_service`
|
||||
- `iotid_identity`
|
||||
- `iov_iot_vehicle_cloud`
|
||||
- `ipv6_gateway`
|
||||
- `isoc_iot_security_operations_center`
|
||||
- `isu_intelligent_semantic_understanding`
|
||||
- `ivision`
|
||||
- `ivpd_intelligent_visual_production`
|
||||
- `kafka`
|
||||
- `linkedmall`
|
||||
- `linkwan`
|
||||
- `live`
|
||||
- `livinglink`
|
||||
- `log_streaming`
|
||||
- `logic_composer`
|
||||
- `machine_learning`
|
||||
- `man_mobile_analytics`
|
||||
- `mariadb`
|
||||
- `mas_mobile_acceleration_service`
|
||||
- `maxcompute`
|
||||
- `memcache`
|
||||
- `miniappdev`
|
||||
- `mns_message_service`
|
||||
- `mobile_hotfix`
|
||||
- `mobsec`
|
||||
- `mongodb`
|
||||
- `mps-ai`
|
||||
- `mps-censor`
|
||||
- `mps-cover`
|
||||
- `mps-dna`
|
||||
- `mps-multimod`
|
||||
- `mps-produce`
|
||||
- `mps_apsaravideo_media_processing`
|
||||
- `mq_message_queue`
|
||||
- `mqc_mobile_quality_center`
|
||||
- `mse_microservices_engine`
|
||||
- `multi-cloud_finops`
|
||||
- `multi-mode_database_lindorm`
|
||||
- `multimediaai`
|
||||
- `mxgraph.alibaba_cloud`
|
||||
- `mysql`
|
||||
- `nas_network_attached_storage`
|
||||
- `nat_gateway`
|
||||
- `network_acl_access_control_list`
|
||||
- `nlb_network_load_balancer_01`
|
||||
- `nlb_network_load_balancer_02`
|
||||
- `nlp-address`
|
||||
- `nlp-automl`
|
||||
- `nlp-ie_text_information_extraction`
|
||||
- `nlp-ke_keyword_extraction`
|
||||
- `nlp-ner_named_entity_recognition`
|
||||
- `nlp-pos_part-of-speech_tagging`
|
||||
- `nlp-ra_reflexive_anaphora`
|
||||
- `nlp-sa_sentiment_analysis`
|
||||
- `nlp-tc_text_categorization`
|
||||
- `nlp-ws_word_segmentation`
|
||||
- `nlp_natural_language_processing`
|
||||
- `nls`
|
||||
- `nls-asrbag`
|
||||
- `nls-asrcustommodel`
|
||||
- `nls-filebag`
|
||||
- `nls-service`
|
||||
- `nls-shortasrbag`
|
||||
- `nls-ttsbag`
|
||||
- `nodejs_performance_platform`
|
||||
- `oceanbase`
|
||||
- `ocr_optical_character_recognition`
|
||||
- `onsmqtt_micro_message_queuing_telemetry_transport`
|
||||
- `oos_operation_orchestration_service`
|
||||
- `openanalytics`
|
||||
- `openapi_explorer`
|
||||
- `opensearch`
|
||||
- `oss_object_storage_service`
|
||||
- `ots_tablestore`
|
||||
- `outboundbot`
|
||||
- `pcdn_p2p_cdn`
|
||||
- `petadata_hybriddb_for_mysql`
|
||||
- `physical_connection`
|
||||
- `pnvs_phone_number_verification_service`
|
||||
- `polardb`
|
||||
- `porana_portrait_analysis`
|
||||
- `postgresql`
|
||||
- `ppas_pay-as-you-go_database`
|
||||
- `privatelink`
|
||||
- `prometheus`
|
||||
- `prophet`
|
||||
- `pts_performance_test_service`
|
||||
- `quickbi`
|
||||
- `ram_resource_access_management`
|
||||
- `re_recommendation_engine`
|
||||
- `realtime_compute`
|
||||
- `redis_kvstore`
|
||||
- `region`
|
||||
- `retailir`
|
||||
- `ros_resource_orchestration_service`
|
||||
- `route_table`
|
||||
- `router`
|
||||
- `rsimganalys`
|
||||
- `rtc_real-time_communication`
|
||||
- `sae_serverless_app_engine`
|
||||
- `sag_smart_access_gateway_01`
|
||||
- `sag_smart_access_gateway_02`
|
||||
- `sas_situational_awareness`
|
||||
- `sca_smart_conversation_analysis_01`
|
||||
- `sca_smart_conversation_analysis_02`
|
||||
- `scc_super_computing_cluster`
|
||||
- `scdn_secure_cdn`
|
||||
- `scu_storage_capacity_unit`
|
||||
- `sddp_sensitive_data_protection`
|
||||
- `shared_bandwidth`
|
||||
- `shared_flow_bag`
|
||||
- `shc_shield_hybrid_cloud`
|
||||
- `slb_server_load_balancer_01`
|
||||
- `slb_server_load_balancer_02`
|
||||
- `slb_server_load_balancer_03`
|
||||
- `sls_simple_log_service`
|
||||
- `smc_server_migration_center`
|
||||
- `sms_short_message_service`
|
||||
- `sos`
|
||||
- `spark_data_insights`
|
||||
- `sppc`
|
||||
- `sqlserver`
|
||||
- `swas_simple_application_server`
|
||||
- `tr_transit_router`
|
||||
- `trademark_service`
|
||||
- `uis_ultimate_internet_service`
|
||||
- `user`
|
||||
- `user_feedback_01`
|
||||
- `user_feedback_02`
|
||||
- `vbr_virtual_border_router`
|
||||
- `vcs_visual_computing_service`
|
||||
- `vms_voice_messaging_service`
|
||||
- `voicebot_intelligent_voice_navigation`
|
||||
- `vpc_virtual_private_cloud`
|
||||
- `vpn_gateway`
|
||||
- `vs_video_surveillance`
|
||||
- `vswitch`
|
||||
- `waf_web_application_firewall`
|
||||
- `webplus_web_app_service`
|
||||
- `xdragon_bare_metal_server`
|
||||
- `xtrace`
|
||||
- `yida`
|
||||
62
docs/shape-libraries/android.md
Normal file
62
docs/shape-libraries/android.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# android
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.android`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.android.phone2;strokeColor=#c0c0c0;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="200" height="390" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
## Shapes (47)
|
||||
|
||||
- `action_bar`
|
||||
- `action_bar_landscape`
|
||||
- `anchor`
|
||||
- `checkbox`
|
||||
- `contact_badge_focused`
|
||||
- `contextual_action_bar`
|
||||
- `contextual_action_bar_landscape`
|
||||
- `contextual_split_action_bar`
|
||||
- `contextual_split_action_bar_landscape`
|
||||
- `contextual_split_action_bar_landscape_white`
|
||||
- `indeterminateSpinner`
|
||||
- `indeterminate_progress_bar`
|
||||
- `keyboard`
|
||||
- `navigation_bar_1`
|
||||
- `navigation_bar_1_landscape`
|
||||
- `navigation_bar_1_vertical`
|
||||
- `navigation_bar_2`
|
||||
- `navigation_bar_3`
|
||||
- `navigation_bar_3_landscape`
|
||||
- `navigation_bar_4`
|
||||
- `navigation_bar_5`
|
||||
- `navigation_bar_5_vertical`
|
||||
- `navigation_bar_6`
|
||||
- `phone2`
|
||||
- `progressBar`
|
||||
- `progressScrubberDisabled`
|
||||
- `progressScrubberFocused`
|
||||
- `progressScrubberPressed`
|
||||
- `quick_contact`
|
||||
- `quickscroll2`
|
||||
- `quickscroll3`
|
||||
- `rect`
|
||||
- `rrect`
|
||||
- `scrollbars2`
|
||||
- `spinner2`
|
||||
- `split_action_bar`
|
||||
- `split_action_bar_landscape`
|
||||
- `statusBar`
|
||||
- `switch_off`
|
||||
- `switch_on`
|
||||
- `tab2`
|
||||
- `textSelHandles`
|
||||
- `text_insertion_point`
|
||||
- `textfield`
|
||||
- `time_picker`
|
||||
- `time_picker_dark`
|
||||
- `transparent`
|
||||
33
docs/shape-libraries/arrows2.md
Normal file
33
docs/shape-libraries/arrows2.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# arrows2
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.arrows2`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.arrows2.arrow;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="100" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
## Shapes (18)
|
||||
|
||||
- `arrow`
|
||||
- `bendArrow`
|
||||
- `bendDoubleArrow`
|
||||
- `calloutArrow`
|
||||
- `calloutDouble90Arrow`
|
||||
- `calloutDoubleArrow`
|
||||
- `calloutQuadArrow`
|
||||
- `jumpInArrow`
|
||||
- `quadArrow`
|
||||
- `sharpArrow`
|
||||
- `sharpArrow2`
|
||||
- `stripedArrow`
|
||||
- `stylisedArrow`
|
||||
- `tailedArrow`
|
||||
- `tailedNotchedArrow`
|
||||
- `triadArrow`
|
||||
- `twoWayArrow`
|
||||
- `uTurnArrow`
|
||||
32
docs/shape-libraries/atlassian.md
Normal file
32
docs/shape-libraries/atlassian.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# atlassian
|
||||
|
||||
**Type:** SVG images
|
||||
**Path:** `img/lib/atlassian/`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="image;aspect=fixed;image=img/lib/atlassian/Jira_Logo.svg;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
## Shapes (17)
|
||||
|
||||
- `Atlassian_Logo`
|
||||
- `Bamboo_Logo`
|
||||
- `Bitbucket_Logo`
|
||||
- `Clover_Logo`
|
||||
- `Confluence_Logo`
|
||||
- `Crowd_Logo`
|
||||
- `Crucible_Logo`
|
||||
- `Fisheye_Logo`
|
||||
- `Hipchat_Logo`
|
||||
- `Jira_Core_Logo`
|
||||
- `Jira_Logo`
|
||||
- `Jira_Service_Desk_Logo`
|
||||
- `Jira_Software_Logo`
|
||||
- `Sourcetree_Logo`
|
||||
- `Statuspage_Logo`
|
||||
- `Stride_Logo`
|
||||
- `Trello_Logo`
|
||||
1049
docs/shape-libraries/aws4.md
Normal file
1049
docs/shape-libraries/aws4.md
Normal file
File diff suppressed because it is too large
Load Diff
431
docs/shape-libraries/azure2.md
Normal file
431
docs/shape-libraries/azure2.md
Normal file
@@ -0,0 +1,431 @@
|
||||
# azure2
|
||||
|
||||
**Type:** SVG images
|
||||
**Path:** `img/lib/azure2/`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="image;aspect=fixed;image=img/lib/azure2/compute/Virtual_Machine.svg;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
## Shapes (648)
|
||||
|
||||
Shapes are organized by category: `azure2/{category}/{shape}.svg`
|
||||
|
||||
### ai_machine_learning (30)
|
||||
|
||||
- `AI_Studio`
|
||||
- `Anomaly_Detector`
|
||||
- `Azure_Applied_AI`
|
||||
- `Azure_Experimentation_Studio`
|
||||
- `Azure_Object_Understanding`
|
||||
- `Azure_OpenAI`
|
||||
- `Batch_AI`
|
||||
- `Bonsai`
|
||||
- `Bot_Services`
|
||||
- `Cognitive_Services`
|
||||
- `Cognitive_Services_Decisions`
|
||||
- `Computer_Vision`
|
||||
- `Content_Moderators`
|
||||
- `Content_Safety`
|
||||
- `Custom_Vision`
|
||||
- `Face_APIs`
|
||||
- `Form_Recognizers`
|
||||
- `Genomics`
|
||||
- `Immersive_Readers`
|
||||
- `Language_Services`
|
||||
- `Language_Understanding`
|
||||
- `Machine_Learning`
|
||||
- `Machine_Learning_Studio_Classic_Web_Services`
|
||||
- `Machine_Learning_Studio_Web_Service_Plans`
|
||||
- `Machine_Learning_Studio_Workspaces`
|
||||
- `Personalizers`
|
||||
- `QnA_Makers`
|
||||
- `Serverless_Search`
|
||||
- `Speech_Services`
|
||||
- `Translator_Text`
|
||||
|
||||
### analytics (14)
|
||||
|
||||
- `Analysis_Services`
|
||||
- `Azure_Databricks`
|
||||
- `Azure_Synapse_Analytics`
|
||||
- `Azure_Workbooks`
|
||||
- `Data_Lake_Analytics`
|
||||
- `Data_Lake_Store_Gen1`
|
||||
- `Endpoint_Analytics`
|
||||
- `Event_Hub_Clusters`
|
||||
- `Event_Hubs`
|
||||
- `HD_Insight_Clusters`
|
||||
- `Log_Analytics_Workspaces`
|
||||
- `Power_BI_Embedded`
|
||||
- `Power_Platform`
|
||||
- `Stream_Analytics_Jobs`
|
||||
|
||||
### app_services (9)
|
||||
|
||||
- `API_Management_Services`
|
||||
- `App_Service_Certificates`
|
||||
- `App_Service_Domains`
|
||||
- `App_Service_Environments`
|
||||
- `App_Service_Plans`
|
||||
- `App_Services`
|
||||
- `CDN_Profiles`
|
||||
- `Notification_Hubs`
|
||||
- `Search_Services`
|
||||
|
||||
### compute (38)
|
||||
|
||||
- `App_Services`
|
||||
- `Application_Group`
|
||||
- `Automanaged_VM`
|
||||
- `Availability_Sets`
|
||||
- `Azure_Compute_Galleries`
|
||||
- `Azure_Spring_Cloud`
|
||||
- `Batch_Accounts`
|
||||
- `Cloud_Services_Classic`
|
||||
- `Container_Instances`
|
||||
- `Container_Services_Deprecated`
|
||||
- `Disk_Encryption_Sets`
|
||||
- `Disks`
|
||||
- `Disks_Classic`
|
||||
- `Disks_Snapshots`
|
||||
- `Function_Apps`
|
||||
- `Host_Groups`
|
||||
- `Host_Pools`
|
||||
- `Hosts`
|
||||
- `Image_Definitions`
|
||||
- `Image_Templates`
|
||||
- `Image_Versions`
|
||||
- `Images`
|
||||
- `Kubernetes_Services`
|
||||
- `Maintenance_Configuration`
|
||||
- `Managed_Service_Fabric`
|
||||
- `Mesh_Applications`
|
||||
- `Metrics_Advisor`
|
||||
- `OS_Images_Classic`
|
||||
- `Restore_Points`
|
||||
- `Restore_Points_Collections`
|
||||
- `Service_Fabric_Clusters`
|
||||
- `Shared_Image_Galleries`
|
||||
- `VM_Images_Classic`
|
||||
- `VM_Scale_Sets`
|
||||
- `Virtual_Machine`
|
||||
- `Virtual_Machines_Classic`
|
||||
- `Workspaces`
|
||||
- `Workspaces2`
|
||||
|
||||
### containers (7)
|
||||
|
||||
- `App_Services`
|
||||
- `Azure_Red_Hat_OpenShift`
|
||||
- `Batch_Accounts`
|
||||
- `Container_Instances`
|
||||
- `Container_Registries`
|
||||
- `Kubernetes_Services`
|
||||
- `Service_Fabric_Clusters`
|
||||
|
||||
### databases (27)
|
||||
|
||||
- `Azure_Cosmos_DB`
|
||||
- `Azure_Data_Explorer_Clusters`
|
||||
- `Azure_Database_MariaDB_Server`
|
||||
- `Azure_Database_Migration_Services`
|
||||
- `Azure_Database_MySQL_Server`
|
||||
- `Azure_Database_PostgreSQL_Server`
|
||||
- `Azure_Database_PostgreSQL_Server_Group`
|
||||
- `Azure_Purview_Accounts`
|
||||
- `Azure_SQL`
|
||||
- `Azure_SQL_Edge`
|
||||
- `Azure_SQL_Server_Stretch_Databases`
|
||||
- `Azure_SQL_VM`
|
||||
- `Azure_Synapse_Analytics`
|
||||
- `Cache_Redis`
|
||||
- `Data_Factory`
|
||||
- `Elastic_Job_Agents`
|
||||
- `Instance_Pools`
|
||||
- `Managed_Database`
|
||||
- `Oracle_Database`
|
||||
- `SQL_Data_Warehouses`
|
||||
- `SQL_Database`
|
||||
- `SQL_Elastic_Pools`
|
||||
- `SQL_Managed_Instance`
|
||||
- `SQL_Server`
|
||||
- `SQL_Server_Registries`
|
||||
- `SSIS_Lift_And_Shift_IR`
|
||||
- `Virtual_Clusters`
|
||||
|
||||
### identity (35)
|
||||
|
||||
- `AAD_Licenses`
|
||||
- `Active_Directory_Connect_Health`
|
||||
- `Active_Directory_Connect_Health2`
|
||||
- `Administrative_Units`
|
||||
- `App_Registrations`
|
||||
- `Azure_AD_B2C`
|
||||
- `Azure_AD_B2C2`
|
||||
- `Azure_AD_Domain_Services`
|
||||
- `Azure_AD_Identity_Protection`
|
||||
- `Azure_AD_Privilege_Identity_Management`
|
||||
- `Azure_Active_Directory`
|
||||
- `Azure_Information_Protection`
|
||||
- `Custom_Azure_AD_Roles`
|
||||
- `Enterprise_Applications`
|
||||
- `Entra_Connect`
|
||||
- `Entra_Domain_Services`
|
||||
- `Entra_Global_Secure_Access`
|
||||
- `Entra_ID_Protection`
|
||||
- `Entra_Internet_Access`
|
||||
- `Entra_Managed_Identities`
|
||||
- `Entra_Private_Access`
|
||||
- `Entra_Privileged_Identity_Management`
|
||||
- `Entra_Verified_ID`
|
||||
- `External_Identities`
|
||||
- `Groups`
|
||||
- `Identity_Governance`
|
||||
- `Managed_Identities`
|
||||
- `Multi_Factor_Authentication`
|
||||
- `PIM`
|
||||
- `Security`
|
||||
- `Tenant_Properties`
|
||||
- `User_Settings`
|
||||
- `Users`
|
||||
- `Verifiable_Credentials`
|
||||
- `Verification_As_A_Service`
|
||||
|
||||
### networking (51)
|
||||
|
||||
- `ATM_Multistack`
|
||||
- `Application_Gateway_Containers`
|
||||
- `Application_Gateways`
|
||||
- `Azure_Communications_Gateway`
|
||||
- `Azure_Firewall_Manager`
|
||||
- `Azure_Firewall_Policy`
|
||||
- `Bastions`
|
||||
- `CDN_Profiles`
|
||||
- `Connections`
|
||||
- `DDoS_Protection_Plans`
|
||||
- `DNS_Multistack`
|
||||
- `DNS_Private_Resolver`
|
||||
- `DNS_Security_Policy`
|
||||
- `DNS_Zones`
|
||||
- `ExpressRoute_Circuits`
|
||||
- `Firewalls`
|
||||
- `Front_Doors`
|
||||
- `IP_Address_manager`
|
||||
- `IP_Groups`
|
||||
- `Load_Balancer_Hub`
|
||||
- `Load_Balancers`
|
||||
- `Local_Network_Gateways`
|
||||
- `NAT`
|
||||
- `Network_Interfaces`
|
||||
- `Network_Security_Groups`
|
||||
- `Network_Watcher`
|
||||
- `On_Premises_Data_Gateways`
|
||||
- `Private_Endpoint`
|
||||
- `Private_Link`
|
||||
- `Private_Link_Hub`
|
||||
- `Private_Link_Service`
|
||||
- `Proximity_Placement_Groups`
|
||||
- `Public_IP_Addresses`
|
||||
- `Public_IP_Addresses_Classic`
|
||||
- `Public_IP_Prefixes`
|
||||
- `Reserved_IP_Addresses_Classic`
|
||||
- `Resource_Management_Private_Link`
|
||||
- `Route_Filters`
|
||||
- `Route_Tables`
|
||||
- `Service_Endpoint_Policies`
|
||||
- `Spot_VM`
|
||||
- `Spot_VMSS`
|
||||
- `Subnet`
|
||||
- `Traffic_Manager_Profiles`
|
||||
- `Virtual_Network_Gateways`
|
||||
- `Virtual_Networks`
|
||||
- `Virtual_Networks_Classic`
|
||||
- `Virtual_Router`
|
||||
- `Virtual_WAN_Hub`
|
||||
- `Virtual_WANs`
|
||||
- `Web_Application_Firewall_Policies_WAF`
|
||||
|
||||
### security (14)
|
||||
|
||||
- `Application_Security_Groups`
|
||||
- `Azure_AD_Risky_Signins`
|
||||
- `Azure_AD_Risky_Users`
|
||||
- `Azure_Defender`
|
||||
- `Azure_Sentinel`
|
||||
- `Conditional_Access`
|
||||
- `Detonation`
|
||||
- `ExtendedSecurityUpdates`
|
||||
- `Identity_Secure_Score`
|
||||
- `Key_Vaults`
|
||||
- `Keys`
|
||||
- `MS_Defender_EASM`
|
||||
- `Multifactor_Authentication`
|
||||
- `Security_Center`
|
||||
|
||||
### storage (17)
|
||||
|
||||
- `Azure_Fileshare`
|
||||
- `Azure_HCP_Cache`
|
||||
- `Azure_NetApp_Files`
|
||||
- `Azure_Stack_Edge`
|
||||
- `Data_Box`
|
||||
- `Data_Box_Edge`
|
||||
- `Data_Lake_Storage_Gen1`
|
||||
- `Data_Share_Invitations`
|
||||
- `Data_Shares`
|
||||
- `Import_Export_Jobs`
|
||||
- `Recovery_Services_Vaults`
|
||||
- `StorSimple_Data_Managers`
|
||||
- `StorSimple_Device_Managers`
|
||||
- `Storage_Accounts`
|
||||
- `Storage_Accounts_Classic`
|
||||
- `Storage_Explorer`
|
||||
- `Storage_Sync_Services`
|
||||
|
||||
### general (98)
|
||||
|
||||
- `All_Resources`
|
||||
- `Backlog`
|
||||
- `Biz_Talk`
|
||||
- `Blob_Block`
|
||||
- `Blob_Page`
|
||||
- `Branch`
|
||||
- `Browser`
|
||||
- `Bug`
|
||||
- `Builds`
|
||||
- `Cache`
|
||||
- `Code`
|
||||
- `Commit`
|
||||
- `Controls`
|
||||
- `Controls_Horizontal`
|
||||
- `Cost_Alerts`
|
||||
- `Cost_Analysis`
|
||||
- `Cost_Budgets`
|
||||
- `Cost_Management`
|
||||
- `Cost_Management_and_Billing`
|
||||
- `Counter`
|
||||
- `Cubes`
|
||||
- `Dashboard`
|
||||
- `Dashboard2`
|
||||
- `Dev_Console`
|
||||
- `Download`
|
||||
- `Error`
|
||||
- `Extensions`
|
||||
- `FTP`
|
||||
- `File`
|
||||
- `Files`
|
||||
- `Folder_Blank`
|
||||
- `Folder_Website`
|
||||
- `Free_Services`
|
||||
- `Gear`
|
||||
- `Globe`
|
||||
- `Globe_Error`
|
||||
- `Globe_Success`
|
||||
- `Globe_Warning`
|
||||
- `Guide`
|
||||
- `Heart`
|
||||
- `Help_and_Support`
|
||||
- `Image`
|
||||
- `Information`
|
||||
- `Input_Output`
|
||||
- `Journey_Hub`
|
||||
- `Launch_Portal`
|
||||
- `Learn`
|
||||
- `Load_Test`
|
||||
- `Location`
|
||||
- `Log_Streaming`
|
||||
- `Management_Groups`
|
||||
- `Management_Portal`
|
||||
- `Marketplace`
|
||||
- `Media`
|
||||
- `Media_File`
|
||||
- `Mobile`
|
||||
- `Mobile_Engagement`
|
||||
- `Module`
|
||||
- `Power`
|
||||
- `Power_Up`
|
||||
- `Powershell`
|
||||
- `Preview`
|
||||
- `Preview_Features`
|
||||
- `Process_Explorer`
|
||||
- `Production_Ready_Database`
|
||||
- `Quickstart_Center`
|
||||
- `Recent`
|
||||
- `Reservations`
|
||||
- `Resource_Explorer`
|
||||
- `Resource_Group_List`
|
||||
- `Resource_Groups`
|
||||
- `Resource_Linked`
|
||||
- `SSD`
|
||||
- `Scale`
|
||||
- `Scheduler`
|
||||
- `Search`
|
||||
- `Search_Grid`
|
||||
- `Server_Farm`
|
||||
- `Service_Bus`
|
||||
- `Service_Health`
|
||||
- `Storage_Azure_Files`
|
||||
- `Storage_Container`
|
||||
- `Storage_Queue`
|
||||
- `Subscriptions`
|
||||
- `TFS_VC_Repository`
|
||||
- `Table`
|
||||
- `Tag`
|
||||
- `Tags`
|
||||
- `Templates`
|
||||
- `Toolbox`
|
||||
- `Troubleshoot`
|
||||
- `Versions`
|
||||
- `Web_Slots`
|
||||
- `Web_Test`
|
||||
- `Website_Power`
|
||||
- `Website_Staging`
|
||||
- `Workbooks`
|
||||
- `Workflow`
|
||||
|
||||
### other (149)
|
||||
|
||||
(See draw.io for complete list of 149 shapes in the "other" category)
|
||||
|
||||
Selected shapes:
|
||||
- `Azure_Backup_Center`
|
||||
- `Azure_Chaos_Studio`
|
||||
- `Azure_Cloud_Shell`
|
||||
- `Azure_Communication_Services`
|
||||
- `Azure_Deployment_Environments`
|
||||
- `Azure_Load_Testing`
|
||||
- `Azure_Monitor_Dashboard`
|
||||
- `Azure_Network_Manager`
|
||||
- `Azure_Orbital`
|
||||
- `Azure_Sphere`
|
||||
- `Azure_Storage_Mover`
|
||||
- `Grafana`
|
||||
- `Kubernetes_Fleet_Manager`
|
||||
- `SSH_Keys`
|
||||
|
||||
### Additional Categories
|
||||
|
||||
- **azure_ecosystem** (3): Applens, Azure_Hybrid_Center, Collaborative_Service
|
||||
- **azure_stack** (8): Azure_Stack, Capacity, Infrastructure_Backup, Multi_Tenancy, Offers, Plans, Updates, User_Subscriptions
|
||||
- **azure_vmware_solution** (1): AVS
|
||||
- **blockchain** (6): ABS_Member, Azure_Blockchain_Service, Azure_Token_Service, Blockchain_Applications, Consortium, Outbound_Connection
|
||||
- **cxp** (2): Elixir, Elixir_Purple
|
||||
- **devops** (10): API_Connections, Application_Insights, Azure_DevOps, Change_Analysis, CloudTest, Code_Optimization, DevOps_Starter, DevTest_Labs, Lab_Accounts, Lab_Services
|
||||
- **hybrid_multicloud** (5): Azure_Operator_5G_Core, Azure_Operator_Insights, Azure_Operator_Nexus, Azure_Operator_Service_Manager, Azure_Programmable_Connectivity
|
||||
- **integration** (21): API_Management_Services, App_Configuration, Azure_API_for_FHIR, Azure_Data_Catalog, Event_Grid_Domains, Event_Grid_Subscriptions, Event_Grid_Topics, Integration_Accounts, Integration_Environments, Integration_Service_Environments, Logic_Apps, Logic_Apps_Custom_Connector, Partner_Namespace, Partner_Registration, Partner_Topic, Relays, SQL_Data_Warehouses, SendGrid_Accounts, Service_Bus, Software_as_a_Service, System_Topic
|
||||
- **internet_of_things** (3): Digital_Twins, Logic_Apps, Time_Series_Insights_Access_Policies
|
||||
- **intune** (17): Azure_AD_Roles_and_Administrators, Client_Apps, Device_Compliance, Device_Configuration, Device_Enrollment, Device_Security_Apple, Device_Security_Google, Device_Security_Windows, Devices, Exchange_Access, Intune, Intune_For_Education, Mindaro, Security_Baselines, Software_Updates, Tenant_Status, eBooks
|
||||
- **iot** (19): Azure_IoT_Operations, Azure_Maps_Accounts, Azure_Stack_HCI_Sizer, Device_Provisioning_Services, Digital_Twins, Event_Hubs, Function_Apps, Industrial_IoT, IoT_Central_Applications, IoT_Edge, IoT_Hub, Logic_Apps, Notification_Hubs, Stack_HCI_Premium, Stream_Analytics_Jobs, Time_Series_Data_Sets, Time_Series_Insights_Environments, Time_Series_Insights_Event_Sources, Windows10_Core_Services
|
||||
- **management_governance** (32): Activity_Log, Advisor, Alerts, Application_Insights, Arc_Machines, Automation_Accounts, Azure_Arc, Azure_Lighthouse, Blueprints, Compliance, Cost_Management_and_Billing, Customer_Lockbox_for_MS_Azure, Diagnostics_Settings, Education, Log_Analytics_Workspaces, MachinesAzureArc, Managed_Applications_Center, Managed_Desktop, Metrics, Monitor, My_Customers, Operation_Log_Classic, Policy, Recovery_Services_Vaults, Resource_Graph_Explorer, Resources_Provider, Scheduler_Job_Collections, Service_Catalog_MAD, Service_Providers, Solutions, Universal_Print, User_Privacy
|
||||
- **menu** (1): Keys
|
||||
- **migrate** (5): Azure_Migrate, Cost_Management_and_Billing, Data_Box, Data_Box_Edge, Recovery_Services_Vaults
|
||||
- **mixed_reality** (2): Remote_Rendering, Spatial_Anchor_Accounts
|
||||
- **monitor** (1): SAP_Azure_Monitor
|
||||
- **power_platform** (9): AIBuilder, CopilotStudio, Dataverse, PowerApps, PowerAutomate, PowerBI, PowerFx, PowerPages, PowerPlatform
|
||||
- **preview** (9): Azure_Cloud_Shell, Azure_Sphere, Azure_Workbooks, IoT_Edge, Private_Link_Hub, RTOS, Static_Apps, Time_Series_Data_Sets, Web_Environment
|
||||
- **web** (5): API_Center, App_Space, Azure_Media_Service, Notification_Hub_Namespaces, SignalR
|
||||
48
docs/shape-libraries/basic.md
Normal file
48
docs/shape-libraries/basic.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# basic
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.basic`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.basic.{shape};fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Shapes (31)
|
||||
|
||||
- `4_point_star`
|
||||
- `6_point_star`
|
||||
- `8_point_star`
|
||||
- `banner`
|
||||
- `cloud_callout`
|
||||
- `cloud_rect`
|
||||
- `cone`
|
||||
- `cross`
|
||||
- `document`
|
||||
- `flash`
|
||||
- `half_circle`
|
||||
- `heart`
|
||||
- `loud_callout`
|
||||
- `moon`
|
||||
- `mxgraph.basic`
|
||||
- `no_symbol`
|
||||
- `octagon`
|
||||
- `orthogonal_triangle`
|
||||
- `oval_callout`
|
||||
- `parallelepiped`
|
||||
- `pentagon`
|
||||
- `pointed_oval`
|
||||
- `rectangular_callout`
|
||||
- `rounded_rectangular_callout`
|
||||
- `smiley`
|
||||
- `star`
|
||||
- `sun`
|
||||
- `tick`
|
||||
- `trapezoid`
|
||||
- `wave`
|
||||
- `x`
|
||||
60
docs/shape-libraries/bpmn.md
Normal file
60
docs/shape-libraries/bpmn.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# bpmn
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.bpmn`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.bpmn.shape;symbol=message;outline=throwing;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
- `outline` - Event type: `start`, `end`, `catching`, `throwing`, `none`
|
||||
- `symbol` - Icon inside: `message`, `timer`, `error`, `cancel`, `compensation`, `link`, `terminate`, `general`, `multiple`, `rule`
|
||||
|
||||
## Shapes (40)
|
||||
|
||||
- `ad_hoc`
|
||||
- `business_rule_task`
|
||||
- `cancel_end`
|
||||
- `cancel_intermediate`
|
||||
- `compensation`
|
||||
- `compensation_end`
|
||||
- `compensation_intermediate`
|
||||
- `error_end`
|
||||
- `error_intermediate`
|
||||
- `gateway`
|
||||
- `gateway_and`
|
||||
- `gateway_complex`
|
||||
- `gateway_or`
|
||||
- `gateway_xor_(data)`
|
||||
- `gateway_xor_(event)`
|
||||
- `general_end`
|
||||
- `general_intermediate`
|
||||
- `general_start`
|
||||
- `link_end`
|
||||
- `link_intermediate`
|
||||
- `link_start`
|
||||
- `loop`
|
||||
- `loop_marker`
|
||||
- `manual_task`
|
||||
- `message_end`
|
||||
- `message_intermediate`
|
||||
- `message_start`
|
||||
- `multiple_end`
|
||||
- `multiple_instances`
|
||||
- `multiple_intermediate`
|
||||
- `multiple_start`
|
||||
- `mxgraph.bpmn`
|
||||
- `rule_intermediate`
|
||||
- `rule_start`
|
||||
- `script_task`
|
||||
- `service_task`
|
||||
- `terminate`
|
||||
- `timer_intermediate`
|
||||
- `timer_start`
|
||||
- `user_task`
|
||||
71
docs/shape-libraries/cabinets.md
Normal file
71
docs/shape-libraries/cabinets.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# cabinets
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.cabinets`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.cabinets.{shape};" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Shapes (54)
|
||||
|
||||
- `auxiliary_contact_contactor_1_32a`
|
||||
- `auxiliary_contact_contactor_32_125a`
|
||||
- `cb_1p`
|
||||
- `cb_1p_x10`
|
||||
- `cb_2p`
|
||||
- `cb_2p_x10`
|
||||
- `cb_3p`
|
||||
- `cb_3p_x5`
|
||||
- `cb_4p`
|
||||
- `cb_4p_x5`
|
||||
- `cb_auxiliary_contact`
|
||||
- `contactor_125_400a`
|
||||
- `contactor_1_32a`
|
||||
- `contactor_32_125a`
|
||||
- `din_rail`
|
||||
- `distribution_block_4p_125a_11_connections`
|
||||
- `distribution_block_4p_125a_11_connections_2`
|
||||
- `mccb_25_63a_3p`
|
||||
- `mccb_25_63a_4p`
|
||||
- `mccb_63_250a_3p`
|
||||
- `mccb_63_250a_4p`
|
||||
- `motor_cb_125_400a`
|
||||
- `motor_cb_1_32a`
|
||||
- `motor_cb_32_125a`
|
||||
- `motor_protection_cb`
|
||||
- `motor_starter_125_400a`
|
||||
- `motor_starter_1_32a`
|
||||
- `motor_starter_32_125a`
|
||||
- `motorized_switch_3p`
|
||||
- `motorized_switch_4p`
|
||||
- `mxgraph.cabinets`
|
||||
- `overcurrent_relay_125_400a`
|
||||
- `overcurrent_relay_1_32a`
|
||||
- `overcurrent_relay_32_125a`
|
||||
- `plugin_relay_1`
|
||||
- `plugin_relay_2`
|
||||
- `residual_current_device_2p`
|
||||
- `residual_current_device_4p`
|
||||
- `surge_protection_1p`
|
||||
- `surge_protection_2p`
|
||||
- `surge_protection_3p`
|
||||
- `surge_protection_4p`
|
||||
- `terminal_40mm2`
|
||||
- `terminal_40mm2_x10`
|
||||
- `terminal_4_6mm2`
|
||||
- `terminal_4_6mm2_x10`
|
||||
- `terminal_4mm2`
|
||||
- `terminal_4mm2_x10`
|
||||
- `terminal_50mm2`
|
||||
- `terminal_50mm2_x10`
|
||||
- `terminal_6_25mm2`
|
||||
- `terminal_6_25mm2_x10`
|
||||
- `terminal_75mm2`
|
||||
- `terminal_75mm2_x10`
|
||||
250
docs/shape-libraries/cisco19.md
Normal file
250
docs/shape-libraries/cisco19.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# cisco19
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.cisco19`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.cisco19.rect;prIcon={shape};fillColor=#00bceb;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Shapes (233)
|
||||
|
||||
- `3g_4g_indicator`
|
||||
- `6500_vss`
|
||||
- `6500_vss2`
|
||||
- `access_control_and_trustsec`
|
||||
- `aci`
|
||||
- `aci2`
|
||||
- `acibg`
|
||||
- `acs`
|
||||
- `ad_decoder`
|
||||
- `ad_encoder`
|
||||
- `analysis_correlation`
|
||||
- `anomaly_detection`
|
||||
- `anti_malware`
|
||||
- `anti_malware2`
|
||||
- `appnav`
|
||||
- `asa_5500`
|
||||
- `asr_1000`
|
||||
- `asr_9000`
|
||||
- `avc_application_visibility_control`
|
||||
- `avc_application_visibility_control2`
|
||||
- `bg1`
|
||||
- `bg10`
|
||||
- `bg2`
|
||||
- `bg3`
|
||||
- `bg4`
|
||||
- `bg5`
|
||||
- `bg6`
|
||||
- `bg7`
|
||||
- `bg8`
|
||||
- `bg9`
|
||||
- `blade_server`
|
||||
- `branch`
|
||||
- `branch2`
|
||||
- `camera`
|
||||
- `camera2`
|
||||
- `cell_phone`
|
||||
- `cell_phone2`
|
||||
- `cisco_15800`
|
||||
- `cisco_dna`
|
||||
- `cisco_dna_center`
|
||||
- `cisco_meetingplace_express`
|
||||
- `cisco_security_manager`
|
||||
- `cisco_unified_contact_center_enterprise_and_hosted`
|
||||
- `cisco_unified_presence_service`
|
||||
- `clock`
|
||||
- `cloud`
|
||||
- `cloud2`
|
||||
- `cognitive`
|
||||
- `collab1`
|
||||
- `collab2`
|
||||
- `collab3`
|
||||
- `collab4`
|
||||
- `communications_manager`
|
||||
- `contact_center_express`
|
||||
- `content_recording_streaming_server`
|
||||
- `content_router`
|
||||
- `csr_1000v`
|
||||
- `da_decoder`
|
||||
- `da_encoder`
|
||||
- `data_center`
|
||||
- `data_center2`
|
||||
- `database_relational`
|
||||
- `dns_server`
|
||||
- `dns_server2`
|
||||
- `dual_mode_access_point`
|
||||
- `email_security`
|
||||
- `fabric_interconnect`
|
||||
- `fibre_channel_director_mds_9000`
|
||||
- `fibre_channel_fabric_switch`
|
||||
- `firewall`
|
||||
- `flow_analytics`
|
||||
- `flow_analytics2`
|
||||
- `flow_collector`
|
||||
- `h323`
|
||||
- `handheld`
|
||||
- `handheld2`
|
||||
- `hdtv`
|
||||
- `hdtv2`
|
||||
- `home_office`
|
||||
- `home_office2`
|
||||
- `host_based_security`
|
||||
- `hypervisor`
|
||||
- `immersive_telepresence_endpoint`
|
||||
- `ip_ip_gateway`
|
||||
- `ip_phone`
|
||||
- `ip_phone2`
|
||||
- `ip_telephone_router`
|
||||
- `ips_ids`
|
||||
- `ironport`
|
||||
- `ise`
|
||||
- `joystick_keyboard`
|
||||
- `joystick_keyboard2`
|
||||
- `key`
|
||||
- `key2`
|
||||
- `l2_modular`
|
||||
- `l2_modular2`
|
||||
- `l2_switch`
|
||||
- `l2_switch_with_dual_supervisor`
|
||||
- `l3_modular`
|
||||
- `l3_modular2`
|
||||
- `l3_modular3`
|
||||
- `l3_switch`
|
||||
- `l3_switch_with_dual_supervisor`
|
||||
- `laptop`
|
||||
- `laptop2`
|
||||
- `laptop_video_client`
|
||||
- `laptop_video_client2`
|
||||
- `layer3_nexus_5k_switch`
|
||||
- `ldap`
|
||||
- `ldap2`
|
||||
- `load_balancer`
|
||||
- `lock`
|
||||
- `lock2`
|
||||
- `media_server`
|
||||
- `meeting_scheduling_and_management_server`
|
||||
- `mesh_access_point`
|
||||
- `monitor`
|
||||
- `monitoring`
|
||||
- `multipoint_meeting_server`
|
||||
- `mxgraph.cisco19`
|
||||
- `nac_appliance`
|
||||
- `nam_virtual_service_blade`
|
||||
- `net_mgmt_appliance`
|
||||
- `netflow_router`
|
||||
- `netflow_router2`
|
||||
- `netflow_router3`
|
||||
- `next_generation_intrusion_prevention_system`
|
||||
- `nexus_1010`
|
||||
- `nexus_1k`
|
||||
- `nexus_1kv_vsm`
|
||||
- `nexus_2000_10ge`
|
||||
- `nexus_2k`
|
||||
- `nexus_3k`
|
||||
- `nexus_4k`
|
||||
- `nexus_5k`
|
||||
- `nexus_5k_with_integrated_vsm`
|
||||
- `nexus_7k`
|
||||
- `nexus_9300`
|
||||
- `nexus_9500`
|
||||
- `operations_manager`
|
||||
- `phone_polycom`
|
||||
- `phone_polycom2`
|
||||
- `policy_configuration`
|
||||
- `pos`
|
||||
- `pos2`
|
||||
- `posture_assessment`
|
||||
- `primary_codec`
|
||||
- `printer`
|
||||
- `printer2`
|
||||
- `router`
|
||||
- `router_with_firewall`
|
||||
- `router_with_firewall2`
|
||||
- `router_with_voice`
|
||||
- `rps`
|
||||
- `secondary_codec`
|
||||
- `secure_catalyst_switch_color`
|
||||
- `secure_catalyst_switch_color2`
|
||||
- `secure_catalyst_switch_color3`
|
||||
- `secure_catalyst_switch_subdued`
|
||||
- `secure_catalyst_switch_subdued2`
|
||||
- `secure_endpoint_pc`
|
||||
- `secure_endpoint_pc2`
|
||||
- `secure_endpoints`
|
||||
- `secure_endpoints2`
|
||||
- `secure_router`
|
||||
- `secure_server`
|
||||
- `secure_server2`
|
||||
- `secure_switch`
|
||||
- `security_management`
|
||||
- `server`
|
||||
- `server2`
|
||||
- `service_ready_engine`
|
||||
- `set_top`
|
||||
- `set_top2`
|
||||
- `shield`
|
||||
- `ssl_terminator`
|
||||
- `stealthwatch_management_console_smc`
|
||||
- `stealthwatch_management_console_smc2`
|
||||
- `storage`
|
||||
- `surveillance_camera`
|
||||
- `surveillance_camera2`
|
||||
- `tablet`
|
||||
- `tablet2`
|
||||
- `telepresence_endpoint`
|
||||
- `telepresence_endpoint_twin_data_display`
|
||||
- `telepresence_exchange`
|
||||
- `threat_intelligence`
|
||||
- `transcoder`
|
||||
- `ucs_5108_blade_chassis`
|
||||
- `ucs_c_series_server`
|
||||
- `ucs_express`
|
||||
- `unity`
|
||||
- `upc_unified_personal_communicator`
|
||||
- `upc_unified_personal_communicator2`
|
||||
- `ups`
|
||||
- `user`
|
||||
- `user2`
|
||||
- `vbond`
|
||||
- `video_analytics`
|
||||
- `video_call_server`
|
||||
- `video_gateway`
|
||||
- `virtual_desktop_service`
|
||||
- `virtual_matrix_switch`
|
||||
- `virtual_private_network`
|
||||
- `virtual_private_network2`
|
||||
- `virtual_private_network_connector`
|
||||
- `vmanage`
|
||||
- `vpn_concentrator`
|
||||
- `vsmart`
|
||||
- `vts`
|
||||
- `vts2`
|
||||
- `web_application_firewall`
|
||||
- `web_reputation_filtering`
|
||||
- `web_reputation_filtering_2`
|
||||
- `web_security`
|
||||
- `web_security_services`
|
||||
- `web_security_services2`
|
||||
- `webex`
|
||||
- `wifi_indicator`
|
||||
- `wireless_access_point`
|
||||
- `wireless_access_point2`
|
||||
- `wireless_bridge`
|
||||
- `wireless_bridge2`
|
||||
- `wireless_connector`
|
||||
- `wireless_intrusion_prevention`
|
||||
- `wireless_lan_controller`
|
||||
- `wireless_location_appliance`
|
||||
- `wireless_router`
|
||||
- `workgroup_switch`
|
||||
- `workstation`
|
||||
- `workstation2`
|
||||
- `x509_certificate`
|
||||
- `x509_certificate2`
|
||||
115
docs/shape-libraries/citrix.md
Normal file
115
docs/shape-libraries/citrix.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# citrix
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.citrix`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.citrix.{shape};fillColor=#00A4E4;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Shapes (98)
|
||||
|
||||
- `1u_2u_server`
|
||||
- `access_card`
|
||||
- `branch_repeater`
|
||||
- `browser`
|
||||
- `cache_server`
|
||||
- `calendar`
|
||||
- `cell_phone`
|
||||
- `chassis`
|
||||
- `citrix_hdx`
|
||||
- `citrix_logo`
|
||||
- `cloud`
|
||||
- `command_center`
|
||||
- `database`
|
||||
- `database_server`
|
||||
- `datacenter`
|
||||
- `desktop`
|
||||
- `desktop_web`
|
||||
- `dhcp_server`
|
||||
- `directory_server`
|
||||
- `dns_server`
|
||||
- `document`
|
||||
- `edgesight_server`
|
||||
- `file_server`
|
||||
- `firewall`
|
||||
- `ftp_server`
|
||||
- `geolocation_database`
|
||||
- `globe`
|
||||
- `goto_meeting`
|
||||
- `government`
|
||||
- `home_office`
|
||||
- `hq_enterprise`
|
||||
- `inspection`
|
||||
- `ip_phone`
|
||||
- `kiosk`
|
||||
- `laptop_1`
|
||||
- `laptop_2`
|
||||
- `license_server`
|
||||
- `merchandising_server`
|
||||
- `middleware`
|
||||
- `mxgraph.citrix`
|
||||
- `netscaler_gateway`
|
||||
- `netscaler_mpx`
|
||||
- `netscaler_sdx`
|
||||
- `netscaler_vpx`
|
||||
- `pbx_server`
|
||||
- `pda`
|
||||
- `podio`
|
||||
- `printer`
|
||||
- `process`
|
||||
- `provisioning_server`
|
||||
- `proxy_server`
|
||||
- `radius_server`
|
||||
- `remote_office`
|
||||
- `reporting`
|
||||
- `role_appcontroller`
|
||||
- `role_applications`
|
||||
- `role_cloudbridge`
|
||||
- `role_desktops`
|
||||
- `role_load_testing_controller`
|
||||
- `role_load_testing_launcher`
|
||||
- `role_receiver`
|
||||
- `role_repeater`
|
||||
- `role_secure_access`
|
||||
- `role_security`
|
||||
- `role_services`
|
||||
- `role_storefront`
|
||||
- `role_storefront_services`
|
||||
- `role_synchronizer`
|
||||
- `role_xenmobile`
|
||||
- `role_xenmobile_device_manager`
|
||||
- `router`
|
||||
- `security`
|
||||
- `sharefile`
|
||||
- `site`
|
||||
- `smtp_server`
|
||||
- `storefront_services`
|
||||
- `switch`
|
||||
- `tablet_1`
|
||||
- `tablet_2`
|
||||
- `thin_client`
|
||||
- `tower_server`
|
||||
- `user_control`
|
||||
- `users`
|
||||
- `web_server`
|
||||
- `web_service`
|
||||
- `worxenroll`
|
||||
- `worxhome`
|
||||
- `worxmail`
|
||||
- `worxweb`
|
||||
- `xenapp_server`
|
||||
- `xenapp_services`
|
||||
- `xenapp_web`
|
||||
- `xencenter`
|
||||
- `xenclient`
|
||||
- `xenclient_synchronizer`
|
||||
- `xendesktop_server`
|
||||
- `xenmobile`
|
||||
- `xenserver`
|
||||
50
docs/shape-libraries/electrical.md
Normal file
50
docs/shape-libraries/electrical.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# electrical
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.electrical`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.electrical.resistors.resistor_1;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
Shapes are organized by category: `mxgraph.electrical.{category}.{shape}`
|
||||
|
||||
## Categories
|
||||
|
||||
### resistors
|
||||
- `resistor_1`
|
||||
- `resistor_2`
|
||||
|
||||
### capacitors
|
||||
- `capacitor_1`
|
||||
- `capacitor_3`
|
||||
|
||||
### inductors
|
||||
- `inductor_3`
|
||||
- `transformer_1`
|
||||
|
||||
### diodes
|
||||
- `diode`
|
||||
- `zener_diode_1`
|
||||
|
||||
### transistors
|
||||
- `npn_transistor_1`
|
||||
- `pnp_transistor_1`
|
||||
|
||||
### mosfets1
|
||||
- `n-channel_mosfet_1`
|
||||
- `p-channel_mosfet_1`
|
||||
|
||||
### logic_gates
|
||||
- `logic_gate`
|
||||
- `dual_inline_ic`
|
||||
|
||||
### electro-mechanical
|
||||
- `singleSwitch`
|
||||
- `pushbutton`
|
||||
|
||||
(See draw.io Electrical shape library for complete list)
|
||||
62
docs/shape-libraries/floorplan.md
Normal file
62
docs/shape-libraries/floorplan.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# floorplan
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.floorplan`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.floorplan.{shape};fillColor=#ffffff;strokeColor=#000000;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Shapes (45)
|
||||
|
||||
- `bathtub`
|
||||
- `bathtub2`
|
||||
- `bed_double`
|
||||
- `bed_single`
|
||||
- `bookcase`
|
||||
- `chair`
|
||||
- `copier`
|
||||
- `couch`
|
||||
- `crt_tv`
|
||||
- `desk_corner`
|
||||
- `desk_corner_2`
|
||||
- `dresser`
|
||||
- `drying_machine`
|
||||
- `elevator`
|
||||
- `fireplace`
|
||||
- `flat_tv`
|
||||
- `floor_lamp`
|
||||
- `laptop`
|
||||
- `mxgraph.floorplan`
|
||||
- `office_chair`
|
||||
- `piano`
|
||||
- `plant`
|
||||
- `printer`
|
||||
- `range_1`
|
||||
- `range_2`
|
||||
- `refrigerator`
|
||||
- `shower`
|
||||
- `shower2`
|
||||
- `sink_1`
|
||||
- `sink_2`
|
||||
- `sink_22`
|
||||
- `sink_double`
|
||||
- `sink_double2`
|
||||
- `sofa`
|
||||
- `spiral_stairs`
|
||||
- `table`
|
||||
- `table_1`
|
||||
- `table_2`
|
||||
- `table_3`
|
||||
- `table_4`
|
||||
- `table_5`
|
||||
- `toilet`
|
||||
- `washing_machine`
|
||||
- `water_cooler`
|
||||
- `workstation`
|
||||
52
docs/shape-libraries/flowchart.md
Normal file
52
docs/shape-libraries/flowchart.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# flowchart
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.flowchart`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.flowchart.{shape};fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Shapes (35)
|
||||
|
||||
- `annotation_1`
|
||||
- `annotation_2`
|
||||
- `card`
|
||||
- `collate`
|
||||
- `data`
|
||||
- `database`
|
||||
- `decision`
|
||||
- `delay`
|
||||
- `direct_data`
|
||||
- `display`
|
||||
- `document`
|
||||
- `extract_or_measurement`
|
||||
- `internal_storage`
|
||||
- `loop_limit`
|
||||
- `manual_input`
|
||||
- `manual_operation`
|
||||
- `merge_or_storage`
|
||||
- `multi-document`
|
||||
- `mxgraph.flowchart`
|
||||
- `off-page_reference`
|
||||
- `on-page_reference`
|
||||
- `or`
|
||||
- `paper_tape`
|
||||
- `parallel_mode`
|
||||
- `predefined_process`
|
||||
- `preparation`
|
||||
- `process`
|
||||
- `sequential_data`
|
||||
- `sort`
|
||||
- `start_1`
|
||||
- `start_2`
|
||||
- `stored_data`
|
||||
- `summing_function`
|
||||
- `terminator`
|
||||
- `transfer`
|
||||
264
docs/shape-libraries/fluidpower.md
Normal file
264
docs/shape-libraries/fluidpower.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# fluidpower
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.fluid_power`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.fluid_power.{shape};fillColor=strokeColor;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
Shapes are named like x10010, x10020, etc.
|
||||
|
||||
## Shapes (247)
|
||||
|
||||
- `mxgraph.fluid_power`
|
||||
- `x10010`
|
||||
- `x10020`
|
||||
- `x10030`
|
||||
- `x10040`
|
||||
- `x10050`
|
||||
- `x10060`
|
||||
- `x10070`
|
||||
- `x10080`
|
||||
- `x10090`
|
||||
- `x10100`
|
||||
- `x10110`
|
||||
- `x10120`
|
||||
- `x10130`
|
||||
- `x10140`
|
||||
- `x10150`
|
||||
- `x10160`
|
||||
- `x10170`
|
||||
- `x10180`
|
||||
- `x10190`
|
||||
- `x10200`
|
||||
- `x10210`
|
||||
- `x10220`
|
||||
- `x10230`
|
||||
- `x10240`
|
||||
- `x10250`
|
||||
- `x10260`
|
||||
- `x10270`
|
||||
- `x10280`
|
||||
- `x10290`
|
||||
- `x10300`
|
||||
- `x10310`
|
||||
- `x10320`
|
||||
- `x10330`
|
||||
- `x10340`
|
||||
- `x10350`
|
||||
- `x10360`
|
||||
- `x10370`
|
||||
- `x10380`
|
||||
- `x10390`
|
||||
- `x10400`
|
||||
- `x10410`
|
||||
- `x10420`
|
||||
- `x10430`
|
||||
- `x10440`
|
||||
- `x10441`
|
||||
- `x10442`
|
||||
- `x10450`
|
||||
- `x10460`
|
||||
- `x10470`
|
||||
- `x10480`
|
||||
- `x10490`
|
||||
- `x10500`
|
||||
- `x10510`
|
||||
- `x10520`
|
||||
- `x10530`
|
||||
- `x10540`
|
||||
- `x10550`
|
||||
- `x10560`
|
||||
- `x10570`
|
||||
- `x10580`
|
||||
- `x10590`
|
||||
- `x10600`
|
||||
- `x10610`
|
||||
- `x10620`
|
||||
- `x10630`
|
||||
- `x10640`
|
||||
- `x10650`
|
||||
- `x10660`
|
||||
- `x10670`
|
||||
- `x10680`
|
||||
- `x10690`
|
||||
- `x10700`
|
||||
- `x10710`
|
||||
- `x10720`
|
||||
- `x10730`
|
||||
- `x10740`
|
||||
- `x10750`
|
||||
- `x10760`
|
||||
- `x10770`
|
||||
- `x10780`
|
||||
- `x10790`
|
||||
- `x10800`
|
||||
- `x10810`
|
||||
- `x10820`
|
||||
- `x10830`
|
||||
- `x10840`
|
||||
- `x10850`
|
||||
- `x10860`
|
||||
- `x10870`
|
||||
- `x10880`
|
||||
- `x10890`
|
||||
- `x10900`
|
||||
- `x10910`
|
||||
- `x10920`
|
||||
- `x10930`
|
||||
- `x10940`
|
||||
- `x10950`
|
||||
- `x10960`
|
||||
- `x10970`
|
||||
- `x10980`
|
||||
- `x10990`
|
||||
- `x11000`
|
||||
- `x11010`
|
||||
- `x11020`
|
||||
- `x11030`
|
||||
- `x11040`
|
||||
- `x11050`
|
||||
- `x11060`
|
||||
- `x11070`
|
||||
- `x11080`
|
||||
- `x11090`
|
||||
- `x11100`
|
||||
- `x11110`
|
||||
- `x11120`
|
||||
- `x11130`
|
||||
- `x11140`
|
||||
- `x11150`
|
||||
- `x11160`
|
||||
- `x11170`
|
||||
- `x11180`
|
||||
- `x11190`
|
||||
- `x11200`
|
||||
- `x11210`
|
||||
- `x11220`
|
||||
- `x11230`
|
||||
- `x11240`
|
||||
- `x11250`
|
||||
- `x11260`
|
||||
- `x11270`
|
||||
- `x11280`
|
||||
- `x11290`
|
||||
- `x11300`
|
||||
- `x11310`
|
||||
- `x11320`
|
||||
- `x11330`
|
||||
- `x11340`
|
||||
- `x11350`
|
||||
- `x11360`
|
||||
- `x11370`
|
||||
- `x11380`
|
||||
- `x11390`
|
||||
- `x11400`
|
||||
- `x11410`
|
||||
- `x11420`
|
||||
- `x11430`
|
||||
- `x11440`
|
||||
- `x11450`
|
||||
- `x11460`
|
||||
- `x11470`
|
||||
- `x11480`
|
||||
- `x11490`
|
||||
- `x11500`
|
||||
- `x11510`
|
||||
- `x11520`
|
||||
- `x11530`
|
||||
- `x11540`
|
||||
- `x11550`
|
||||
- `x11560`
|
||||
- `x11570`
|
||||
- `x11580`
|
||||
- `x11590`
|
||||
- `x11600`
|
||||
- `x11610`
|
||||
- `x11620`
|
||||
- `x11630`
|
||||
- `x11640`
|
||||
- `x11650`
|
||||
- `x11660`
|
||||
- `x11670`
|
||||
- `x11680`
|
||||
- `x11690`
|
||||
- `x11700`
|
||||
- `x11710`
|
||||
- `x11720`
|
||||
- `x11730`
|
||||
- `x11740`
|
||||
- `x11750`
|
||||
- `x11760`
|
||||
- `x11770`
|
||||
- `x11780`
|
||||
- `x11790`
|
||||
- `x11800`
|
||||
- `x11810`
|
||||
- `x11820`
|
||||
- `x11830`
|
||||
- `x11840`
|
||||
- `x11850`
|
||||
- `x11860`
|
||||
- `x11870`
|
||||
- `x11880`
|
||||
- `x11890`
|
||||
- `x11900`
|
||||
- `x11910`
|
||||
- `x11920`
|
||||
- `x11930`
|
||||
- `x11940`
|
||||
- `x11950`
|
||||
- `x11960`
|
||||
- `x11970`
|
||||
- `x11980`
|
||||
- `x11990`
|
||||
- `x12000`
|
||||
- `x12010`
|
||||
- `x12020`
|
||||
- `x12030`
|
||||
- `x12040`
|
||||
- `x12050`
|
||||
- `x12060`
|
||||
- `x12070`
|
||||
- `x12080`
|
||||
- `x12090`
|
||||
- `x12100`
|
||||
- `x12110`
|
||||
- `x12120`
|
||||
- `x12130`
|
||||
- `x12140`
|
||||
- `x12150`
|
||||
- `x12160_detailed`
|
||||
- `x12160_simplified`
|
||||
- `x12170`
|
||||
- `x12180`
|
||||
- `x12190`
|
||||
- `x12200`
|
||||
- `x12210`
|
||||
- `x12220`
|
||||
- `x12230`
|
||||
- `x12240`
|
||||
- `x12250`
|
||||
- `x12260`
|
||||
- `x12270`
|
||||
- `x12280`
|
||||
- `x12290`
|
||||
- `x12300`
|
||||
- `x12310`
|
||||
- `x12320`
|
||||
- `x12330`
|
||||
- `x12340`
|
||||
- `x12350`
|
||||
- `x12360`
|
||||
- `x12370`
|
||||
- `x12380`
|
||||
- `x12390`
|
||||
- `x12400`
|
||||
- `x12410`
|
||||
- `x12420`
|
||||
- `x12430`
|
||||
315
docs/shape-libraries/gcp2.md
Normal file
315
docs/shape-libraries/gcp2.md
Normal file
@@ -0,0 +1,315 @@
|
||||
# gcp2
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.gcp2`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.gcp2.{shape};fillColor=#4285F4;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Shapes (298)
|
||||
|
||||
- `a7_power`
|
||||
- `admin_connected`
|
||||
- `admob`
|
||||
- `advanced_solutions_lab`
|
||||
- `ai_hub`
|
||||
- `anomaly_detection`
|
||||
- `api_analytics`
|
||||
- `api_monetization`
|
||||
- `apigee_api_platform`
|
||||
- `apigee_sense`
|
||||
- `app_engine`
|
||||
- `app_engine_icon`
|
||||
- `application`
|
||||
- `application_system`
|
||||
- `arrow_cycle`
|
||||
- `arrows_system`
|
||||
- `aspect_ratio`
|
||||
- `automl_natural_language`
|
||||
- `automl_tables`
|
||||
- `automl_translation`
|
||||
- `automl_video_intelligence`
|
||||
- `automl_vision`
|
||||
- `avere`
|
||||
- `beacon`
|
||||
- `beyondcorp`
|
||||
- `big_query`
|
||||
- `bigquery`
|
||||
- `biomedical_beaker`
|
||||
- `biomedical_test_tube`
|
||||
- `biomedical_trio`
|
||||
- `blank`
|
||||
- `blue_hexagon`
|
||||
- `bucket`
|
||||
- `bucket_scale`
|
||||
- `calculator`
|
||||
- `campaign_manager`
|
||||
- `capabilities`
|
||||
- `certified_industry_standard`
|
||||
- `check`
|
||||
- `check_2`
|
||||
- `check_available`
|
||||
- `check_scale`
|
||||
- `circuit_board`
|
||||
- `clock`
|
||||
- `cloud`
|
||||
- `cloud_apis`
|
||||
- `cloud_armor`
|
||||
- `cloud_automl`
|
||||
- `cloud_bigtable`
|
||||
- `cloud_cdn`
|
||||
- `cloud_checkmark`
|
||||
- `cloud_code`
|
||||
- `cloud_composer`
|
||||
- `cloud_computer`
|
||||
- `cloud_connected_insight`
|
||||
- `cloud_data_catalog`
|
||||
- `cloud_data_fusion`
|
||||
- `cloud_dataflow`
|
||||
- `cloud_dataflow_icon`
|
||||
- `cloud_datalab`
|
||||
- `cloud_dataprep`
|
||||
- `cloud_dataproc`
|
||||
- `cloud_dataproc_icon`
|
||||
- `cloud_datastore`
|
||||
- `cloud_deployment_manager`
|
||||
- `cloud_dns`
|
||||
- `cloud_endpoints`
|
||||
- `cloud_external_ip_addresses`
|
||||
- `cloud_filestore`
|
||||
- `cloud_firestore`
|
||||
- `cloud_firewall_rules`
|
||||
- `cloud_functions`
|
||||
- `cloud_iam`
|
||||
- `cloud_inference_api`
|
||||
- `cloud_information`
|
||||
- `cloud_iot_core`
|
||||
- `cloud_iot_edge`
|
||||
- `cloud_jobs_api`
|
||||
- `cloud_load_balancing`
|
||||
- `cloud_machine_learning`
|
||||
- `cloud_memorystore`
|
||||
- `cloud_messaging`
|
||||
- `cloud_monitoring`
|
||||
- `cloud_nat`
|
||||
- `cloud_natural_language_api`
|
||||
- `cloud_network`
|
||||
- `cloud_pubsub`
|
||||
- `cloud_router`
|
||||
- `cloud_routes`
|
||||
- `cloud_run`
|
||||
- `cloud_scheduler`
|
||||
- `cloud_security`
|
||||
- `cloud_security_command_center`
|
||||
- `cloud_security_scanner`
|
||||
- `cloud_server`
|
||||
- `cloud_service_mesh`
|
||||
- `cloud_spanner`
|
||||
- `cloud_speech_api`
|
||||
- `cloud_sql`
|
||||
- `cloud_storage`
|
||||
- `cloud_sub_pub`
|
||||
- `cloud_tasks`
|
||||
- `cloud_test_lab`
|
||||
- `cloud_text_to_speech`
|
||||
- `cloud_tools_for_powershell`
|
||||
- `cloud_tpu`
|
||||
- `cloud_translation_api`
|
||||
- `cloud_video_intelligence_api`
|
||||
- `cloud_vision_api`
|
||||
- `cloud_vpn`
|
||||
- `cluster`
|
||||
- `compute_engine`
|
||||
- `compute_engine_2`
|
||||
- `compute_engine_icon`
|
||||
- `connected`
|
||||
- `container_builder`
|
||||
- `container_engine`
|
||||
- `container_engine_icon`
|
||||
- `container_optimized_os`
|
||||
- `container_registry`
|
||||
- `cost`
|
||||
- `cost_arrows`
|
||||
- `cost_savings`
|
||||
- `data_access`
|
||||
- `data_increase`
|
||||
- `data_loss_prevention_api`
|
||||
- `data_storage_cost`
|
||||
- `data_studio`
|
||||
- `database`
|
||||
- `database_2`
|
||||
- `database_3`
|
||||
- `database_cycle`
|
||||
- `database_speed`
|
||||
- `database_uploading`
|
||||
- `debugger`
|
||||
- `dedicated_game_server`
|
||||
- `dedicated_interconnect`
|
||||
- `desktop`
|
||||
- `desktop_and_mobile`
|
||||
- `developer_portal`
|
||||
- `dialogflow_enterprise_edition`
|
||||
- `enhance_ui`
|
||||
- `enhance_ui_2`
|
||||
- `error_reporting`
|
||||
- `external_data_center`
|
||||
- `external_data_resource`
|
||||
- `external_payment_form`
|
||||
- `fastly`
|
||||
- `files`
|
||||
- `firebase`
|
||||
- `folders`
|
||||
- `forseti_lockup`
|
||||
- `forseti_logo`
|
||||
- `frontend_platform_services`
|
||||
- `game`
|
||||
- `gateway`
|
||||
- `gateway_icon`
|
||||
- `gear`
|
||||
- `gear_arrow`
|
||||
- `gear_chain`
|
||||
- `gear_load`
|
||||
- `genomics`
|
||||
- `gke_on_prem`
|
||||
- `globe_world`
|
||||
- `google_ad_manager`
|
||||
- `google_ads`
|
||||
- `google_analytics`
|
||||
- `google_analytics_360`
|
||||
- `google_cloud_platform`
|
||||
- `google_cloud_platform_lockup`
|
||||
- `google_network`
|
||||
- `google_network_edge_cache`
|
||||
- `google_play_game_service`
|
||||
- `gpu`
|
||||
- `half_cloud`
|
||||
- `https_load_balancer`
|
||||
- `identity_aware_proxy`
|
||||
- `image_services`
|
||||
- `increase_cost_arrows`
|
||||
- `internal_payment_authorization`
|
||||
- `internet_connection`
|
||||
- `istio_logo`
|
||||
- `key`
|
||||
- `key_management_service`
|
||||
- `kubernetes_logo`
|
||||
- `kubernetes_name`
|
||||
- `laptop`
|
||||
- `legacy_cloud`
|
||||
- `legacy_cloud_2`
|
||||
- `lifecycle`
|
||||
- `lightbulb`
|
||||
- `list`
|
||||
- `live`
|
||||
- `load_balancing`
|
||||
- `loading`
|
||||
- `loading_2`
|
||||
- `loading_3`
|
||||
- `lock`
|
||||
- `logging`
|
||||
- `logs_api`
|
||||
- `management_security`
|
||||
- `maps_api`
|
||||
- `mem_instances`
|
||||
- `memcache`
|
||||
- `memory_card`
|
||||
- `mobile_devices`
|
||||
- `modifiers_autoscaling`
|
||||
- `modifiers_custom_virtual_machine`
|
||||
- `modifiers_high_cpu_machine`
|
||||
- `modifiers_high_memory_machine`
|
||||
- `modifiers_preemptable_vm`
|
||||
- `modifiers_shared_core_machine_f1`
|
||||
- `modifiers_shared_core_machine_g1`
|
||||
- `modifiers_standard_machine`
|
||||
- `modifiers_storage`
|
||||
- `monitor`
|
||||
- `monitor_2`
|
||||
- `mxgraph.gcp2`
|
||||
- `nat`
|
||||
- `network`
|
||||
- `network_load_balancer`
|
||||
- `node`
|
||||
- `outline_blank_1`
|
||||
- `outline_blank_2`
|
||||
- `outline_blank_3`
|
||||
- `outline_highcomp`
|
||||
- `outline_highmem`
|
||||
- `partner_interconnect`
|
||||
- `payment`
|
||||
- `people_security_management`
|
||||
- `persistent_disk`
|
||||
- `persistent_disk_snapshot`
|
||||
- `phone`
|
||||
- `phone_android`
|
||||
- `placeholder`
|
||||
- `play_gear`
|
||||
- `play_start`
|
||||
- `prediction_api`
|
||||
- `premium_network_tier`
|
||||
- `primary`
|
||||
- `process`
|
||||
- `profiler`
|
||||
- `push_notification_service`
|
||||
- `recommendations_ai`
|
||||
- `record`
|
||||
- `replication_controller`
|
||||
- `replication_controller_2`
|
||||
- `replication_controller_3`
|
||||
- `report`
|
||||
- `repository`
|
||||
- `repository_2`
|
||||
- `repository_3`
|
||||
- `repository_primary`
|
||||
- `retail`
|
||||
- `safety`
|
||||
- `save`
|
||||
- `scale`
|
||||
- `scheduled_tasks`
|
||||
- `search`
|
||||
- `search_api`
|
||||
- `security_key_enforcement`
|
||||
- `segments`
|
||||
- `segments_2`
|
||||
- `segments_overlap`
|
||||
- `servers_stacked`
|
||||
- `service`
|
||||
- `service_discovery`
|
||||
- `social_media_time`
|
||||
- `solution`
|
||||
- `speaker`
|
||||
- `speed`
|
||||
- `squid_proxy`
|
||||
- `stackdriver`
|
||||
- `stacked_ownership`
|
||||
- `standard_network_tier`
|
||||
- `storage`
|
||||
- `stream`
|
||||
- `swap`
|
||||
- `systems_check`
|
||||
- `tape_record`
|
||||
- `task_queues`
|
||||
- `task_queues_2`
|
||||
- `tensorflow_lockup`
|
||||
- `tensorflow_logo`
|
||||
- `thumbs_up`
|
||||
- `time_clock`
|
||||
- `trace`
|
||||
- `traffic_director`
|
||||
- `transfer_appliance`
|
||||
- `users`
|
||||
- `view_list`
|
||||
- `virtual_file_system`
|
||||
- `virtual_private_cloud`
|
||||
- `visibility`
|
||||
- `vpn`
|
||||
- `vpn_gateway`
|
||||
- `webcam`
|
||||
- `website`
|
||||
24
docs/shape-libraries/infographic.md
Normal file
24
docs/shape-libraries/infographic.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# infographic
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.infographic`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="html=1;shape=mxgraph.infographic.shadedCube;isoAngle=15;fillColor=#10739E;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
## Shapes
|
||||
|
||||
- `shadedCube` (needs `isoAngle=15;`)
|
||||
- `ribbonSimple` (needs `notch1=20;notch2=20;`)
|
||||
- `ribbonRolled`
|
||||
- `ribbonDoubleFolded`
|
||||
- `shadedTriangle`
|
||||
- `shadedPyramid`
|
||||
- `cylinder`
|
||||
- `banner`
|
||||
- `flag`
|
||||
58
docs/shape-libraries/kubernetes.md
Normal file
58
docs/shape-libraries/kubernetes.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# kubernetes
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.kubernetes`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.kubernetes.icon;prIcon={shape};fillColor=#326CE5;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Shapes (41)
|
||||
|
||||
- `api`
|
||||
- `c_c_m`
|
||||
- `c_m`
|
||||
- `c_role`
|
||||
- `cm`
|
||||
- `crb`
|
||||
- `crd`
|
||||
- `cronjob`
|
||||
- `deploy`
|
||||
- `ds`
|
||||
- `ep`
|
||||
- `etcd`
|
||||
- `frame`
|
||||
- `group`
|
||||
- `hpa`
|
||||
- `ing`
|
||||
- `job`
|
||||
- `k_proxy`
|
||||
- `kubelet`
|
||||
- `limits`
|
||||
- `master`
|
||||
- `mxgraph.kubernetes`
|
||||
- `netpol`
|
||||
- `node`
|
||||
- `ns`
|
||||
- `pod`
|
||||
- `psp`
|
||||
- `pv`
|
||||
- `pvc`
|
||||
- `quota`
|
||||
- `rb`
|
||||
- `role`
|
||||
- `rs`
|
||||
- `sa`
|
||||
- `sc`
|
||||
- `sched`
|
||||
- `secret`
|
||||
- `sts`
|
||||
- `svc`
|
||||
- `user`
|
||||
- `vol`
|
||||
31
docs/shape-libraries/lean_mapping.md
Normal file
31
docs/shape-libraries/lean_mapping.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# lean_mapping
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.lean_mapping`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.lean_mapping.{shape};strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Shapes (14)
|
||||
|
||||
- `airplane_7`
|
||||
- `electronic_info_flow`
|
||||
- `finished_goods_to_customer`
|
||||
- `go_see_production_scheduling`
|
||||
- `kaizen_lightening_burst`
|
||||
- `kanban_post`
|
||||
- `load_leveling`
|
||||
- `manual_info_flow`
|
||||
- `move_by_forklift`
|
||||
- `mrp_erp`
|
||||
- `mxgraph.lean_mapping`
|
||||
- `operator`
|
||||
- `quality_problem`
|
||||
- `verbal`
|
||||
22
docs/shape-libraries/mscae.md
Normal file
22
docs/shape-libraries/mscae.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# mscae
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.mscae`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.mscae.cloud.azure;fillColor=#0078D4;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
## Categories
|
||||
|
||||
Shapes are organized by category: `mscae.cloud`, `mscae.intune`, `mscae.oms`, `mscae.system_center`
|
||||
|
||||
- `conditional_access_exchange`
|
||||
- `conditional_access_sharepoint`
|
||||
- `primary_site`
|
||||
|
||||
(See draw.io for complete shape list within each category)
|
||||
72
docs/shape-libraries/network.md
Normal file
72
docs/shape-libraries/network.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# network
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.networks`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.networks.server;fillColor=#29AAE1;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
## Shapes (57)
|
||||
|
||||
- `biometric_reader`
|
||||
- `bus`
|
||||
- `business_center`
|
||||
- `cloud`
|
||||
- `comm_link`
|
||||
- `comm_link_edge`
|
||||
- `community`
|
||||
- `copier`
|
||||
- `desktop_pc`
|
||||
- `external_storage`
|
||||
- `firewall`
|
||||
- `gamepad`
|
||||
- `hub`
|
||||
- `laptop`
|
||||
- `load_balancer`
|
||||
- `mail_server`
|
||||
- `mainframe`
|
||||
- `mobile`
|
||||
- `modem`
|
||||
- `monitor`
|
||||
- `nas_filer`
|
||||
- `patch_panel`
|
||||
- `phone_1`
|
||||
- `phone_2`
|
||||
- `printer`
|
||||
- `proxy_server`
|
||||
- `rack`
|
||||
- `radio_tower`
|
||||
- `router`
|
||||
- `satellite`
|
||||
- `satellite_dish`
|
||||
- `scanner`
|
||||
- `secured`
|
||||
- `security_camera`
|
||||
- `server`
|
||||
- `server_storage`
|
||||
- `storage`
|
||||
- `supercomputer`
|
||||
- `switch`
|
||||
- `tablet`
|
||||
- `tape_storage`
|
||||
- `terminal`
|
||||
- `unsecure`
|
||||
- `ups_enterprise`
|
||||
- `ups_small`
|
||||
- `usb_stick`
|
||||
- `user_female`
|
||||
- `user_male`
|
||||
- `users`
|
||||
- `video_projector`
|
||||
- `video_projector_screen`
|
||||
- `virtual_pc`
|
||||
- `virtual_server`
|
||||
- `virus`
|
||||
- `web_server`
|
||||
- `wireless_hub`
|
||||
- `wireless_modem`
|
||||
36
docs/shape-libraries/openstack.md
Normal file
36
docs/shape-libraries/openstack.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# openstack
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.openstack`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.openstack.{shape};fillColor=#3F51B5;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Shapes (19)
|
||||
|
||||
- `cinder_volume`
|
||||
- `cinder_volumeattachment`
|
||||
- `designate_recordset`
|
||||
- `designate_zone`
|
||||
- `heat_autoscalinggroup`
|
||||
- `heat_resourcegroup`
|
||||
- `heat_scalingpolicy`
|
||||
- `mxgraph.openstack`
|
||||
- `neutron_floatingip`
|
||||
- `neutron_floatingipassociation`
|
||||
- `neutron_net`
|
||||
- `neutron_port`
|
||||
- `neutron_router`
|
||||
- `neutron_routerinterface`
|
||||
- `neutron_securitygroup`
|
||||
- `neutron_subnet`
|
||||
- `nova_keypair`
|
||||
- `nova_server`
|
||||
- `swift_container`
|
||||
22
docs/shape-libraries/pid.md
Normal file
22
docs/shape-libraries/pid.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# pid
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.pid2valves`, `mxgraph.pid2inst`, `mxgraph.pid2misc`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.pid2valves.valve;valveType=gate;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
## Valve Types
|
||||
|
||||
For `mxgraph.pid2valves.valve`, use `valveType=` with:
|
||||
- `gate`, `globe`, `needle`, `ball`, `butterfly`, `diaphragm`, `plug`, `check`
|
||||
|
||||
## Other Prefixes
|
||||
|
||||
- `mxgraph.pid2inst` - Instruments (discInst, sharedCont, compFunc)
|
||||
- `mxgraph.pid2misc` - Miscellaneous
|
||||
57
docs/shape-libraries/rack.md
Normal file
57
docs/shape-libraries/rack.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# rack
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.rack`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.rack.f5.arx_500;strokeColor=#666666;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="200" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
Shapes are organized by vendor: `mxgraph.rack.{vendor}.{model}`
|
||||
|
||||
## Vendors
|
||||
|
||||
### F5
|
||||
|
||||
- `arx_500`
|
||||
- `big_ip_1600`
|
||||
- `big_ip_2000`
|
||||
- `big_ip_4000`
|
||||
|
||||
### Dell
|
||||
|
||||
- `dell_poweredge_1u`
|
||||
- `poweredge_630`
|
||||
- `poweredge_730`
|
||||
|
||||
### HPE Aruba
|
||||
|
||||
HPE Aruba shapes have subcategories: `mxgraph.rack.hpe_aruba.{category}.{model}`
|
||||
|
||||
**gateways_controllers:**
|
||||
- `aruba_7010_mobility_controller_front`
|
||||
- `aruba_7010_mobility_controller_rear`
|
||||
- `aruba_7024_mobility_controller_front`
|
||||
- `aruba_7205_mobility_controller_front`
|
||||
|
||||
**security:**
|
||||
- `aruba_clearpass_c1000_front`
|
||||
- `aruba_clearpass_c2000_front`
|
||||
- `aruba_clearpass_c3000_front`
|
||||
|
||||
**switches:**
|
||||
- `j9772a_2530_48g_poeplus_switch`
|
||||
- `j9773a_2530_24g_poeplus_switch`
|
||||
- `jl253a_aruba_2930f_24g_4sfpplus_switch`
|
||||
|
||||
### General (rackGeneral)
|
||||
|
||||
Use `mxgraph.rackGeneral.{shape}` for generic rack items:
|
||||
- `rackCabinet3`
|
||||
- `plate`
|
||||
|
||||
(See draw.io Rack shape library for complete list)
|
||||
116
docs/shape-libraries/salesforce.md
Normal file
116
docs/shape-libraries/salesforce.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# salesforce
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.salesforce`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.salesforce.analytics;fillColor=#7f8de1;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
Replace `analytics` with any shape from the list below.
|
||||
|
||||
|
||||
|
||||
## Shapes (97)
|
||||
|
||||
- `analytics`
|
||||
- `analytics2`
|
||||
- `apps`
|
||||
- `apps2`
|
||||
- `automation`
|
||||
- `automation2`
|
||||
- `automotive`
|
||||
- `automotive2`
|
||||
- `bots`
|
||||
- `bots2`
|
||||
- `builders`
|
||||
- `builders2`
|
||||
- `channels`
|
||||
- `channels2`
|
||||
- `commerce`
|
||||
- `commerce2`
|
||||
- `communications`
|
||||
- `communications2`
|
||||
- `consumer_goods`
|
||||
- `consumer_goods2`
|
||||
- `customer_360`
|
||||
- `customer_3602`
|
||||
- `data`
|
||||
- `data2`
|
||||
- `education`
|
||||
- `education2`
|
||||
- `employees`
|
||||
- `employees2`
|
||||
- `energy`
|
||||
- `energy2`
|
||||
- `field_service`
|
||||
- `field_service2`
|
||||
- `financial_services`
|
||||
- `financial_services2`
|
||||
- `government`
|
||||
- `government2`
|
||||
- `health`
|
||||
- `health2`
|
||||
- `heroku`
|
||||
- `heroku2`
|
||||
- `inbox`
|
||||
- `inbox2`
|
||||
- `industries`
|
||||
- `industries2`
|
||||
- `integration`
|
||||
- `integration2`
|
||||
- `iot`
|
||||
- `iot2`
|
||||
- `learning`
|
||||
- `learning2`
|
||||
- `loyalty`
|
||||
- `loyalty2`
|
||||
- `manufacturing`
|
||||
- `manufacturing2`
|
||||
- `marketing`
|
||||
- `marketing2`
|
||||
- `media`
|
||||
- `media2`
|
||||
- `mxgraph.salesforce`
|
||||
- `non_profit`
|
||||
- `non_profit2`
|
||||
- `partners`
|
||||
- `partners2`
|
||||
- `personalization`
|
||||
- `personalization2`
|
||||
- `philantrophy`
|
||||
- `philantrophy2`
|
||||
- `platform`
|
||||
- `platform2`
|
||||
- `privacy`
|
||||
- `privacy2`
|
||||
- `retail`
|
||||
- `retail2`
|
||||
- `sales`
|
||||
- `sales2`
|
||||
- `segments`
|
||||
- `segments2`
|
||||
- `service`
|
||||
- `service2`
|
||||
- `smb`
|
||||
- `smb2`
|
||||
- `social_studio`
|
||||
- `social_studio2`
|
||||
- `stream`
|
||||
- `stream2`
|
||||
- `success`
|
||||
- `success2`
|
||||
- `sustainability`
|
||||
- `sustainability2`
|
||||
- `transportation_and_technology`
|
||||
- `transportation_and_technology2`
|
||||
- `web`
|
||||
- `web2`
|
||||
- `work_com`
|
||||
- `work_com2`
|
||||
- `workflow`
|
||||
- `workflow2`
|
||||
179
docs/shape-libraries/sap.md
Normal file
179
docs/shape-libraries/sap.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# sap
|
||||
|
||||
**Type:** SVG images
|
||||
**Path:** `img/lib/sap/`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="image;aspect=fixed;image=img/lib/sap/SAP_Logo.svg;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
## Shapes (164)
|
||||
|
||||
- `1`
|
||||
- `2`
|
||||
- `3`
|
||||
- `4`
|
||||
- `5`
|
||||
- `6`
|
||||
- `7`
|
||||
- `8`
|
||||
- `9`
|
||||
- `10`
|
||||
- `11`
|
||||
- `12`
|
||||
- `13`
|
||||
- `Adapter`
|
||||
- `Admin`
|
||||
- `Alert`
|
||||
- `API`
|
||||
- `API_Business_Hub_Enterprise`
|
||||
- `App`
|
||||
- `Application_Autoscaler`
|
||||
- `Application_Frontend_Service`
|
||||
- `Application_Vulnerability_Report`
|
||||
- `Building`
|
||||
- `Business_Application_Studio`
|
||||
- `Business_Entity_Recognition`
|
||||
- `Business_Process_Model_Connector_for_SAP_Signavio_Solutions`
|
||||
- `Cloud`
|
||||
- `Cloud_Connector`
|
||||
- `Cloud_Connector2`
|
||||
- `Cloud_Integration_Automation`
|
||||
- `Cloud_Integration_Automation2`
|
||||
- `Cloud_Transport_Management`
|
||||
- `Data_Attribute_Recommendation`
|
||||
- `Deploy`
|
||||
- `Desktop`
|
||||
- `Devices`
|
||||
- `Document`
|
||||
- `Document_Information_Extraction`
|
||||
- `Documents`
|
||||
- `Edge_Integration_Cell`
|
||||
- `Event`
|
||||
- `Extensibility_Service`
|
||||
- `Factory`
|
||||
- `Feature`
|
||||
- `HTML5_App_Repository`
|
||||
- `Identity_Authentication`
|
||||
- `Identity_Authentication2`
|
||||
- `Identity_Directory`
|
||||
- `Identity_Directory2`
|
||||
- `Identity_Provisioning`
|
||||
- `Identity_Provisioning2`
|
||||
- `Info`
|
||||
- `Intelligent_Situation_Automation`
|
||||
- `Invoice_Object_Recommendation`
|
||||
- `Invoice_Object_Recommendation2`
|
||||
- `Key`
|
||||
- `Landscape_Portal_for_SAP_S4HANA_Cloud_ABAP_Environment`
|
||||
- `Link`
|
||||
- `Locked`
|
||||
- `Machine`
|
||||
- `Message`
|
||||
- `Mobile`
|
||||
- `OAuth_20`
|
||||
- `Object_Store_on_SAP_BTP`
|
||||
- `On-Premise`
|
||||
- `Personalized_Recommendation`
|
||||
- `SAP_AI_Core`
|
||||
- `SAP_AI_Launchpad`
|
||||
- `SAP_Alert_Notification_service_for_SAP_BTP`
|
||||
- `SAP_Analytics_Cloud`
|
||||
- `SAP_Analytics_Cloud_Embedded_Edition`
|
||||
- `SAP_Application_Logging_service_for_SAP_BTP`
|
||||
- `SAP_Asset_Performance_Management`
|
||||
- `SAP_Audit_Log_Service`
|
||||
- `SAP_Authorization_Management_Service`
|
||||
- `SAP_Authorization_and_Trust_Management_service`
|
||||
- `SAP_Automation_Pilot`
|
||||
- `SAP_BTP,_ABAP_environment`
|
||||
- `SAP_BTP,_Cloud_Foundry_runtime`
|
||||
- `SAP_BTP,_Kyma_runtime`
|
||||
- `SAP_Build`
|
||||
- `SAP_Build_Apps`
|
||||
- `SAP_Build_Apps_-_Copy`
|
||||
- `SAP_Build_Code`
|
||||
- `SAP_Build_Process_Automation`
|
||||
- `SAP_Build_Process_Automation_-_Copy`
|
||||
- `SAP_Build_Work_Zone_-_Advanced_Edition`
|
||||
- `SAP_Build_Work_Zone_-_Standard_Edition`
|
||||
- `SAP_Business_Accelerator_Hub`
|
||||
- `SAP_Business_Data_Cloud`
|
||||
- `SAP_Cloud_ALM`
|
||||
- `SAP_Cloud_Application_Programming_Model`
|
||||
- `SAP_Cloud_Identity,_SAP_Malware_Scanning_Service`
|
||||
- `SAP_Cloud_Identity_Service`
|
||||
- `SAP_Cloud_Logging`
|
||||
- `SAP_Cloud_Management_Service`
|
||||
- `SAP_Cloud_Transport_Management`
|
||||
- `SAP_Collaborative_Demand_and_Capacity_Management`
|
||||
- `SAP_Connectivity_Service`
|
||||
- `SAP_Content_Agent_Service`
|
||||
- `SAP_Continuous_Integration_and_Delivery`
|
||||
- `SAP_Credential_Store`
|
||||
- `SAP_Custom_Domain_service`
|
||||
- `SAP_Data_Privacy_Integration`
|
||||
- `SAP_Data_Retention_Manager`
|
||||
- `SAP_Datasphere`
|
||||
- `SAP_Destination_service`
|
||||
- `SAP_Digital_Assistant`
|
||||
- `SAP_Digital_Assistant_Service`
|
||||
- `SAP_Digital_Manufacturing`
|
||||
- `SAP_Document_Grounding`
|
||||
- `SAP_Document_Management_Service`
|
||||
- `SAP_Event_Broker_for_SAP_Cloud_Applications`
|
||||
- `SAP_Green_Token`
|
||||
- `SAP_HANA_Cloud`
|
||||
- `SAP_HANA_Spatial_Services`
|
||||
- `SAP_Health_Data_Services_for_FHIR`
|
||||
- `SAP_Integration_Suite`
|
||||
- `SAP_Integration_Suite_-_API_Managment`
|
||||
- `SAP_Integration_Suite_-_Advanced_Event_Mesh`
|
||||
- `SAP_Integration_Suite_-_Cloud_Integration`
|
||||
- `SAP_Integration_Suite_-_Data_Space_Integration`
|
||||
- `SAP_Integration_Suite_-_Event_Mesh`
|
||||
- `SAP_Integration_Suite_-_Integration_Advisor`
|
||||
- `SAP_Integration_Suite_-_Integration_Assessment`
|
||||
- `SAP_Integration_Suite_-_Migration_Assessment`
|
||||
- `SAP_Integration_Suite_-_Open_Connectors`
|
||||
- `SAP_Integration_Suite_-_SAP_Graph`
|
||||
- `SAP_Integration_Suite_-_Trading_Partner_Management`
|
||||
- `SAP_Job_Scheduling_service`
|
||||
- `SAP_Keystore_Service`
|
||||
- `SAP_Landscape_Management_Cloud`
|
||||
- `SAP_Logo`
|
||||
- `SAP_Master_Data_Governance`
|
||||
- `SAP_Master_Data_Integration`
|
||||
- `SAP_Mobile_Services`
|
||||
- `SAP_Monitoring_service_for_SAP_BTP`
|
||||
- `SAP_Omnichannel_Promotion_Pricing`
|
||||
- `SAP_PKI_Certificate_Service`
|
||||
- `SAP_Persistence_Service_ASE`
|
||||
- `SAP_Personal_Data_Manager`
|
||||
- `SAP_Private_Link_service`
|
||||
- `SAP_Project_and_Resource_Management`
|
||||
- `SAP_Responsibility_Management_Service`
|
||||
- `SAP_S4HANA_Cloud_for_Intelligent_Intercompany_Reconciliation`
|
||||
- `SAP_S4HANA_for_MS_Teams`
|
||||
- `SAP_Secure_Login_Service_for_SAP_GUI`
|
||||
- `SAP_Service_Manager`
|
||||
- `SAP_Software_as_a_Service_Provisioning_Service`
|
||||
- `SAP_Solution_Lifecycle_Management_Service`
|
||||
- `SAP_Sustainability_Data_Exchange`
|
||||
- `SAP_Task_Center`
|
||||
- `SAP_Translation_Hub`
|
||||
- `SAP_Variant_Configuration_and_Pricing`
|
||||
- `SAP_Watch_List_Screening`
|
||||
- `Service_Ticket_Intelligence`
|
||||
- `Service_Ticket_Intelligence2`
|
||||
- `Settings`
|
||||
- `Success`
|
||||
- `Third_Party`
|
||||
- `UI5_flexibility_for_key_users`
|
||||
- `UI_Theme_Designer`
|
||||
- `User`
|
||||
- `Web`
|
||||
68
docs/shape-libraries/sitemap.md
Normal file
68
docs/shape-libraries/sitemap.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# sitemap
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.sitemap`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.sitemap.{shape};fillColor=#7ea6e0;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Shapes (51)
|
||||
|
||||
- `about_us`
|
||||
- `audio`
|
||||
- `biography`
|
||||
- `blog`
|
||||
- `calendar`
|
||||
- `chart`
|
||||
- `chat`
|
||||
- `cloud`
|
||||
- `contact`
|
||||
- `contact_us`
|
||||
- `document`
|
||||
- `download`
|
||||
- `error`
|
||||
- `faq`
|
||||
- `form`
|
||||
- `gallery`
|
||||
- `game`
|
||||
- `home`
|
||||
- `info`
|
||||
- `jobs`
|
||||
- `log`
|
||||
- `login`
|
||||
- `mail`
|
||||
- `map`
|
||||
- `mxgraph.sitemap`
|
||||
- `news`
|
||||
- `page`
|
||||
- `payment`
|
||||
- `photo`
|
||||
- `portfolio`
|
||||
- `post`
|
||||
- `pricing`
|
||||
- `print`
|
||||
- `products`
|
||||
- `profile`
|
||||
- `references`
|
||||
- `script`
|
||||
- `search`
|
||||
- `security`
|
||||
- `services`
|
||||
- `settings`
|
||||
- `shopping`
|
||||
- `sitemap`
|
||||
- `slideshow`
|
||||
- `sports`
|
||||
- `success`
|
||||
- `text`
|
||||
- `upload`
|
||||
- `user`
|
||||
- `video`
|
||||
- `warning`
|
||||
112
docs/shape-libraries/vvd.md
Normal file
112
docs/shape-libraries/vvd.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# vvd
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.vvd`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.vvd.{shape};fillColor=#00AEEF;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Shapes (95)
|
||||
|
||||
- `administrator`
|
||||
- `app`
|
||||
- `app_volumes_manager`
|
||||
- `appstack_volume`
|
||||
- `array_manager`
|
||||
- `blueprint`
|
||||
- `business_continuity_data_protection`
|
||||
- `cd`
|
||||
- `cloud_computing`
|
||||
- `collective_nsx_esg`
|
||||
- `consumption_plane`
|
||||
- `cpu`
|
||||
- `datacenter`
|
||||
- `datastore`
|
||||
- `disk`
|
||||
- `document`
|
||||
- `edge_gateway`
|
||||
- `endpoint`
|
||||
- `ethernet_port`
|
||||
- `external_networks`
|
||||
- `flash_drive`
|
||||
- `folder`
|
||||
- `guest_agent_customization`
|
||||
- `horizon`
|
||||
- `infrastructure`
|
||||
- `key`
|
||||
- `keyboard`
|
||||
- `laptop`
|
||||
- `log_files`
|
||||
- `logical_distribution`
|
||||
- `logical_firewall`
|
||||
- `machine`
|
||||
- `memory`
|
||||
- `monitor`
|
||||
- `mouse`
|
||||
- `mxgraph.vvd`
|
||||
- `networking`
|
||||
- `networks`
|
||||
- `nfvo`
|
||||
- `nsx`
|
||||
- `nsx_controller`
|
||||
- `nsx_dashboard`
|
||||
- `nsx_edge_and_load_balancer`
|
||||
- `nsx_esg`
|
||||
- `nsx_manager`
|
||||
- `nsx_public_cloud_gateway`
|
||||
- `on_demand_self_service`
|
||||
- `ovdc_networks`
|
||||
- `pair_sites`
|
||||
- `phone`
|
||||
- `physical_network_adapter`
|
||||
- `physical_storage`
|
||||
- `physical_upstream_router`
|
||||
- `platform_services_controller`
|
||||
- `protection_group`
|
||||
- `protection_group_config`
|
||||
- `recovery_plan`
|
||||
- `resource_pool`
|
||||
- `scsi_controller`
|
||||
- `security`
|
||||
- `server`
|
||||
- `service_provider_cloud_environment`
|
||||
- `site`
|
||||
- `site_container`
|
||||
- `site_recovery`
|
||||
- `site_recovery_functional_icon`
|
||||
- `ssd`
|
||||
- `storage`
|
||||
- `switch`
|
||||
- `telco_network`
|
||||
- `template`
|
||||
- `tenant_key`
|
||||
- `user_group`
|
||||
- `vapp_network`
|
||||
- `vcenter_server`
|
||||
- `vcloud_director`
|
||||
- `virtual_appliance`
|
||||
- `virtual_machine`
|
||||
- `virtual_switch`
|
||||
- `vm_group`
|
||||
- `vnf_m`
|
||||
- `volumes_agent`
|
||||
- `vpn`
|
||||
- `vrealize_automation`
|
||||
- `vrealize_log_insight`
|
||||
- `vrealize_operations`
|
||||
- `vrealize_orchestrator`
|
||||
- `vrops`
|
||||
- `vsan`
|
||||
- `vshield`
|
||||
- `vxlan`
|
||||
- `wavefront`
|
||||
- `web_browser`
|
||||
- `wi_fi`
|
||||
- `writable_volume`
|
||||
194
docs/shape-libraries/webicons.md
Normal file
194
docs/shape-libraries/webicons.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# webicons
|
||||
|
||||
**Type:** mxgraph shapes
|
||||
**Prefix:** `mxgraph.webicons`
|
||||
|
||||
## Usage
|
||||
|
||||
```xml
|
||||
<mxCell value="label" style="shape=mxgraph.webicons.{shape};fillColor=#3b5998;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="0" y="0" width="60" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Shapes (177)
|
||||
|
||||
- `adfty`
|
||||
- `adobe_pdf`
|
||||
- `aim`
|
||||
- `allvoices`
|
||||
- `amazon`
|
||||
- `amazon_2`
|
||||
- `android`
|
||||
- `apache`
|
||||
- `apple`
|
||||
- `apple_classic`
|
||||
- `arduino`
|
||||
- `ask`
|
||||
- `atlassian`
|
||||
- `audioboo`
|
||||
- `aws`
|
||||
- `aws_s3`
|
||||
- `baidu`
|
||||
- `bebo`
|
||||
- `behance`
|
||||
- `bing`
|
||||
- `bitbucket`
|
||||
- `blinklist`
|
||||
- `blogger`
|
||||
- `blogmarks`
|
||||
- `bookmarks.fr`
|
||||
- `box`
|
||||
- `buddymarks`
|
||||
- `buffer`
|
||||
- `buzzfeed`
|
||||
- `chrome`
|
||||
- `citeulike`
|
||||
- `confluence`
|
||||
- `connotea`
|
||||
- `dealsplus`
|
||||
- `delicious`
|
||||
- `designfloat`
|
||||
- `deviantart`
|
||||
- `digg`
|
||||
- `diigo`
|
||||
- `dopplr`
|
||||
- `drawio1`
|
||||
- `drawio2`
|
||||
- `dribbble`
|
||||
- `dropbox`
|
||||
- `dropbox2`
|
||||
- `drupal`
|
||||
- `dzone`
|
||||
- `ebay`
|
||||
- `edmodo`
|
||||
- `evernote`
|
||||
- `facebook`
|
||||
- `fancy`
|
||||
- `fark`
|
||||
- `fashiolista`
|
||||
- `feed`
|
||||
- `feedburner`
|
||||
- `flickr`
|
||||
- `folkd`
|
||||
- `forrst`
|
||||
- `fotolog`
|
||||
- `freshbump`
|
||||
- `fresqui`
|
||||
- `friendfeed`
|
||||
- `funp`
|
||||
- `fwisp`
|
||||
- `gabbr`
|
||||
- `gamespot`
|
||||
- `github`
|
||||
- `gmail`
|
||||
- `google`
|
||||
- `google_drive`
|
||||
- `google_hangout`
|
||||
- `google_photos`
|
||||
- `google_play`
|
||||
- `google_play_light`
|
||||
- `google_plus`
|
||||
- `grooveshark`
|
||||
- `hatena`
|
||||
- `html5`
|
||||
- `identi.ca`
|
||||
- `instagram`
|
||||
- `instapaper`
|
||||
- `ios`
|
||||
- `jamespot`
|
||||
- `java`
|
||||
- `joomla`
|
||||
- `jquery`
|
||||
- `json`
|
||||
- `json_2`
|
||||
- `last.fm`
|
||||
- `linkagogo`
|
||||
- `linkedin`
|
||||
- `livejournal`
|
||||
- `mail.ru`
|
||||
- `meetup`
|
||||
- `meneame`
|
||||
- `messenger`
|
||||
- `messenger_2`
|
||||
- `messenger_3`
|
||||
- `mind_body_green`
|
||||
- `mongodb`
|
||||
- `mxgraph.webicons`
|
||||
- `myspace`
|
||||
- `n4g`
|
||||
- `netlog`
|
||||
- `netvibes`
|
||||
- `netvouz`
|
||||
- `networkedblogs`
|
||||
- `newsvine`
|
||||
- `odnoklassniki`
|
||||
- `oknotizie`
|
||||
- `onedrive`
|
||||
- `oracle`
|
||||
- `paypal`
|
||||
- `phone`
|
||||
- `phonefavs`
|
||||
- `pinterest`
|
||||
- `plaxo`
|
||||
- `playfire`
|
||||
- `plurk`
|
||||
- `pocket`
|
||||
- `protopage`
|
||||
- `readernaut`
|
||||
- `reddit`
|
||||
- `rss`
|
||||
- `scoopit`
|
||||
- `scribd`
|
||||
- `segnalo`
|
||||
- `sina`
|
||||
- `sitejot`
|
||||
- `skype`
|
||||
- `skyrock`
|
||||
- `slashdot`
|
||||
- `sms`
|
||||
- `socialvibe`
|
||||
- `society6`
|
||||
- `sonico`
|
||||
- `soundcloud`
|
||||
- `sourceforge`
|
||||
- `sourceforge_2`
|
||||
- `spring.me`
|
||||
- `stackexchange`
|
||||
- `stackoverflow`
|
||||
- `startaid`
|
||||
- `startlap`
|
||||
- `steam`
|
||||
- `stumbleupon`
|
||||
- `stumpedia`
|
||||
- `technorati`
|
||||
- `translate`
|
||||
- `tumblr`
|
||||
- `tunein`
|
||||
- `twitter`
|
||||
- `two`
|
||||
- `typepad`
|
||||
- `viadeo`
|
||||
- `viber`
|
||||
- `viddler`
|
||||
- `vimeo`
|
||||
- `virb`
|
||||
- `vkontakte`
|
||||
- `wakoopa`
|
||||
- `weheartit`
|
||||
- `whatsapp`
|
||||
- `wix`
|
||||
- `wordpress`
|
||||
- `wordpress_2`
|
||||
- `xanga`
|
||||
- `xerpi`
|
||||
- `xing`
|
||||
- `yahoo`
|
||||
- `yahoo_2`
|
||||
- `yammer`
|
||||
- `yandex`
|
||||
- `yelp`
|
||||
- `yoolink`
|
||||
- `youmob`
|
||||
55
env.example
55
env.example
@@ -1,6 +1,6 @@
|
||||
# AI Provider Configuration
|
||||
# AI_PROVIDER: Which provider to use
|
||||
# Options: bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek
|
||||
# Options: bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow, gateway
|
||||
# Default: bedrock
|
||||
AI_PROVIDER=bedrock
|
||||
|
||||
@@ -11,28 +11,49 @@ AI_MODEL=global.anthropic.claude-sonnet-4-5-20250929-v1:0
|
||||
# AWS_REGION=us-east-1
|
||||
# AWS_ACCESS_KEY_ID=your-access-key-id
|
||||
# AWS_SECRET_ACCESS_KEY=your-secret-access-key
|
||||
# Note: Claude and Nova models support reasoning/extended thinking
|
||||
# BEDROCK_REASONING_BUDGET_TOKENS=12000 # Optional: Claude reasoning budget in tokens (1024-64000)
|
||||
# BEDROCK_REASONING_EFFORT=medium # Optional: Nova reasoning effort (low/medium/high)
|
||||
|
||||
# OpenAI Configuration
|
||||
# OPENAI_API_KEY=sk-...
|
||||
# OPENAI_BASE_URL=https://api.openai.com/v1 # Optional: Custom OpenAI-compatible endpoint
|
||||
# OPENAI_ORGANIZATION=org-... # Optional
|
||||
# OPENAI_PROJECT=proj_... # Optional
|
||||
# Note: o1/o3/gpt-5 models automatically enable reasoning summary (default: detailed)
|
||||
# OPENAI_REASONING_EFFORT=low # Optional: Reasoning effort (minimal/low/medium/high) - for o1/o3/gpt-5
|
||||
# OPENAI_REASONING_SUMMARY=detailed # Optional: Override reasoning summary (none/brief/detailed)
|
||||
|
||||
# Anthropic (Direct) Configuration
|
||||
# ANTHROPIC_API_KEY=sk-ant-...
|
||||
# ANTHROPIC_BASE_URL=https://your-custom-anthropic/v1
|
||||
# ANTHROPIC_THINKING_TYPE=enabled # Optional: Anthropic extended thinking (enabled)
|
||||
# ANTHROPIC_THINKING_BUDGET_TOKENS=12000 # Optional: Budget for extended thinking in tokens
|
||||
|
||||
# Google Generative AI Configuration
|
||||
# GOOGLE_GENERATIVE_AI_API_KEY=...
|
||||
# GOOGLE_BASE_URL=https://generativelanguage.googleapis.com/v1beta # Optional: Custom endpoint
|
||||
# GOOGLE_CANDIDATE_COUNT=1 # Optional: Number of candidates to generate
|
||||
# GOOGLE_TOP_K=40 # Optional: Top K sampling parameter
|
||||
# GOOGLE_TOP_P=0.95 # Optional: Nucleus sampling parameter
|
||||
# Note: Gemini 2.5/3 models automatically enable reasoning display (includeThoughts: true)
|
||||
# GOOGLE_THINKING_BUDGET=8192 # Optional: Gemini 2.5 thinking budget in tokens (for more/less thinking)
|
||||
# GOOGLE_THINKING_LEVEL=high # Optional: Gemini 3 thinking level (low/high)
|
||||
|
||||
# Azure OpenAI Configuration
|
||||
# Configure endpoint using ONE of these methods:
|
||||
# 1. AZURE_RESOURCE_NAME - SDK constructs: https://{name}.openai.azure.com/openai/v1{path}
|
||||
# 2. AZURE_BASE_URL - SDK appends /v1{path} to your URL
|
||||
# If both are set, AZURE_BASE_URL takes precedence.
|
||||
# AZURE_RESOURCE_NAME=your-resource-name
|
||||
# AZURE_API_KEY=...
|
||||
# AZURE_BASE_URL=https://your-resource.openai.azure.com # Optional: Custom endpoint (overrides resourceName)
|
||||
# AZURE_BASE_URL=https://your-resource.openai.azure.com/openai # Alternative: Custom endpoint
|
||||
# AZURE_REASONING_EFFORT=low # Optional: Azure reasoning effort (low, medium, high)
|
||||
# AZURE_REASONING_SUMMARY=detailed
|
||||
|
||||
# Ollama (Local) Configuration
|
||||
# OLLAMA_BASE_URL=http://localhost:11434/api # Optional, defaults to localhost
|
||||
# OLLAMA_ENABLE_THINKING=true # Optional: Enable thinking for models that support it (e.g., qwen3)
|
||||
|
||||
# OpenRouter Configuration
|
||||
# OPENROUTER_API_KEY=sk-or-v1-...
|
||||
@@ -42,8 +63,38 @@ AI_MODEL=global.anthropic.claude-sonnet-4-5-20250929-v1:0
|
||||
# DEEPSEEK_API_KEY=sk-...
|
||||
# DEEPSEEK_BASE_URL=https://api.deepseek.com/v1 # Optional: Custom endpoint
|
||||
|
||||
# SiliconFlow Configuration (OpenAI-compatible)
|
||||
# Base domain can be .com or .cn, defaults to https://api.siliconflow.com/v1
|
||||
# SILICONFLOW_API_KEY=sk-...
|
||||
# SILICONFLOW_BASE_URL=https://api.siliconflow.com/v1 # Optional: switch to https://api.siliconflow.cn/v1 if needed
|
||||
|
||||
# Vercel AI Gateway Configuration
|
||||
# Get your API key from: https://vercel.com/ai-gateway
|
||||
# Model format: "provider/model" e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4-5"
|
||||
# AI_GATEWAY_API_KEY=...
|
||||
# AI_GATEWAY_BASE_URL=https://your-custom-gateway.com/v1/ai # Optional: Custom Gateway URL (for local dev or self-hosted Gateway)
|
||||
# # If not set, uses Vercel default: https://ai-gateway.vercel.sh/v1/ai
|
||||
|
||||
# Langfuse Observability (Optional)
|
||||
# Enable LLM tracing and analytics - https://langfuse.com
|
||||
# LANGFUSE_PUBLIC_KEY=pk-lf-...
|
||||
# LANGFUSE_SECRET_KEY=sk-lf-...
|
||||
# LANGFUSE_BASEURL=https://cloud.langfuse.com # EU region, use https://us.cloud.langfuse.com for US
|
||||
|
||||
# Temperature (Optional)
|
||||
# Controls randomness in AI responses. Lower = more deterministic.
|
||||
# Leave unset for models that don't support temperature (e.g., GPT-5.1 reasoning models)
|
||||
# TEMPERATURE=0
|
||||
|
||||
# Access Control (Optional)
|
||||
# ACCESS_CODE_LIST=your-secret-code,another-code
|
||||
|
||||
# Draw.io Configuration (Optional)
|
||||
# NEXT_PUBLIC_DRAWIO_BASE_URL=https://embed.diagrams.net # Default: https://embed.diagrams.net
|
||||
# Use this to point to a self-hosted draw.io instance
|
||||
|
||||
# PDF Input Feature (Optional)
|
||||
# Enable PDF file upload to extract text and generate diagrams
|
||||
# Enabled by default. Set to "false" to disable.
|
||||
# ENABLE_PDF_INPUT=true
|
||||
# NEXT_PUBLIC_MAX_EXTRACTED_CHARS=150000 # Max characters for PDF/text extraction (default: 150000)
|
||||
|
||||
29
hooks/use-dictionary.ts
Normal file
29
hooks/use-dictionary.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
"use client"
|
||||
|
||||
import React, { createContext, useContext } from "react"
|
||||
import type { Dictionary } from "@/lib/i18n/dictionaries"
|
||||
|
||||
const DictionaryContext = createContext<Dictionary | null>(null)
|
||||
|
||||
export function DictionaryProvider({
|
||||
children,
|
||||
dictionary,
|
||||
}: React.PropsWithChildren<{ dictionary: Dictionary }>) {
|
||||
return React.createElement(
|
||||
DictionaryContext.Provider,
|
||||
{ value: dictionary },
|
||||
children,
|
||||
)
|
||||
}
|
||||
|
||||
export function useDictionary() {
|
||||
const dict = useContext(DictionaryContext)
|
||||
if (!dict) {
|
||||
throw new Error(
|
||||
"useDictionary must be used within a DictionaryProvider",
|
||||
)
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
export default useDictionary
|
||||
@@ -1,22 +1,39 @@
|
||||
import { LangfuseSpanProcessor } from '@langfuse/otel';
|
||||
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
||||
import { LangfuseSpanProcessor } from "@langfuse/otel"
|
||||
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node"
|
||||
|
||||
export function register() {
|
||||
// Skip telemetry if Langfuse env vars are not configured
|
||||
if (!process.env.LANGFUSE_PUBLIC_KEY || !process.env.LANGFUSE_SECRET_KEY) {
|
||||
console.warn('[Langfuse] Environment variables not configured - telemetry disabled');
|
||||
return;
|
||||
}
|
||||
// Skip telemetry if Langfuse env vars are not configured
|
||||
if (!process.env.LANGFUSE_PUBLIC_KEY || !process.env.LANGFUSE_SECRET_KEY) {
|
||||
console.warn(
|
||||
"[Langfuse] Environment variables not configured - telemetry disabled",
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const langfuseSpanProcessor = new LangfuseSpanProcessor({
|
||||
publicKey: process.env.LANGFUSE_PUBLIC_KEY,
|
||||
secretKey: process.env.LANGFUSE_SECRET_KEY,
|
||||
baseUrl: process.env.LANGFUSE_BASEURL,
|
||||
});
|
||||
const langfuseSpanProcessor = new LangfuseSpanProcessor({
|
||||
publicKey: process.env.LANGFUSE_PUBLIC_KEY,
|
||||
secretKey: process.env.LANGFUSE_SECRET_KEY,
|
||||
baseUrl: process.env.LANGFUSE_BASEURL,
|
||||
// Filter out Next.js HTTP request spans so AI SDK spans become root traces
|
||||
shouldExportSpan: ({ otelSpan }) => {
|
||||
const spanName = otelSpan.name
|
||||
// Skip Next.js HTTP infrastructure spans
|
||||
if (
|
||||
spanName.startsWith("POST /") ||
|
||||
spanName.startsWith("GET /") ||
|
||||
spanName.includes("BaseServer") ||
|
||||
spanName.includes("handleRequest")
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
})
|
||||
|
||||
const tracerProvider = new NodeTracerProvider({
|
||||
spanProcessors: [langfuseSpanProcessor],
|
||||
});
|
||||
const tracerProvider = new NodeTracerProvider({
|
||||
spanProcessors: [langfuseSpanProcessor],
|
||||
})
|
||||
|
||||
tracerProvider.register();
|
||||
// Register globally so AI SDK's telemetry also uses this processor
|
||||
tracerProvider.register()
|
||||
}
|
||||
|
||||
26
lib/ai-config.ts
Normal file
26
lib/ai-config.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { STORAGE_KEYS } from "./storage"
|
||||
|
||||
/**
|
||||
* Get AI configuration from localStorage.
|
||||
* Returns API keys and settings for custom AI providers.
|
||||
* Used to override server defaults when user provides their own API key.
|
||||
*/
|
||||
export function getAIConfig() {
|
||||
if (typeof window === "undefined") {
|
||||
return {
|
||||
accessCode: "",
|
||||
aiProvider: "",
|
||||
aiBaseUrl: "",
|
||||
aiApiKey: "",
|
||||
aiModel: "",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
accessCode: localStorage.getItem(STORAGE_KEYS.accessCode) || "",
|
||||
aiProvider: localStorage.getItem(STORAGE_KEYS.aiProvider) || "",
|
||||
aiBaseUrl: localStorage.getItem(STORAGE_KEYS.aiBaseUrl) || "",
|
||||
aiApiKey: localStorage.getItem(STORAGE_KEYS.aiApiKey) || "",
|
||||
aiModel: localStorage.getItem(STORAGE_KEYS.aiModel) || "",
|
||||
}
|
||||
}
|
||||
@@ -1,69 +1,432 @@
|
||||
import { bedrock } from '@ai-sdk/amazon-bedrock';
|
||||
import { openai, createOpenAI } from '@ai-sdk/openai';
|
||||
import { createAnthropic } from '@ai-sdk/anthropic';
|
||||
import { google, createGoogleGenerativeAI } from '@ai-sdk/google';
|
||||
import { azure, createAzure } from '@ai-sdk/azure';
|
||||
import { ollama, createOllama } from 'ollama-ai-provider-v2';
|
||||
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
||||
import { deepseek, createDeepSeek } from '@ai-sdk/deepseek';
|
||||
import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock"
|
||||
import { createAnthropic } from "@ai-sdk/anthropic"
|
||||
import { azure, createAzure } from "@ai-sdk/azure"
|
||||
import { createDeepSeek, deepseek } from "@ai-sdk/deepseek"
|
||||
import { createGateway, gateway } from "@ai-sdk/gateway"
|
||||
import { createGoogleGenerativeAI, google } from "@ai-sdk/google"
|
||||
import { createOpenAI, openai } from "@ai-sdk/openai"
|
||||
import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
|
||||
import { createOpenRouter } from "@openrouter/ai-sdk-provider"
|
||||
import { createOllama, ollama } from "ollama-ai-provider-v2"
|
||||
|
||||
export type ProviderName =
|
||||
| 'bedrock'
|
||||
| 'openai'
|
||||
| 'anthropic'
|
||||
| 'google'
|
||||
| 'azure'
|
||||
| 'ollama'
|
||||
| 'openrouter'
|
||||
| 'deepseek';
|
||||
| "bedrock"
|
||||
| "openai"
|
||||
| "anthropic"
|
||||
| "google"
|
||||
| "azure"
|
||||
| "ollama"
|
||||
| "openrouter"
|
||||
| "deepseek"
|
||||
| "siliconflow"
|
||||
| "gateway"
|
||||
|
||||
interface ModelConfig {
|
||||
model: any;
|
||||
providerOptions?: any;
|
||||
headers?: Record<string, string>;
|
||||
model: any
|
||||
providerOptions?: any
|
||||
headers?: Record<string, string>
|
||||
modelId: string
|
||||
}
|
||||
|
||||
export interface ClientOverrides {
|
||||
provider?: string | null
|
||||
baseUrl?: string | null
|
||||
apiKey?: string | null
|
||||
modelId?: string | null
|
||||
}
|
||||
|
||||
// Providers that can be used with client-provided API keys
|
||||
const ALLOWED_CLIENT_PROVIDERS: ProviderName[] = [
|
||||
"openai",
|
||||
"anthropic",
|
||||
"google",
|
||||
"azure",
|
||||
"openrouter",
|
||||
"deepseek",
|
||||
"siliconflow",
|
||||
"gateway",
|
||||
]
|
||||
|
||||
// Bedrock provider options for Anthropic beta features
|
||||
const BEDROCK_ANTHROPIC_BETA = {
|
||||
bedrock: {
|
||||
anthropicBeta: ['fine-grained-tool-streaming-2025-05-14'],
|
||||
},
|
||||
};
|
||||
bedrock: {
|
||||
anthropicBeta: ["fine-grained-tool-streaming-2025-05-14"],
|
||||
},
|
||||
}
|
||||
|
||||
// Direct Anthropic API headers for beta features
|
||||
const ANTHROPIC_BETA_HEADERS = {
|
||||
'anthropic-beta': 'fine-grained-tool-streaming-2025-05-14',
|
||||
};
|
||||
"anthropic-beta": "fine-grained-tool-streaming-2025-05-14",
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely parse integer from environment variable with validation
|
||||
*/
|
||||
function parseIntSafe(
|
||||
value: string | undefined,
|
||||
varName: string,
|
||||
min?: number,
|
||||
max?: number,
|
||||
): number | undefined {
|
||||
if (!value) return undefined
|
||||
const parsed = Number.parseInt(value, 10)
|
||||
if (Number.isNaN(parsed)) {
|
||||
throw new Error(`${varName} must be a valid integer, got: ${value}`)
|
||||
}
|
||||
if (min !== undefined && parsed < min) {
|
||||
throw new Error(`${varName} must be >= ${min}, got: ${parsed}`)
|
||||
}
|
||||
if (max !== undefined && parsed > max) {
|
||||
throw new Error(`${varName} must be <= ${max}, got: ${parsed}`)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
/**
|
||||
* Build provider-specific options from environment variables
|
||||
* Supports various AI SDK providers with their unique configuration options
|
||||
*
|
||||
* Environment variables:
|
||||
* - OPENAI_REASONING_EFFORT: OpenAI reasoning effort level (minimal/low/medium/high) - for o1/o3/gpt-5
|
||||
* - OPENAI_REASONING_SUMMARY: OpenAI reasoning summary (none/brief/detailed) - auto-enabled for o1/o3/gpt-5
|
||||
* - ANTHROPIC_THINKING_BUDGET_TOKENS: Anthropic thinking budget in tokens (1024-64000)
|
||||
* - ANTHROPIC_THINKING_TYPE: Anthropic thinking type (enabled)
|
||||
* - GOOGLE_THINKING_BUDGET: Google Gemini 2.5 thinking budget in tokens (1024-100000)
|
||||
* - GOOGLE_THINKING_LEVEL: Google Gemini 3 thinking level (low/high)
|
||||
* - AZURE_REASONING_EFFORT: Azure/OpenAI reasoning effort (low/medium/high)
|
||||
* - AZURE_REASONING_SUMMARY: Azure reasoning summary (none/brief/detailed)
|
||||
* - BEDROCK_REASONING_BUDGET_TOKENS: Bedrock Claude reasoning budget in tokens (1024-64000)
|
||||
* - BEDROCK_REASONING_EFFORT: Bedrock Nova reasoning effort (low/medium/high)
|
||||
* - OLLAMA_ENABLE_THINKING: Enable Ollama thinking mode (set to "true")
|
||||
*/
|
||||
function buildProviderOptions(
|
||||
provider: ProviderName,
|
||||
modelId?: string,
|
||||
): Record<string, any> | undefined {
|
||||
const options: Record<string, any> = {}
|
||||
|
||||
switch (provider) {
|
||||
case "openai": {
|
||||
const reasoningEffort = process.env.OPENAI_REASONING_EFFORT
|
||||
const reasoningSummary = process.env.OPENAI_REASONING_SUMMARY
|
||||
|
||||
// OpenAI reasoning models (o1, o3, gpt-5) need reasoningSummary to return thoughts
|
||||
if (
|
||||
modelId &&
|
||||
(modelId.includes("o1") ||
|
||||
modelId.includes("o3") ||
|
||||
modelId.includes("gpt-5"))
|
||||
) {
|
||||
options.openai = {
|
||||
// Auto-enable reasoning summary for reasoning models (default: detailed)
|
||||
reasoningSummary:
|
||||
(reasoningSummary as "none" | "brief" | "detailed") ||
|
||||
"detailed",
|
||||
}
|
||||
|
||||
// Optionally configure reasoning effort
|
||||
if (reasoningEffort) {
|
||||
options.openai.reasoningEffort = reasoningEffort as
|
||||
| "minimal"
|
||||
| "low"
|
||||
| "medium"
|
||||
| "high"
|
||||
}
|
||||
} else if (reasoningEffort || reasoningSummary) {
|
||||
// Non-reasoning models: only apply if explicitly configured
|
||||
options.openai = {}
|
||||
if (reasoningEffort) {
|
||||
options.openai.reasoningEffort = reasoningEffort as
|
||||
| "minimal"
|
||||
| "low"
|
||||
| "medium"
|
||||
| "high"
|
||||
}
|
||||
if (reasoningSummary) {
|
||||
options.openai.reasoningSummary = reasoningSummary as
|
||||
| "none"
|
||||
| "brief"
|
||||
| "detailed"
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "anthropic": {
|
||||
const thinkingBudget = parseIntSafe(
|
||||
process.env.ANTHROPIC_THINKING_BUDGET_TOKENS,
|
||||
"ANTHROPIC_THINKING_BUDGET_TOKENS",
|
||||
1024,
|
||||
64000,
|
||||
)
|
||||
const thinkingType =
|
||||
process.env.ANTHROPIC_THINKING_TYPE || "enabled"
|
||||
|
||||
if (thinkingBudget) {
|
||||
options.anthropic = {
|
||||
thinking: {
|
||||
type: thinkingType,
|
||||
budgetTokens: thinkingBudget,
|
||||
},
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "google": {
|
||||
const reasoningEffort = process.env.GOOGLE_REASONING_EFFORT
|
||||
const thinkingBudgetVal = parseIntSafe(
|
||||
process.env.GOOGLE_THINKING_BUDGET,
|
||||
"GOOGLE_THINKING_BUDGET",
|
||||
1024,
|
||||
100000,
|
||||
)
|
||||
const thinkingLevel = process.env.GOOGLE_THINKING_LEVEL
|
||||
|
||||
// Google Gemini 2.5/3 models think by default, but need includeThoughts: true
|
||||
// to return the reasoning in the response
|
||||
if (
|
||||
modelId &&
|
||||
(modelId.includes("gemini-2") ||
|
||||
modelId.includes("gemini-3") ||
|
||||
modelId.includes("gemini2") ||
|
||||
modelId.includes("gemini3"))
|
||||
) {
|
||||
const thinkingConfig: Record<string, any> = {
|
||||
includeThoughts: true,
|
||||
}
|
||||
|
||||
// Optionally configure thinking budget or level
|
||||
if (
|
||||
thinkingBudgetVal &&
|
||||
(modelId.includes("2.5") || modelId.includes("2-5"))
|
||||
) {
|
||||
thinkingConfig.thinkingBudget = thinkingBudgetVal
|
||||
} else if (
|
||||
thinkingLevel &&
|
||||
(modelId.includes("gemini-3") ||
|
||||
modelId.includes("gemini3"))
|
||||
) {
|
||||
thinkingConfig.thinkingLevel = thinkingLevel as
|
||||
| "low"
|
||||
| "high"
|
||||
}
|
||||
|
||||
options.google = { thinkingConfig }
|
||||
} else if (reasoningEffort) {
|
||||
options.google = {
|
||||
reasoningEffort: reasoningEffort as
|
||||
| "low"
|
||||
| "medium"
|
||||
| "high",
|
||||
}
|
||||
}
|
||||
|
||||
// Keep existing Google options
|
||||
const options_obj: Record<string, any> = {}
|
||||
const candidateCount = parseIntSafe(
|
||||
process.env.GOOGLE_CANDIDATE_COUNT,
|
||||
"GOOGLE_CANDIDATE_COUNT",
|
||||
1,
|
||||
8,
|
||||
)
|
||||
if (candidateCount) {
|
||||
options_obj.candidateCount = candidateCount
|
||||
}
|
||||
const topK = parseIntSafe(
|
||||
process.env.GOOGLE_TOP_K,
|
||||
"GOOGLE_TOP_K",
|
||||
1,
|
||||
100,
|
||||
)
|
||||
if (topK) {
|
||||
options_obj.topK = topK
|
||||
}
|
||||
if (process.env.GOOGLE_TOP_P) {
|
||||
const topP = Number.parseFloat(process.env.GOOGLE_TOP_P)
|
||||
if (Number.isNaN(topP) || topP < 0 || topP > 1) {
|
||||
throw new Error(
|
||||
`GOOGLE_TOP_P must be a number between 0 and 1, got: ${process.env.GOOGLE_TOP_P}`,
|
||||
)
|
||||
}
|
||||
options_obj.topP = topP
|
||||
}
|
||||
|
||||
if (Object.keys(options_obj).length > 0) {
|
||||
options.google = { ...options.google, ...options_obj }
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "azure": {
|
||||
const reasoningEffort = process.env.AZURE_REASONING_EFFORT
|
||||
const reasoningSummary = process.env.AZURE_REASONING_SUMMARY
|
||||
|
||||
if (reasoningEffort || reasoningSummary) {
|
||||
options.azure = {}
|
||||
if (reasoningEffort) {
|
||||
options.azure.reasoningEffort = reasoningEffort as
|
||||
| "low"
|
||||
| "medium"
|
||||
| "high"
|
||||
}
|
||||
if (reasoningSummary) {
|
||||
options.azure.reasoningSummary = reasoningSummary as
|
||||
| "none"
|
||||
| "brief"
|
||||
| "detailed"
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "bedrock": {
|
||||
const budgetTokens = parseIntSafe(
|
||||
process.env.BEDROCK_REASONING_BUDGET_TOKENS,
|
||||
"BEDROCK_REASONING_BUDGET_TOKENS",
|
||||
1024,
|
||||
64000,
|
||||
)
|
||||
const reasoningEffort = process.env.BEDROCK_REASONING_EFFORT
|
||||
|
||||
// Bedrock reasoning ONLY for Claude and Nova models
|
||||
// Other models (MiniMax, etc.) don't support reasoningConfig
|
||||
if (
|
||||
modelId &&
|
||||
(budgetTokens || reasoningEffort) &&
|
||||
(modelId.includes("claude") ||
|
||||
modelId.includes("anthropic") ||
|
||||
modelId.includes("nova") ||
|
||||
modelId.includes("amazon"))
|
||||
) {
|
||||
const reasoningConfig: Record<string, any> = { type: "enabled" }
|
||||
|
||||
// Claude models: use budgetTokens (1024-64000)
|
||||
if (
|
||||
budgetTokens &&
|
||||
(modelId.includes("claude") ||
|
||||
modelId.includes("anthropic"))
|
||||
) {
|
||||
reasoningConfig.budgetTokens = budgetTokens
|
||||
}
|
||||
// Nova models: use maxReasoningEffort (low/medium/high)
|
||||
else if (
|
||||
reasoningEffort &&
|
||||
(modelId.includes("nova") || modelId.includes("amazon"))
|
||||
) {
|
||||
reasoningConfig.maxReasoningEffort = reasoningEffort as
|
||||
| "low"
|
||||
| "medium"
|
||||
| "high"
|
||||
}
|
||||
|
||||
options.bedrock = { reasoningConfig }
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "ollama": {
|
||||
const enableThinking = process.env.OLLAMA_ENABLE_THINKING
|
||||
// Ollama supports reasoning with think: true for models like qwen3
|
||||
if (enableThinking === "true") {
|
||||
options.ollama = { think: true }
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "deepseek":
|
||||
case "openrouter":
|
||||
case "siliconflow":
|
||||
case "gateway": {
|
||||
// These providers don't have reasoning configs in AI SDK yet
|
||||
// Gateway passes through to underlying providers which handle their own configs
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return Object.keys(options).length > 0 ? options : undefined
|
||||
}
|
||||
|
||||
// Map of provider to required environment variable
|
||||
const PROVIDER_ENV_VARS: Record<ProviderName, string | null> = {
|
||||
bedrock: null, // AWS SDK auto-uses IAM role on AWS, or env vars locally
|
||||
openai: "OPENAI_API_KEY",
|
||||
anthropic: "ANTHROPIC_API_KEY",
|
||||
google: "GOOGLE_GENERATIVE_AI_API_KEY",
|
||||
azure: "AZURE_API_KEY",
|
||||
ollama: null, // No credentials needed for local Ollama
|
||||
openrouter: "OPENROUTER_API_KEY",
|
||||
deepseek: "DEEPSEEK_API_KEY",
|
||||
siliconflow: "SILICONFLOW_API_KEY",
|
||||
gateway: "AI_GATEWAY_API_KEY",
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-detect provider based on available API keys
|
||||
* Returns the provider if exactly one is configured, otherwise null
|
||||
*/
|
||||
function detectProvider(): ProviderName | null {
|
||||
const configuredProviders: ProviderName[] = []
|
||||
|
||||
for (const [provider, envVar] of Object.entries(PROVIDER_ENV_VARS)) {
|
||||
if (envVar === null) {
|
||||
// Skip ollama - it doesn't require credentials
|
||||
continue
|
||||
}
|
||||
if (process.env[envVar]) {
|
||||
// Azure requires additional config (baseURL or resourceName)
|
||||
if (provider === "azure") {
|
||||
const hasBaseUrl = !!process.env.AZURE_BASE_URL
|
||||
const hasResourceName = !!process.env.AZURE_RESOURCE_NAME
|
||||
if (hasBaseUrl || hasResourceName) {
|
||||
configuredProviders.push(provider as ProviderName)
|
||||
}
|
||||
} else {
|
||||
configuredProviders.push(provider as ProviderName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (configuredProviders.length === 1) {
|
||||
return configuredProviders[0]
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that required API keys are present for the selected provider
|
||||
*/
|
||||
function validateProviderCredentials(provider: ProviderName): void {
|
||||
const requiredEnvVars: Record<ProviderName, string | null> = {
|
||||
bedrock: 'AWS_ACCESS_KEY_ID',
|
||||
openai: 'OPENAI_API_KEY',
|
||||
anthropic: 'ANTHROPIC_API_KEY',
|
||||
google: 'GOOGLE_GENERATIVE_AI_API_KEY',
|
||||
azure: 'AZURE_API_KEY',
|
||||
ollama: null, // No credentials needed for local Ollama
|
||||
openrouter: 'OPENROUTER_API_KEY',
|
||||
deepseek: 'DEEPSEEK_API_KEY',
|
||||
};
|
||||
const requiredVar = PROVIDER_ENV_VARS[provider]
|
||||
if (requiredVar && !process.env[requiredVar]) {
|
||||
throw new Error(
|
||||
`${requiredVar} environment variable is required for ${provider} provider. ` +
|
||||
`Please set it in your .env.local file.`,
|
||||
)
|
||||
}
|
||||
|
||||
const requiredVar = requiredEnvVars[provider];
|
||||
if (requiredVar && !process.env[requiredVar]) {
|
||||
throw new Error(
|
||||
`${requiredVar} environment variable is required for ${provider} provider. ` +
|
||||
`Please set it in your .env.local file.`
|
||||
);
|
||||
}
|
||||
// Azure requires either AZURE_BASE_URL or AZURE_RESOURCE_NAME in addition to API key
|
||||
if (provider === "azure") {
|
||||
const hasBaseUrl = !!process.env.AZURE_BASE_URL
|
||||
const hasResourceName = !!process.env.AZURE_RESOURCE_NAME
|
||||
if (!hasBaseUrl && !hasResourceName) {
|
||||
throw new Error(
|
||||
`Azure requires either AZURE_BASE_URL or AZURE_RESOURCE_NAME to be set. ` +
|
||||
`Please set one in your .env.local file.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the AI model based on environment variables
|
||||
*
|
||||
* Environment variables:
|
||||
* - AI_PROVIDER: The provider to use (bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek)
|
||||
* - AI_PROVIDER: The provider to use (bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow)
|
||||
* - AI_MODEL: The model ID/name for the selected provider
|
||||
*
|
||||
* Provider-specific env vars:
|
||||
@@ -77,124 +440,290 @@ function validateProviderCredentials(provider: ProviderName): void {
|
||||
* - OPENROUTER_API_KEY: OpenRouter API key
|
||||
* - DEEPSEEK_API_KEY: DeepSeek API key
|
||||
* - DEEPSEEK_BASE_URL: DeepSeek endpoint (optional)
|
||||
* - SILICONFLOW_API_KEY: SiliconFlow API key
|
||||
* - SILICONFLOW_BASE_URL: SiliconFlow endpoint (optional, defaults to https://api.siliconflow.com/v1)
|
||||
*/
|
||||
export function getAIModel(): ModelConfig {
|
||||
const provider = (process.env.AI_PROVIDER || 'bedrock') as ProviderName;
|
||||
const modelId = process.env.AI_MODEL;
|
||||
export function getAIModel(overrides?: ClientOverrides): ModelConfig {
|
||||
// SECURITY: Prevent SSRF attacks (GHSA-9qf7-mprq-9qgm)
|
||||
// If a custom baseUrl is provided, an API key MUST also be provided.
|
||||
// This prevents attackers from redirecting server API keys to malicious endpoints.
|
||||
if (overrides?.baseUrl && !overrides?.apiKey) {
|
||||
throw new Error(
|
||||
`API key is required when using a custom base URL. ` +
|
||||
`Please provide your own API key in Settings.`,
|
||||
)
|
||||
}
|
||||
|
||||
if (!modelId) {
|
||||
throw new Error(
|
||||
`AI_MODEL environment variable is required. Example: AI_MODEL=claude-sonnet-4-5`
|
||||
);
|
||||
}
|
||||
// Check if client is providing their own provider override
|
||||
const isClientOverride = !!(overrides?.provider && overrides?.apiKey)
|
||||
|
||||
// Validate provider credentials
|
||||
validateProviderCredentials(provider);
|
||||
// Use client override if provided, otherwise fall back to env vars
|
||||
const modelId = overrides?.modelId || process.env.AI_MODEL
|
||||
|
||||
// Log initialization for debugging
|
||||
console.log(`[AI Provider] Initializing ${provider} with model: ${modelId}`);
|
||||
if (!modelId) {
|
||||
if (isClientOverride) {
|
||||
throw new Error(
|
||||
`Model ID is required when using custom AI provider. Please specify a model in Settings.`,
|
||||
)
|
||||
}
|
||||
throw new Error(
|
||||
`AI_MODEL environment variable is required. Example: AI_MODEL=claude-sonnet-4-5`,
|
||||
)
|
||||
}
|
||||
|
||||
let model: any;
|
||||
let providerOptions: any = undefined;
|
||||
let headers: Record<string, string> | undefined = undefined;
|
||||
// Determine provider: client override > explicit config > auto-detect > error
|
||||
let provider: ProviderName
|
||||
if (overrides?.provider) {
|
||||
// Validate client-provided provider
|
||||
if (
|
||||
!ALLOWED_CLIENT_PROVIDERS.includes(
|
||||
overrides.provider as ProviderName,
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`Invalid provider: ${overrides.provider}. Allowed providers: ${ALLOWED_CLIENT_PROVIDERS.join(", ")}`,
|
||||
)
|
||||
}
|
||||
provider = overrides.provider as ProviderName
|
||||
} else if (process.env.AI_PROVIDER) {
|
||||
provider = process.env.AI_PROVIDER as ProviderName
|
||||
} else {
|
||||
const detected = detectProvider()
|
||||
if (detected) {
|
||||
provider = detected
|
||||
console.log(`[AI Provider] Auto-detected provider: ${provider}`)
|
||||
} else {
|
||||
// List configured providers for better error message
|
||||
const configured = Object.entries(PROVIDER_ENV_VARS)
|
||||
.filter(([, envVar]) => envVar && process.env[envVar as string])
|
||||
.map(([p]) => p)
|
||||
|
||||
switch (provider) {
|
||||
case 'bedrock':
|
||||
model = bedrock(modelId);
|
||||
// Add Anthropic beta options if using Claude models via Bedrock
|
||||
if (modelId.includes('anthropic.claude')) {
|
||||
providerOptions = BEDROCK_ANTHROPIC_BETA;
|
||||
}
|
||||
break;
|
||||
if (configured.length === 0) {
|
||||
throw new Error(
|
||||
`No AI provider configured. Please set one of the following API keys in your .env.local file:\n` +
|
||||
`- AI_GATEWAY_API_KEY for Vercel AI Gateway\n` +
|
||||
`- DEEPSEEK_API_KEY for DeepSeek\n` +
|
||||
`- OPENAI_API_KEY for OpenAI\n` +
|
||||
`- ANTHROPIC_API_KEY for Anthropic\n` +
|
||||
`- GOOGLE_GENERATIVE_AI_API_KEY for Google\n` +
|
||||
`- AWS_ACCESS_KEY_ID for Bedrock\n` +
|
||||
`- OPENROUTER_API_KEY for OpenRouter\n` +
|
||||
`- AZURE_API_KEY for Azure\n` +
|
||||
`- SILICONFLOW_API_KEY for SiliconFlow\n` +
|
||||
`Or set AI_PROVIDER=ollama for local Ollama.`,
|
||||
)
|
||||
} else {
|
||||
throw new Error(
|
||||
`Multiple AI providers configured (${configured.join(", ")}). ` +
|
||||
`Please set AI_PROVIDER to specify which one to use.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 'openai':
|
||||
if (process.env.OPENAI_BASE_URL) {
|
||||
const customOpenAI = createOpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
baseURL: process.env.OPENAI_BASE_URL,
|
||||
});
|
||||
model = customOpenAI.chat(modelId);
|
||||
} else {
|
||||
model = openai(modelId);
|
||||
}
|
||||
break;
|
||||
// Only validate server credentials if client isn't providing their own API key
|
||||
if (!isClientOverride) {
|
||||
validateProviderCredentials(provider)
|
||||
}
|
||||
|
||||
case 'anthropic':
|
||||
const customProvider = createAnthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY,
|
||||
baseURL: process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com/v1',
|
||||
headers: ANTHROPIC_BETA_HEADERS,
|
||||
});
|
||||
model = customProvider(modelId);
|
||||
// Add beta headers for fine-grained tool streaming
|
||||
headers = ANTHROPIC_BETA_HEADERS;
|
||||
break;
|
||||
console.log(`[AI Provider] Initializing ${provider} with model: ${modelId}`)
|
||||
|
||||
case 'google':
|
||||
if (process.env.GOOGLE_BASE_URL) {
|
||||
const customGoogle = createGoogleGenerativeAI({
|
||||
apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
|
||||
baseURL: process.env.GOOGLE_BASE_URL,
|
||||
});
|
||||
model = customGoogle(modelId);
|
||||
} else {
|
||||
model = google(modelId);
|
||||
}
|
||||
break;
|
||||
let model: any
|
||||
let providerOptions: any
|
||||
let headers: Record<string, string> | undefined
|
||||
|
||||
case 'azure':
|
||||
if (process.env.AZURE_BASE_URL) {
|
||||
const customAzure = createAzure({
|
||||
apiKey: process.env.AZURE_API_KEY,
|
||||
baseURL: process.env.AZURE_BASE_URL,
|
||||
});
|
||||
model = customAzure(modelId);
|
||||
} else {
|
||||
model = azure(modelId);
|
||||
}
|
||||
break;
|
||||
// Build provider-specific options from environment variables
|
||||
const customProviderOptions = buildProviderOptions(provider, modelId)
|
||||
|
||||
case 'ollama':
|
||||
if (process.env.OLLAMA_BASE_URL) {
|
||||
const customOllama = createOllama({
|
||||
baseURL: process.env.OLLAMA_BASE_URL,
|
||||
});
|
||||
model = customOllama(modelId);
|
||||
} else {
|
||||
model = ollama(modelId);
|
||||
}
|
||||
break;
|
||||
switch (provider) {
|
||||
case "bedrock": {
|
||||
// Use credential provider chain for IAM role support (Lambda, EC2, etc.)
|
||||
// Falls back to env vars (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) for local dev
|
||||
const bedrockProvider = createAmazonBedrock({
|
||||
region: process.env.AWS_REGION || "us-west-2",
|
||||
credentialProvider: fromNodeProviderChain(),
|
||||
})
|
||||
model = bedrockProvider(modelId)
|
||||
// Add Anthropic beta options if using Claude models via Bedrock
|
||||
if (modelId.includes("anthropic.claude")) {
|
||||
// Deep merge to preserve both anthropicBeta and reasoningConfig
|
||||
providerOptions = {
|
||||
bedrock: {
|
||||
...BEDROCK_ANTHROPIC_BETA.bedrock,
|
||||
...(customProviderOptions?.bedrock || {}),
|
||||
},
|
||||
}
|
||||
} else if (customProviderOptions) {
|
||||
providerOptions = customProviderOptions
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'openrouter':
|
||||
const openrouter = createOpenRouter({
|
||||
apiKey: process.env.OPENROUTER_API_KEY,
|
||||
...(process.env.OPENROUTER_BASE_URL && { baseURL: process.env.OPENROUTER_BASE_URL }),
|
||||
});
|
||||
model = openrouter(modelId);
|
||||
break;
|
||||
case "openai": {
|
||||
const apiKey = overrides?.apiKey || process.env.OPENAI_API_KEY
|
||||
const baseURL = overrides?.baseUrl || process.env.OPENAI_BASE_URL
|
||||
if (baseURL || overrides?.apiKey) {
|
||||
const customOpenAI = createOpenAI({
|
||||
apiKey,
|
||||
...(baseURL && { baseURL }),
|
||||
})
|
||||
model = customOpenAI.chat(modelId)
|
||||
} else {
|
||||
model = openai(modelId)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'deepseek':
|
||||
if (process.env.DEEPSEEK_BASE_URL) {
|
||||
const customDeepSeek = createDeepSeek({
|
||||
apiKey: process.env.DEEPSEEK_API_KEY,
|
||||
baseURL: process.env.DEEPSEEK_BASE_URL,
|
||||
});
|
||||
model = customDeepSeek(modelId);
|
||||
} else {
|
||||
model = deepseek(modelId);
|
||||
}
|
||||
break;
|
||||
case "anthropic": {
|
||||
const apiKey = overrides?.apiKey || process.env.ANTHROPIC_API_KEY
|
||||
const baseURL =
|
||||
overrides?.baseUrl ||
|
||||
process.env.ANTHROPIC_BASE_URL ||
|
||||
"https://api.anthropic.com/v1"
|
||||
const customProvider = createAnthropic({
|
||||
apiKey,
|
||||
baseURL,
|
||||
headers: ANTHROPIC_BETA_HEADERS,
|
||||
})
|
||||
model = customProvider(modelId)
|
||||
// Add beta headers for fine-grained tool streaming
|
||||
headers = ANTHROPIC_BETA_HEADERS
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown AI provider: ${provider}. Supported providers: bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek`
|
||||
);
|
||||
}
|
||||
case "google": {
|
||||
const apiKey =
|
||||
overrides?.apiKey || process.env.GOOGLE_GENERATIVE_AI_API_KEY
|
||||
const baseURL = overrides?.baseUrl || process.env.GOOGLE_BASE_URL
|
||||
if (baseURL || overrides?.apiKey) {
|
||||
const customGoogle = createGoogleGenerativeAI({
|
||||
apiKey,
|
||||
...(baseURL && { baseURL }),
|
||||
})
|
||||
model = customGoogle(modelId)
|
||||
} else {
|
||||
model = google(modelId)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Log if provider options or headers are being applied
|
||||
if (providerOptions || headers) {
|
||||
console.log('[AI Provider] Applying provider-specific options/headers');
|
||||
}
|
||||
case "azure": {
|
||||
const apiKey = overrides?.apiKey || process.env.AZURE_API_KEY
|
||||
const baseURL = overrides?.baseUrl || process.env.AZURE_BASE_URL
|
||||
const resourceName = process.env.AZURE_RESOURCE_NAME
|
||||
// Azure requires either baseURL or resourceName to construct the endpoint
|
||||
// resourceName constructs: https://{resourceName}.openai.azure.com/openai/v1{path}
|
||||
if (baseURL || resourceName || overrides?.apiKey) {
|
||||
const customAzure = createAzure({
|
||||
apiKey,
|
||||
// baseURL takes precedence over resourceName per SDK behavior
|
||||
...(baseURL && { baseURL }),
|
||||
...(!baseURL && resourceName && { resourceName }),
|
||||
})
|
||||
model = customAzure(modelId)
|
||||
} else {
|
||||
model = azure(modelId)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return { model, providerOptions, headers };
|
||||
case "ollama":
|
||||
if (process.env.OLLAMA_BASE_URL) {
|
||||
const customOllama = createOllama({
|
||||
baseURL: process.env.OLLAMA_BASE_URL,
|
||||
})
|
||||
model = customOllama(modelId)
|
||||
} else {
|
||||
model = ollama(modelId)
|
||||
}
|
||||
break
|
||||
|
||||
case "openrouter": {
|
||||
const apiKey = overrides?.apiKey || process.env.OPENROUTER_API_KEY
|
||||
const baseURL =
|
||||
overrides?.baseUrl || process.env.OPENROUTER_BASE_URL
|
||||
const openrouter = createOpenRouter({
|
||||
apiKey,
|
||||
...(baseURL && { baseURL }),
|
||||
})
|
||||
model = openrouter(modelId)
|
||||
break
|
||||
}
|
||||
|
||||
case "deepseek": {
|
||||
const apiKey = overrides?.apiKey || process.env.DEEPSEEK_API_KEY
|
||||
const baseURL = overrides?.baseUrl || process.env.DEEPSEEK_BASE_URL
|
||||
if (baseURL || overrides?.apiKey) {
|
||||
const customDeepSeek = createDeepSeek({
|
||||
apiKey,
|
||||
...(baseURL && { baseURL }),
|
||||
})
|
||||
model = customDeepSeek(modelId)
|
||||
} else {
|
||||
model = deepseek(modelId)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "siliconflow": {
|
||||
const apiKey = overrides?.apiKey || process.env.SILICONFLOW_API_KEY
|
||||
const baseURL =
|
||||
overrides?.baseUrl ||
|
||||
process.env.SILICONFLOW_BASE_URL ||
|
||||
"https://api.siliconflow.com/v1"
|
||||
const siliconflowProvider = createOpenAI({
|
||||
apiKey,
|
||||
baseURL,
|
||||
})
|
||||
model = siliconflowProvider.chat(modelId)
|
||||
break
|
||||
}
|
||||
|
||||
case "gateway": {
|
||||
// Vercel AI Gateway - unified access to multiple AI providers
|
||||
// Model format: "provider/model" e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4-5"
|
||||
// See: https://vercel.com/ai-gateway
|
||||
const apiKey = overrides?.apiKey || process.env.AI_GATEWAY_API_KEY
|
||||
const baseURL =
|
||||
overrides?.baseUrl || process.env.AI_GATEWAY_BASE_URL
|
||||
// Only use custom configuration if explicitly set (local dev or custom Gateway)
|
||||
// Otherwise undefined → AI SDK uses Vercel default (https://ai-gateway.vercel.sh/v1/ai) + OIDC
|
||||
if (baseURL || overrides?.apiKey) {
|
||||
const customGateway = createGateway({
|
||||
apiKey,
|
||||
...(baseURL && { baseURL }),
|
||||
})
|
||||
model = customGateway(modelId)
|
||||
} else {
|
||||
model = gateway(modelId)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown AI provider: ${provider}. Supported providers: bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow, gateway`,
|
||||
)
|
||||
}
|
||||
|
||||
// Apply provider-specific options for all providers except bedrock (which has special handling)
|
||||
if (customProviderOptions && provider !== "bedrock" && !providerOptions) {
|
||||
providerOptions = customProviderOptions
|
||||
}
|
||||
|
||||
return { model, providerOptions, headers, modelId }
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a model supports prompt caching.
|
||||
* Currently only Claude models on Bedrock support prompt caching.
|
||||
*/
|
||||
export function supportsPromptCaching(modelId: string): boolean {
|
||||
// Bedrock prompt caching is supported for Claude models
|
||||
return (
|
||||
modelId.includes("claude") ||
|
||||
modelId.includes("anthropic") ||
|
||||
modelId.startsWith("us.anthropic") ||
|
||||
modelId.startsWith("eu.anthropic")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,128 +1,124 @@
|
||||
export interface CachedResponse {
|
||||
promptText: string;
|
||||
hasImage: boolean;
|
||||
xml: string;
|
||||
promptText: string
|
||||
hasImage: boolean
|
||||
xml: string
|
||||
}
|
||||
|
||||
export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
{
|
||||
promptText: "Give me a **animated connector** diagram of transformer's architecture",
|
||||
hasImage: false,
|
||||
xml: `<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
|
||||
<!-- Title -->
|
||||
<mxCell id="title" value="Transformer Architecture" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;" vertex="1" parent="1">
|
||||
{
|
||||
promptText:
|
||||
"Give me a **animated connector** diagram of transformer's architecture",
|
||||
hasImage: false,
|
||||
xml: `<mxCell id="title" value="Transformer Architecture" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="300" y="20" width="250" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Input Embedding (Left - Encoder Side) -->
|
||||
|
||||
<mxCell id="input_embed" value="Input Embedding" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=11;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="480" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Positional Encoding (Left) -->
|
||||
|
||||
<mxCell id="pos_enc_left" value="Positional Encoding" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=11;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="420" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Encoder Stack -->
|
||||
|
||||
<mxCell id="encoder_box" value="ENCODER" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;verticalAlign=top;fontSize=12;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="180" width="160" height="220" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Multi-Head Attention (Encoder) -->
|
||||
|
||||
<mxCell id="mha_enc" value="Multi-Head
Attention" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=10;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="330" width="120" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Add & Norm 1 (Encoder) -->
|
||||
|
||||
<mxCell id="add_norm1_enc" value="Add & Norm" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=10;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="280" width="120" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Feed Forward (Encoder) -->
|
||||
|
||||
<mxCell id="ff_enc" value="Feed Forward" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=10;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="240" width="120" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Add & Norm 2 (Encoder) -->
|
||||
|
||||
<mxCell id="add_norm2_enc" value="Add & Norm" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=10;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="200" width="120" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Nx label for encoder -->
|
||||
|
||||
<mxCell id="nx_enc" value="Nx" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="30" y="275" width="30" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Output Embedding (Right - Decoder Side) -->
|
||||
|
||||
<mxCell id="output_embed" value="Output Embedding" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=11;" vertex="1" parent="1">
|
||||
<mxGeometry x="650" y="480" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Positional Encoding (Right) -->
|
||||
|
||||
<mxCell id="pos_enc_right" value="Positional Encoding" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=11;" vertex="1" parent="1">
|
||||
<mxGeometry x="650" y="420" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Decoder Stack -->
|
||||
|
||||
<mxCell id="decoder_box" value="DECODER" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;verticalAlign=top;fontSize=12;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="630" y="140" width="160" height="260" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Masked Multi-Head Attention (Decoder) -->
|
||||
|
||||
<mxCell id="masked_mha_dec" value="Masked Multi-Head
Attention" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=10;" vertex="1" parent="1">
|
||||
<mxGeometry x="650" y="340" width="120" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Add & Norm 1 (Decoder) -->
|
||||
|
||||
<mxCell id="add_norm1_dec" value="Add & Norm" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=10;" vertex="1" parent="1">
|
||||
<mxGeometry x="650" y="290" width="120" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Multi-Head Attention (Decoder - Cross Attention) -->
|
||||
|
||||
<mxCell id="mha_dec" value="Multi-Head
Attention" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=10;" vertex="1" parent="1">
|
||||
<mxGeometry x="650" y="240" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Add & Norm 2 (Decoder) -->
|
||||
|
||||
<mxCell id="add_norm2_dec" value="Add & Norm" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=10;" vertex="1" parent="1">
|
||||
<mxGeometry x="650" y="200" width="120" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Feed Forward (Decoder) -->
|
||||
|
||||
<mxCell id="ff_dec" value="Feed Forward" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=10;" vertex="1" parent="1">
|
||||
<mxGeometry x="650" y="160" width="120" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Add & Norm 3 (Decoder) -->
|
||||
|
||||
<mxCell id="add_norm3_dec" value="Add & Norm" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=10;" vertex="1" parent="1">
|
||||
<mxGeometry x="650" y="120" width="120" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Nx label for decoder -->
|
||||
|
||||
<mxCell id="nx_dec" value="Nx" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="790" y="255" width="30" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Linear -->
|
||||
|
||||
<mxCell id="linear" value="Linear" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;" vertex="1" parent="1">
|
||||
<mxGeometry x="650" y="80" width="120" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Softmax -->
|
||||
|
||||
<mxCell id="softmax" value="Softmax" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;" vertex="1" parent="1">
|
||||
<mxGeometry x="650" y="40" width="120" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Output Probabilities -->
|
||||
|
||||
<mxCell id="output" value="Output Probabilities" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=11;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="640" y="0" width="140" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Animated Connectors - Encoder Side -->
|
||||
|
||||
<mxCell id="conn1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#6c8ebf;flowAnimation=1;" edge="1" parent="1" source="input_embed" target="pos_enc_left">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
@@ -143,7 +139,7 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Encoder to Decoder Cross Attention -->
|
||||
|
||||
<mxCell id="conn_cross" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;strokeColor=#9673a6;flowAnimation=1;dashed=1;" edge="1" parent="1" source="add_norm2_enc" target="mha_dec">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
@@ -158,7 +154,7 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Animated Connectors - Decoder Side -->
|
||||
|
||||
<mxCell id="conn6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#d79b00;flowAnimation=1;" edge="1" parent="1" source="output_embed" target="pos_enc_right">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
@@ -199,7 +195,7 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Residual Connections (Encoder) -->
|
||||
|
||||
<mxCell id="res1_enc" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=1.5;strokeColor=#999999;dashed=1;flowAnimation=1;" edge="1" parent="1" source="mha_enc" target="add_norm1_enc">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
@@ -218,7 +214,7 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Residual Connections (Decoder) -->
|
||||
|
||||
<mxCell id="res1_dec" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=1.5;strokeColor=#999999;dashed=1;flowAnimation=1;" edge="1" parent="1" source="masked_mha_dec" target="add_norm1_dec">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
@@ -246,54 +242,48 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Input/Output Labels -->
|
||||
|
||||
<mxCell id="input_label" value="Inputs" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="110" y="530" width="60" height="20" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="output_label" value="Outputs
(shifted right)" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="660" y="530" width="100" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>`,
|
||||
},
|
||||
{
|
||||
promptText: "Replicate this in aws style",
|
||||
hasImage: true,
|
||||
xml: `<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
|
||||
<!-- AWS Cloud Container -->
|
||||
<mxCell id="2" value="AWS" style="sketch=0;outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_aws_cloud;strokeColor=#232F3E;fillColor=none;verticalAlign=top;align=left;spacingLeft=30;fontColor=#232F3E;dashed=0;rounded=1;arcSize=5;" vertex="1" parent="1">
|
||||
</mxCell>`,
|
||||
},
|
||||
{
|
||||
promptText: "Replicate this in aws style",
|
||||
hasImage: true,
|
||||
xml: `<mxCell id="2" value="AWS" style="sketch=0;outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_aws_cloud;strokeColor=#232F3E;fillColor=none;verticalAlign=top;align=left;spacingLeft=30;fontColor=#232F3E;dashed=0;rounded=1;arcSize=5;" vertex="1" parent="1">
|
||||
<mxGeometry x="340" y="40" width="880" height="520" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- User -->
|
||||
|
||||
<mxCell id="3" value="User" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#232F3D;strokeColor=none;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=14;fontStyle=0;aspect=fixed;pointerEvents=1;shape=mxgraph.aws4.user;rounded=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="240" width="78" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- EC2 Instance -->
|
||||
|
||||
<mxCell id="4" value="EC2" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#ED7100;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=14;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.ec2;rounded=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="560" y="240" width="78" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- S3 Bucket -->
|
||||
|
||||
<mxCell id="5" value="S3" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#7AA116;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=14;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.s3;rounded=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="960" y="120" width="78" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Bedrock -->
|
||||
|
||||
<mxCell id="6" value="bedrock" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#01A88D;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=14;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.bedrock;rounded=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="960" y="260" width="78" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- DynamoDB -->
|
||||
|
||||
<mxCell id="7" value="DynamoDB" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#C925D1;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=14;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.dynamodb;rounded=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="960" y="400" width="78" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Arrow: User to EC2 -->
|
||||
|
||||
<mxCell id="8" value="" style="endArrow=classic;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="3" target="4">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="400" y="350" as="sourcePoint"/>
|
||||
@@ -301,7 +291,7 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Arrow: EC2 to S3 -->
|
||||
|
||||
<mxCell id="9" value="" style="endArrow=classic;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=1;exitY=0.25;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="4" target="5">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="700" y="350" as="sourcePoint"/>
|
||||
@@ -309,7 +299,7 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Arrow: EC2 to Bedrock -->
|
||||
|
||||
<mxCell id="10" value="" style="endArrow=classic;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="4" target="6">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="700" y="350" as="sourcePoint"/>
|
||||
@@ -317,130 +307,474 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Arrow: EC2 to DynamoDB -->
|
||||
|
||||
<mxCell id="11" value="" style="endArrow=classic;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=1;exitY=0.75;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="4" target="7">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="700" y="350" as="sourcePoint"/>
|
||||
<mxPoint x="750" y="300" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>`,
|
||||
},
|
||||
{
|
||||
promptText: "Replicate this flowchart.",
|
||||
hasImage: true,
|
||||
xml: `<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
|
||||
<!-- Start: Lamp doesn't work -->
|
||||
<mxCell id="2" value="Lamp doesn't work" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffcccc;strokeColor=#000000;strokeWidth=2;fontSize=18;fontStyle=0;" vertex="1" parent="1">
|
||||
</mxCell>`,
|
||||
},
|
||||
{
|
||||
promptText: "Replicate this flowchart.",
|
||||
hasImage: true,
|
||||
xml: `<mxCell id="2" value="Lamp doesn't work" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffcccc;strokeColor=#000000;strokeWidth=2;fontSize=18;fontStyle=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="140" y="40" width="180" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Arrow from start to first decision -->
|
||||
|
||||
<mxCell id="3" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#000000;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="2" target="4">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Decision: Lamp plugged in? -->
|
||||
|
||||
<mxCell id="4" value="Lamp<br>plugged in?" style="rhombus;whiteSpace=wrap;html=1;fillColor=#ffff99;strokeColor=#000000;strokeWidth=2;fontSize=18;fontStyle=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="130" y="150" width="200" height="200" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Arrow to Plug in lamp (No) -->
|
||||
|
||||
<mxCell id="5" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#000000;strokeWidth=2;endArrow=block;endFill=1;fontSize=16;" edge="1" parent="1" source="4" target="6">
|
||||
<mxGeometry x="-0.2" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Action: Plug in lamp -->
|
||||
|
||||
<mxCell id="6" value="Plug in lamp" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#99ff99;strokeColor=#000000;strokeWidth=2;fontSize=18;fontStyle=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="420" y="220" width="200" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Arrow down to second decision (Yes) -->
|
||||
|
||||
<mxCell id="7" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#000000;strokeWidth=2;endArrow=block;endFill=1;fontSize=16;" edge="1" parent="1" source="4" target="8">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Decision: Bulb burned out? -->
|
||||
|
||||
<mxCell id="8" value="Bulb<br>burned out?" style="rhombus;whiteSpace=wrap;html=1;fillColor=#ffff99;strokeColor=#000000;strokeWidth=2;fontSize=18;fontStyle=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="130" y="400" width="200" height="200" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Arrow to Replace bulb (Yes) -->
|
||||
|
||||
<mxCell id="9" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#000000;strokeWidth=2;endArrow=block;endFill=1;fontSize=16;" edge="1" parent="1" source="8" target="10">
|
||||
<mxGeometry x="-0.2" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Action: Replace bulb -->
|
||||
|
||||
<mxCell id="10" value="Replace bulb" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#99ff99;strokeColor=#000000;strokeWidth=2;fontSize=18;fontStyle=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="420" y="470" width="200" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Arrow down to Repair lamp (No) -->
|
||||
|
||||
<mxCell id="11" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#000000;strokeWidth=2;endArrow=block;endFill=1;fontSize=16;" edge="1" parent="1" source="8" target="12">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Action: Repair lamp -->
|
||||
|
||||
<mxCell id="12" value="Repair lamp" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#99ff99;strokeColor=#000000;strokeWidth=2;fontSize=18;fontStyle=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="130" y="650" width="200" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>`,
|
||||
},
|
||||
{
|
||||
promptText: "Draw a cat for me",
|
||||
hasImage: false,
|
||||
xml: `<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
|
||||
<!-- Cat's head -->
|
||||
<mxCell id="2" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;" vertex="1" parent="1">
|
||||
</mxCell>`,
|
||||
},
|
||||
{
|
||||
promptText: "Summarize this paper as a diagram",
|
||||
hasImage: true,
|
||||
xml: `<mxCell id="title_bg" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1a237e;strokeColor=none;arcSize=8;"
|
||||
value="" vertex="1">
|
||||
<mxGeometry height="80" width="720" x="40" y="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="title" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=22;fontStyle=1;fontColor=#FFFFFF;"
|
||||
value="Chain-of-Thought Prompting<br><font style="font-size: 14px;">Elicits Reasoning in Large Language Models</font>"
|
||||
vertex="1">
|
||||
<mxGeometry height="70" width="720" x="40" y="25" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="authors" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontColor=#666666;"
|
||||
value="Wei et al. (Google Research, Brain Team) | NeurIPS 2022" vertex="1">
|
||||
<mxGeometry height="20" width="720" x="40" y="100" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="core_header" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;fontColor=#1a237e;"
|
||||
value="💡 Core Idea" vertex="1">
|
||||
<mxGeometry height="30" width="150" x="40" y="125" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="core_box" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E3F2FD;strokeColor=#1565C0;align=left;spacingLeft=10;spacingRight=10;fontSize=11;"
|
||||
value="<b>Chain of Thought</b> = A series of intermediate reasoning steps that lead to the final answer<br><br>Simply provide a few CoT demonstrations as exemplars in few-shot prompting"
|
||||
vertex="1">
|
||||
<mxGeometry height="75" width="340" x="40" y="155" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="compare_header" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;fontColor=#1a237e;"
|
||||
value="⚖️ Standard vs Chain-of-Thought Prompting" vertex="1">
|
||||
<mxGeometry height="30" width="350" x="40" y="240" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="std_box" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFEBEE;strokeColor=#C62828;arcSize=8;"
|
||||
value="" vertex="1">
|
||||
<mxGeometry height="160" width="170" x="40" y="275" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="std_title" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=1;fontColor=#C62828;"
|
||||
value="Standard Prompting" vertex="1">
|
||||
<mxGeometry height="25" width="170" x="40" y="280" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="std_q" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=top;whiteSpace=wrap;rounded=0;fontSize=9;spacingLeft=5;spacingRight=5;"
|
||||
value="Q: Roger has 5 tennis balls. He buys 2 more cans. Each can has 3 balls. How many now?"
|
||||
vertex="1">
|
||||
<mxGeometry height="55" width="160" x="45" y="305" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="std_a" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=#FFCDD2;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=1;fontSize=10;fontStyle=1;spacingLeft=5;"
|
||||
value="A: The answer is 11." vertex="1">
|
||||
<mxGeometry height="25" width="150" x="50" y="365" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="std_result" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontColor=#C62828;"
|
||||
value="❌ Often Wrong" vertex="1">
|
||||
<mxGeometry height="30" width="170" x="40" y="400" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cot_box" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E8F5E9;strokeColor=#2E7D32;arcSize=8;"
|
||||
value="" vertex="1">
|
||||
<mxGeometry height="160" width="170" x="220" y="275" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cot_title" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=1;fontColor=#2E7D32;"
|
||||
value="Chain-of-Thought" vertex="1">
|
||||
<mxGeometry height="25" width="170" x="220" y="280" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cot_q" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=top;whiteSpace=wrap;rounded=0;fontSize=9;spacingLeft=5;spacingRight=5;"
|
||||
value="Q: Roger has 5 tennis balls. He buys 2 more cans. Each can has 3 balls. How many now?"
|
||||
vertex="1">
|
||||
<mxGeometry height="55" width="160" x="225" y="305" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cot_a" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=#C8E6C9;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=1;fontSize=9;fontStyle=1;spacingLeft=5;"
|
||||
value="A: 2 cans × 3 = 6 balls.<br>5 + 6 = 11. Answer: 11" vertex="1">
|
||||
<mxGeometry height="35" width="150" x="230" y="360" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="cot_result" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontColor=#2E7D32;"
|
||||
value="✓ Correct!" vertex="1">
|
||||
<mxGeometry height="30" width="170" x="220" y="400" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="vs_arrow" edge="1" parent="1"
|
||||
style="shape=flexArrow;endArrow=classic;startArrow=classic;html=1;fillColor=#FFC107;strokeColor=none;width=8;endSize=4;startSize=4;"
|
||||
value="">
|
||||
<mxGeometry relative="1" width="100" as="geometry">
|
||||
<mxPoint x="195" y="355" as="sourcePoint" />
|
||||
<mxPoint x="235" y="355" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="props_header" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;fontColor=#1a237e;"
|
||||
value="🔑 Key Properties" vertex="1">
|
||||
<mxGeometry height="30" width="150" x="400" y="125" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="prop1" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFF3E0;strokeColor=#EF6C00;fontSize=10;align=left;spacingLeft=8;"
|
||||
value="1️⃣ Decomposes multi-step problems" vertex="1">
|
||||
<mxGeometry height="32" width="180" x="400" y="155" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="prop2" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFF3E0;strokeColor=#EF6C00;fontSize=10;align=left;spacingLeft=8;"
|
||||
value="2️⃣ Interpretable reasoning window" vertex="1">
|
||||
<mxGeometry height="32" width="180" x="400" y="192" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="prop3" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFF3E0;strokeColor=#EF6C00;fontSize=10;align=left;spacingLeft=8;"
|
||||
value="3️⃣ Applicable to any language task" vertex="1">
|
||||
<mxGeometry height="32" width="180" x="400" y="229" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="prop4" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFF3E0;strokeColor=#EF6C00;fontSize=10;align=left;spacingLeft=8;"
|
||||
value="4️⃣ No finetuning required" vertex="1">
|
||||
<mxGeometry height="32" width="180" x="400" y="266" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="emergent_header" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;fontColor=#1a237e;"
|
||||
value="📈 Emergent Ability" vertex="1">
|
||||
<mxGeometry height="30" width="180" x="400" y="310" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="emergent_box" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#F3E5F5;strokeColor=#7B1FA2;arcSize=8;"
|
||||
value="" vertex="1">
|
||||
<mxGeometry height="95" width="180" x="400" y="340" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="emergent_text" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;"
|
||||
value="CoT only works with<br><b>~100B+ parameters</b><br><br>Small models produce<br>fluent but illogical chains"
|
||||
vertex="1">
|
||||
<mxGeometry height="85" width="180" x="400" y="345" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="results_header" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;fontColor=#1a237e;"
|
||||
value="📊 Key Results" vertex="1">
|
||||
<mxGeometry height="30" width="150" x="600" y="125" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="gsm_box" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E8F5E9;strokeColor=#2E7D32;arcSize=8;"
|
||||
value="" vertex="1">
|
||||
<mxGeometry height="100" width="160" x="600" y="155" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="gsm_title" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=1;fontColor=#2E7D32;"
|
||||
value="GSM8K (Math)" vertex="1">
|
||||
<mxGeometry height="20" width="160" x="600" y="160" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="gsm_bar1" parent="1"
|
||||
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFCDD2;strokeColor=none;"
|
||||
value="" vertex="1">
|
||||
<mxGeometry height="30" width="40" x="615" y="185" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="gsm_bar2" parent="1"
|
||||
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#4CAF50;strokeColor=none;"
|
||||
value="" vertex="1">
|
||||
<mxGeometry height="30" width="80" x="665" y="185" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="gsm_label1" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=1;"
|
||||
value="18%" vertex="1">
|
||||
<mxGeometry height="15" width="40" x="615" y="215" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="gsm_label2" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=1;fontColor=#2E7D32;"
|
||||
value="57%" vertex="1">
|
||||
<mxGeometry height="15" width="80" x="665" y="215" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="gsm_legend" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=9;fontColor=#666666;"
|
||||
value="Standard → CoT (PaLM 540B)" vertex="1">
|
||||
<mxGeometry height="20" width="160" x="600" y="232" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="bench_header" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;fontColor=#1a237e;"
|
||||
value="🧪 Benchmarks Tested" vertex="1">
|
||||
<mxGeometry height="30" width="180" x="600" y="265" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="bench_arith" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E3F2FD;strokeColor=#1565C0;fontSize=10;align=center;"
|
||||
value="🔢 Arithmetic<br><font style="font-size: 9px;">GSM8K, SVAMP, ASDiv, AQuA, MAWPS</font>"
|
||||
vertex="1">
|
||||
<mxGeometry height="45" width="160" x="600" y="295" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="bench_common" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E3F2FD;strokeColor=#1565C0;fontSize=10;align=center;"
|
||||
value="🧠 Commonsense<br><font style="font-size: 9px;">CSQA, StrategyQA, Date, Sports, SayCan</font>"
|
||||
vertex="1">
|
||||
<mxGeometry height="45" width="160" x="600" y="345" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="bench_symbol" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E3F2FD;strokeColor=#1565C0;fontSize=10;align=center;"
|
||||
value="🔣 Symbolic<br><font style="font-size: 9px;">Last Letter Concat, Coin Flip</font>"
|
||||
vertex="1">
|
||||
<mxGeometry height="40" width="160" x="600" y="395" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="task_header" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;fontColor=#1a237e;"
|
||||
value="🎯 Task Types & Results" vertex="1">
|
||||
<mxGeometry height="30" width="200" x="40" y="445" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="task_arith" parent="1"
|
||||
style="ellipse;whiteSpace=wrap;html=1;fillColor=#BBDEFB;strokeColor=#1565C0;fontSize=11;fontStyle=1;"
|
||||
value="Arithmetic<br>Reasoning" vertex="1">
|
||||
<mxGeometry height="60" width="90" x="40" y="480" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="task_arith_res" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=top;whiteSpace=wrap;rounded=0;fontSize=9;fontColor=#1565C0;"
|
||||
value="SOTA on GSM8K<br>(57% vs 55% prior)" vertex="1">
|
||||
<mxGeometry height="30" width="110" x="30" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="task_common" parent="1"
|
||||
style="ellipse;whiteSpace=wrap;html=1;fillColor=#C8E6C9;strokeColor=#2E7D32;fontSize=11;fontStyle=1;"
|
||||
value="Commonsense<br>Reasoning" vertex="1">
|
||||
<mxGeometry height="60" width="90" x="160" y="480" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="task_common_res" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=top;whiteSpace=wrap;rounded=0;fontSize=9;fontColor=#2E7D32;"
|
||||
value="SOTA StrategyQA<br>(75.6% vs 69.4%)" vertex="1">
|
||||
<mxGeometry height="30" width="110" x="150" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="task_symbol" parent="1"
|
||||
style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFE0B2;strokeColor=#EF6C00;fontSize=11;fontStyle=1;"
|
||||
value="Symbolic<br>Reasoning" vertex="1">
|
||||
<mxGeometry height="60" width="90" x="280" y="480" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="task_symbol_res" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=top;whiteSpace=wrap;rounded=0;fontSize=9;fontColor=#EF6C00;"
|
||||
value="OOD Generalization<br>to longer sequences" vertex="1">
|
||||
<mxGeometry height="30" width="110" x="270" y="540" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="task_arrow1" edge="1" parent="1"
|
||||
style="endArrow=classic;html=1;strokeColor=#9E9E9E;strokeWidth=2;" value="">
|
||||
<mxGeometry height="50" relative="1" width="50" as="geometry">
|
||||
<mxPoint x="130" y="510" as="sourcePoint" />
|
||||
<mxPoint x="160" y="510" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="task_arrow2" edge="1" parent="1"
|
||||
style="endArrow=classic;html=1;strokeColor=#9E9E9E;strokeWidth=2;" value="">
|
||||
<mxGeometry height="50" relative="1" width="50" as="geometry">
|
||||
<mxPoint x="250" y="510" as="sourcePoint" />
|
||||
<mxPoint x="280" y="510" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="models_header" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;fontColor=#1a237e;"
|
||||
value="🤖 Models Tested" vertex="1">
|
||||
<mxGeometry height="30" width="150" x="400" y="445" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="models_box" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ECEFF1;strokeColor=#607D8B;arcSize=8;"
|
||||
value="" vertex="1">
|
||||
<mxGeometry height="95" width="180" x="400" y="475" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="model1" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;spacingLeft=10;"
|
||||
value="• GPT-3 (175B)" vertex="1">
|
||||
<mxGeometry height="20" width="90" x="400" y="480" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="model2" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;spacingLeft=10;"
|
||||
value="• LaMDA (137B)" vertex="1">
|
||||
<mxGeometry height="20" width="90" x="400" y="500" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="model3" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;spacingLeft=10;"
|
||||
value="• PaLM (540B)" vertex="1">
|
||||
<mxGeometry height="20" width="90" x="400" y="520" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="model4" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;spacingLeft=10;"
|
||||
value="• Codex" vertex="1">
|
||||
<mxGeometry height="20" width="80" x="490" y="480" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="model5" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;spacingLeft=10;"
|
||||
value="• UL2 (20B)" vertex="1">
|
||||
<mxGeometry height="20" width="80" x="490" y="500" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="model_note" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=2;fontColor=#607D8B;"
|
||||
value="No finetuning - prompting only!" vertex="1">
|
||||
<mxGeometry height="20" width="180" x="400" y="545" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="takeaway_header" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;fontColor=#1a237e;"
|
||||
value="✨ Key Takeaways" vertex="1">
|
||||
<mxGeometry height="30" width="160" x="600" y="445" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="takeaway_box" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFF8E1;strokeColor=#FFA000;arcSize=8;"
|
||||
value="" vertex="1">
|
||||
<mxGeometry height="95" width="160" x="600" y="475" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="take1" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;spacingLeft=5;"
|
||||
value="✓ Simple yet powerful" vertex="1">
|
||||
<mxGeometry height="18" width="150" x="605" y="480" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="take2" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;spacingLeft=5;"
|
||||
value="✓ Emergent at scale" vertex="1">
|
||||
<mxGeometry height="18" width="150" x="605" y="498" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="take3" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;spacingLeft=5;"
|
||||
value="✓ Broadly applicable" vertex="1">
|
||||
<mxGeometry height="18" width="150" x="605" y="516" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="take4" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;spacingLeft=5;"
|
||||
value="✓ No training needed" vertex="1">
|
||||
<mxGeometry height="18" width="150" x="605" y="534" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="take5" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;spacingLeft=5;"
|
||||
value="✓ State-of-the-art results" vertex="1">
|
||||
<mxGeometry height="18" width="150" x="605" y="552" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="format_header" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=14;fontStyle=1;fontColor=#1a237e;"
|
||||
value="📝 Prompt Format" vertex="1">
|
||||
<mxGeometry height="25" width="150" x="40" y="575" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="format_box" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E1BEE7;strokeColor=#7B1FA2;fontSize=12;fontStyle=1;"
|
||||
value="〈 Input, Chain of Thought, Output 〉" vertex="1">
|
||||
<mxGeometry height="35" width="250" x="40" y="600" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="limit_header" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=14;fontStyle=1;fontColor=#1a237e;"
|
||||
value="⚠️ Limitations" vertex="1">
|
||||
<mxGeometry height="25" width="120" x="310" y="575" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="limit_box" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFEBEE;strokeColor=#C62828;fontSize=10;align=left;spacingLeft=8;"
|
||||
value="• Requires large models (~100B+)<br>• No guarantee of correct reasoning<br>• Costly to serve in production"
|
||||
vertex="1">
|
||||
<mxGeometry height="55" width="200" x="310" y="600" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="impact_header" parent="1"
|
||||
style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=14;fontStyle=1;fontColor=#1a237e;"
|
||||
value="🚀 Impact" vertex="1">
|
||||
<mxGeometry height="25" width="100" x="530" y="575" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="impact_box" parent="1"
|
||||
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E8F5E9;strokeColor=#2E7D32;fontSize=10;align=left;spacingLeft=8;spacingRight=8;"
|
||||
value="Foundational technique for modern LLM reasoning - inspired many follow-up works including Self-Consistency, Tree-of-Thought, etc."
|
||||
vertex="1">
|
||||
<mxGeometry height="55" width="230" x="530" y="600" as="geometry" />
|
||||
</mxCell>`,
|
||||
},
|
||||
{
|
||||
promptText: "Draw a cat for me",
|
||||
hasImage: false,
|
||||
xml: `<mxCell id="2" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="300" y="150" width="120" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Left ear -->
|
||||
|
||||
<mxCell id="3" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;rotation=30;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="120" width="50" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Right ear -->
|
||||
|
||||
<mxCell id="4" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;rotation=-30;" vertex="1" parent="1">
|
||||
<mxGeometry x="390" y="120" width="50" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Left ear inner -->
|
||||
|
||||
<mxCell id="5" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFB6C1;strokeColor=none;rotation=30;" vertex="1" parent="1">
|
||||
<mxGeometry x="290" y="135" width="30" height="35" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Right ear inner -->
|
||||
|
||||
<mxCell id="6" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFB6C1;strokeColor=none;rotation=-30;" vertex="1" parent="1">
|
||||
<mxGeometry x="400" y="135" width="30" height="35" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Left eye -->
|
||||
|
||||
<mxCell id="7" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#000000;strokeColor=#000000;" vertex="1" parent="1">
|
||||
<mxGeometry x="325" y="185" width="15" height="15" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Right eye -->
|
||||
|
||||
<mxCell id="8" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#000000;strokeColor=#000000;" vertex="1" parent="1">
|
||||
<mxGeometry x="380" y="185" width="15" height="15" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Nose -->
|
||||
|
||||
<mxCell id="9" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFB6C1;strokeColor=#000000;strokeWidth=1;rotation=180;" vertex="1" parent="1">
|
||||
<mxGeometry x="350" y="210" width="20" height="15" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Mouth left -->
|
||||
|
||||
<mxCell id="10" value="" style="curved=1;endArrow=none;html=1;strokeColor=#000000;strokeWidth=2;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="360" y="220" as="sourcePoint"/>
|
||||
@@ -451,7 +785,7 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Mouth right -->
|
||||
|
||||
<mxCell id="11" value="" style="curved=1;endArrow=none;html=1;strokeColor=#000000;strokeWidth=2;" edge="1" parent="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="360" y="220" as="sourcePoint"/>
|
||||
@@ -462,7 +796,7 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Left whisker 1 -->
|
||||
|
||||
<mxCell id="12" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="310" y="200" as="sourcePoint"/>
|
||||
@@ -470,7 +804,7 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Left whisker 2 -->
|
||||
|
||||
<mxCell id="13" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="310" y="210" as="sourcePoint"/>
|
||||
@@ -478,7 +812,7 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Left whisker 3 -->
|
||||
|
||||
<mxCell id="14" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="310" y="220" as="sourcePoint"/>
|
||||
@@ -486,7 +820,7 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Right whisker 1 -->
|
||||
|
||||
<mxCell id="15" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="410" y="200" as="sourcePoint"/>
|
||||
@@ -494,7 +828,7 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Right whisker 2 -->
|
||||
|
||||
<mxCell id="16" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="410" y="210" as="sourcePoint"/>
|
||||
@@ -502,7 +836,7 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Right whisker 3 -->
|
||||
|
||||
<mxCell id="17" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="410" y="220" as="sourcePoint"/>
|
||||
@@ -510,27 +844,27 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
<!-- Body -->
|
||||
|
||||
<mxCell id="18" value="" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="285" y="250" width="150" height="180" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Belly -->
|
||||
|
||||
<mxCell id="19" value="" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="315" y="280" width="90" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Left front paw -->
|
||||
|
||||
<mxCell id="20" value="" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="300" y="410" width="40" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Right front paw -->
|
||||
|
||||
<mxCell id="21" value="" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="380" y="410" width="40" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<!-- Tail -->
|
||||
|
||||
<mxCell id="22" value="" style="curved=1;endArrow=none;html=1;strokeColor=#000000;strokeWidth=3;fillColor=#FFE6CC;" edge="1" parent="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="285" y="340" as="sourcePoint"/>
|
||||
@@ -541,17 +875,18 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
|
||||
<mxPoint x="235" y="290"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
|
||||
</root>`,
|
||||
},
|
||||
];
|
||||
</mxCell>`,
|
||||
},
|
||||
]
|
||||
|
||||
export function findCachedResponse(
|
||||
promptText: string,
|
||||
hasImage: boolean
|
||||
promptText: string,
|
||||
hasImage: boolean,
|
||||
): CachedResponse | undefined {
|
||||
return CACHED_EXAMPLE_RESPONSES.find(
|
||||
(c) => c.promptText === promptText && c.hasImage === hasImage && c.xml !== ''
|
||||
);
|
||||
return CACHED_EXAMPLE_RESPONSES.find(
|
||||
(c) =>
|
||||
c.promptText === promptText &&
|
||||
c.hasImage === hasImage &&
|
||||
c.xml !== "",
|
||||
)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user