mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-03 08:46:15 -04:00
Compare commits
158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2517b22552 | |||
| 64617c113a | |||
| 860c6338fc | |||
| 4a7551e87b | |||
| 285cc4b9fd | |||
| d8a15e7bc9 | |||
| 542b9fa342 | |||
| 9159afb54b | |||
| 536934548a | |||
| 1c59530115 | |||
| ab8471a7ff | |||
| 4c674b075b | |||
| ba8a4c5e9f | |||
| 790fe72f39 | |||
| 2d2d4641cb | |||
| d3caa55c10 | |||
| ca534a36e5 | |||
| 278ffb9a4e | |||
| b2ff4be4c6 | |||
| 2267ce2511 | |||
| e29d1d339c | |||
| 92bc78a2d3 | |||
| 1ba5535460 | |||
| 7fa9a73bf0 | |||
| b3fcc9a81d | |||
| e8751d976e | |||
| 43c9702aa7 | |||
| ae609be710 | |||
| 86ee36f562 | |||
| 0657f09139 | |||
| 182949dee4 | |||
| 83655a3b09 | |||
| 62e5f4b154 | |||
| ea926f0e1a | |||
| 6191232d5f | |||
| 95f4ce86d6 | |||
| 5999aefde3 | |||
| babe3a0f40 | |||
| 29b95dee53 | |||
| ef9a1e911e | |||
| 7eddaa806d | |||
| d07e79e6ad | |||
| f17a7cde8d | |||
| 6d446c2a03 | |||
| 61f6091de1 | |||
| 289783f627 | |||
| 4c464cf4c0 | |||
| 83be5b0171 | |||
| 0c022ef39d | |||
| 717b544633 | |||
| c1a420717a | |||
| 42c2ffd842 | |||
| 5192c51843 | |||
| 96d7ccea48 | |||
| 49e859cfd6 | |||
| 6c57a69af4 | |||
| 4d019430e2 | |||
| 37e6c8342f | |||
| c04e892991 | |||
| bb82d43094 | |||
| 2893b6e3a5 | |||
| 54c3361be7 | |||
| c50cf21f18 | |||
| cb73e2d9e1 | |||
| 48057c2c21 | |||
| 1923ddab6e | |||
| b8249cde4b | |||
| 19b3f3d7ce | |||
| e5e05d390d | |||
| 38ad6707cf | |||
| 7ef246f98f | |||
| b91582d68a | |||
| 682d30bd12 | |||
| 4d68ee5d2c | |||
| dbe9fd00b7 | |||
| cd13a8524e | |||
| 59765e0157 | |||
| d0519be0d0 | |||
| 066e4f064d | |||
| f81c469f17 | |||
| a398013ecb | |||
| 53d9717d90 | |||
| 5885b691b9 | |||
| fd70b9b057 | |||
| de13ccb757 | |||
| 7e1abb7bbf | |||
| 83afcb9c42 | |||
| afb406c5ff | |||
| 36cf9b9922 | |||
| 0d21164255 | |||
| 3ad6f84adb | |||
| 24a5b16af8 | |||
| b4171aa8e8 | |||
| d7a79733ea | |||
| 34e5b9bdb0 | |||
| d32ec9bd52 | |||
| 89fcfcc50b | |||
| 9a6fd6a5ee | |||
| f144a0384d | |||
| a67920a25e | |||
| 67f894e5d0 | |||
| fc1eda5c77 | |||
| 371fddc820 | |||
| 8e89c38480 | |||
| b732b4caeb | |||
| 1940d1cf87 | |||
| 1f0ed24402 | |||
| 133da0f448 | |||
| f93e1e5c92 | |||
| ae4af54c7d | |||
| 9d30bc692c | |||
| 44b63dc259 | |||
| de2b4f6538 | |||
| b6b82aa847 | |||
| 2d35b78333 | |||
| c7dfbbeed0 | |||
| b946fd21b1 | |||
| daa0ca40f2 | |||
| 5b27130d60 | |||
| ee1eb35269 | |||
| 4dda7cc6a4 | |||
| cc590364e9 | |||
| f14cd4a3db | |||
| 07645e0705 | |||
| f053862018 | |||
| 69127aeaa0 | |||
| 847455383d | |||
| 9da95cb805 | |||
| 48008f91ac | |||
| d8b3aa9382 | |||
| ea9b5b8d76 | |||
| 4227b89ebc | |||
| ee846235f2 | |||
| 9463ce8006 | |||
| 756fb61691 | |||
| 94d0a3d888 | |||
| d83af721a6 | |||
| 0bc00bef32 | |||
| 98c13a965b | |||
| 310065bd0a | |||
| 34ec6cc978 | |||
| 5a90e5f9e2 | |||
| 5ee3063aab | |||
| 920373d252 | |||
| c9155c117a | |||
| 28d617d867 | |||
| 593d0737b5 | |||
| 64409182ec | |||
| 8d4607ebd5 | |||
| 250393978b | |||
| fec70ae9c9 | |||
| ad7b4b1fcd | |||
| 03d5089436 | |||
| 9b52d33889 | |||
| bc0e00cbb7 | |||
| 096710a8cc | |||
| 60dd987efd | |||
| 51e9979457 |
@@ -0,0 +1,20 @@
|
||||
name: "Setup Bun"
|
||||
description: "Setup Bun with caching and install dependencies"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Cache ~/.bun
|
||||
id: cache-bun
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.bun
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lockb', 'bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
shell: bash
|
||||
@@ -15,11 +15,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.2.21
|
||||
|
||||
- run: bun install
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- run: bun sst deploy --stage=${{ github.ref_name }}
|
||||
env:
|
||||
|
||||
@@ -20,13 +20,10 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.2.21
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: run
|
||||
run: |
|
||||
bun install
|
||||
./script/format.ts
|
||||
env:
|
||||
CI: true
|
||||
|
||||
@@ -8,7 +8,9 @@ jobs:
|
||||
opencode:
|
||||
if: |
|
||||
contains(github.event.comment.body, ' /oc') ||
|
||||
contains(github.event.comment.body, ' /opencode')
|
||||
startsWith(github.event.comment.body, '/oc') ||
|
||||
contains(github.event.comment.body, ' /opencode') ||
|
||||
startsWith(github.event.comment.body, '/opencode')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -24,4 +26,4 @@ jobs:
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
with:
|
||||
model: opencode/sonic
|
||||
model: opencode/kimi-k2
|
||||
@@ -19,16 +19,13 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.2.21
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- run: git fetch --force --tags
|
||||
- run: bun install -g @vscode/vsce
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
bun install
|
||||
./script/publish
|
||||
working-directory: ./sdks/vscode
|
||||
env:
|
||||
|
||||
@@ -35,18 +35,7 @@ jobs:
|
||||
cache: true
|
||||
cache-dependency-path: go.sum
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.2.21
|
||||
|
||||
- name: Cache ~/.bun
|
||||
id: cache-bun
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.bun
|
||||
key: ${{ runner.os }}-bun-1-2-21-${{ hashFiles('bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-1-2-21-
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Install makepkg
|
||||
run: |
|
||||
@@ -60,8 +49,6 @@ jobs:
|
||||
git config --global user.email "opencode@sst.dev"
|
||||
git config --global user.name "opencode"
|
||||
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Install OpenCode
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
@@ -71,6 +58,7 @@ jobs:
|
||||
./script/publish.ts
|
||||
env:
|
||||
OPENCODE_BUMP: ${{ inputs.bump }}
|
||||
OPENCODE_CHANNEL: latest
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
@@ -24,27 +24,11 @@ jobs:
|
||||
cache: true
|
||||
cache-dependency-path: go.sum
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.2.21
|
||||
|
||||
- name: Cache ~/.bun
|
||||
id: cache-bun
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.bun
|
||||
key: ${{ runner.os }}-bun-1-2-21-${{ hashFiles('bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-1-2-21-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
./packages/opencode/script/publish.ts
|
||||
./script/publish.ts
|
||||
env:
|
||||
OPENCODE_SNAPSHOT: true
|
||||
OPENCODE_TAG: ${{ github.ref_name }}
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
@@ -16,9 +16,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Run stats script
|
||||
run: bun script/stats.ts
|
||||
|
||||
@@ -18,15 +18,13 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.2.21
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: run
|
||||
run: |
|
||||
git config --global user.email "bot@opencode.ai"
|
||||
git config --global user.name "opencode"
|
||||
bun install
|
||||
bun turbo typecheck
|
||||
bun turbo test
|
||||
env:
|
||||
CI: true
|
||||
|
||||
@@ -13,12 +13,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.2.21
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Run typecheck
|
||||
run: bun typecheck
|
||||
|
||||
@@ -13,7 +13,7 @@ avoid repeating the title of the page, should be 5-10 words long
|
||||
|
||||
Chunks of text should not be more than 2 sentences long
|
||||
|
||||
Each section is spearated by a divider of 3 dashes
|
||||
Each section is separated by a divider of 3 dashes
|
||||
|
||||
The section titles are short with only the first letter of the word capitalized
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
description: Git commit and push
|
||||
---
|
||||
|
||||
commit and push
|
||||
|
||||
make sure it includes a prefix like
|
||||
@@ -8,5 +12,9 @@ ci:
|
||||
ignore:
|
||||
wip:
|
||||
|
||||
For anything in the packages/web use the docs: prefix.
|
||||
|
||||
For anything in the packages/app use the ignore: prefix.
|
||||
|
||||
prefer to explain WHY something was done from an end user perspective instead of
|
||||
WHAT was done.
|
||||
|
||||
@@ -76,7 +76,7 @@ Take a look at the git history to see what kind of PRs we end up merging.
|
||||
|
||||
To run OpenCode locally you need.
|
||||
|
||||
- Bun
|
||||
- Bun 1.3 or higher
|
||||
- Golang 1.24.x
|
||||
|
||||
And run.
|
||||
@@ -98,6 +98,7 @@ It's very similar to Claude Code in terms of capability. Here are the key differ
|
||||
|
||||
- 100% open source
|
||||
- Not coupled to any provider. Although Anthropic is recommended, OpenCode can be used with OpenAI, Google or even local models. As models evolve the gaps between them will close and pricing will drop so being provider-agnostic is important.
|
||||
- Out of the box LSP support
|
||||
- A focus on TUI. OpenCode is built by neovim users and the creators of [terminal.shop](https://terminal.shop); we are going to push the limits of what's possible in the terminal.
|
||||
- A client/server architecture. This for example can allow OpenCode to run on your computer, while you can drive it remotely from a mobile app. Meaning that the TUI frontend is just one of the possible clients.
|
||||
|
||||
|
||||
@@ -1,106 +1,112 @@
|
||||
# Download Stats
|
||||
|
||||
| Date | GitHub Downloads | npm Downloads | Total |
|
||||
| ---------- | ---------------- | ----------------- | ----------------- |
|
||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
||||
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
|
||||
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
||||
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
||||
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
||||
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
||||
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
||||
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
||||
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
|
||||
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
|
||||
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
|
||||
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
||||
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
||||
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
||||
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
||||
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
||||
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
||||
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
||||
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
||||
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
||||
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
||||
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
||||
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
||||
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
||||
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
||||
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
||||
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
||||
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
||||
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
||||
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
||||
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
|
||||
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
||||
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
|
||||
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
|
||||
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
|
||||
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
|
||||
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
|
||||
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
|
||||
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
|
||||
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
|
||||
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
|
||||
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
|
||||
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
|
||||
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
|
||||
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
|
||||
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
|
||||
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
|
||||
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
|
||||
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
|
||||
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
|
||||
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
|
||||
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
|
||||
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
|
||||
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
|
||||
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
|
||||
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
|
||||
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
|
||||
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
|
||||
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
|
||||
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
|
||||
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
|
||||
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
|
||||
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
|
||||
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
|
||||
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
|
||||
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
|
||||
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
|
||||
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
|
||||
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
|
||||
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
|
||||
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
|
||||
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
|
||||
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
|
||||
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
|
||||
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
|
||||
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
|
||||
| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
|
||||
| 2025-09-15 | 334,201 (+5,325) | 250,983 (+2,738) | 585,184 (+8,063) |
|
||||
| 2025-09-16 | 342,609 (+8,408) | 255,264 (+4,281) | 597,873 (+12,689) |
|
||||
| 2025-09-17 | 351,117 (+8,508) | 260,970 (+5,706) | 612,087 (+14,214) |
|
||||
| 2025-09-18 | 358,717 (+7,600) | 266,922 (+5,952) | 625,639 (+13,552) |
|
||||
| 2025-09-19 | 365,401 (+6,684) | 271,859 (+4,937) | 637,260 (+11,621) |
|
||||
| 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
|
||||
| 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331) |
|
||||
| 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161) |
|
||||
| 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636) |
|
||||
| 2025-09-24 | 393,325 (+6,317) | 294,927 (+5,798) | 688,252 (+12,115) |
|
||||
| 2025-09-25 | 398,879 (+5,554) | 301,663 (+6,736) | 700,542 (+12,290) |
|
||||
| 2025-09-26 | 404,334 (+5,455) | 306,713 (+5,050) | 711,047 (+10,505) |
|
||||
| 2025-09-27 | 411,618 (+7,284) | 317,763 (+11,050) | 729,381 (+18,334) |
|
||||
| 2025-09-28 | 414,910 (+3,292) | 322,522 (+4,759) | 737,432 (+8,051) |
|
||||
| 2025-09-29 | 419,919 (+5,009) | 328,033 (+5,511) | 747,952 (+10,520) |
|
||||
| 2025-09-30 | 427,991 (+8,072) | 336,472 (+8,439) | 764,463 (+16,511) |
|
||||
| 2025-10-01 | 433,591 (+5,600) | 341,742 (+5,270) | 775,333 (+10,870) |
|
||||
| 2025-10-02 | 440,852 (+7,261) | 348,099 (+6,357) | 788,951 (+13,618) |
|
||||
| 2025-10-03 | 446,829 (+5,977) | 359,937 (+11,838) | 806,766 (+17,815) |
|
||||
| 2025-10-04 | 452,561 (+5,732) | 370,386 (+10,449) | 822,947 (+16,181) |
|
||||
| 2025-10-05 | 455,559 (+2,998) | 374,745 (+4,359) | 830,304 (+7,357) |
|
||||
| 2025-10-06 | 460,927 (+5,368) | 379,489 (+4,744) | 840,416 (+10,112) |
|
||||
| 2025-10-07 | 467,336 (+6,409) | 385,438 (+5,949) | 852,774 (+12,358) |
|
||||
| 2025-10-08 | 474,643 (+7,307) | 394,139 (+8,701) | 868,782 (+16,008) |
|
||||
| 2025-10-09 | 479,203 (+4,560) | 400,526 (+6,387) | 879,729 (+10,947) |
|
||||
| Date | GitHub Downloads | npm Downloads | Total |
|
||||
| ---------- | ----------------- | ----------------- | ----------------- |
|
||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
||||
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
|
||||
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
||||
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
||||
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
||||
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
||||
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
||||
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
||||
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
|
||||
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
|
||||
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
|
||||
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
||||
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
||||
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
||||
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
||||
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
||||
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
||||
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
||||
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
||||
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
||||
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
||||
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
||||
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
||||
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
||||
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
||||
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
||||
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
||||
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
||||
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
||||
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
||||
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
|
||||
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
||||
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
|
||||
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
|
||||
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
|
||||
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
|
||||
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
|
||||
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
|
||||
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
|
||||
| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
|
||||
| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
|
||||
| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
|
||||
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
|
||||
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
|
||||
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
|
||||
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
|
||||
| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
|
||||
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
|
||||
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
|
||||
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
|
||||
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
|
||||
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
|
||||
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
|
||||
| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
|
||||
| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
|
||||
| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
|
||||
| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
|
||||
| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
|
||||
| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
|
||||
| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
|
||||
| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
|
||||
| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
|
||||
| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
|
||||
| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
|
||||
| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
|
||||
| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
|
||||
| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
|
||||
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
|
||||
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
|
||||
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
|
||||
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
|
||||
| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
|
||||
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
|
||||
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
|
||||
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
|
||||
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
|
||||
| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
|
||||
| 2025-09-15 | 334,201 (+5,325) | 250,983 (+2,738) | 585,184 (+8,063) |
|
||||
| 2025-09-16 | 342,609 (+8,408) | 255,264 (+4,281) | 597,873 (+12,689) |
|
||||
| 2025-09-17 | 351,117 (+8,508) | 260,970 (+5,706) | 612,087 (+14,214) |
|
||||
| 2025-09-18 | 358,717 (+7,600) | 266,922 (+5,952) | 625,639 (+13,552) |
|
||||
| 2025-09-19 | 365,401 (+6,684) | 271,859 (+4,937) | 637,260 (+11,621) |
|
||||
| 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
|
||||
| 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331) |
|
||||
| 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161) |
|
||||
| 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636) |
|
||||
| 2025-09-24 | 393,325 (+6,317) | 294,927 (+5,798) | 688,252 (+12,115) |
|
||||
| 2025-09-25 | 398,879 (+5,554) | 301,663 (+6,736) | 700,542 (+12,290) |
|
||||
| 2025-09-26 | 404,334 (+5,455) | 306,713 (+5,050) | 711,047 (+10,505) |
|
||||
| 2025-09-27 | 411,618 (+7,284) | 317,763 (+11,050) | 729,381 (+18,334) |
|
||||
| 2025-09-28 | 414,910 (+3,292) | 322,522 (+4,759) | 737,432 (+8,051) |
|
||||
| 2025-09-29 | 419,919 (+5,009) | 328,033 (+5,511) | 747,952 (+10,520) |
|
||||
| 2025-09-30 | 427,991 (+8,072) | 336,472 (+8,439) | 764,463 (+16,511) |
|
||||
| 2025-10-01 | 433,591 (+5,600) | 341,742 (+5,270) | 775,333 (+10,870) |
|
||||
| 2025-10-02 | 440,852 (+7,261) | 348,099 (+6,357) | 788,951 (+13,618) |
|
||||
| 2025-10-03 | 446,829 (+5,977) | 359,937 (+11,838) | 806,766 (+17,815) |
|
||||
| 2025-10-04 | 452,561 (+5,732) | 370,386 (+10,449) | 822,947 (+16,181) |
|
||||
| 2025-10-05 | 455,559 (+2,998) | 374,745 (+4,359) | 830,304 (+7,357) |
|
||||
| 2025-10-06 | 460,927 (+5,368) | 379,489 (+4,744) | 840,416 (+10,112) |
|
||||
| 2025-10-07 | 467,336 (+6,409) | 385,438 (+5,949) | 852,774 (+12,358) |
|
||||
| 2025-10-08 | 474,643 (+7,307) | 394,139 (+8,701) | 868,782 (+16,008) |
|
||||
| 2025-10-09 | 479,203 (+4,560) | 400,526 (+6,387) | 879,729 (+10,947) |
|
||||
| 2025-10-10 | 484,374 (+5,171) | 406,015 (+5,489) | 890,389 (+10,660) |
|
||||
| 2025-10-11 | 488,427 (+4,053) | 414,699 (+8,684) | 903,126 (+12,737) |
|
||||
| 2025-10-12 | 492,125 (+3,698) | 418,745 (+4,046) | 910,870 (+7,744) |
|
||||
| 2025-10-14 | 505,130 (+13,005) | 429,286 (+10,541) | 934,416 (+23,546) |
|
||||
| 2025-10-15 | 512,717 (+7,587) | 439,290 (+10,004) | 952,007 (+17,591) |
|
||||
| 2025-10-16 | 517,719 (+5,002) | 447,137 (+7,847) | 964,856 (+12,849) |
|
||||
|
||||
+1
-1
@@ -14,6 +14,6 @@
|
||||
"@actions/github": "6.0.1",
|
||||
"@octokit/graphql": "9.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@opencode-ai/sdk": "0.5.4"
|
||||
"@opencode-ai/sdk": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
+24
-5
@@ -3,7 +3,7 @@
|
||||
"name": "opencode",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.2.21",
|
||||
"packageManager": "bun@1.3.0",
|
||||
"scripts": {
|
||||
"dev": "bun run packages/opencode/src/index.ts",
|
||||
"typecheck": "bun turbo typecheck",
|
||||
@@ -13,30 +13,45 @@
|
||||
"packages": [
|
||||
"packages/*",
|
||||
"packages/console/*",
|
||||
"packages/sdk/js"
|
||||
"packages/sdk/js",
|
||||
"packages/slack"
|
||||
],
|
||||
"catalog": {
|
||||
"@types/bun": "1.2.21",
|
||||
"@types/bun": "1.3.0",
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@types/node": "22.13.9",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"diff": "8.0.2",
|
||||
"ai": "5.0.8",
|
||||
"hono": "4.7.10",
|
||||
"fuzzysort": "3.1.0",
|
||||
"luxon": "3.6.1",
|
||||
"typescript": "5.8.2",
|
||||
"@typescript/native-preview": "7.0.0-dev.20251014.1",
|
||||
"zod": "4.1.8",
|
||||
"remeda": "2.26.0",
|
||||
"solid-js": "1.9.9"
|
||||
"solid-js": "1.9.9",
|
||||
"tailwindcss": "4.1.11",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"vite": "7.1.4",
|
||||
"vite-plugin-solid": "2.11.8"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/bun": "catalog:",
|
||||
"husky": "9.1.7",
|
||||
"prettier": "3.6.2",
|
||||
"sst": "3.17.13",
|
||||
"sst": "3.17.19",
|
||||
"turbo": "2.5.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/script": "workspace:*"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sst/opencode"
|
||||
@@ -56,5 +71,9 @@
|
||||
],
|
||||
"patchedDependencies": {
|
||||
"@solidjs/start@1.1.7": "patches/@solidjs%2Fstart@1.1.7.patch"
|
||||
},
|
||||
"overrides": {
|
||||
"@types/bun": "catalog:",
|
||||
"@types/node": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
"name": "@opencode-ai/console-app",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
"typecheck": "tsgo --noEmit",
|
||||
"dev": "vinxi dev --host 0.0.0.0",
|
||||
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
|
||||
"build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
|
||||
"start": "vinxi start",
|
||||
"version": "0.14.7"
|
||||
"version": "0.15.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -22,6 +22,10 @@
|
||||
"vinxi": "^0.5.7",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "catalog:",
|
||||
"@typescript/native-preview": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
[data-component="dropdown"] {
|
||||
position: relative;
|
||||
|
||||
[data-slot="trigger"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: none;
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: transparent;
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-sans);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surface-hover);
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="chevron"] {
|
||||
flex-shrink: 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
[data-slot="dropdown"] {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
z-index: 1000;
|
||||
margin-top: var(--space-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
min-width: 160px;
|
||||
|
||||
&[data-align="left"] {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&[data-align="right"] {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="item"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--space-2-5) var(--space-3);
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-sans);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg-surface);
|
||||
}
|
||||
|
||||
&[data-selected="true"] {
|
||||
background-color: var(--color-accent-alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { JSX, Show, createEffect, onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { IconChevron } from "./icon"
|
||||
import "./dropdown.css"
|
||||
|
||||
interface DropdownProps {
|
||||
trigger: JSX.Element | string
|
||||
children: JSX.Element
|
||||
open?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
align?: "left" | "right"
|
||||
class?: string
|
||||
}
|
||||
|
||||
export function Dropdown(props: DropdownProps) {
|
||||
const [store, setStore] = createStore({
|
||||
isOpen: props.open ?? false,
|
||||
})
|
||||
let dropdownRef: HTMLDivElement | undefined
|
||||
|
||||
createEffect(() => {
|
||||
if (props.open !== undefined) {
|
||||
setStore("isOpen", props.open)
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef && !dropdownRef.contains(event.target as Node)) {
|
||||
setStore("isOpen", false)
|
||||
props.onOpenChange?.(false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("click", handleClickOutside)
|
||||
onCleanup(() => document.removeEventListener("click", handleClickOutside))
|
||||
})
|
||||
|
||||
const toggle = () => {
|
||||
const newValue = !store.isOpen
|
||||
setStore("isOpen", newValue)
|
||||
props.onOpenChange?.(newValue)
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-component="dropdown" class={props.class} ref={dropdownRef}>
|
||||
<button data-slot="trigger" type="button" onClick={toggle}>
|
||||
{typeof props.trigger === "string" ? <span>{props.trigger}</span> : props.trigger}
|
||||
<IconChevron data-slot="chevron" />
|
||||
</button>
|
||||
|
||||
<Show when={store.isOpen}>
|
||||
<div data-slot="dropdown" data-align={props.align ?? "left"}>
|
||||
{props.children}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface DropdownItemProps {
|
||||
children: JSX.Element
|
||||
selected?: boolean
|
||||
onClick?: () => void
|
||||
type?: "button" | "submit" | "reset"
|
||||
}
|
||||
|
||||
export function DropdownItem(props: DropdownItemProps) {
|
||||
return (
|
||||
<button
|
||||
data-slot="item"
|
||||
data-selected={props.selected ?? false}
|
||||
type={props.type ?? "button"}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -4,9 +4,11 @@ import { A, createAsync } from "@solidjs/router"
|
||||
import { createMemo, Match, Show, Switch } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { github } from "~/lib/github"
|
||||
import { queryIsLoggedIn } from "~/routes/workspace/common"
|
||||
|
||||
export function Header(props: { zen?: boolean }) {
|
||||
const githubData = createAsync(() => github())
|
||||
const isLoggedIn = createAsync(() => queryIsLoggedIn())
|
||||
const starCount = createMemo(() =>
|
||||
githubData()?.stars
|
||||
? new Intl.NumberFormat("en-US", {
|
||||
@@ -39,7 +41,7 @@ export function Header(props: { zen?: boolean }) {
|
||||
<li>
|
||||
<Switch>
|
||||
<Match when={props.zen}>
|
||||
<a href="/auth">Login</a>
|
||||
<a href="/auth">{isLoggedIn() ? "Workspace" : "Login"}</a>
|
||||
</Match>
|
||||
<Match when={!props.zen}>
|
||||
<A href="/zen">Zen</A>
|
||||
@@ -110,7 +112,7 @@ export function Header(props: { zen?: boolean }) {
|
||||
<li>
|
||||
<Switch>
|
||||
<Match when={props.zen}>
|
||||
<a href="/auth">Login</a>
|
||||
<a href="/auth">{isLoggedIn() ? "Workspace" : "Login"}</a>
|
||||
</Match>
|
||||
<Match when={!props.zen}>
|
||||
<A href="/zen">Zen</A>
|
||||
|
||||
@@ -2,26 +2,70 @@ import { JSX } from "solid-js"
|
||||
|
||||
export function IconLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} width="234" height="42" viewBox="0 0 234 42" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M54 36H36V42H30V6H54V36ZM36 30H48V12H36V30Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M24 36H0V6H24V36ZM6 30H18V12H6V30Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z" fill="currentColor"/>
|
||||
<path d="M108 12H96V36H90V6H108V12Z" fill="currentColor"/>
|
||||
<path d="M114 36H108V12H114V36Z" fill="currentColor"/>
|
||||
<path d="M144 12H126V30H144V36H120V6H144V12Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M174 36H150V6H174V36ZM156 30H168V12H156V30Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M204 36H180V6H198V0H204V36ZM186 30H198V12H186V30Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M234 24H216V30H234V36H210V6H234V24ZM216 18H228V12H216V18Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
)
|
||||
<svg width="64" height="32" viewBox="0 0 64 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 9.14333V4.5719H4.57143V9.14333H0Z" fill="currentColor" />
|
||||
<path d="M4.57178 9.14333V4.5719H9.14321V9.14333H4.57178Z" fill="currentColor" />
|
||||
<path d="M9.1438 9.14333V4.5719H13.7152V9.14333H9.1438Z" fill="currentColor" />
|
||||
<path d="M13.7124 9.14333V4.5719H18.2838V9.14333H13.7124Z" fill="currentColor" />
|
||||
<path d="M13.7124 13.7136V9.14221H18.2838V13.7136H13.7124Z" fill="currentColor" />
|
||||
<path d="M0 18.2857V13.7142H4.57143V18.2857H0Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<rect width="4.57143" height="4.57143" transform="translate(4.57178 13.7141)" fill="currentColor" />
|
||||
<path d="M4.57178 18.2855V13.7141H9.14321V18.2855H4.57178Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<path d="M9.1438 18.2855V13.7141H13.7152V18.2855H9.1438Z" fill="currentColor" />
|
||||
<path d="M13.7156 18.2855V13.7141H18.287V18.2855H13.7156Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<rect width="4.57143" height="4.57143" transform="translate(0 18.2859)" fill="currentColor" />
|
||||
<path d="M0 22.8572V18.2858H4.57143V22.8572H0Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<rect
|
||||
width="4.57143"
|
||||
height="4.57143"
|
||||
transform="translate(4.57178 18.2859)"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.2"
|
||||
/>
|
||||
<path d="M4.57178 22.8573V18.2859H9.14321V22.8573H4.57178Z" fill="currentColor" />
|
||||
<path d="M9.1438 22.8573V18.2859H13.7152V22.8573H9.1438Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<path d="M13.7156 22.8573V18.2859H18.287V22.8573H13.7156Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<path d="M0 27.4292V22.8578H4.57143V27.4292H0Z" fill="currentColor" />
|
||||
<path d="M4.57178 27.4292V22.8578H9.14321V27.4292H4.57178Z" fill="currentColor" />
|
||||
<path d="M9.1438 27.4276V22.8562H13.7152V27.4276H9.1438Z" fill="currentColor" />
|
||||
<path d="M13.7124 27.4292V22.8578H18.2838V27.4292H13.7124Z" fill="currentColor" />
|
||||
<path d="M22.8572 9.14333V4.5719H27.4286V9.14333H22.8572Z" fill="currentColor" />
|
||||
<path d="M27.426 9.14333V4.5719H31.9975V9.14333H27.426Z" fill="currentColor" />
|
||||
<path d="M32.001 9.14333V4.5719H36.5724V9.14333H32.001Z" fill="currentColor" />
|
||||
<path d="M36.5698 9.14333V4.5719H41.1413V9.14333H36.5698Z" fill="currentColor" />
|
||||
<path d="M22.8572 13.7152V9.1438H27.4286V13.7152H22.8572Z" fill="currentColor" />
|
||||
<path d="M36.5698 13.7152V9.1438H41.1413V13.7152H36.5698Z" fill="currentColor" />
|
||||
<path d="M22.8572 18.2855V13.7141H27.4286V18.2855H22.8572Z" fill="currentColor" />
|
||||
<path d="M27.4292 18.2855V13.7141H32.0006V18.2855H27.4292Z" fill="currentColor" />
|
||||
<path d="M32.001 18.2855V13.7141H36.5724V18.2855H32.001Z" fill="currentColor" />
|
||||
<path d="M36.5698 18.2855V13.7141H41.1413V18.2855H36.5698Z" fill="currentColor" />
|
||||
<path d="M22.8572 22.8573V18.2859H27.4286V22.8573H22.8572Z" fill="currentColor" />
|
||||
<path d="M27.4292 22.8573V18.2859H32.0006V22.8573H27.4292Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<path d="M32.001 22.8573V18.2859H36.5724V22.8573H32.001Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<path d="M36.5698 22.8573V18.2859H41.1413V22.8573H36.5698Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<path d="M22.8572 27.4292V22.8578H27.4286V27.4292H22.8572Z" fill="currentColor" />
|
||||
<path d="M27.4292 27.4276V22.8562H32.0006V27.4276H27.4292Z" fill="currentColor" />
|
||||
<path d="M32.001 27.4276V22.8562H36.5724V27.4276H32.001Z" fill="currentColor" />
|
||||
<path d="M36.5698 27.4292V22.8578H41.1413V27.4292H36.5698Z" fill="currentColor" />
|
||||
<path d="M45.7144 9.14333V4.5719H50.2858V9.14333H45.7144Z" fill="currentColor" />
|
||||
<path d="M50.2861 9.14333V4.5719H54.8576V9.14333H50.2861Z" fill="currentColor" />
|
||||
<path d="M54.855 9.14333V4.5719H59.4264V9.14333H54.855Z" fill="currentColor" />
|
||||
<path d="M45.7144 13.7136V9.14221H50.2858V13.7136H45.7144Z" fill="currentColor" />
|
||||
<path d="M59.4299 13.7152V9.1438H64.0014V13.7152H59.4299Z" fill="currentColor" />
|
||||
<path d="M45.7144 18.2855V13.7141H50.2858V18.2855H45.7144Z" fill="currentColor" />
|
||||
<path d="M50.2861 18.2857V13.7142H54.8576V18.2857H50.2861Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<path d="M54.8579 18.2855V13.7141H59.4293V18.2855H54.8579Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<path d="M59.4299 18.2855V13.7141H64.0014V18.2855H59.4299Z" fill="currentColor" />
|
||||
<path d="M45.7144 22.8573V18.2859H50.2858V22.8573H45.7144Z" fill="currentColor" />
|
||||
<path d="M50.2861 22.8572V18.2858H54.8576V22.8572H50.2861Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<path d="M54.8579 22.8573V18.2859H59.4293V22.8573H54.8579Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<path d="M59.4299 22.8573V18.2859H64.0014V22.8573H59.4299Z" fill="currentColor" />
|
||||
<path d="M45.7144 27.4292V22.8578H50.2858V27.4292H45.7144Z" fill="currentColor" />
|
||||
<path d="M50.2861 27.4286V22.8572H54.8576V27.4286H50.2861Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<path d="M54.8579 27.4285V22.8571H59.4293V27.4285H54.8579Z" fill="currentColor" fill-opacity="0.2" />
|
||||
<path d="M59.4299 27.4292V22.8578H64.0014V27.4292H59.4299Z" fill="currentColor" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconCopy(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
@@ -29,7 +73,7 @@ export function IconCopy(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
<svg {...props} width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8.75 8.75V2.75H21.25V15.25H15.25M15.25 8.75H2.75V21.25H15.25V8.75Z"
|
||||
stroke="#8E8B8B"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
@@ -55,3 +99,105 @@ export function IconCreditCard(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconChevron(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} width="8" height="6" viewBox="0 0 8 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M4.00024 5.04041L7.37401 1.66663L6.66691 0.959525L4.00024 3.62619L1.33357 0.959525L0.626465 1.66663L4.00024 5.04041Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconWorkspaceLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} width="24" height="30" viewBox="0 0 24 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 6H6V24H18V6ZM24 30H0V0H24V30Z" fill="currentColor" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconOpenAI(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" fill="none">
|
||||
<path
|
||||
d="M8.43799 8.06943V6.09387C8.43799 5.92749 8.50347 5.80267 8.65601 5.71959L12.8206 3.43211C13.3875 3.1202 14.0635 2.9747 14.7611 2.9747C17.3775 2.9747 19.0347 4.9087 19.0347 6.96734C19.0347 7.11288 19.0347 7.27926 19.0128 7.44564L14.6956 5.03335C14.434 4.88785 14.1723 4.88785 13.9107 5.03335L8.43799 8.06943ZM18.1624 15.7637V11.0431C18.1624 10.7519 18.0315 10.544 17.7699 10.3984L12.2972 7.36234L14.0851 6.3849C14.2377 6.30182 14.3686 6.30182 14.5212 6.3849L18.6858 8.67238C19.8851 9.3379 20.6917 10.7519 20.6917 12.1243C20.6917 13.7047 19.7106 15.1604 18.1624 15.7636V15.7637ZM7.15158 11.6047L5.36369 10.6066C5.21114 10.5235 5.14566 10.3986 5.14566 10.2323V5.65735C5.14566 3.43233 6.93355 1.7478 9.35381 1.7478C10.2697 1.7478 11.1199 2.039 11.8396 2.55886L7.54424 4.92959C7.28268 5.07508 7.15181 5.28303 7.15181 5.57427V11.6049L7.15158 11.6047ZM11 13.7258L8.43799 12.3533V9.44209L11 8.06965L13.5618 9.44209V12.3533L11 13.7258ZM12.6461 20.0476C11.7303 20.0476 10.8801 19.7564 10.1604 19.2366L14.4557 16.8658C14.7173 16.7203 14.8482 16.5124 14.8482 16.2211V10.1905L16.658 11.1886C16.8105 11.2717 16.876 11.3965 16.876 11.563V16.1379C16.876 18.3629 15.0662 20.0474 12.6461 20.0474V20.0476ZM7.47863 15.4103L3.314 13.1229C2.11471 12.4573 1.30808 11.0433 1.30808 9.67088C1.30808 8.06965 2.31106 6.6348 3.85903 6.03168V10.773C3.85903 11.0642 3.98995 11.2721 4.25151 11.4177L9.70253 14.4328L7.91464 15.4103C7.76209 15.4934 7.63117 15.4934 7.47863 15.4103ZM7.23892 18.8207C4.77508 18.8207 2.96533 17.0531 2.96533 14.8696C2.96533 14.7032 2.98719 14.5368 3.00886 14.3704L7.30418 16.7412C7.56574 16.8867 7.82752 16.8867 8.08909 16.7412L13.5618 13.726V15.7015C13.5618 15.8679 13.4964 15.9927 13.3438 16.0758L9.17918 18.3633C8.61225 18.6752 7.93631 18.8207 7.23869 18.8207H7.23892ZM12.6461 21.2952C15.2844 21.2952 17.4865 19.5069 17.9882 17.1362C20.4301 16.5331 22 14.3495 22 12.1245C22 10.6688 21.346 9.25482 20.1685 8.23581C20.2775 7.79908 20.343 7.36234 20.343 6.92582C20.343 3.95215 17.8137 1.72691 14.892 1.72691C14.3034 1.72691 13.7365 1.80999 13.1695 1.99726C12.1882 1.08223 10.8364 0.5 9.35381 0.5C6.71557 0.5 4.51352 2.28829 4.01185 4.65902C1.56987 5.26214 0 7.44564 0 9.67067C0 11.1264 0.654039 12.5404 1.83147 13.5594C1.72246 13.9961 1.65702 14.4328 1.65702 14.8694C1.65702 17.8431 4.1863 20.0683 7.108 20.0683C7.69661 20.0683 8.26354 19.9852 8.83046 19.7979C9.81155 20.713 11.1634 21.2952 12.6461 21.2952Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconAnthropic(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor" d="M13.7891 3.93188L20.2223 20.068H23.7502L17.317 3.93188H13.7891Z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M6.32538 13.6827L8.52662 8.01201L10.7279 13.6827H6.32538ZM6.68225 3.93188L0.25 20.068H3.84652L5.16202 16.6794H11.8914L13.2067 20.068H16.8033L10.371 3.93188H6.68225Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconXai(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<path
|
||||
d="M9.16861 16.0529L17.2018 9.85156C17.5957 9.54755 18.1586 9.66612 18.3463 10.1384C19.3339 12.6288 18.8926 15.6217 16.9276 17.6766C14.9626 19.7314 12.2285 20.1821 9.72948 19.1557L6.9995 20.4775C10.9151 23.2763 15.6699 22.5841 18.6411 19.4749C20.9979 17.0103 21.7278 13.6508 21.0453 10.6214L21.0515 10.6278C20.0617 6.17736 21.2948 4.39847 23.8207 0.760904C23.8804 0.674655 23.9402 0.588405 24 0.5L20.6762 3.97585V3.96506L9.16658 16.0551"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M7.37742 16.7017C4.67579 14.0395 5.14158 9.91963 7.44676 7.54383C9.15135 5.78544 11.9442 5.06779 14.3821 6.12281L17.0005 4.87559C16.5288 4.52392 15.9242 4.14566 15.2305 3.87986C12.0948 2.54882 8.34069 3.21127 5.79171 5.8386C3.33985 8.36779 2.56881 12.2567 3.89286 15.5751C4.88192 18.0552 3.26056 19.8094 1.62731 21.5801C1.04853 22.2078 0.467774 22.8355 0 23.5L7.3754 16.7037"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconAlibaba(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" fill="none">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="currentColor"
|
||||
d="M11.6043 0.340162C11.9973 1.03016 12.3883 1.72215 12.7783 2.41514C12.7941 2.44286 12.8169 2.46589 12.8445 2.48187C12.8721 2.49786 12.9034 2.50624 12.9353 2.50614H18.4873C18.6612 2.50614 18.8092 2.61614 18.9332 2.83314L20.3872 5.40311C20.5772 5.74011 20.6272 5.88111 20.4112 6.24011C20.1512 6.6701 19.8982 7.1041 19.6512 7.54009L19.2842 8.19809C19.1782 8.39409 19.0612 8.47809 19.2442 8.71008L21.8962 13.347C22.0682 13.648 22.0072 13.841 21.8532 14.117C21.4162 14.902 20.9712 15.681 20.5182 16.457C20.3592 16.729 20.1662 16.832 19.8382 16.827C19.0612 16.811 18.2863 16.817 17.5113 16.843C17.4946 16.8439 17.4785 16.8489 17.4644 16.8576C17.4502 16.8664 17.4385 16.8785 17.4303 16.893C16.5361 18.4773 15.6344 20.0573 14.7253 21.633C14.5563 21.926 14.3453 21.996 14.0003 21.997C13.0033 22 11.9983 22.001 10.9833 21.999C10.8889 21.9987 10.7961 21.9735 10.7145 21.9259C10.6328 21.8783 10.5652 21.8101 10.5184 21.728L9.18337 19.405C9.1756 19.3898 9.16368 19.3771 9.14898 19.3684C9.13429 19.3598 9.11743 19.3554 9.10037 19.356H3.98244C3.69744 19.386 3.42944 19.355 3.17745 19.264L1.57447 16.494C1.52706 16.412 1.50193 16.319 1.50158 16.2243C1.50123 16.1296 1.52567 16.0364 1.57247 15.954L2.77945 13.834C2.79665 13.8041 2.80569 13.7701 2.80569 13.7355C2.80569 13.701 2.79665 13.667 2.77945 13.637C2.15073 12.5485 1.52573 11.4579 0.904476 10.3651L0.114486 8.97008C-0.0455115 8.66008 -0.0585113 8.47409 0.209485 8.00509C0.674479 7.1921 1.13647 6.38011 1.59647 5.56911C1.72847 5.33512 1.90046 5.23512 2.18046 5.23412C3.04344 5.23048 3.90644 5.23015 4.76943 5.23312C4.79123 5.23295 4.81259 5.22704 4.83138 5.21597C4.85016 5.20491 4.8657 5.1891 4.87643 5.17012L7.68239 0.275163C7.72491 0.200697 7.78631 0.138751 7.86039 0.0955646C7.93448 0.0523783 8.01863 0.0294762 8.10439 0.0291651C8.62838 0.0281651 9.15737 0.029165 9.68736 0.0231651L10.7044 0.000165317C11.0453 -0.00283466 11.4283 0.032165 11.6043 0.340162ZM8.17238 0.743158C8.16185 0.743152 8.15149 0.745921 8.14236 0.751187C8.13323 0.756454 8.12565 0.764031 8.12038 0.773158L5.25442 5.78811C5.24066 5.81174 5.22097 5.83137 5.19729 5.84505C5.17361 5.85873 5.14677 5.86599 5.11942 5.86611H2.25346C2.19746 5.86611 2.18346 5.89111 2.21246 5.94011L8.02239 16.096C8.04739 16.138 8.03539 16.158 7.98839 16.159L5.19342 16.174C5.15256 16.1727 5.11214 16.1828 5.07678 16.2033C5.04141 16.2238 5.01253 16.2539 4.99342 16.29L3.67344 18.6C3.62944 18.678 3.65244 18.718 3.74144 18.718L9.45737 18.726C9.50337 18.726 9.53737 18.746 9.56137 18.787L10.9643 21.241C11.0103 21.322 11.0563 21.323 11.1033 21.241L16.1093 12.481L16.8923 11.0991C16.897 11.0905 16.904 11.0834 16.9125 11.0785C16.9209 11.0735 16.9305 11.0709 16.9403 11.0709C16.9501 11.0709 16.9597 11.0735 16.9681 11.0785C16.9765 11.0834 16.9835 11.0905 16.9883 11.0991L18.4123 13.629C18.4229 13.648 18.4385 13.6637 18.4573 13.6746C18.4761 13.6855 18.4975 13.6912 18.5193 13.691L21.2822 13.671C21.2893 13.6711 21.2963 13.6693 21.3024 13.6658C21.3086 13.6623 21.3137 13.6572 21.3172 13.651C21.3206 13.6449 21.3224 13.638 21.3224 13.631C21.3224 13.624 21.3206 13.6172 21.3172 13.611L18.4173 8.52508C18.4068 8.50809 18.4013 8.48853 18.4013 8.46859C18.4013 8.44864 18.4068 8.42908 18.4173 8.41209L18.7102 7.90509L19.8302 5.92811C19.8542 5.88711 19.8422 5.86611 19.7952 5.86611H8.20038C8.14138 5.86611 8.12738 5.84011 8.15738 5.78911L9.59137 3.28413C9.60211 3.26706 9.60781 3.24731 9.60781 3.22714C9.60781 3.20697 9.60211 3.18721 9.59137 3.17014L8.22538 0.774158C8.22016 0.764697 8.21248 0.756822 8.20315 0.751365C8.19382 0.745909 8.18319 0.743073 8.17238 0.743158ZM14.4623 8.76308C14.5083 8.76308 14.5203 8.78308 14.4963 8.82308L13.6643 10.2881L11.0513 14.873C11.0464 14.8819 11.0392 14.8894 11.0304 14.8945C11.0216 14.8996 11.0115 14.9022 11.0013 14.902C10.9912 14.902 10.9813 14.8993 10.9725 14.8942C10.9637 14.8891 10.9564 14.8818 10.9513 14.873L7.49839 8.84108C7.47839 8.80708 7.48839 8.78908 7.52639 8.78708L7.74239 8.77508L14.4643 8.76308H14.4623Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconMoonshotAI(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M20.052 6.0364C18.7527 4.28846 16.8758 2.94985 14.6066 2.34296C12.3374 1.73606 10.0411 1.95816 8.04092 2.82331L20.052 6.0364ZM3.75774 6.34071C4.64576 5.04902 5.81872 3.99836 7.16325 3.25459L16.0115 5.62191C15.4308 5.95635 14.8088 6.53268 14.3273 7.16626L21.6025 9.11263C21.809 9.79398 21.9435 10.5003 22 11.2213L3.75774 6.34071ZM21.6866 14.5876C21.584 14.9707 21.4603 15.3425 21.3172 15.7019L2.10543 10.5623C2.16147 10.1792 2.24079 9.7957 2.34339 9.41263C2.58479 8.51262 2.94172 7.67459 3.39435 6.91016L12.7957 9.42554C12.4194 9.96271 12.0766 10.5464 11.7749 11.1709L21.8517 13.8671C21.8056 14.1077 21.7504 14.3478 21.6862 14.5884L21.6866 14.5876ZM2.58134 15.3529C2.11535 14.05 1.91662 12.6408 2.03215 11.2088L10.8985 13.5808C10.8347 13.7823 10.7748 13.9863 10.7192 14.1933C10.6062 14.6147 10.514 15.0352 10.4424 15.4527L20.0166 18.0142C19.5709 18.6051 19.0631 19.1406 18.5057 19.6132L2.58134 15.3529ZM9.42338 21.6568C6.40111 20.8481 4.07415 18.7424 2.88266 16.1001L10.0976 18.0305C10.0859 18.7024 10.1278 19.3571 10.2196 19.9851L15.4619 21.3874C13.5906 22.0743 11.496 22.2112 9.42338 21.6568Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconZai(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.105 2L9.927 4.953H.653L2.83 2h9.276zM23.254 19.048L21.078 22h-9.242l2.174-2.952h9.244zM24 2L9.264 22H0L14.736 2H24z"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconStealth(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 18" fill="none">
|
||||
<path
|
||||
d="M24 15.5L18.1816 14.2871L18.1328 14.3115C16.9036 11.7879 15.6135 9.29301 14.2607 6.82812C14.0172 6.38435 13.771 5.94188 13.5234 5.5C13.6911 5.97998 13.8606 6.45942 14.0322 6.9375C14.9902 9.60529 16.012 12.2429 17.0947 14.8516L12 17.5L6.9043 14.8516C7.98712 12.2428 9.00977 9.6054 9.96777 6.9375C10.1394 6.45942 10.3089 5.97998 10.4766 5.5C10.229 5.94188 9.98281 6.38435 9.73926 6.82812C8.38629 9.29339 7.09557 11.7884 5.86621 14.3125L5.81738 14.2871L0 15.5L12 0.5L24 15.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="modal"][data-slot="overlay"] {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
animation: fadeIn 0.2s ease;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
[data-slot="content"] {
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-md);
|
||||
padding: var(--space-6);
|
||||
min-width: 400px;
|
||||
max-width: 90vw;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||||
animation: slideUp 0.2s ease;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
min-width: 300px;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="title"] {
|
||||
margin: 0 0 var(--space-4) 0;
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { JSX, Show } from "solid-js"
|
||||
import "./modal.css"
|
||||
|
||||
interface ModalProps {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
title?: string
|
||||
children: JSX.Element
|
||||
}
|
||||
|
||||
export function Modal(props: ModalProps) {
|
||||
return (
|
||||
<Show when={props.open}>
|
||||
<div data-component="modal" data-slot="overlay" onClick={props.onClose}>
|
||||
<div data-slot="content" onClick={(e) => e.stopPropagation()}>
|
||||
<Show when={props.title}>
|
||||
<h2 data-slot="title">{props.title}</h2>
|
||||
</Show>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { query } from "@solidjs/router"
|
||||
import { Resource } from "@opencode-ai/console-resource"
|
||||
|
||||
export const beta = query(async (workspaceID?: string) => {
|
||||
"use server"
|
||||
return Resource.App.stage === "production" ? workspaceID === "wrk_01K46JDFR0E75SG2Q8K172KF3Y" : true
|
||||
}, "beta")
|
||||
@@ -43,7 +43,7 @@ export default function Home() {
|
||||
<main data-page="opencode">
|
||||
<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />
|
||||
<Title>OpenCode | The AI coding agent built for the terminal</Title>
|
||||
<Link rel="icon" type="image/svg+xml" href="/favicon-zen.svg" />
|
||||
<Link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<Meta property="og:image" content="/social-share.png" />
|
||||
<Meta name="twitter:image" content="/social-share.png" />
|
||||
<div data-component="container">
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[data-component="user-menu"] {
|
||||
[data-component="dropdown"] {
|
||||
[data-slot="trigger"] span {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
[data-slot="dropdown"] {
|
||||
form {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="item"] {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { action, redirect } from "@solidjs/router"
|
||||
import { getRequestEvent } from "solid-js/web"
|
||||
import { useAuthSession } from "~/context/auth.session"
|
||||
import { Dropdown } from "~/component/dropdown"
|
||||
import "./user-menu.css"
|
||||
|
||||
const logout = action(async () => {
|
||||
"use server"
|
||||
const auth = await useAuthSession()
|
||||
const event = getRequestEvent()
|
||||
const current = auth.data.current
|
||||
if (current)
|
||||
await auth.update((val) => {
|
||||
delete val.account?.[current]
|
||||
const first = Object.keys(val.account ?? {})[0]
|
||||
val.current = first
|
||||
event!.locals.actor = undefined
|
||||
return val
|
||||
})
|
||||
throw redirect("/zen")
|
||||
})
|
||||
|
||||
export function UserMenu(props: { email: string | null | undefined }) {
|
||||
return (
|
||||
<div data-component="user-menu">
|
||||
<Dropdown trigger={props.email ?? ""} align="right">
|
||||
<form action={logout} method="post">
|
||||
<button type="submit" formaction={logout} data-slot="item">
|
||||
Logout
|
||||
</button>
|
||||
</form>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,71 +1,24 @@
|
||||
[data-component="workspace-picker"] {
|
||||
position: relative;
|
||||
/* Override blue accent colors with neutral colors */
|
||||
--color-accent: var(--color-border);
|
||||
--color-accent-hover: var(--color-border);
|
||||
--color-accent-active: var(--color-border);
|
||||
--color-primary: var(--color-border);
|
||||
--color-primary-hover: var(--color-border);
|
||||
--color-primary-active: var(--color-border);
|
||||
--color-primary-alpha-20: transparent;
|
||||
[data-component="dropdown"] {
|
||||
[data-slot="trigger"] {
|
||||
/* Override blue accent colors with neutral colors for dropdown trigger */
|
||||
--color-accent: var(--color-border);
|
||||
--color-accent-hover: var(--color-border);
|
||||
--color-accent-active: var(--color-border);
|
||||
--color-primary: var(--color-border);
|
||||
--color-primary-hover: var(--color-border);
|
||||
--color-primary-active: var(--color-border);
|
||||
--color-primary-alpha-20: transparent;
|
||||
}
|
||||
|
||||
[data-slot="trigger"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-sans);
|
||||
cursor: pointer;
|
||||
min-width: 200px;
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
[data-slot="dropdown"] {
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="chevron"] {
|
||||
flex-shrink: 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
[data-slot="dropdown"] button {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
/* Ensure text inside buttons has no underline */
|
||||
[data-slot="dropdown"] button * {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
[data-slot="dropdown"] {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
margin-top: var(--space-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="option"],
|
||||
[data-slot="create-option"] {
|
||||
[data-slot="create-item"] {
|
||||
width: 100%;
|
||||
padding: var(--space-2-5) var(--space-3);
|
||||
border: none;
|
||||
@@ -75,59 +28,27 @@
|
||||
font-family: var(--font-sans);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surface);
|
||||
text-decoration: none;
|
||||
background-color: var(--color-bg-surface);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: var(--border-radius-sm);
|
||||
border-top-right-radius: var(--border-radius-sm);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom-left-radius: var(--border-radius-sm);
|
||||
border-bottom-right-radius: var(--border-radius-sm);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="option"][data-selected="true"] {
|
||||
background-color: transparent;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
[data-slot="create-option"] {
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
[data-slot="create-form"] {
|
||||
margin-top: var(--space-4);
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-surface);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[data-slot="create-input-group"] {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
[data-slot="button-group"] {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
[data-slot="create-input"] {
|
||||
@@ -150,35 +71,4 @@
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
button[type="submit"],
|
||||
button[type="button"] {
|
||||
padding: var(--space-2-5) var(--space-4);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
&[data-color="primary"] {
|
||||
background-color: var(--color-text-secondary);
|
||||
border-color: var(--color-text-secondary);
|
||||
color: var(--color-bg);
|
||||
}
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { query, useParams, action, createAsync, redirect } from "@solidjs/router"
|
||||
import { For, Show, createEffect, onCleanup } from "solid-js"
|
||||
import { query, useParams, action, createAsync, redirect, useSubmission } from "@solidjs/router"
|
||||
import { For, Show, createEffect } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||
@@ -7,6 +7,8 @@ import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/ind
|
||||
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
|
||||
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
||||
import { Workspace } from "@opencode-ai/console-core/workspace.js"
|
||||
import { Dropdown, DropdownItem } from "~/component/dropdown"
|
||||
import { Modal } from "~/component/modal"
|
||||
import "./workspace-picker.css"
|
||||
|
||||
const getWorkspaces = query(async () => {
|
||||
@@ -40,11 +42,11 @@ const createWorkspace = action(async (form: FormData) => {
|
||||
export function WorkspacePicker() {
|
||||
const params = useParams()
|
||||
const workspaces = createAsync(() => getWorkspaces())
|
||||
const submission = useSubmission(createWorkspace)
|
||||
const [store, setStore] = createStore({
|
||||
showForm: false,
|
||||
showDropdown: false,
|
||||
})
|
||||
let dropdownRef: HTMLDivElement | undefined
|
||||
let inputRef: HTMLInputElement | undefined
|
||||
|
||||
const currentWorkspace = () => {
|
||||
const ws = workspaces()?.find((w) => w.id === params.id)
|
||||
@@ -52,15 +54,17 @@ export function WorkspacePicker() {
|
||||
}
|
||||
|
||||
const handleWorkspaceNew = () => {
|
||||
setStore({ showForm: true, showDropdown: false })
|
||||
setStore("showForm", true)
|
||||
}
|
||||
|
||||
const handleSelectWorkspace = (workspaceID: string) => {
|
||||
if (workspaceID === params.id) {
|
||||
setStore("showDropdown", false)
|
||||
return
|
||||
createEffect(() => {
|
||||
if (store.showForm && inputRef) {
|
||||
setTimeout(() => inputRef?.focus(), 0)
|
||||
}
|
||||
})
|
||||
|
||||
const handleSelectWorkspace = (workspaceID: string) => {
|
||||
if (workspaceID === params.id) return
|
||||
window.location.href = `/workspace/${workspaceID}`
|
||||
}
|
||||
|
||||
@@ -68,77 +72,45 @@ export function WorkspacePicker() {
|
||||
createEffect(() => {
|
||||
params.id
|
||||
setStore("showForm", false)
|
||||
setStore("showDropdown", false)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef && !dropdownRef.contains(event.target as Node)) {
|
||||
setStore("showDropdown", false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("click", handleClickOutside)
|
||||
onCleanup(() => document.removeEventListener("click", handleClickOutside))
|
||||
})
|
||||
|
||||
return (
|
||||
<div data-component="workspace-picker">
|
||||
<div ref={dropdownRef}>
|
||||
<div data-slot="trigger" onClick={() => setStore("showDropdown", !store.showDropdown)}>
|
||||
<span>{currentWorkspace()}</span>
|
||||
<svg data-slot="chevron" width="12" height="8" viewBox="0 0 12 8" fill="none">
|
||||
<path
|
||||
d="M1 1L6 6L11 1"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<Dropdown trigger={currentWorkspace()} align="left">
|
||||
<For each={workspaces()}>
|
||||
{(workspace) => (
|
||||
<DropdownItem selected={workspace.id === params.id} onClick={() => handleSelectWorkspace(workspace.id)}>
|
||||
{workspace.name || workspace.slug}
|
||||
</DropdownItem>
|
||||
)}
|
||||
</For>
|
||||
<button data-slot="create-item" type="button" onClick={() => handleWorkspaceNew()}>
|
||||
+ Create New Workspace
|
||||
</button>
|
||||
</Dropdown>
|
||||
|
||||
<Show when={store.showDropdown}>
|
||||
<div data-slot="dropdown">
|
||||
<For each={workspaces()}>
|
||||
{(workspace) => (
|
||||
<button
|
||||
data-slot="option"
|
||||
data-selected={workspace.id === params.id}
|
||||
type="button"
|
||||
onClick={() => handleSelectWorkspace(workspace.id)}
|
||||
>
|
||||
{workspace.name || workspace.slug}
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
<button data-slot="create-option" type="button" onClick={() => handleWorkspaceNew()}>
|
||||
+ Create New Workspace
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Show when={store.showForm}>
|
||||
<Modal open={store.showForm} onClose={() => setStore("showForm", false)} title="Create New Workspace">
|
||||
<form data-slot="create-form" action={createWorkspace} method="post">
|
||||
<div data-slot="create-input-group">
|
||||
<input
|
||||
ref={inputRef}
|
||||
data-slot="create-input"
|
||||
type="text"
|
||||
name="workspaceName"
|
||||
placeholder="Enter workspace name"
|
||||
required
|
||||
autofocus
|
||||
/>
|
||||
<button type="submit" data-color="primary">
|
||||
Create
|
||||
</button>
|
||||
<button type="button" onClick={() => setStore("showForm", false)}>
|
||||
Cancel
|
||||
</button>
|
||||
<div data-slot="button-group">
|
||||
<button type="button" data-color="ghost" onClick={() => setStore("showForm", false)}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" data-color="primary" disabled={submission.pending}>
|
||||
{submission.pending ? "Creating..." : "Create"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Show>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
@@ -55,9 +54,6 @@
|
||||
|
||||
a {
|
||||
color: var(--color-text);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: var(--space-0-75);
|
||||
text-decoration-thickness: 1px;
|
||||
}
|
||||
|
||||
/* Workspace Header */
|
||||
@@ -80,16 +76,14 @@
|
||||
[data-slot="header-brand"] {
|
||||
flex: 0 0 auto;
|
||||
padding-top: 4px;
|
||||
|
||||
svg {
|
||||
width: 138px;
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
|
||||
[data-component="site-title"] {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
}
|
||||
@@ -109,19 +103,5 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
a,
|
||||
button {
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
color: var(--color-text);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: var(--space-0-75);
|
||||
text-decoration-thickness: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,37 @@
|
||||
import { Show } from "solid-js"
|
||||
import { getRequestEvent } from "solid-js/web"
|
||||
import { query, action, redirect, createAsync, RouteSectionProps, useParams, A } from "@solidjs/router"
|
||||
import { query, createAsync, RouteSectionProps, useParams, A } from "@solidjs/router"
|
||||
import "./workspace.css"
|
||||
import { useAuthSession } from "~/context/auth.session"
|
||||
import { IconLogo } from "../component/icon"
|
||||
import { IconWorkspaceLogo } from "../component/icon"
|
||||
import { WorkspacePicker } from "./workspace-picker"
|
||||
import { UserMenu } from "./user-menu"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { User } from "@opencode-ai/console-core/user.js"
|
||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||
import { beta } from "~/lib/beta"
|
||||
import { Link } from "@solidjs/meta"
|
||||
|
||||
const getUserInfo = query(async (workspaceID: string) => {
|
||||
const getUserEmail = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
const actor = Actor.assert("user")
|
||||
const email = await User.getAccountEmail(actor.properties.userID)
|
||||
return { email }
|
||||
return email
|
||||
}, workspaceID)
|
||||
}, "userInfo")
|
||||
|
||||
const logout = action(async () => {
|
||||
"use server"
|
||||
const auth = await useAuthSession()
|
||||
const event = getRequestEvent()
|
||||
const current = auth.data.current
|
||||
if (current)
|
||||
await auth.update((val) => {
|
||||
delete val.account?.[current]
|
||||
const first = Object.keys(val.account ?? {})[0]
|
||||
val.current = first
|
||||
event!.locals.actor = undefined
|
||||
return val
|
||||
})
|
||||
throw redirect("/zen")
|
||||
})
|
||||
}, "userEmail")
|
||||
|
||||
export default function WorkspaceLayout(props: RouteSectionProps) {
|
||||
const params = useParams()
|
||||
const userInfo = createAsync(() => getUserInfo(params.id))
|
||||
const isBeta = createAsync(() => beta(params.id))
|
||||
const userEmail = createAsync(() => getUserEmail(params.id))
|
||||
return (
|
||||
<main data-page="workspace">
|
||||
<Link rel="icon" type="image/svg+xml" href="/favicon-zen.svg" />
|
||||
<header data-component="workspace-header">
|
||||
<div data-slot="header-brand">
|
||||
<A href="/" data-component="site-title">
|
||||
<IconLogo />
|
||||
<IconWorkspaceLogo />
|
||||
</A>
|
||||
<WorkspacePicker />
|
||||
</div>
|
||||
<div data-slot="header-actions">
|
||||
<Show when={isBeta()}>
|
||||
<WorkspacePicker />
|
||||
</Show>
|
||||
<span data-slot="user">{userInfo()?.email}</span>
|
||||
<form action={logout} method="post">
|
||||
<button type="submit" formaction={logout}>
|
||||
Logout
|
||||
</button>
|
||||
</form>
|
||||
<UserMenu email={userEmail()} />
|
||||
</div>
|
||||
</header>
|
||||
<div>{props.children}</div>
|
||||
|
||||
@@ -1,7 +1,72 @@
|
||||
[data-page="workspace"] {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Workspace Layout */
|
||||
[data-component="workspace-container"] {
|
||||
display: flex;
|
||||
height: calc(100vh - 73px);
|
||||
}
|
||||
|
||||
[data-component="workspace-nav"] {
|
||||
width: 240px;
|
||||
flex-shrink: 0;
|
||||
padding: var(--space-6) var(--space-4);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
[data-component="workspace-nav-items"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
|
||||
[data-nav-button] {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-radius: var(--border-radius-sm);
|
||||
color: var(--color-text-muted);
|
||||
text-decoration: none;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--color-text);
|
||||
font-weight: 700;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: calc(-1 * var(--space-0-5));
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background-color: var(--color-text);
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="workspace-content"] {
|
||||
flex: 1;
|
||||
padding: var(--space-6) var(--space-8);
|
||||
overflow-y: auto;
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
padding: var(--space-6) var(--space-4);
|
||||
}
|
||||
}
|
||||
|
||||
[data-page="workspace-[id]"] {
|
||||
max-width: 64rem;
|
||||
padding: var(--space-10) var(--space-4);
|
||||
margin: 0 auto;
|
||||
padding: var(--space-2) var(--space-4);
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -32,7 +97,6 @@
|
||||
gap: var(--space-6);
|
||||
}
|
||||
|
||||
/* Section titles */
|
||||
[data-slot="section-title"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -44,8 +108,7 @@
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.03125rem;
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: var(--font-size-md);
|
||||
@@ -66,7 +129,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="section-content"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
margin-top: var(--space-8);
|
||||
}
|
||||
}
|
||||
|
||||
section:not(:last-child) {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding-bottom: var(--space-16);
|
||||
@@ -78,7 +149,7 @@
|
||||
}
|
||||
|
||||
/* Title section */
|
||||
[data-component="title-section"] {
|
||||
[data-component="header-section"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
@@ -105,11 +176,50 @@
|
||||
p {
|
||||
line-height: 1.5;
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-text-muted);
|
||||
color: var(--color-text);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-4);
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
[data-slot="billing-info"] {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
[data-slot="balance"] {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
|
||||
b {
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
[data-component="workspace-container"] {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
[data-component="workspace-nav"] {
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: var(--space-4);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +1,37 @@
|
||||
import "./[id].css"
|
||||
import { MonthlyLimitSection } from "./monthly-limit-section"
|
||||
import { NewUserSection } from "./new-user-section"
|
||||
import { BillingSection } from "./billing-section"
|
||||
import { PaymentSection } from "./payment-section"
|
||||
import { UsageSection } from "./usage-section"
|
||||
import { KeySection } from "./key-section"
|
||||
import { MemberSection } from "./member-section"
|
||||
import { SettingsSection } from "./settings-section"
|
||||
import { ModelSection } from "./model-section"
|
||||
import { ProviderSection } from "./provider-section"
|
||||
import { Show } from "solid-js"
|
||||
import { createAsync, query, useParams } from "@solidjs/router"
|
||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { User } from "@opencode-ai/console-core/user.js"
|
||||
import { beta } from "~/lib/beta"
|
||||
import { createAsync, RouteSectionProps, useParams, A } from "@solidjs/router"
|
||||
import { querySessionInfo } from "./common"
|
||||
import "./[id].css"
|
||||
|
||||
const getUserInfo = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
const actor = Actor.assert("user")
|
||||
const user = await User.fromID(actor.properties.userID)
|
||||
return {
|
||||
isAdmin: user?.role === "admin",
|
||||
}
|
||||
}, workspaceID)
|
||||
}, "user.get")
|
||||
|
||||
export default function () {
|
||||
export default function WorkspaceLayout(props: RouteSectionProps) {
|
||||
const params = useParams()
|
||||
const userInfo = createAsync(() => getUserInfo(params.id))
|
||||
const isBeta = createAsync(() => beta(params.id))
|
||||
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id))
|
||||
return (
|
||||
<div data-page="workspace-[id]">
|
||||
<section data-component="title-section">
|
||||
<h1>Zen</h1>
|
||||
<p>
|
||||
Curated list of models provided by opencode.{" "}
|
||||
<a target="_blank" href="/docs/zen">
|
||||
Learn more
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div data-slot="sections">
|
||||
<NewUserSection />
|
||||
<KeySection />
|
||||
<Show when={isBeta()}>
|
||||
<MemberSection />
|
||||
</Show>
|
||||
<Show when={userInfo()?.isAdmin}>
|
||||
<Show when={isBeta()}>
|
||||
<SettingsSection />
|
||||
<ModelSection />
|
||||
<ProviderSection />
|
||||
</Show>
|
||||
<BillingSection />
|
||||
<MonthlyLimitSection />
|
||||
</Show>
|
||||
<UsageSection />
|
||||
<Show when={userInfo()?.isAdmin}>
|
||||
<PaymentSection />
|
||||
</Show>
|
||||
<main data-page="workspace">
|
||||
<div data-component="workspace-container">
|
||||
<nav data-component="workspace-nav">
|
||||
<div data-component="workspace-nav-items">
|
||||
<A href={`/workspace/${params.id}`} end activeClass="active" data-nav-button>
|
||||
Zen
|
||||
</A>
|
||||
<A href={`/workspace/${params.id}/keys`} activeClass="active" data-nav-button>
|
||||
API Keys
|
||||
</A>
|
||||
<A href={`/workspace/${params.id}/members`} activeClass="active" data-nav-button>
|
||||
Members
|
||||
</A>
|
||||
<Show when={userInfo()?.isAdmin}>
|
||||
<A href={`/workspace/${params.id}/billing`} activeClass="active" data-nav-button>
|
||||
Billing
|
||||
</A>
|
||||
<A href={`/workspace/${params.id}/settings`} activeClass="active" data-nav-button>
|
||||
Settings
|
||||
</A>
|
||||
</Show>
|
||||
</div>
|
||||
</nav>
|
||||
<div data-component="workspace-content">{props.children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
+6
-9
@@ -1,10 +1,4 @@
|
||||
.root {
|
||||
[data-slot="section-content"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
[data-slot="reload-error"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -29,6 +23,7 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="payment"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -86,7 +81,7 @@
|
||||
@media (max-width: 30rem) {
|
||||
flex-direction: column;
|
||||
|
||||
> button {
|
||||
>button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -96,19 +91,21 @@
|
||||
}
|
||||
|
||||
/* Make Enable Billing button full width when it's the only button */
|
||||
> button {
|
||||
>button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="usage"] {
|
||||
p {
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: 1.5;
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
b {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-5
@@ -6,11 +6,7 @@ import { IconCreditCard } from "~/component/icon"
|
||||
import styles from "./billing-section.module.css"
|
||||
import { Database, eq } from "@opencode-ai/console-core/drizzle/index.js"
|
||||
import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
||||
|
||||
const createCheckoutUrl = action(async (workspaceID: string, successUrl: string, cancelUrl: string) => {
|
||||
"use server"
|
||||
return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }), workspaceID)
|
||||
}, "checkoutUrl")
|
||||
import { createCheckoutUrl } from "../../common"
|
||||
|
||||
const reload = action(async (form: FormData) => {
|
||||
"use server"
|
||||
@@ -0,0 +1,23 @@
|
||||
import { MonthlyLimitSection } from "./monthly-limit-section"
|
||||
import { BillingSection } from "./billing-section"
|
||||
import { PaymentSection } from "./payment-section"
|
||||
import { Show } from "solid-js"
|
||||
import { createAsync, useParams } from "@solidjs/router"
|
||||
import { querySessionInfo } from "../../common"
|
||||
|
||||
export default function () {
|
||||
const params = useParams()
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id))
|
||||
|
||||
return (
|
||||
<div data-page="workspace-[id]">
|
||||
<div data-slot="sections">
|
||||
<Show when={userInfo()?.isAdmin}>
|
||||
<BillingSection />
|
||||
<MonthlyLimitSection />
|
||||
<PaymentSection />
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+1
-7
@@ -1,10 +1,4 @@
|
||||
.root {
|
||||
[data-slot="section-content"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
[data-slot="balance"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -99,4 +93,4 @@
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-7
@@ -1,8 +1,8 @@
|
||||
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||
import { query, action, useParams, createAsync, useAction } from "@solidjs/router"
|
||||
import { For } from "solid-js"
|
||||
import { For, Show } from "solid-js"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { formatDateUTC, formatDateForTable } from "./common"
|
||||
import { formatDateUTC, formatDateForTable } from "../../common"
|
||||
import styles from "./payment-section.module.css"
|
||||
|
||||
const getPaymentsInfo = query(async (workspaceID: string) => {
|
||||
@@ -19,7 +19,6 @@ const downloadReceipt = action(async (workspaceID: string, paymentID: string) =>
|
||||
|
||||
export function PaymentSection() {
|
||||
const params = useParams()
|
||||
// ORIGINAL CODE - COMMENTED OUT FOR TESTING
|
||||
const payments = createAsync(() => getPaymentsInfo(params.id))
|
||||
const downloadReceiptAction = useAction(downloadReceipt)
|
||||
|
||||
@@ -58,8 +57,7 @@ export function PaymentSection() {
|
||||
// ]
|
||||
|
||||
return (
|
||||
payments() &&
|
||||
payments()!.length > 0 && (
|
||||
<Show when={payments() && payments()!.length > 0}>
|
||||
<section class={styles.root}>
|
||||
<div data-slot="section-title">
|
||||
<h2>Payments History</h2>
|
||||
@@ -98,7 +96,7 @@ export function PaymentSection() {
|
||||
}}
|
||||
data-slot="receipt-button"
|
||||
>
|
||||
view
|
||||
View
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -109,6 +107,6 @@ export function PaymentSection() {
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { NewUserSection } from "./new-user-section"
|
||||
import { UsageSection } from "./usage-section"
|
||||
import { ModelSection } from "./model-section"
|
||||
import { ProviderSection } from "./provider-section"
|
||||
import { IconLogo } from "~/component/icon"
|
||||
import { createAsync, useParams, useAction, useSubmission } from "@solidjs/router"
|
||||
import { querySessionInfo, queryBillingInfo, createCheckoutUrl } from "../common"
|
||||
import { Show, createMemo } from "solid-js"
|
||||
|
||||
export default function () {
|
||||
const params = useParams()
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id))
|
||||
const billingInfo = createAsync(() => queryBillingInfo(params.id))
|
||||
const createCheckoutUrlAction = useAction(createCheckoutUrl)
|
||||
const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
|
||||
|
||||
const balanceAmount = createMemo(() => {
|
||||
return ((billingInfo()?.balance ?? 0) / 100000000).toFixed(2)
|
||||
})
|
||||
|
||||
return (
|
||||
<div data-page="workspace-[id]">
|
||||
<section data-component="header-section">
|
||||
<IconLogo />
|
||||
<p>
|
||||
<span>
|
||||
Reliable optimized models for coding agents.{" "}
|
||||
<a target="_blank" href="/docs/zen">
|
||||
Learn more
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
<Show when={userInfo()?.isAdmin}>
|
||||
<span data-slot="billing-info">
|
||||
<Show
|
||||
when={billingInfo()?.reload}
|
||||
fallback={
|
||||
<button
|
||||
data-color="primary"
|
||||
data-size="sm"
|
||||
disabled={createCheckoutUrlSubmission.pending}
|
||||
onClick={async () => {
|
||||
const baseUrl = window.location.href
|
||||
const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
|
||||
if (checkoutUrl) {
|
||||
window.location.href = checkoutUrl
|
||||
}
|
||||
}}
|
||||
>
|
||||
{createCheckoutUrlSubmission.pending ? "Loading..." : "Enable billing"}
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<span data-slot="balance">
|
||||
Current balance <b>${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()}</b>
|
||||
</span>
|
||||
</Show>
|
||||
</span>
|
||||
</Show>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div data-slot="sections">
|
||||
<NewUserSection />
|
||||
<ModelSection />
|
||||
<Show when={userInfo()?.isAdmin}>
|
||||
<ProviderSection />
|
||||
</Show>
|
||||
<UsageSection />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { KeySection } from "./key-section"
|
||||
|
||||
export default function () {
|
||||
return (
|
||||
<div data-page="workspace-[id]">
|
||||
<div data-slot="sections">
|
||||
<KeySection />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+31
-3
@@ -1,4 +1,11 @@
|
||||
.root {
|
||||
[data-slot="title-row"] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
[data-component="empty-state"] {
|
||||
padding: var(--space-20) var(--space-6);
|
||||
text-align: center;
|
||||
@@ -107,6 +114,7 @@
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
margin-left: calc(-1 * var(--space-3));
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 400;
|
||||
border: none;
|
||||
@@ -140,16 +148,30 @@
|
||||
|
||||
&[data-slot="key-actions"] {
|
||||
font-family: var(--font-sans);
|
||||
|
||||
button {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:hover {
|
||||
[data-slot="key-actions"] button {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
@@ -157,16 +179,22 @@
|
||||
}
|
||||
|
||||
th {
|
||||
&:nth-child(3) /* Date */ {
|
||||
&:nth-child(3)
|
||||
|
||||
/* Date */
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(3) /* Date */ {
|
||||
&:nth-child(3)
|
||||
|
||||
/* Date */
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-45
@@ -4,7 +4,7 @@ import { IconCopy, IconCheck } from "~/component/icon"
|
||||
import { Key } from "@opencode-ai/console-core/key.js"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { formatDateUTC, formatDateForTable } from "./common"
|
||||
import { formatDateUTC, formatDateForTable } from "../../common"
|
||||
import styles from "./key-section.module.css"
|
||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||
|
||||
@@ -43,8 +43,9 @@ const listKeys = query(async (workspaceID: string) => {
|
||||
return withActor(() => Key.list(), workspaceID)
|
||||
}, "key.list")
|
||||
|
||||
export function KeyCreateForm() {
|
||||
export function KeySection() {
|
||||
const params = useParams()
|
||||
const keys = createAsync(() => listKeys(params.id))
|
||||
const submission = useSubmission(createKey)
|
||||
const [store, setStore] = createStore({ show: false })
|
||||
|
||||
@@ -52,69 +53,59 @@ export function KeyCreateForm() {
|
||||
|
||||
createEffect(() => {
|
||||
if (!submission.pending && submission.result && !submission.result.error) {
|
||||
hide()
|
||||
setStore("show", false)
|
||||
}
|
||||
})
|
||||
|
||||
function show() {
|
||||
// submission.clear() does not clear the result in some cases, ie.
|
||||
// 1. Create key with empty name => error shows
|
||||
// 2. Put in a key name and creates the key => form hides
|
||||
// 3. Click add key button again => form shows with the same error if
|
||||
// submission.clear() is called only once
|
||||
while (true) {
|
||||
submission.clear()
|
||||
if (!submission.result) break
|
||||
}
|
||||
setStore("show", true)
|
||||
input.focus()
|
||||
setTimeout(() => input?.focus(), 0)
|
||||
}
|
||||
|
||||
function hide() {
|
||||
setStore("show", false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={store.show}
|
||||
fallback={
|
||||
<button data-color="primary" onClick={() => show()}>
|
||||
Create API Key
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<form action={createKey} method="post" data-slot="create-form">
|
||||
<div data-slot="input-container">
|
||||
<input ref={(r) => (input = r)} data-component="input" name="name" type="text" placeholder="Enter key name" />
|
||||
<Show when={submission.result && submission.result.error}>
|
||||
{(err) => <div data-slot="form-error">{err()}</div>}
|
||||
</Show>
|
||||
</div>
|
||||
<input type="hidden" name="workspaceID" value={params.id} />
|
||||
<div data-slot="form-actions">
|
||||
<button type="reset" data-color="ghost" onClick={() => hide()}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" data-color="primary" disabled={submission.pending}>
|
||||
{submission.pending ? "Creating..." : "Create"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
export function KeySection() {
|
||||
const params = useParams()
|
||||
const keys = createAsync(() => listKeys(params.id))
|
||||
|
||||
return (
|
||||
<section class={styles.root}>
|
||||
<div data-slot="section-title">
|
||||
<h2>API Keys</h2>
|
||||
<p>Manage your API keys for accessing opencode services.</p>
|
||||
<div data-slot="title-row">
|
||||
<p>Manage your API keys for accessing opencode services.</p>
|
||||
<button data-color="primary" onClick={() => show()}>
|
||||
Create API Key
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<KeyCreateForm />
|
||||
<Show when={store.show}>
|
||||
<form action={createKey} method="post" data-slot="create-form">
|
||||
<div data-slot="input-container">
|
||||
<input
|
||||
ref={(r) => (input = r)}
|
||||
data-component="input"
|
||||
name="name"
|
||||
type="text"
|
||||
placeholder="Enter key name"
|
||||
/>
|
||||
<Show when={submission.result && submission.result.error}>
|
||||
{(err) => <div data-slot="form-error">{err()}</div>}
|
||||
</Show>
|
||||
</div>
|
||||
<input type="hidden" name="workspaceID" value={params.id} />
|
||||
<div data-slot="form-actions">
|
||||
<button type="reset" data-color="ghost" onClick={() => hide()}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" data-color="primary" disabled={submission.pending}>
|
||||
{submission.pending ? "Creating..." : "Create"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Show>
|
||||
<div data-slot="api-keys-table">
|
||||
<Show
|
||||
when={keys()?.length}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { MemberSection } from "./member-section"
|
||||
|
||||
export default function () {
|
||||
return (
|
||||
<div data-page="workspace-[id]">
|
||||
<div data-slot="sections">
|
||||
<MemberSection />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+101
-44
@@ -1,4 +1,11 @@
|
||||
.root {
|
||||
[data-slot="title-row"] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
[data-component="empty-state"] {
|
||||
padding: var(--space-20) var(--space-6);
|
||||
text-align: center;
|
||||
@@ -23,51 +30,74 @@
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
|
||||
[data-slot="input-container"] {
|
||||
[data-slot="input-row"] {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--space-3);
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="input-field"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
p {
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-disabled);
|
||||
input {
|
||||
flex: 1;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: 1.5;
|
||||
min-width: 0;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 3px var(--color-accent-alpha);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="form-actions"] {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
|
||||
>button[type="reset"] {
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="form-error"] {
|
||||
color: var(--color-danger);
|
||||
font-size: var(--font-size-sm);
|
||||
margin-top: var(--space-1);
|
||||
line-height: 1.4;
|
||||
margin-top: calc(var(--space-1) * -1);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="members-table"] {
|
||||
overflow-x: auto;
|
||||
padding-bottom: 200px;
|
||||
margin-bottom: -200px;
|
||||
}
|
||||
|
||||
[data-slot="members-table-element"] {
|
||||
@@ -85,6 +115,14 @@
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
|
||||
&:nth-child(2) {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
@@ -101,35 +139,29 @@
|
||||
|
||||
&[data-slot="member-role"] {
|
||||
font-family: var(--font-mono);
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
&[data-slot="member-usage"] {
|
||||
input {
|
||||
width: 100%;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 400;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
text-transform: none;
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: 1.5;
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: var(--color-bg-surface);
|
||||
color: var(--color-text);
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 3px var(--color-accent-alpha);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
span {
|
||||
font-family: inherit;
|
||||
&::placeholder {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,10 +172,35 @@
|
||||
|
||||
&[data-slot="member-actions"] {
|
||||
font-family: var(--font-sans);
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
|
||||
[data-slot="inline-edit-form"] {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
|
||||
button {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
form:not([data-slot="inline-edit-form"]) button {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:hover {
|
||||
[data-slot="member-actions"] form:not([data-slot="inline-edit-form"]) button {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router"
|
||||
import { createEffect, For, Show } from "solid-js"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { createStore } from "solid-js/store"
|
||||
import styles from "./member-section.module.css"
|
||||
import { UserRole } from "@opencode-ai/console-core/schema/user.sql.js"
|
||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||
import { User } from "@opencode-ai/console-core/user.js"
|
||||
import { RoleDropdown } from "./role-dropdown"
|
||||
|
||||
const listMembers = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
return {
|
||||
members: await User.list(),
|
||||
actorID: Actor.userID(),
|
||||
actorRole: Actor.userRole(),
|
||||
}
|
||||
}, workspaceID)
|
||||
}, "member.list")
|
||||
|
||||
const inviteMember = action(async (form: FormData) => {
|
||||
"use server"
|
||||
const email = form.get("email")?.toString().trim()
|
||||
if (!email) return { error: "Email is required" }
|
||||
const workspaceID = form.get("workspaceID")?.toString()
|
||||
if (!workspaceID) return { error: "Workspace ID is required" }
|
||||
const role = form.get("role")?.toString() as (typeof UserRole)[number]
|
||||
if (!role) return { error: "Role is required" }
|
||||
const limit = form.get("limit")?.toString()
|
||||
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
|
||||
if (monthlyLimit !== null && monthlyLimit < 0) return { error: "Set a valid monthly limit" }
|
||||
return json(
|
||||
await withActor(
|
||||
() =>
|
||||
User.invite({ email, role, monthlyLimit })
|
||||
.then((data) => ({ error: undefined, data }))
|
||||
.catch((e) => ({ error: e.message as string })),
|
||||
workspaceID,
|
||||
),
|
||||
{ revalidate: listMembers.key },
|
||||
)
|
||||
}, "member.create")
|
||||
|
||||
const removeMember = action(async (form: FormData) => {
|
||||
"use server"
|
||||
const id = form.get("id")?.toString()
|
||||
if (!id) return { error: "ID is required" }
|
||||
const workspaceID = form.get("workspaceID")?.toString()
|
||||
if (!workspaceID) return { error: "Workspace ID is required" }
|
||||
return json(
|
||||
await withActor(
|
||||
() =>
|
||||
User.remove(id)
|
||||
.then((data) => ({ error: undefined, data }))
|
||||
.catch((e) => ({ error: e.message as string })),
|
||||
workspaceID,
|
||||
),
|
||||
{ revalidate: listMembers.key },
|
||||
)
|
||||
}, "member.remove")
|
||||
|
||||
const updateMember = action(async (form: FormData) => {
|
||||
"use server"
|
||||
|
||||
const id = form.get("id")?.toString()
|
||||
if (!id) return { error: "ID is required" }
|
||||
const workspaceID = form.get("workspaceID")?.toString()
|
||||
if (!workspaceID) return { error: "Workspace ID is required" }
|
||||
const role = form.get("role")?.toString() as (typeof UserRole)[number]
|
||||
if (!role) return { error: "Role is required" }
|
||||
const limit = form.get("limit")?.toString()
|
||||
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
|
||||
if (monthlyLimit !== null && monthlyLimit < 0) return { error: "Set a valid monthly limit" }
|
||||
|
||||
return json(
|
||||
await withActor(
|
||||
() =>
|
||||
User.update({ id, role, monthlyLimit })
|
||||
.then((data) => ({ error: undefined, data }))
|
||||
.catch((e) => ({ error: e.message as string })),
|
||||
workspaceID,
|
||||
),
|
||||
{ revalidate: listMembers.key },
|
||||
)
|
||||
}, "member.update")
|
||||
|
||||
function MemberRow(props: { member: any; workspaceID: string; actorID: string; actorRole: string }) {
|
||||
const submission = useSubmission(updateMember)
|
||||
const isCurrentUser = () => props.actorID === props.member.id
|
||||
const isAdmin = () => props.actorRole === "admin"
|
||||
const [store, setStore] = createStore({
|
||||
editing: false,
|
||||
selectedRole: props.member.role as (typeof UserRole)[number],
|
||||
limit: "",
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!submission.pending && submission.result && !submission.result.error) {
|
||||
setStore("editing", false)
|
||||
}
|
||||
})
|
||||
|
||||
function show() {
|
||||
while (true) {
|
||||
submission.clear()
|
||||
if (!submission.result) break
|
||||
}
|
||||
setStore("editing", true)
|
||||
setStore("selectedRole", props.member.role)
|
||||
setStore("limit", props.member.monthlyLimit?.toString() ?? "")
|
||||
}
|
||||
|
||||
function hide() {
|
||||
setStore("editing", false)
|
||||
}
|
||||
|
||||
function getUsageDisplay() {
|
||||
const currentUsage = (() => {
|
||||
const dateLastUsed = props.member.timeMonthlyUsageUpdated
|
||||
if (!dateLastUsed) return 0
|
||||
|
||||
const current = new Date().toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
timeZone: "UTC",
|
||||
})
|
||||
const lastUsed = dateLastUsed.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
timeZone: "UTC",
|
||||
})
|
||||
return current === lastUsed ? (props.member.monthlyUsage ?? 0) : 0
|
||||
})()
|
||||
|
||||
const limit = props.member.monthlyLimit ? `$${props.member.monthlyLimit}` : "no limit"
|
||||
return `$${(currentUsage / 100000000).toFixed(2)} / ${limit}`
|
||||
}
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td data-slot="member-email">{props.member.accountEmail ?? props.member.email}</td>
|
||||
<td data-slot="member-role">
|
||||
<Show when={store.editing && !isCurrentUser()} fallback={<span>{props.member.role}</span>}>
|
||||
<RoleDropdown
|
||||
value={store.selectedRole}
|
||||
options={roleOptions}
|
||||
onChange={(value) => setStore("selectedRole", value as (typeof UserRole)[number])}
|
||||
/>
|
||||
</Show>
|
||||
</td>
|
||||
<td data-slot="member-usage">
|
||||
<Show when={store.editing} fallback={<span>{getUsageDisplay()}</span>}>
|
||||
<input
|
||||
data-component="input"
|
||||
type="number"
|
||||
value={store.limit}
|
||||
onInput={(e) => setStore("limit", e.currentTarget.value)}
|
||||
placeholder="No limit"
|
||||
min="0"
|
||||
/>
|
||||
</Show>
|
||||
</td>
|
||||
<td data-slot="member-joined">{props.member.timeSeen ? "" : "invited"}</td>
|
||||
<Show when={isAdmin()}>
|
||||
<td data-slot="member-actions">
|
||||
<Show
|
||||
when={store.editing}
|
||||
fallback={
|
||||
<>
|
||||
<button data-color="ghost" onClick={() => show()}>
|
||||
Edit
|
||||
</button>
|
||||
<Show when={!isCurrentUser()}>
|
||||
<form action={removeMember} method="post">
|
||||
<input type="hidden" name="id" value={props.member.id} />
|
||||
<input type="hidden" name="workspaceID" value={props.workspaceID} />
|
||||
<button data-color="ghost">Delete</button>
|
||||
</form>
|
||||
</Show>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<form action={updateMember} method="post" data-slot="inline-edit-form">
|
||||
<input type="hidden" name="id" value={props.member.id} />
|
||||
<input type="hidden" name="workspaceID" value={props.workspaceID} />
|
||||
<input type="hidden" name="role" value={store.selectedRole} />
|
||||
<input type="hidden" name="limit" value={store.limit} />
|
||||
<button type="submit" data-color="ghost" disabled={submission.pending}>
|
||||
{submission.pending ? "Saving..." : "Save"}
|
||||
</button>
|
||||
<Show when={!submission.pending}>
|
||||
<button type="button" data-color="ghost" onClick={() => hide()}>
|
||||
Cancel
|
||||
</button>
|
||||
</Show>
|
||||
</form>
|
||||
</Show>
|
||||
</td>
|
||||
</Show>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
const roleOptions = [
|
||||
{ value: "admin", description: "Can manage models, members, and billing" },
|
||||
{ value: "member", description: "Can only generate API keys for themselves" },
|
||||
]
|
||||
|
||||
export function MemberSection() {
|
||||
const params = useParams()
|
||||
const data = createAsync(() => listMembers(params.id))
|
||||
const submission = useSubmission(inviteMember)
|
||||
const [store, setStore] = createStore({
|
||||
show: false,
|
||||
selectedRole: "member" as (typeof UserRole)[number],
|
||||
limit: "",
|
||||
})
|
||||
|
||||
let input: HTMLInputElement
|
||||
|
||||
createEffect(() => {
|
||||
if (!submission.pending && submission.result && !submission.result.error) {
|
||||
setStore("show", false)
|
||||
}
|
||||
})
|
||||
|
||||
function show() {
|
||||
while (true) {
|
||||
submission.clear()
|
||||
if (!submission.result) break
|
||||
}
|
||||
setStore("show", true)
|
||||
setStore("selectedRole", "member")
|
||||
setStore("limit", "")
|
||||
setTimeout(() => input?.focus(), 0)
|
||||
}
|
||||
|
||||
function hide() {
|
||||
setStore("show", false)
|
||||
}
|
||||
|
||||
return (
|
||||
<section class={styles.root}>
|
||||
<div data-slot="section-title">
|
||||
<h2>Members</h2>
|
||||
<div data-slot="title-row">
|
||||
<p>Manage workspace members and their permissions.</p>
|
||||
<Show when={data()?.actorRole === "admin"}>
|
||||
<button data-color="primary" onClick={() => show()}>
|
||||
Invite Member
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={store.show}>
|
||||
<form action={inviteMember} method="post" data-slot="create-form">
|
||||
<div data-slot="input-row">
|
||||
<div data-slot="input-field">
|
||||
<p>Invitee</p>
|
||||
<input
|
||||
ref={(r) => (input = r)}
|
||||
data-component="input"
|
||||
name="email"
|
||||
type="text"
|
||||
placeholder="Enter email"
|
||||
/>
|
||||
</div>
|
||||
<div data-slot="input-field">
|
||||
<p>Role</p>
|
||||
<RoleDropdown
|
||||
value={store.selectedRole}
|
||||
options={roleOptions}
|
||||
onChange={(value) => setStore("selectedRole", value as (typeof UserRole)[number])}
|
||||
/>
|
||||
</div>
|
||||
<div data-slot="input-field">
|
||||
<p>Monthly spending limit</p>
|
||||
<input
|
||||
data-component="input"
|
||||
name="limit"
|
||||
type="number"
|
||||
placeholder="No limit"
|
||||
value={store.limit}
|
||||
onInput={(e) => setStore("limit", e.currentTarget.value)}
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={submission.result && submission.result.error}>
|
||||
{(err) => <div data-slot="form-error">{err()}</div>}
|
||||
</Show>
|
||||
<input type="hidden" name="role" value={store.selectedRole} />
|
||||
<input type="hidden" name="workspaceID" value={params.id} />
|
||||
<div data-slot="form-actions">
|
||||
<button type="reset" data-color="ghost" onClick={() => hide()}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" data-color="primary" disabled={submission.pending}>
|
||||
{submission.pending ? "Inviting..." : "Invite"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Show>
|
||||
<div data-slot="members-table">
|
||||
<table data-slot="members-table-element">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Month limit</th>
|
||||
<th></th>
|
||||
<Show when={data()?.actorRole === "admin"}>
|
||||
<th></th>
|
||||
</Show>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Show when={data() && data()!.members.length > 0}>
|
||||
<For each={data()!.members}>
|
||||
{(member) => (
|
||||
<MemberRow
|
||||
member={member}
|
||||
workspaceID={params.id}
|
||||
actorID={data()!.actorID}
|
||||
actorRole={data()!.actorRole}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
.role-dropdown {
|
||||
[data-slot="trigger"] {
|
||||
border: 1px solid var(--color-border);
|
||||
background-color: var(--color-bg);
|
||||
width: 100%;
|
||||
text-transform: capitalize;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-radius: var(--border-radius-sm);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: 1.5;
|
||||
min-width: 0;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-accent);
|
||||
background-color: var(--color-bg);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 3px var(--color-accent-alpha);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="chevron"] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
[data-slot="dropdown"] {
|
||||
padding: var(--space-1);
|
||||
min-width: 280px;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
[data-slot="role-item"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
border-radius: var(--border-radius-sm);
|
||||
transition: background-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg-surface);
|
||||
}
|
||||
|
||||
&[data-selected="true"] {
|
||||
background-color: var(--color-accent-alpha);
|
||||
}
|
||||
|
||||
div {
|
||||
strong {
|
||||
display: block;
|
||||
color: var(--color-text);
|
||||
margin-bottom: var(--space-1);
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { createSignal } from "solid-js"
|
||||
import { Dropdown } from "~/component/dropdown"
|
||||
import "./role-dropdown.css"
|
||||
|
||||
interface RoleOption {
|
||||
value: string
|
||||
description: string
|
||||
}
|
||||
|
||||
interface RoleDropdownProps {
|
||||
value: string
|
||||
options: RoleOption[]
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
export function RoleDropdown(props: RoleDropdownProps) {
|
||||
const [open, setOpen] = createSignal(false)
|
||||
|
||||
const handleSelect = (value: string) => {
|
||||
props.onChange(value)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown trigger={props.value} open={open()} onOpenChange={setOpen} class="role-dropdown">
|
||||
<>
|
||||
{props.options.map((option) => (
|
||||
<button
|
||||
data-slot="role-item"
|
||||
data-selected={props.value === option.value}
|
||||
type="button"
|
||||
onClick={() => handleSelect(option.value)}
|
||||
>
|
||||
<div>
|
||||
<strong>{option.value}</strong>
|
||||
<p>{option.description}</p>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
[data-slot="models-list"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
[data-slot="models-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
[data-slot="models-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&[data-slot="model-name"] {
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-mono);
|
||||
font-weight: 500;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-slot="training-data"] {
|
||||
text-align: center;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="model-toggle"] {
|
||||
text-align: left;
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
[data-slot="model-toggle-label"] {
|
||||
/* Toggle container */
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 2.5rem;
|
||||
height: 1.5rem;
|
||||
cursor: pointer;
|
||||
|
||||
/* Hidden checkbox input */
|
||||
input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* Toggle track (background) */
|
||||
span {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-color: #ccc;
|
||||
border: 1px solid #bbb;
|
||||
border-radius: 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
|
||||
/* Toggle handle (slider) */
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0.125rem;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
background-color: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 50%;
|
||||
transform: translateY(-50%);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
/* Checked state - track */
|
||||
input:checked + span {
|
||||
background-color: #21ad0e;
|
||||
border-color: #148605;
|
||||
|
||||
/* Checked state - handle */
|
||||
&::before {
|
||||
transform: translateX(1rem) translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Hover states */
|
||||
&:hover span {
|
||||
box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
|
||||
}
|
||||
|
||||
input:checked:hover + span {
|
||||
box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.3);
|
||||
}
|
||||
|
||||
/* Disabled state */
|
||||
&:has(input:disabled) {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
input:disabled + span {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
input:disabled:checked + span {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
input:disabled ~ span:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&[data-disabled="true"] {
|
||||
td[data-slot="model-name"] {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
[data-slot="models-table-element"] {
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
th {
|
||||
&:nth-child(2)
|
||||
|
||||
/* Training Data */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(2)
|
||||
|
||||
/* Training Data */ {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import { Model } from "@opencode-ai/console-core/model.js"
|
||||
import { query, action, useParams, createAsync, json } from "@solidjs/router"
|
||||
import { createMemo, For, Show } from "solid-js"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { ZenModel } from "@opencode-ai/console-core/model.js"
|
||||
import styles from "./model-section.module.css"
|
||||
import { querySessionInfo } from "../common"
|
||||
import { IconAlibaba, IconAnthropic, IconMoonshotAI, IconOpenAI, IconStealth, IconXai, IconZai } from "~/component/icon"
|
||||
|
||||
const getModelLab = (modelId: string) => {
|
||||
if (modelId.startsWith("claude")) return "Anthropic"
|
||||
if (modelId.startsWith("gpt")) return "OpenAI"
|
||||
if (modelId.startsWith("kimi")) return "Moonshot AI"
|
||||
if (modelId.startsWith("glm")) return "Z.ai"
|
||||
if (modelId.startsWith("qwen")) return "Alibaba"
|
||||
if (modelId.startsWith("grok")) return "xAI"
|
||||
return "Stealth"
|
||||
}
|
||||
|
||||
const getModelsInfo = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
return {
|
||||
all: Object.entries(ZenModel.list())
|
||||
.filter(([id, _model]) => !["claude-3-5-haiku"].includes(id))
|
||||
.filter(([id, _model]) => !id.startsWith("an-"))
|
||||
.sort(([_idA, modelA], [_idB, modelB]) => modelA.name.localeCompare(modelB.name))
|
||||
.map(([id, model]) => ({ id, name: model.name })),
|
||||
disabled: await Model.listDisabled(),
|
||||
}
|
||||
}, workspaceID)
|
||||
}, "model.info")
|
||||
|
||||
const updateModel = action(async (form: FormData) => {
|
||||
"use server"
|
||||
const model = form.get("model")?.toString()
|
||||
if (!model) return { error: "Model is required" }
|
||||
const workspaceID = form.get("workspaceID")?.toString()
|
||||
if (!workspaceID) return { error: "Workspace ID is required" }
|
||||
const enabled = form.get("enabled")?.toString() === "true"
|
||||
return json(
|
||||
withActor(async () => {
|
||||
if (enabled) {
|
||||
await Model.disable({ model })
|
||||
} else {
|
||||
await Model.enable({ model })
|
||||
}
|
||||
}, workspaceID),
|
||||
{ revalidate: getModelsInfo.key },
|
||||
)
|
||||
}, "model.toggle")
|
||||
|
||||
export function ModelSection() {
|
||||
const params = useParams()
|
||||
const modelsInfo = createAsync(() => getModelsInfo(params.id))
|
||||
const userInfo = createAsync(() => querySessionInfo(params.id))
|
||||
|
||||
const modelsWithLab = createMemo(() => {
|
||||
const info = modelsInfo()
|
||||
if (!info) return []
|
||||
return info.all.map((model) => ({
|
||||
...model,
|
||||
lab: getModelLab(model.id),
|
||||
}))
|
||||
})
|
||||
return (
|
||||
<section class={styles.root}>
|
||||
<div data-slot="section-title">
|
||||
<h2>Models</h2>
|
||||
<p>
|
||||
Manage which models workspace members can access. <a href="/docs/zen#pricing ">Learn more</a>.
|
||||
</p>
|
||||
</div>
|
||||
<div data-slot="models-list">
|
||||
<Show when={modelsInfo()}>
|
||||
<div data-slot="models-table">
|
||||
<table data-slot="models-table-element">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Model</th>
|
||||
<th></th>
|
||||
<th>Enabled</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={modelsWithLab()}>
|
||||
{({ id, name, lab }) => {
|
||||
const isEnabled = createMemo(() => !modelsInfo()!.disabled.includes(id))
|
||||
return (
|
||||
<tr data-slot="model-row" data-disabled={!isEnabled()}>
|
||||
<td data-slot="model-name">
|
||||
<div>
|
||||
{(() => {
|
||||
switch (lab) {
|
||||
case "OpenAI":
|
||||
return <IconOpenAI width={16} height={16} />
|
||||
case "Anthropic":
|
||||
return <IconAnthropic width={16} height={16} />
|
||||
case "Moonshot AI":
|
||||
return <IconMoonshotAI width={16} height={16} />
|
||||
case "Z.ai":
|
||||
return <IconZai width={16} height={16} />
|
||||
case "Alibaba":
|
||||
return <IconAlibaba width={16} height={16} />
|
||||
case "xAI":
|
||||
return <IconXai width={16} height={16} />
|
||||
default:
|
||||
return <IconStealth width={16} height={16} />
|
||||
}
|
||||
})()}
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td data-slot="model-lab">{lab}</td>
|
||||
<td data-slot="model-toggle">
|
||||
<form action={updateModel} method="post">
|
||||
<input type="hidden" name="model" value={id} />
|
||||
<input type="hidden" name="workspaceID" value={params.id} />
|
||||
<input type="hidden" name="enabled" value={isEnabled().toString()} />
|
||||
<label data-slot="model-toggle-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isEnabled()}
|
||||
disabled={!userInfo()?.isAdmin}
|
||||
onChange={(e) => {
|
||||
const form = e.currentTarget.closest("form")
|
||||
if (form) form.requestSubmit()
|
||||
}}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
+1
-21
@@ -53,26 +53,6 @@
|
||||
flex-direction: column;
|
||||
gap: var(--space-6);
|
||||
|
||||
[data-slot="section-title"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.03125rem;
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
font-size: var(--font-size-md);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="key-display"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -160,4 +140,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-3
@@ -28,7 +28,14 @@ export function NewUserSection() {
|
||||
const usageList = usage()
|
||||
return keysList?.length === 1 && (!usageList || usageList.length === 0)
|
||||
})
|
||||
const defaultKey = createMemo(() => keys()?.at(-1)?.key)
|
||||
const defaultKey = createMemo(() => {
|
||||
const key = keys()?.at(-1)?.key
|
||||
if (!key) return undefined
|
||||
return {
|
||||
actual: key,
|
||||
masked: key.slice(0, 8) + "*".repeat(key.length - 12) + key.slice(-4),
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Show when={isNew()}>
|
||||
@@ -52,12 +59,12 @@ export function NewUserSection() {
|
||||
<Show when={defaultKey()}>
|
||||
<div data-slot="key-display">
|
||||
<div data-slot="key-container">
|
||||
<code data-slot="key-value">{defaultKey()}</code>
|
||||
<code data-slot="key-value">{defaultKey()?.masked}</code>
|
||||
<button
|
||||
data-color="primary"
|
||||
disabled={copiedKey()}
|
||||
onClick={async () => {
|
||||
await navigator.clipboard.writeText(defaultKey() ?? "")
|
||||
await navigator.clipboard.writeText(defaultKey()?.actual ?? "")
|
||||
setCopiedKey(true)
|
||||
setTimeout(() => setCopiedKey(false), 2000)
|
||||
}}
|
||||
+44
-12
@@ -18,6 +18,14 @@
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
|
||||
&:nth-child(1) {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
@@ -32,24 +40,21 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&[data-slot="provider-status"] {
|
||||
&[data-slot="provider-key"] {
|
||||
text-align: left;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="provider-toggle"] {
|
||||
text-align: left;
|
||||
font-family: var(--font-sans);
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
[data-slot="edit-form"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
max-width: 100%;
|
||||
|
||||
[data-slot="input-wrapper"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
max-width: 100%;
|
||||
|
||||
input {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
@@ -59,6 +64,8 @@
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-mono);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
@@ -76,18 +83,43 @@
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="form-actions"] {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
&[data-slot="provider-action"] {
|
||||
text-align: left;
|
||||
font-family: var(--font-sans);
|
||||
white-space: nowrap;
|
||||
|
||||
[data-slot="configured-actions"] {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
|
||||
[data-slot="delete-form"] {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
&:hover [data-slot="delete-form"] {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="form-actions"] {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&[data-enabled="false"] {
|
||||
opacity: 0.6;
|
||||
&:hover {
|
||||
[data-slot="provider-action"] [data-slot="delete-form"] {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child td {
|
||||
+53
-31
@@ -12,6 +12,10 @@ const PROVIDERS = [
|
||||
|
||||
type Provider = (typeof PROVIDERS)[number]
|
||||
|
||||
function maskCredentials(credentials: string) {
|
||||
return `${credentials.slice(0, 8)}...${credentials.slice(-8)}`
|
||||
}
|
||||
|
||||
const removeProvider = action(async (form: FormData) => {
|
||||
"use server"
|
||||
const provider = form.get("provider")?.toString()
|
||||
@@ -58,7 +62,7 @@ function ProviderRow(props: { provider: Provider }) {
|
||||
|
||||
let input: HTMLInputElement
|
||||
|
||||
const isEnabled = () => providers()?.some((p) => p.provider === props.provider.key)
|
||||
const providerData = () => providers()?.find((p) => p.provider === props.provider.key)
|
||||
|
||||
createEffect(() => {
|
||||
if (!saveSubmission.pending && saveSubmission.result && !saveSubmission.result.error) {
|
||||
@@ -80,32 +84,14 @@ function ProviderRow(props: { provider: Provider }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<tr data-slot="provider-row" data-enabled={isEnabled()}>
|
||||
<tr data-slot="provider-row">
|
||||
<td data-slot="provider-name">{props.provider.name}</td>
|
||||
<td data-slot="provider-status">{isEnabled() ? "Configured" : "Not Configured"}</td>
|
||||
<td data-slot="provider-toggle">
|
||||
<td data-slot="provider-key">
|
||||
<Show
|
||||
when={store.editing}
|
||||
fallback={
|
||||
<Show
|
||||
when={isEnabled()}
|
||||
fallback={
|
||||
<button data-color="ghost" onClick={() => show()}>
|
||||
Configure
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<form action={removeProvider} method="post">
|
||||
<input type="hidden" name="provider" value={props.provider.key} />
|
||||
<input type="hidden" name="workspaceID" value={params.id} />
|
||||
<button data-color="ghost" type="submit" disabled={removeSubmission.pending}>
|
||||
Disable
|
||||
</button>
|
||||
</form>
|
||||
</Show>
|
||||
}
|
||||
fallback={<span>{providerData() ? maskCredentials(providerData()!.credentials) : "-"}</span>}
|
||||
>
|
||||
<form action={saveProvider} method="post" data-slot="edit-form">
|
||||
<form id={`provider-form-${props.provider.key}`} action={saveProvider} method="post" data-slot="edit-form">
|
||||
<div data-slot="input-wrapper">
|
||||
<input
|
||||
ref={(r) => (input = r)}
|
||||
@@ -122,15 +108,51 @@ function ProviderRow(props: { provider: Provider }) {
|
||||
</div>
|
||||
<input type="hidden" name="provider" value={props.provider.key} />
|
||||
<input type="hidden" name="workspaceID" value={params.id} />
|
||||
<div data-slot="form-actions">
|
||||
</form>
|
||||
</Show>
|
||||
</td>
|
||||
<td data-slot="provider-action">
|
||||
<Show
|
||||
when={store.editing}
|
||||
fallback={
|
||||
<Show
|
||||
when={!!providerData()}
|
||||
fallback={
|
||||
<button data-color="ghost" onClick={() => show()}>
|
||||
Configure
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<div data-slot="configured-actions">
|
||||
<button data-color="ghost" onClick={() => show()}>
|
||||
Edit
|
||||
</button>
|
||||
<form action={removeProvider} method="post" data-slot="delete-form">
|
||||
<input type="hidden" name="provider" value={props.provider.key} />
|
||||
<input type="hidden" name="workspaceID" value={params.id} />
|
||||
<button data-color="ghost" type="submit" disabled={removeSubmission.pending}>
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<div data-slot="form-actions">
|
||||
<button
|
||||
type="submit"
|
||||
data-color="ghost"
|
||||
disabled={saveSubmission.pending}
|
||||
form={`provider-form-${props.provider.key}`}
|
||||
>
|
||||
{saveSubmission.pending ? "Saving..." : "Save"}
|
||||
</button>
|
||||
<Show when={!saveSubmission.pending}>
|
||||
<button type="reset" data-color="ghost" onClick={() => hide()}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" data-color="ghost" disabled={saveSubmission.pending}>
|
||||
{saveSubmission.pending ? "Saving..." : "Save"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -149,8 +171,8 @@ export function ProviderSection() {
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Provider</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
<th>API Key</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -0,0 +1,11 @@
|
||||
import { SettingsSection } from "./settings-section"
|
||||
|
||||
export default function () {
|
||||
return (
|
||||
<div data-page="workspace-[id]">
|
||||
<div data-slot="sections">
|
||||
<SettingsSection />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+43
-44
@@ -1,63 +1,61 @@
|
||||
.root {
|
||||
[data-slot="section-content"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
max-width: 40rem;
|
||||
|
||||
[data-slot="setting"] {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-4);
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="setting-info"] {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
gap: var(--space-3);
|
||||
|
||||
h3 {
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 500;
|
||||
p {
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
color: var(--color-text);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
[data-slot="value-with-action"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="current-value"] {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
color: var(--color-text);
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
>button {
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="create-form"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
min-width: 15rem;
|
||||
width: fit-content;
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
width: 100%;
|
||||
min-width: auto;
|
||||
}
|
||||
gap: var(--space-2);
|
||||
|
||||
[data-slot="input-container"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
button {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
@@ -68,11 +66,13 @@
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-mono);
|
||||
line-height: 1.5;
|
||||
min-width: 0;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 3px var(--color-accent-alpha);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
@@ -80,16 +80,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="form-actions"] {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
justify-content: flex-end;
|
||||
>button[type="reset"] {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
[data-slot="form-error"] {
|
||||
color: var(--color-danger);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: 1.4;
|
||||
margin-top: calc(var(--space-1) * -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-16
@@ -79,10 +79,7 @@ export function SettingsSection() {
|
||||
</div>
|
||||
<div data-slot="section-content">
|
||||
<div data-slot="setting">
|
||||
<div data-slot="setting-info">
|
||||
<h3>Workspace Name</h3>
|
||||
<p data-slot="current-value">{workspaceInfo()?.name}</p>
|
||||
</div>
|
||||
<p>Workspace name</p>
|
||||
<Show
|
||||
when={!store.show}
|
||||
fallback={
|
||||
@@ -97,25 +94,26 @@ export function SettingsSection() {
|
||||
placeholder="Workspace name"
|
||||
value={workspaceInfo()?.name ?? "Default"}
|
||||
/>
|
||||
<Show when={submission.result && submission.result.error}>
|
||||
{(err) => <div data-slot="form-error">{err()}</div>}
|
||||
</Show>
|
||||
</div>
|
||||
<input type="hidden" name="workspaceID" value={params.id} />
|
||||
<div data-slot="form-actions">
|
||||
<input type="hidden" name="workspaceID" value={params.id} />
|
||||
<button type="submit" data-color="primary" disabled={submission.pending}>
|
||||
{submission.pending ? "Updating..." : "Save"}
|
||||
</button>
|
||||
<button type="reset" data-color="ghost" onClick={() => hide()}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" data-color="primary" disabled={submission.pending}>
|
||||
{submission.pending ? "Updating..." : "Update"}
|
||||
</button>
|
||||
</div>
|
||||
<Show when={submission.result && submission.result.error}>
|
||||
{(err) => <div data-slot="form-error">{err()}</div>}
|
||||
</Show>
|
||||
</form>
|
||||
}
|
||||
>
|
||||
<button data-color="primary" onClick={() => show()}>
|
||||
Edit Name
|
||||
</button>
|
||||
<div data-slot="value-with-action">
|
||||
<p data-slot="current-value">{workspaceInfo()?.name}</p>
|
||||
<button data-color="primary" onClick={() => show()}>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||
import { query, useParams, createAsync } from "@solidjs/router"
|
||||
import { createMemo, For, Show } from "solid-js"
|
||||
import { formatDateUTC, formatDateForTable } from "./common"
|
||||
import { formatDateUTC, formatDateForTable } from "../common"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import styles from "./usage-section.module.css"
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { Resource } from "@opencode-ai/console-resource"
|
||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||
import { action, query } from "@solidjs/router"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||
|
||||
export function formatDateForTable(date: Date) {
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
day: "numeric",
|
||||
@@ -23,3 +29,35 @@ export function formatDateUTC(date: Date) {
|
||||
}
|
||||
return date.toLocaleDateString("en-US", options)
|
||||
}
|
||||
|
||||
export const queryIsLoggedIn = query(async () => {
|
||||
"use server"
|
||||
return withActor(() => {
|
||||
try {
|
||||
Actor.assert("account")
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}, "isLoggedIn.get")
|
||||
|
||||
export const querySessionInfo = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(() => {
|
||||
return {
|
||||
isAdmin: Actor.userRole() === "admin",
|
||||
isBeta: Resource.App.stage === "production" ? workspaceID === "wrk_01K46JDFR0E75SG2Q8K172KF3Y" : true,
|
||||
}
|
||||
}, workspaceID)
|
||||
}, "session.get")
|
||||
|
||||
export const createCheckoutUrl = action(async (workspaceID: string, successUrl: string, cancelUrl: string) => {
|
||||
"use server"
|
||||
return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }), workspaceID)
|
||||
}, "checkoutUrl")
|
||||
|
||||
export const queryBillingInfo = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(() => Billing.get(), workspaceID)
|
||||
}, "billing.get")
|
||||
|
||||
@@ -1,328 +0,0 @@
|
||||
import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router"
|
||||
import { createEffect, createSignal, For, Show } from "solid-js"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { createStore } from "solid-js/store"
|
||||
import styles from "./member-section.module.css"
|
||||
import { UserRole } from "@opencode-ai/console-core/schema/user.sql.js"
|
||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||
import { User } from "@opencode-ai/console-core/user.js"
|
||||
|
||||
const listMembers = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
return {
|
||||
members: await User.list(),
|
||||
actorID: Actor.userID(),
|
||||
actorRole: Actor.userRole(),
|
||||
}
|
||||
}, workspaceID)
|
||||
}, "member.list")
|
||||
|
||||
const inviteMember = action(async (form: FormData) => {
|
||||
"use server"
|
||||
const email = form.get("email")?.toString().trim()
|
||||
if (!email) return { error: "Email is required" }
|
||||
const workspaceID = form.get("workspaceID")?.toString()
|
||||
if (!workspaceID) return { error: "Workspace ID is required" }
|
||||
const role = form.get("role")?.toString() as (typeof UserRole)[number]
|
||||
if (!role) return { error: "Role is required" }
|
||||
return json(
|
||||
await withActor(
|
||||
() =>
|
||||
User.invite({ email, role })
|
||||
.then((data) => ({ error: undefined, data }))
|
||||
.catch((e) => ({ error: e.message as string })),
|
||||
workspaceID,
|
||||
),
|
||||
{ revalidate: listMembers.key },
|
||||
)
|
||||
}, "member.create")
|
||||
|
||||
const removeMember = action(async (form: FormData) => {
|
||||
"use server"
|
||||
const id = form.get("id")?.toString()
|
||||
if (!id) return { error: "ID is required" }
|
||||
const workspaceID = form.get("workspaceID")?.toString()
|
||||
if (!workspaceID) return { error: "Workspace ID is required" }
|
||||
return json(
|
||||
await withActor(
|
||||
() =>
|
||||
User.remove(id)
|
||||
.then((data) => ({ error: undefined, data }))
|
||||
.catch((e) => ({ error: e.message as string })),
|
||||
workspaceID,
|
||||
),
|
||||
{ revalidate: listMembers.key },
|
||||
)
|
||||
}, "member.remove")
|
||||
|
||||
const updateMember = action(async (form: FormData) => {
|
||||
"use server"
|
||||
|
||||
const id = form.get("id")?.toString()
|
||||
if (!id) return { error: "ID is required" }
|
||||
const workspaceID = form.get("workspaceID")?.toString()
|
||||
if (!workspaceID) return { error: "Workspace ID is required" }
|
||||
const role = form.get("role")?.toString() as (typeof UserRole)[number]
|
||||
if (!role) return { error: "Role is required" }
|
||||
const limit = form.get("limit")?.toString()
|
||||
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
|
||||
if (monthlyLimit !== null && monthlyLimit < 0) return { error: "Set a valid monthly limit" }
|
||||
|
||||
return json(
|
||||
await withActor(
|
||||
() =>
|
||||
User.update({ id, role, monthlyLimit })
|
||||
.then((data) => ({ error: undefined, data }))
|
||||
.catch((e) => ({ error: e.message as string })),
|
||||
workspaceID,
|
||||
),
|
||||
{ revalidate: listMembers.key },
|
||||
)
|
||||
}, "member.update")
|
||||
|
||||
export function MemberCreateForm() {
|
||||
const params = useParams()
|
||||
const submission = useSubmission(inviteMember)
|
||||
const [store, setStore] = createStore({ show: false })
|
||||
|
||||
let input: HTMLInputElement
|
||||
|
||||
createEffect(() => {
|
||||
if (!submission.pending && submission.result && !submission.result.error) {
|
||||
hide()
|
||||
}
|
||||
})
|
||||
|
||||
function show() {
|
||||
// submission.clear() does not clear the result in some cases, ie.
|
||||
// 1. Create key with empty name => error shows
|
||||
// 2. Put in a key name and creates the key => form hides
|
||||
// 3. Click add key button again => form shows with the same error if
|
||||
// submission.clear() is called only once
|
||||
while (true) {
|
||||
submission.clear()
|
||||
if (!submission.result) break
|
||||
}
|
||||
setStore("show", true)
|
||||
input.focus()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
setStore("show", false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={store.show}
|
||||
fallback={
|
||||
<button data-color="primary" onClick={() => show()}>
|
||||
Invite Member
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<form action={inviteMember} method="post" data-slot="create-form">
|
||||
<div data-slot="input-container">
|
||||
<input ref={(r) => (input = r)} data-component="input" name="email" type="text" placeholder="Enter email" />
|
||||
<div data-slot="role-selector">
|
||||
<label>
|
||||
<input type="radio" name="role" value="admin" checked />
|
||||
<div>
|
||||
<strong>Admin</strong>
|
||||
<p>Can manage models, members, and billing</p>
|
||||
</div>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="role" value="member" />
|
||||
<div>
|
||||
<strong>Member</strong>
|
||||
<p>Can only generate API keys for themselves</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<Show when={submission.result && submission.result.error}>
|
||||
{(err) => <div data-slot="form-error">{err()}</div>}
|
||||
</Show>
|
||||
</div>
|
||||
<input type="hidden" name="workspaceID" value={params.id} />
|
||||
<div data-slot="form-actions">
|
||||
<button type="reset" data-color="ghost" onClick={() => hide()}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" data-color="primary" disabled={submission.pending}>
|
||||
{submission.pending ? "Inviting..." : "Invite"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function MemberRow(props: { member: any; workspaceID: string; actorID: string; actorRole: string }) {
|
||||
const [editing, setEditing] = createSignal(false)
|
||||
const submission = useSubmission(updateMember)
|
||||
const isCurrentUser = () => props.actorID === props.member.id
|
||||
const isAdmin = () => props.actorRole === "admin"
|
||||
|
||||
createEffect(() => {
|
||||
if (!submission.pending && submission.result && !submission.result.error) {
|
||||
setEditing(false)
|
||||
}
|
||||
})
|
||||
|
||||
function getUsageDisplay() {
|
||||
const currentUsage = (() => {
|
||||
const dateLastUsed = props.member.timeMonthlyUsageUpdated
|
||||
if (!dateLastUsed) return 0
|
||||
|
||||
const current = new Date().toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
timeZone: "UTC",
|
||||
})
|
||||
const lastUsed = dateLastUsed.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
timeZone: "UTC",
|
||||
})
|
||||
if (current !== lastUsed) return 0
|
||||
return ((props.member.monthlyUsage ?? 0) / 100000000).toFixed(2)
|
||||
})()
|
||||
|
||||
const limit = props.member.monthlyLimit ? `$${props.member.monthlyLimit}` : "no limit"
|
||||
return `$${currentUsage} / ${limit}`
|
||||
}
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={editing()}
|
||||
fallback={
|
||||
<tr>
|
||||
<td data-slot="member-email">{props.member.accountEmail ?? props.member.email}</td>
|
||||
<td data-slot="member-role">{props.member.role}</td>
|
||||
<td data-slot="member-usage">{getUsageDisplay()}</td>
|
||||
<td data-slot="member-joined">{props.member.timeSeen ? "" : "invited"}</td>
|
||||
<td data-slot="member-actions">
|
||||
<Show when={isAdmin()}>
|
||||
<button data-color="ghost" onClick={() => setEditing(true)}>
|
||||
Edit
|
||||
</button>
|
||||
<Show when={!isCurrentUser()}>
|
||||
<form action={removeMember} method="post">
|
||||
<input type="hidden" name="id" value={props.member.id} />
|
||||
<input type="hidden" name="workspaceID" value={props.workspaceID} />
|
||||
<button data-color="ghost">Delete</button>
|
||||
</form>
|
||||
</Show>
|
||||
</Show>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
>
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<form action={updateMember} method="post">
|
||||
<div data-slot="edit-member-email">{props.member.accountEmail ?? props.member.email}</div>
|
||||
<input type="hidden" name="id" value={props.member.id} />
|
||||
<input type="hidden" name="workspaceID" value={props.workspaceID} />
|
||||
|
||||
<Show
|
||||
when={!isCurrentUser()}
|
||||
fallback={
|
||||
<>
|
||||
<div data-slot="current-user-role">Role: {props.member.role}</div>
|
||||
<input type="hidden" name="role" value={props.member.role} />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div data-slot="role-selector">
|
||||
<label>
|
||||
<input type="radio" name="role" value="admin" checked={props.member.role === "admin"} />
|
||||
<div>
|
||||
<strong>Admin</strong>
|
||||
<p>Can manage models, members, and billing</p>
|
||||
</div>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="role" value="member" checked={props.member.role === "member"} />
|
||||
<div>
|
||||
<strong>Member</strong>
|
||||
<p>Can only generate API keys for themselves</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div data-slot="limit-selector">
|
||||
<label>
|
||||
<strong>Monthly Limit</strong>
|
||||
<input
|
||||
type="number"
|
||||
name="limit"
|
||||
value={props.member.monthlyLimit ?? ""}
|
||||
placeholder="No limit"
|
||||
min="0"
|
||||
/>
|
||||
<p>Set a monthly spending limit for this user</p>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Show when={submission.result && submission.result.error}>
|
||||
{(err) => <div data-slot="form-error">{err()}</div>}
|
||||
</Show>
|
||||
|
||||
<div data-slot="form-actions">
|
||||
<button type="button" data-color="ghost" onClick={() => setEditing(false)}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" data-color="primary" disabled={submission.pending}>
|
||||
{submission.pending ? "Saving..." : "Save"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
export function MemberSection() {
|
||||
const params = useParams()
|
||||
const data = createAsync(() => listMembers(params.id))
|
||||
|
||||
return (
|
||||
<section class={styles.root}>
|
||||
<div data-slot="section-title">
|
||||
<h2>Members</h2>
|
||||
</div>
|
||||
<Show when={data()?.actorRole === "admin"}>
|
||||
<MemberCreateForm />
|
||||
</Show>
|
||||
<div data-slot="members-table">
|
||||
<table data-slot="members-table-element">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Usage</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={data()?.members || []}>
|
||||
{(member) => (
|
||||
<MemberRow
|
||||
member={member}
|
||||
workspaceID={params.id}
|
||||
actorID={data()!.actorID}
|
||||
actorRole={data()!.actorRole}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
.root {}
|
||||
|
||||
[data-slot="section-title"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
[data-slot="section-title"] h2 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
[data-slot="section-title"] p {
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
[data-slot="models-list"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
[data-slot="models-table"] {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
[data-slot="models-table-element"] {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
|
||||
&[data-slot="model-name"] {
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-mono);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&[data-slot="training-data"] {
|
||||
text-align: center;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="model-status"] {
|
||||
text-align: left;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
&[data-slot="model-toggle"] {
|
||||
text-align: left;
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&[data-enabled="false"] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
th {
|
||||
&:nth-child(2)
|
||||
|
||||
/* Training Data */
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:nth-child(2)
|
||||
|
||||
/* Training Data */
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[data-component="empty-state"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
import { Model } from "@opencode-ai/console-core/model.js"
|
||||
import { query, action, useParams, createAsync, json } from "@solidjs/router"
|
||||
import { createMemo, For, Show } from "solid-js"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { ZenModel } from "@opencode-ai/console-core/model.js"
|
||||
import styles from "./model-section.module.css"
|
||||
|
||||
const getModelsInfo = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
return {
|
||||
all: Object.keys(ZenModel.list())
|
||||
.filter((model) => !["claude-3-5-haiku", "glm-4.6", "qwen3-max"].includes(model))
|
||||
.sort(([a], [b]) => a.localeCompare(b)),
|
||||
disabled: await Model.listDisabled(),
|
||||
}
|
||||
}, workspaceID)
|
||||
}, "model.info")
|
||||
|
||||
const updateModel = action(async (form: FormData) => {
|
||||
"use server"
|
||||
const model = form.get("model")?.toString()
|
||||
if (!model) return { error: "Model is required" }
|
||||
const workspaceID = form.get("workspaceID")?.toString()
|
||||
if (!workspaceID) return { error: "Workspace ID is required" }
|
||||
const enabled = form.get("enabled")?.toString() === "true"
|
||||
return json(
|
||||
withActor(async () => {
|
||||
if (enabled) {
|
||||
await Model.disable({ model })
|
||||
} else {
|
||||
await Model.enable({ model })
|
||||
}
|
||||
}, workspaceID),
|
||||
{ revalidate: getModelsInfo.key },
|
||||
)
|
||||
}, "model.toggle")
|
||||
|
||||
export function ModelSection() {
|
||||
const params = useParams()
|
||||
const modelsInfo = createAsync(() => getModelsInfo(params.id))
|
||||
return (
|
||||
<section class={styles.root}>
|
||||
<div data-slot="section-title">
|
||||
<h2>Models</h2>
|
||||
<p>Manage models for your workspace.</p>
|
||||
</div>
|
||||
<div data-slot="models-list">
|
||||
<Show
|
||||
when={modelsInfo()}
|
||||
fallback={
|
||||
<div data-component="empty-state">
|
||||
<p>Loading models...</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div data-slot="models-table">
|
||||
<table data-slot="models-table-element">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Model</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={modelsInfo()!.all}>
|
||||
{(modelId) => {
|
||||
const isEnabled = createMemo(() => !modelsInfo()!.disabled.includes(modelId))
|
||||
return (
|
||||
<tr data-slot="model-row" data-enabled={isEnabled()}>
|
||||
<td data-slot="model-name">{modelId}</td>
|
||||
<td data-slot="model-status">{isEnabled() ? "Enabled" : "Disabled"}</td>
|
||||
<td data-slot="model-toggle">
|
||||
<form action={updateModel} method="post">
|
||||
<input type="hidden" name="model" value={modelId} />
|
||||
<input type="hidden" name="workspaceID" value={params.id} />
|
||||
<input type="hidden" name="enabled" value={isEnabled().toString()} />
|
||||
<button data-color="ghost">{isEnabled() ? "Disable" : "Enable"}</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import "./index.css"
|
||||
import { createAsync } from "@solidjs/router"
|
||||
import { Title, Meta, Link } from "@solidjs/meta"
|
||||
import { HttpHeader } from "@solidjs/start"
|
||||
import zenLogoLight from "../../asset/zen-ornate-light.svg"
|
||||
@@ -15,8 +16,10 @@ import { Faq } from "~/component/faq"
|
||||
import { Legal } from "~/component/legal"
|
||||
import { Footer } from "~/component/footer"
|
||||
import { Header } from "~/component/header"
|
||||
import { queryIsLoggedIn } from "~/routes/workspace/common"
|
||||
|
||||
export default function Home() {
|
||||
const isLoggedIn = createAsync(() => queryIsLoggedIn())
|
||||
return (
|
||||
<main data-page="zen">
|
||||
<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />
|
||||
@@ -102,7 +105,7 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
<a href="/auth">
|
||||
<span>Get started with Zen </span>
|
||||
<span>{isLoggedIn() ? "Go to workspace " : "Get started with Zen "}</span>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M6.5 12L17 12M13 16.5L17.5 12L13 7.5"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
@@ -14,7 +14,8 @@
|
||||
"drizzle-orm": "0.41.0",
|
||||
"postgres": "3.4.7",
|
||||
"stripe": "18.0.0",
|
||||
"ulid": "3.0.0"
|
||||
"ulid": "3.0.0",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"exports": {
|
||||
"./*": "./src/*"
|
||||
@@ -26,10 +27,16 @@
|
||||
"update-models": "script/update-models.ts",
|
||||
"promote-models-to-dev": "script/promote-models.ts dev",
|
||||
"promote-models-to-prod": "script/promote-models.ts production",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"typecheck": "tsgo --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "catalog:",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@types/bun": "1.3.0",
|
||||
"@types/node": "catalog:",
|
||||
"drizzle-kit": "0.30.5",
|
||||
"mysql2": "3.14.4"
|
||||
"mysql2": "3.14.4",
|
||||
"typescript": "catalog:",
|
||||
"@typescript/native-preview": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,11 @@ export namespace Actor {
|
||||
return actor as Extract<Info, { type: T }>
|
||||
}
|
||||
|
||||
export const assertAdmin = () => {
|
||||
if (userRole() === "admin") return
|
||||
throw new Error(`Action not allowed. Ask your workspace admin to perform this action.`)
|
||||
}
|
||||
|
||||
export function workspace() {
|
||||
const actor = use()
|
||||
if ("workspaceID" in actor.properties) {
|
||||
|
||||
@@ -18,6 +18,7 @@ export namespace Billing {
|
||||
export const stripe = () =>
|
||||
new Stripe(Resource.STRIPE_SECRET_KEY.value, {
|
||||
apiVersion: "2025-03-31.basil",
|
||||
httpClient: Stripe.createFetchHttpClient(),
|
||||
})
|
||||
|
||||
export const get = async () => {
|
||||
|
||||
@@ -14,7 +14,7 @@ export namespace Context {
|
||||
return result
|
||||
},
|
||||
provide<R>(value: T, fn: () => R) {
|
||||
return storage.run<R>(value, fn)
|
||||
return storage.run(value, fn)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ export namespace ZenModel {
|
||||
})
|
||||
|
||||
export const ModelSchema = z.object({
|
||||
name: z.string(),
|
||||
cost: ModelCostSchema,
|
||||
cost200K: ModelCostSchema.optional(),
|
||||
allowAnonymous: z.boolean().optional(),
|
||||
@@ -40,13 +41,14 @@ export namespace ZenModel {
|
||||
|
||||
export namespace Model {
|
||||
export const enable = fn(z.object({ model: z.string() }), ({ model }) => {
|
||||
const workspaceID = Actor.workspace()
|
||||
Actor.assertAdmin()
|
||||
return Database.use((db) =>
|
||||
db.delete(ModelTable).where(and(eq(ModelTable.workspaceID, workspaceID), eq(ModelTable.model, model))),
|
||||
db.delete(ModelTable).where(and(eq(ModelTable.workspaceID, Actor.workspace()), eq(ModelTable.model, model))),
|
||||
)
|
||||
})
|
||||
|
||||
export const disable = fn(z.object({ model: z.string() }), ({ model }) => {
|
||||
Actor.assertAdmin()
|
||||
return Database.use((db) =>
|
||||
db
|
||||
.insert(ModelTable)
|
||||
|
||||
@@ -20,8 +20,9 @@ export namespace Provider {
|
||||
provider: z.string().min(1).max(64),
|
||||
credentials: z.string(),
|
||||
}),
|
||||
({ provider, credentials }) =>
|
||||
Database.use((tx) =>
|
||||
async ({ provider, credentials }) => {
|
||||
Actor.assertAdmin()
|
||||
return Database.use((tx) =>
|
||||
tx
|
||||
.insert(ProviderTable)
|
||||
.values({
|
||||
@@ -36,14 +37,21 @@ export namespace Provider {
|
||||
timeDeleted: null,
|
||||
},
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export const remove = fn(z.object({ provider: z.string() }), ({ provider }) =>
|
||||
Database.transaction((tx) =>
|
||||
tx
|
||||
.delete(ProviderTable)
|
||||
.where(and(eq(ProviderTable.provider, provider), eq(ProviderTable.workspaceID, Actor.workspace()))),
|
||||
),
|
||||
export const remove = fn(
|
||||
z.object({
|
||||
provider: z.string(),
|
||||
}),
|
||||
async ({ provider }) => {
|
||||
Actor.assertAdmin()
|
||||
return Database.transaction((tx) =>
|
||||
tx
|
||||
.delete(ProviderTable)
|
||||
.where(and(eq(ProviderTable.provider, provider), eq(ProviderTable.workspaceID, Actor.workspace()))),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,13 +11,9 @@ import { Account } from "./account"
|
||||
import { AccountTable } from "./schema/account.sql"
|
||||
import { Key } from "./key"
|
||||
import { KeyTable } from "./schema/key.sql"
|
||||
import { WorkspaceTable } from "./schema/workspace.sql"
|
||||
|
||||
export namespace User {
|
||||
const assertAdmin = () => {
|
||||
if (Actor.userRole() === "admin") return
|
||||
throw new Error(`Expected admin user, got ${Actor.userRole()}`)
|
||||
}
|
||||
|
||||
const assertNotSelf = (id: string) => {
|
||||
if (Actor.userID() !== id) return
|
||||
throw new Error(`Expected not self actor, got self actor`)
|
||||
@@ -63,9 +59,10 @@ export namespace User {
|
||||
z.object({
|
||||
email: z.string(),
|
||||
role: z.enum(UserRole),
|
||||
monthlyLimit: z.number().nullable().optional(),
|
||||
}),
|
||||
async ({ email, role }) => {
|
||||
assertAdmin()
|
||||
async ({ email, role, monthlyLimit }) => {
|
||||
Actor.assertAdmin()
|
||||
const workspaceID = Actor.workspace()
|
||||
|
||||
// create user
|
||||
@@ -85,10 +82,12 @@ export namespace User {
|
||||
}),
|
||||
workspaceID,
|
||||
role,
|
||||
monthlyLimit,
|
||||
})
|
||||
.onDuplicateKeyUpdate({
|
||||
set: {
|
||||
role,
|
||||
monthlyLimit,
|
||||
timeDeleted: null,
|
||||
},
|
||||
}),
|
||||
@@ -117,15 +116,32 @@ export namespace User {
|
||||
|
||||
// send email, ignore errors
|
||||
try {
|
||||
const emailInfo = await Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
email: AccountTable.email,
|
||||
workspaceName: WorkspaceTable.name,
|
||||
})
|
||||
.from(UserTable)
|
||||
.innerJoin(AccountTable, eq(UserTable.accountID, AccountTable.id))
|
||||
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, workspaceID))
|
||||
.where(
|
||||
and(eq(UserTable.workspaceID, workspaceID), eq(UserTable.id, Actor.assert("user").properties.userID)),
|
||||
)
|
||||
.then((rows) => rows[0]),
|
||||
)
|
||||
|
||||
const { InviteEmail } = await import("@opencode-ai/console-mail/InviteEmail.jsx")
|
||||
await AWS.sendEmail({
|
||||
to: email,
|
||||
subject: `You've been invited to join the ${workspaceID} workspace on OpenCode Zen`,
|
||||
subject: `You've been invited to join the ${emailInfo.workspaceName} workspace on OpenCode`,
|
||||
body: render(
|
||||
// @ts-ignore
|
||||
InviteEmail({
|
||||
inviter: emailInfo.email,
|
||||
assetsUrl: `https://opencode.ai/email`,
|
||||
workspace: workspaceID,
|
||||
workspaceID: workspaceID,
|
||||
workspaceName: emailInfo.workspaceName,
|
||||
}),
|
||||
),
|
||||
})
|
||||
@@ -176,7 +192,7 @@ export namespace User {
|
||||
monthlyLimit: z.number().nullable(),
|
||||
}),
|
||||
async ({ id, role, monthlyLimit }) => {
|
||||
assertAdmin()
|
||||
Actor.assertAdmin()
|
||||
if (role === "member") assertNotSelf(id)
|
||||
return await Database.use((tx) =>
|
||||
tx
|
||||
@@ -188,7 +204,7 @@ export namespace User {
|
||||
)
|
||||
|
||||
export const remove = fn(z.string(), async (id) => {
|
||||
assertAdmin()
|
||||
Actor.assertAdmin()
|
||||
assertNotSelf(id)
|
||||
|
||||
return await Database.use((tx) =>
|
||||
|
||||
@@ -52,6 +52,7 @@ export namespace Workspace {
|
||||
name: z.string().min(1).max(255),
|
||||
}),
|
||||
async ({ name }) => {
|
||||
Actor.assertAdmin()
|
||||
const workspaceID = Actor.workspace()
|
||||
return await Database.use((tx) =>
|
||||
tx
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.5",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
"typecheck": "tsgo --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "4.20250522.0",
|
||||
"@cloudflare/workers-types": "catalog:",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@types/node": "catalog:",
|
||||
"openai": "5.11.0",
|
||||
"typescript": "catalog:"
|
||||
"typescript": "catalog:",
|
||||
"@typescript/native-preview": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
"@ai-sdk/openai-compatible": "1.0.1",
|
||||
"@hono/zod-validator": "catalog:",
|
||||
"@opencode-ai/console-core": "workspace:*",
|
||||
"@opencode-ai/console-resource": "workspace:*",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"ai": "catalog:",
|
||||
"hono": "catalog:",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { KVNamespace } from "@cloudflare/workers-types"
|
||||
import { z } from "zod"
|
||||
import { issuer } from "@openauthjs/openauth"
|
||||
import type { Theme } from "@openauthjs/openauth/ui/theme"
|
||||
@@ -94,6 +95,7 @@ export default {
|
||||
// }),
|
||||
},
|
||||
storage: CloudflareStorage({
|
||||
// @ts-ignore
|
||||
namespace: env.AuthStorage,
|
||||
}),
|
||||
subjects,
|
||||
@@ -118,6 +120,10 @@ export default {
|
||||
|
||||
if (!email) throw new Error("No email found")
|
||||
|
||||
if (Resource.App.stage !== "production" && !email.endsWith("@anoma.ly")) {
|
||||
throw new Error("Invalid email")
|
||||
}
|
||||
|
||||
let accountID = await Account.fromEmail(email).then((x) => x?.id)
|
||||
if (!accountID) {
|
||||
console.log("creating account for", email)
|
||||
|
||||
@@ -1,26 +1,10 @@
|
||||
// @ts-nocheck
|
||||
import React from "react"
|
||||
import { Font, Hr as JEHr, Text as JEText, type HrProps, type TextProps } from "@jsx-email/all"
|
||||
import { DIVIDER_COLOR, SURFACE_DIVIDER_COLOR, textColor } from "./styles"
|
||||
import { Font, Text as JEText, type TextProps } from "@jsx-email/all"
|
||||
import { baseText } from "./styles"
|
||||
|
||||
export function Text(props: TextProps) {
|
||||
return <JEText {...props} style={{ ...textColor, ...props.style }} />
|
||||
}
|
||||
|
||||
export function Hr(props: HrProps) {
|
||||
return <JEHr {...props} style={{ borderTop: `1px solid ${DIVIDER_COLOR}`, ...props.style }} />
|
||||
}
|
||||
|
||||
export function SurfaceHr(props: HrProps) {
|
||||
return (
|
||||
<JEHr
|
||||
{...props}
|
||||
style={{
|
||||
borderTop: `1px solid ${SURFACE_DIVIDER_COLOR}`,
|
||||
...props.style,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
return <JEText {...props} style={{ ...baseText, ...props.style }} />
|
||||
}
|
||||
|
||||
export function Title({ children }: TitleProps) {
|
||||
@@ -43,45 +27,25 @@ export function Fonts({ assetsUrl }: { assetsUrl: string }) {
|
||||
return (
|
||||
<>
|
||||
<Font
|
||||
fontFamily="IBM Plex Mono"
|
||||
fontFamily="JetBrains Mono"
|
||||
fallbackFontFamily="monospace"
|
||||
webFont={{
|
||||
url: `${assetsUrl}/ibm-plex-mono-latin-400.woff2`,
|
||||
url: `${assetsUrl}/JetBrainsMono-Regular.woff2`,
|
||||
format: "woff2",
|
||||
}}
|
||||
fontWeight="400"
|
||||
fontStyle="normal"
|
||||
/>
|
||||
<Font
|
||||
fontFamily="IBM Plex Mono"
|
||||
fontFamily="JetBrains Mono"
|
||||
fallbackFontFamily="monospace"
|
||||
webFont={{
|
||||
url: `${assetsUrl}/ibm-plex-mono-latin-500.woff2`,
|
||||
url: `${assetsUrl}/JetBrainsMono-Medium.woff2`,
|
||||
format: "woff2",
|
||||
}}
|
||||
fontWeight="500"
|
||||
fontStyle="normal"
|
||||
/>
|
||||
<Font
|
||||
fontFamily="IBM Plex Mono"
|
||||
fallbackFontFamily="monospace"
|
||||
webFont={{
|
||||
url: `${assetsUrl}/ibm-plex-mono-latin-600.woff2`,
|
||||
format: "woff2",
|
||||
}}
|
||||
fontWeight="600"
|
||||
fontStyle="normal"
|
||||
/>
|
||||
<Font
|
||||
fontFamily="IBM Plex Mono"
|
||||
fallbackFontFamily="monospace"
|
||||
webFont={{
|
||||
url: `${assetsUrl}/ibm-plex-mono-latin-700.woff2`,
|
||||
format: "woff2",
|
||||
}}
|
||||
fontWeight="700"
|
||||
fontStyle="normal"
|
||||
/>
|
||||
<Font
|
||||
fontFamily="Rubik"
|
||||
fallbackFontFamily={["Helvetica", "Arial", "sans-serif"]}
|
||||
|
||||
@@ -1,110 +1,91 @@
|
||||
export const unit = 16;
|
||||
|
||||
export const GREY_COLOR = [
|
||||
"#1A1A2E", //0
|
||||
"#2F2F41", //1
|
||||
"#444454", //2
|
||||
"#585867", //3
|
||||
"#6D6D7A", //4
|
||||
"#82828D", //5
|
||||
"#9797A0", //6
|
||||
"#ACACB3", //7
|
||||
"#C1C1C6", //8
|
||||
"#D5D5D9", //9
|
||||
"#EAEAEC", //10
|
||||
"#FFFFFF", //11
|
||||
];
|
||||
|
||||
export const BLUE_COLOR = "#395C6B";
|
||||
export const DANGER_COLOR = "#ED322C";
|
||||
export const TEXT_COLOR = GREY_COLOR[0];
|
||||
export const SECONDARY_COLOR = GREY_COLOR[5];
|
||||
export const DIMMED_COLOR = GREY_COLOR[7];
|
||||
export const DIVIDER_COLOR = GREY_COLOR[10];
|
||||
export const BACKGROUND_COLOR = "#F0F0F1";
|
||||
export const SURFACE_COLOR = DIVIDER_COLOR;
|
||||
export const SURFACE_DIVIDER_COLOR = GREY_COLOR[9];
|
||||
// @ts-nocheck
|
||||
export const unit = 12
|
||||
export const PRIMARY_COLOR = "#211E1E"
|
||||
export const TEXT_COLOR = "#656363"
|
||||
export const LINK_COLOR = "#007AFF"
|
||||
export const LINK_BACKGROUND_COLOR = "#F9F8F8"
|
||||
export const BACKGROUND_COLOR = "#F0F0F1"
|
||||
export const SURFACE_DIVIDER_COLOR = "#D5D5D9"
|
||||
|
||||
export const body = {
|
||||
background: BACKGROUND_COLOR,
|
||||
};
|
||||
}
|
||||
|
||||
export const container = {
|
||||
minWidth: "600px",
|
||||
};
|
||||
|
||||
export const medium = {
|
||||
fontWeight: 500,
|
||||
};
|
||||
|
||||
export const danger = {
|
||||
color: DANGER_COLOR,
|
||||
};
|
||||
padding: "64px 0px",
|
||||
}
|
||||
|
||||
export const frame = {
|
||||
padding: `${unit * 1.5}px`,
|
||||
padding: `${unit * 2}px`,
|
||||
border: `1px solid ${SURFACE_DIVIDER_COLOR}`,
|
||||
background: "#FFF",
|
||||
borderRadius: "6px",
|
||||
boxShadow: `0 1px 2px rgba(0,0,0,0.03),
|
||||
0 2px 4px rgba(0,0,0,0.03),
|
||||
0 2px 6px rgba(0,0,0,0.03)`,
|
||||
};
|
||||
}
|
||||
|
||||
export const textColor = {
|
||||
export const baseText = {
|
||||
fontFamily: "JetBrains Mono, monospace",
|
||||
}
|
||||
|
||||
export const headingText = {
|
||||
color: PRIMARY_COLOR,
|
||||
fontSize: "16px",
|
||||
fontStyle: "normal",
|
||||
fontWeight: 500,
|
||||
lineHeight: "normal",
|
||||
}
|
||||
|
||||
export const contentText = {
|
||||
color: TEXT_COLOR,
|
||||
};
|
||||
fontSize: "14px",
|
||||
fontStyle: "normal",
|
||||
fontWeight: 400,
|
||||
lineHeight: "180%",
|
||||
}
|
||||
|
||||
export const code = {
|
||||
fontFamily: "IBM Plex Mono, monospace",
|
||||
};
|
||||
export const buttonText = {
|
||||
color: "#FDFCFC",
|
||||
fontSize: "16px",
|
||||
fontWeight: 500,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: "12px",
|
||||
}
|
||||
|
||||
export const headingHr = {
|
||||
margin: `${unit}px 0`,
|
||||
};
|
||||
|
||||
export const buttonPrimary = {
|
||||
...code,
|
||||
padding: "12px 18px",
|
||||
color: "#FFF",
|
||||
export const linkText = {
|
||||
color: LINK_COLOR,
|
||||
fontSize: "14px",
|
||||
fontStyle: "normal",
|
||||
fontWeight: 400,
|
||||
lineHeight: "150%",
|
||||
textDecorationLine: "underline",
|
||||
textDecorationStyle: "solid" as const,
|
||||
textDecorationSkipInk: "auto" as const,
|
||||
textDecorationThickness: "auto",
|
||||
textUnderlineOffset: "auto",
|
||||
textUnderlinePosition: "from-font",
|
||||
borderRadius: "4px",
|
||||
background: BLUE_COLOR,
|
||||
fontSize: "12px",
|
||||
fontWeight: 500,
|
||||
};
|
||||
background: LINK_BACKGROUND_COLOR,
|
||||
padding: "8px 12px",
|
||||
textAlign: "center" as const,
|
||||
}
|
||||
|
||||
export const compactText = {
|
||||
margin: "0 0 2px",
|
||||
};
|
||||
export const contentHighlightText = {
|
||||
color: PRIMARY_COLOR,
|
||||
}
|
||||
|
||||
export const breadcrumb = {
|
||||
fontSize: "14px",
|
||||
color: SECONDARY_COLOR,
|
||||
};
|
||||
|
||||
export const breadcrumbColonSeparator = {
|
||||
padding: " 0 4px",
|
||||
color: DIMMED_COLOR,
|
||||
};
|
||||
|
||||
export const breadcrumbSeparator = {
|
||||
color: DIVIDER_COLOR,
|
||||
};
|
||||
|
||||
export const heading = {
|
||||
fontSize: "22px",
|
||||
fontWeight: 500,
|
||||
};
|
||||
|
||||
export const sectionLabel = {
|
||||
...code,
|
||||
...compactText,
|
||||
letterSpacing: "0.5px",
|
||||
fontSize: "13px",
|
||||
fontWeight: 500,
|
||||
color: DIMMED_COLOR,
|
||||
};
|
||||
|
||||
export const footerLink = {
|
||||
fontSize: "14px",
|
||||
};
|
||||
export const button = {
|
||||
display: "inline-grid",
|
||||
padding: "8px 12px 8px 20px",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
flexShrink: "0",
|
||||
borderRadius: "4px",
|
||||
backgroundColor: PRIMARY_COLOR,
|
||||
}
|
||||
|
||||
@@ -1,39 +1,40 @@
|
||||
// @ts-nocheck
|
||||
import React from "react"
|
||||
import { Img, Row, Html, Link, Body, Head, Button, Column, Preview, Section, Container } from "@jsx-email/all"
|
||||
import { Hr, Text, Fonts, SplitString, Title, A, Span } from "../components"
|
||||
import { Text, Fonts, Title, A, Span } from "../components"
|
||||
import {
|
||||
unit,
|
||||
body,
|
||||
code,
|
||||
frame,
|
||||
medium,
|
||||
heading,
|
||||
headingText,
|
||||
container,
|
||||
headingHr,
|
||||
footerLink,
|
||||
breadcrumb,
|
||||
compactText,
|
||||
buttonPrimary,
|
||||
breadcrumbColonSeparator,
|
||||
contentText,
|
||||
button,
|
||||
contentHighlightText,
|
||||
linkText,
|
||||
buttonText,
|
||||
} from "../styles"
|
||||
|
||||
const LOCAL_ASSETS_URL = "/static"
|
||||
const CONSOLE_URL = "https://opencode.ai/"
|
||||
const DOC_URL = "https://opencode.ai/docs/zen"
|
||||
|
||||
interface InviteEmailProps {
|
||||
workspace: string
|
||||
inviter: string
|
||||
workspaceID: string
|
||||
workspaceName: string
|
||||
assetsUrl: string
|
||||
}
|
||||
export const InviteEmail = ({ workspace, assetsUrl = LOCAL_ASSETS_URL }: InviteEmailProps) => {
|
||||
const subject = `Join the ${workspace} workspace`
|
||||
const messagePlain = `You've been invited to join the ${workspace} workspace in the OpenCode Zen Console.`
|
||||
const url = `${CONSOLE_URL}workspace/${workspace}`
|
||||
export const InviteEmail = ({
|
||||
inviter = "test@anoma.ly",
|
||||
workspaceID = "wrk_01K6XFY7V53T8XN0A7X8G9BTN3",
|
||||
workspaceName = "anomaly",
|
||||
assetsUrl = `${CONSOLE_URL}email`,
|
||||
}: InviteEmailProps) => {
|
||||
const messagePlain = `${inviter} invited you to join the ${workspaceName} workspace.`
|
||||
const url = `${CONSOLE_URL}workspace/${workspaceID}`
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<Title>{`OpenCode Zen — ${messagePlain}`}</Title>
|
||||
<Title>{`OpenCode — ${messagePlain}`}</Title>
|
||||
</Head>
|
||||
<Fonts assetsUrl={assetsUrl} />
|
||||
<Preview>{messagePlain}</Preview>
|
||||
@@ -42,67 +43,35 @@ export const InviteEmail = ({ workspace, assetsUrl = LOCAL_ASSETS_URL }: InviteE
|
||||
<Section style={frame}>
|
||||
<Row>
|
||||
<Column>
|
||||
<A href={CONSOLE_URL}>
|
||||
<Img height="32" alt="OpenCode Zen Logo" src={`${assetsUrl}/zen-logo.png`} />
|
||||
<A href={`${CONSOLE_URL}zen`}>
|
||||
<Img height="32" alt="OpenCode Logo" src={`${assetsUrl}/logo.png`} />
|
||||
</A>
|
||||
</Column>
|
||||
<Column align="right">
|
||||
<Button style={buttonPrimary} href={url}>
|
||||
<Span style={code}>Join Workspace</Span>
|
||||
</Button>
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Row style={headingHr}>
|
||||
<Column>
|
||||
<Hr />
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Section>
|
||||
<Text style={{ ...compactText, ...breadcrumb }}>
|
||||
<Span>OpenCode Zen</Span>
|
||||
<Span style={{ ...code, ...breadcrumbColonSeparator }}>:</Span>
|
||||
<Span>{workspace}</Span>
|
||||
</Text>
|
||||
<Text style={{ ...heading, ...compactText }}>
|
||||
<Link href={url}>
|
||||
<SplitString text={subject} split={40} />
|
||||
</Link>
|
||||
<Section style={{ padding: `${unit * 2}px 0 0 0` }}>
|
||||
<Text style={headingText}>Join your team's OpenCode workspace</Text>
|
||||
<Text style={contentText}>
|
||||
You have been invited by <Span style={contentHighlightText}>{inviter}</Span> to join the{" "}
|
||||
<Span style={contentHighlightText}>{workspaceName}</Span> workspace on OpenCode.
|
||||
</Text>
|
||||
</Section>
|
||||
|
||||
<Section style={{ padding: `${unit}px 0 0 0` }}>
|
||||
<Text style={{ ...compactText }}>
|
||||
You've been invited to join the{" "}
|
||||
<Link style={medium} href={url}>
|
||||
{workspace}
|
||||
</Link>{" "}
|
||||
workspace in the{" "}
|
||||
<Link style={medium} href={CONSOLE_URL}>
|
||||
OpenCode Zen Console
|
||||
</Link>
|
||||
.
|
||||
</Text>
|
||||
<Button style={button} href={url}>
|
||||
<Text style={buttonText}>
|
||||
Join workspace
|
||||
<Img width="24" height="24" src={`${assetsUrl}/right-arrow.png`} alt="Arrow right" />
|
||||
</Text>
|
||||
</Button>
|
||||
</Section>
|
||||
|
||||
<Row style={headingHr}>
|
||||
<Column>
|
||||
<Hr />
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Column>
|
||||
<Link href={CONSOLE_URL} style={footerLink}>
|
||||
Console
|
||||
</Link>
|
||||
</Column>
|
||||
<Column align="right">
|
||||
<Link style={footerLink} href={DOC_URL}>
|
||||
About
|
||||
</Link>
|
||||
</Column>
|
||||
</Row>
|
||||
<Section style={{ padding: `${unit}px 0 0 0` }}>
|
||||
<Text style={contentText}>Button not working? Copy the following link...</Text>
|
||||
<Link href={url}>
|
||||
<Text style={linkText}>{url}</Text>
|
||||
</Link>
|
||||
</Section>
|
||||
</Section>
|
||||
</Container>
|
||||
</Body>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 308 B |
@@ -1,19 +1,21 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "0.14.7",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "0.15.5",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@types/react": "18.0.25",
|
||||
"react": "18.2.0"
|
||||
"react": "18.2.0",
|
||||
"solid-js": "catalog:"
|
||||
},
|
||||
"exports": {
|
||||
"./*": "./emails/templates/*"
|
||||
},
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "email preview emails/templates"
|
||||
}
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-resource",
|
||||
"dependencies": {
|
||||
"@cloudflare/workers-types": "^4.20250830.0"
|
||||
"@cloudflare/workers-types": "catalog:"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -11,5 +11,8 @@
|
||||
},
|
||||
"import": "./resource.node.ts"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "22.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-scripts",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.5",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
../../CLAUDE.md
|
||||
@@ -0,0 +1,34 @@
|
||||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
# output
|
||||
out
|
||||
dist
|
||||
*.tgz
|
||||
|
||||
# code coverage
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# logs
|
||||
logs
|
||||
_.log
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# caches
|
||||
.eslintcache
|
||||
.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@opencode-ai/css",
|
||||
"version": "0.15.5",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.css",
|
||||
"./*": "./src/*"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "bun run dev.ts"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||
2. Remove default margins and padding
|
||||
3. Reset all borders.
|
||||
*/
|
||||
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
box-sizing: border-box; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
padding: 0; /* 2 */
|
||||
border: 0 solid; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use a consistent sensible line-height in all browsers.
|
||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
3. Use a more readable tab size.
|
||||
4. Use the user's configured `sans` font-family by default.
|
||||
5. Use the user's configured `sans` font-feature-settings by default.
|
||||
6. Use the user's configured `sans` font-variation-settings by default.
|
||||
7. Disable tap highlights on iOS.
|
||||
*/
|
||||
|
||||
html,
|
||||
:host {
|
||||
line-height: 1.5; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
tab-size: 4; /* 3 */
|
||||
font-family: --theme(
|
||||
--default-font-family,
|
||||
ui-sans-serif,
|
||||
system-ui,
|
||||
sans-serif,
|
||||
"Apple Color Emoji",
|
||||
"Segoe UI Emoji",
|
||||
"Segoe UI Symbol",
|
||||
"Noto Color Emoji"
|
||||
); /* 4 */
|
||||
font-feature-settings: --theme(--default-font-feature-settings, normal); /* 5 */
|
||||
font-variation-settings: --theme(--default-font-variation-settings, normal); /* 6 */
|
||||
-webkit-tap-highlight-color: transparent; /* 7 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Add the correct height in Firefox.
|
||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||
3. Reset the default border style to a 1px solid border.
|
||||
*/
|
||||
|
||||
hr {
|
||||
height: 0; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
border-top-width: 1px; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
abbr:where([title]) {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the default font size and weight for headings.
|
||||
*/
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Reset links to optimize for opt-in styling instead of opt-out.
|
||||
*/
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
-webkit-text-decoration: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font weight in Edge and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use the user's configured `mono` font-family by default.
|
||||
2. Use the user's configured `mono` font-feature-settings by default.
|
||||
3. Use the user's configured `mono` font-variation-settings by default.
|
||||
4. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp,
|
||||
pre {
|
||||
font-family: --theme(
|
||||
--default-mono-font-family,
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
Menlo,
|
||||
Monaco,
|
||||
Consolas,
|
||||
"Liberation Mono",
|
||||
"Courier New",
|
||||
monospace
|
||||
); /* 1 */
|
||||
font-feature-settings: --theme(--default-mono-font-feature-settings, normal); /* 2 */
|
||||
font-variation-settings: --theme(--default-mono-font-variation-settings, normal); /* 3 */
|
||||
font-size: 1em; /* 4 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||
3. Remove gaps between table borders by default.
|
||||
*/
|
||||
|
||||
table {
|
||||
text-indent: 0; /* 1 */
|
||||
border-color: inherit; /* 2 */
|
||||
border-collapse: collapse; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Use the modern Firefox focus style for all focusable elements.
|
||||
*/
|
||||
|
||||
:-moz-focusring {
|
||||
outline: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct vertical alignment in Chrome and Firefox.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct display in Chrome and Safari.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/*
|
||||
Make lists unstyled by default.
|
||||
*/
|
||||
|
||||
ol,
|
||||
ul,
|
||||
menu {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||
*/
|
||||
|
||||
img,
|
||||
svg,
|
||||
video,
|
||||
canvas,
|
||||
audio,
|
||||
iframe,
|
||||
embed,
|
||||
object {
|
||||
display: block; /* 1 */
|
||||
vertical-align: middle; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
*/
|
||||
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Inherit font styles in all browsers.
|
||||
2. Remove border radius in all browsers.
|
||||
3. Remove background color in all browsers.
|
||||
4. Ensure consistent opacity for disabled states in all browsers.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
optgroup,
|
||||
textarea,
|
||||
::file-selector-button {
|
||||
font: inherit; /* 1 */
|
||||
font-feature-settings: inherit; /* 1 */
|
||||
font-variation-settings: inherit; /* 1 */
|
||||
letter-spacing: inherit; /* 1 */
|
||||
color: inherit; /* 1 */
|
||||
border-radius: 0; /* 2 */
|
||||
background-color: transparent; /* 3 */
|
||||
opacity: 1; /* 4 */
|
||||
}
|
||||
|
||||
/*
|
||||
Restore default font weight.
|
||||
*/
|
||||
|
||||
:where(select:is([multiple], [size])) optgroup {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/*
|
||||
Restore indentation.
|
||||
*/
|
||||
|
||||
:where(select:is([multiple], [size])) optgroup option {
|
||||
padding-inline-start: 20px;
|
||||
}
|
||||
|
||||
/*
|
||||
Restore space after button.
|
||||
*/
|
||||
|
||||
::file-selector-button {
|
||||
margin-inline-end: 4px;
|
||||
}
|
||||
|
||||
/*
|
||||
Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||
*/
|
||||
|
||||
::placeholder {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Set the default placeholder color to a semi-transparent version of the current text color in browsers that do not
|
||||
crash when using `color-mix(…)` with `currentcolor`. (https://github.com/tailwindlabs/tailwindcss/issues/17194)
|
||||
*/
|
||||
|
||||
@supports (not (-webkit-appearance: -apple-pay-button)) /* Not Safari */ or (contain-intrinsic-size: 1px)
|
||||
/* Safari 17+ */ {
|
||||
::placeholder {
|
||||
color: color-mix(in oklab, currentcolor 50%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent resizing textareas horizontally by default.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Ensure date/time inputs have the same height when empty in iOS Safari.
|
||||
2. Ensure text alignment can be changed on date/time inputs in iOS Safari.
|
||||
*/
|
||||
|
||||
::-webkit-date-and-time-value {
|
||||
min-height: 1lh; /* 1 */
|
||||
text-align: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent height from changing on date/time inputs in macOS Safari when the input is set to `display: block`.
|
||||
*/
|
||||
|
||||
::-webkit-datetime-edit {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove excess padding from pseudo-elements in date/time inputs to ensure consistent height across browsers.
|
||||
*/
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit,
|
||||
::-webkit-datetime-edit-year-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-minute-field,
|
||||
::-webkit-datetime-edit-second-field,
|
||||
::-webkit-datetime-edit-millisecond-field,
|
||||
::-webkit-datetime-edit-meridiem-field {
|
||||
padding-block: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Center dropdown marker shown on inputs with paired `<datalist>`s in Chrome. (https://github.com/tailwindlabs/tailwindcss/issues/18499)
|
||||
*/
|
||||
|
||||
::-webkit-calendar-picker-indicator {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||
*/
|
||||
|
||||
:-moz-ui-invalid {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/*
|
||||
Correct the inability to style the border radius in iOS Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input:where([type="button"], [type="reset"], [type="submit"]),
|
||||
::file-selector-button {
|
||||
appearance: button;
|
||||
}
|
||||
|
||||
/*
|
||||
Correct the cursor style of increment and decrement buttons in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-inner-spin-button,
|
||||
::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Make elements with the HTML hidden attribute stay hidden by default.
|
||||
*/
|
||||
|
||||
[hidden]:where(:not([hidden="until-found"])) {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -0,0 +1,496 @@
|
||||
:root {
|
||||
--black: #000000;
|
||||
--white: #ffffff;
|
||||
--grey-dark-1: #ffffff;
|
||||
--grey-dark-2: #ffffff;
|
||||
--grey-dark-3: #ffffff;
|
||||
--grey-dark-4: #ffffff;
|
||||
--grey-dark-5: #ffffff;
|
||||
--grey-dark-6: #ffffff;
|
||||
--grey-dark-7: #ffffff;
|
||||
--grey-dark-8: #ffffff;
|
||||
--grey-dark-9: #ffffff;
|
||||
--grey-dark-10: #ffffff;
|
||||
--grey-dark-11: #ffffff;
|
||||
--grey-dark-12: #ffffff;
|
||||
--grey-light-1: #ffffff;
|
||||
--grey-dark-alpha-1: #ffffff;
|
||||
--grey-dark-alpha-2: #ffffff;
|
||||
--grey-dark-alpha-3: #ffffff;
|
||||
--grey-dark-alpha-4: #ffffff;
|
||||
--grey-dark-alpha-5: #ffffff;
|
||||
--grey-dark-alpha-6: #ffffff;
|
||||
--grey-dark-alpha-7: #ffffff;
|
||||
--grey-dark-alpha-8: #ffffff;
|
||||
--grey-dark-alpha-9: #ffffff;
|
||||
--grey-dark-alpha-10: #ffffff;
|
||||
--grey-dark-alpha-11: #ffffff;
|
||||
--grey-dark-alpha-12: #ffffff;
|
||||
--smoke-dark-1: #131010;
|
||||
--grey-light-2: #ffffff;
|
||||
--grey-light-3: #ffffff;
|
||||
--grey-light-4: #ffffff;
|
||||
--grey-light-5: #ffffff;
|
||||
--grey-light-6: #ffffff;
|
||||
--grey-light-7: #ffffff;
|
||||
--grey-light-8: #ffffff;
|
||||
--grey-light-9: #ffffff;
|
||||
--grey-light-10: #ffffff;
|
||||
--grey-light-11: #ffffff;
|
||||
--grey-light-12: #ffffff;
|
||||
--smoke-dark-2: #1b1818;
|
||||
--smoke-dark-3: #252121;
|
||||
--smoke-dark-4: #2d2828;
|
||||
--smoke-dark-5: #343030;
|
||||
--smoke-dark-6: #3e3939;
|
||||
--smoke-dark-7: #4b4646;
|
||||
--smoke-dark-8: #645f5f;
|
||||
--smoke-dark-9: #716c6b;
|
||||
--smoke-dark-10: #7f7979;
|
||||
--smoke-dark-11: #b7b1b1;
|
||||
--smoke-dark-12: #f1ecec;
|
||||
--smoke-light-1: #fdfcfc;
|
||||
--smoke-light-2: #f9f8f8;
|
||||
--smoke-light-3: #f1f0f0;
|
||||
--smoke-light-4: #e9e8e8;
|
||||
--smoke-light-5: #e2e0e0;
|
||||
--smoke-light-6: #dad9d9;
|
||||
--smoke-light-7: #cfcecd;
|
||||
--smoke-light-8: #bcbbbb;
|
||||
--smoke-light-9: #8e8b8b;
|
||||
--smoke-light-10: #848181;
|
||||
--smoke-light-11: #656363;
|
||||
--smoke-light-12: #211e1e;
|
||||
--smoke-dark-alpha-1: #bb000003;
|
||||
--smoke-dark-alpha-2: #f9b4b40b;
|
||||
--smoke-dark-alpha-3: #f9caca16;
|
||||
--smoke-dark-alpha-4: #ffd5d51e;
|
||||
--smoke-dark-alpha-5: #fce2e226;
|
||||
--smoke-dark-alpha-6: #fce2e231;
|
||||
--smoke-dark-alpha-7: #fce8e83f;
|
||||
--smoke-dark-alpha-8: #fff1f159;
|
||||
--smoke-dark-alpha-9: #fff3f067;
|
||||
--smoke-dark-alpha-10: #fff2f276;
|
||||
--smoke-dark-alpha-11: #fff7f7b2;
|
||||
--smoke-dark-alpha-12: #fffafaf0;
|
||||
--smoke-light-alpha-1: #55000003;
|
||||
--smoke-light-alpha-2: #25000007;
|
||||
--smoke-light-alpha-3: #1100000f;
|
||||
--smoke-light-alpha-4: #0c000017;
|
||||
--smoke-light-alpha-5: #1100001f;
|
||||
--smoke-light-alpha-6: #07000026;
|
||||
--smoke-light-alpha-7: #0b060032;
|
||||
--smoke-light-alpha-8: #04000044;
|
||||
--smoke-light-alpha-9: #07000074;
|
||||
--smoke-light-alpha-10: #0400009c;
|
||||
--smoke-light-alpha-11: #0700007e;
|
||||
--smoke-light-alpha-12: #020000df;
|
||||
--yuzu-dark-1: #11120c;
|
||||
--yuzu-light-1: #fdfdfb;
|
||||
--yuzu-light-2: #fbfceb;
|
||||
--yuzu-light-3: #f8fac5;
|
||||
--yuzu-light-4: #f2f4a5;
|
||||
--yuzu-light-5: #e9eb9a;
|
||||
--yuzu-light-6: #dcde8e;
|
||||
--yuzu-light-7: #cccd7e;
|
||||
--yuzu-light-8: #b6b768;
|
||||
--yuzu-light-9: #dcde8d;
|
||||
--yuzu-light-10: #d2d384;
|
||||
--yuzu-light-11: #7c7c2c;
|
||||
--yuzu-light-12: #3d3d23;
|
||||
--yuzu-dark-2: #181810;
|
||||
--yuzu-dark-3: #262614;
|
||||
--yuzu-dark-4: #313115;
|
||||
--yuzu-dark-5: #3d3d18;
|
||||
--yuzu-dark-6: #4a4a21;
|
||||
--yuzu-dark-7: #5a5b2c;
|
||||
--yuzu-dark-8: #6f6f36;
|
||||
--yuzu-dark-9: #fdffca;
|
||||
--yuzu-dark-10: #f4f6c1;
|
||||
--yuzu-dark-11: #dbdda0;
|
||||
--yuzu-dark-12: #eff1bd;
|
||||
--yuzu-dark-alpha-1: #11910002;
|
||||
--yuzu-dark-alpha-2: #f1f10008;
|
||||
--yuzu-dark-alpha-3: #fafa3317;
|
||||
--yuzu-dark-alpha-4: #fbfb2f23;
|
||||
--yuzu-dark-alpha-5: #fbfb3730;
|
||||
--yuzu-dark-alpha-6: #fcfc533e;
|
||||
--yuzu-dark-alpha-7: #fafd6750;
|
||||
--yuzu-dark-alpha-8: #ffff6f65;
|
||||
--yuzu-dark-alpha-9: #fdffca;
|
||||
--yuzu-dark-alpha-10: #fcfec7f6;
|
||||
--yuzu-dark-alpha-11: #fdffb8db;
|
||||
--yuzu-dark-alpha-12: #fdffc8f0;
|
||||
--yuzu-light-alpha-1: #80800004;
|
||||
--yuzu-light-alpha-2: #ccd90014;
|
||||
--yuzu-light-alpha-3: #e1ea003a;
|
||||
--yuzu-light-alpha-4: #dbe0015a;
|
||||
--yuzu-light-alpha-5: #c8cd0065;
|
||||
--yuzu-light-alpha-6: #b1b50071;
|
||||
--yuzu-light-alpha-7: #9b9d0081;
|
||||
--yuzu-light-alpha-8: #84860097;
|
||||
--yuzu-light-alpha-9: #b1b60072;
|
||||
--yuzu-light-alpha-10: #a2a4017b;
|
||||
--yuzu-light-alpha-11: #616100d3;
|
||||
--yuzu-light-alpha-12: #1e1e00dc;
|
||||
--cobalt-dark-1: #091120;
|
||||
--cobalt-dark-2: #0d172b;
|
||||
--cobalt-dark-3: #0c2255;
|
||||
--cobalt-dark-4: #0c2a74;
|
||||
--cobalt-dark-5: #113489;
|
||||
--cobalt-dark-6: #18409b;
|
||||
--cobalt-dark-7: #204cb1;
|
||||
--cobalt-dark-8: #2558d0;
|
||||
--cobalt-dark-9: #034cff;
|
||||
--cobalt-dark-10: #0038ee;
|
||||
--cobalt-dark-11: #89b5ff;
|
||||
--cobalt-dark-12: #cde2ff;
|
||||
--cobalt-light-1: #fcfdff;
|
||||
--cobalt-light-2: #f5faff;
|
||||
--cobalt-light-3: #eaf2ff;
|
||||
--cobalt-light-4: #daeaff;
|
||||
--cobalt-light-5: #c8e0ff;
|
||||
--cobalt-light-6: #b4d2ff;
|
||||
--cobalt-light-7: #98bfff;
|
||||
--cobalt-dark-alpha-1: #0011f211;
|
||||
--cobalt-dark-alpha-2: #0048fe1c;
|
||||
--cobalt-dark-alpha-3: #004dff49;
|
||||
--cobalt-dark-alpha-4: #064dfd6b;
|
||||
--cobalt-dark-alpha-5: #1157ff81;
|
||||
--cobalt-dark-alpha-6: #1e62ff94;
|
||||
--cobalt-dark-alpha-7: #2768feac;
|
||||
--cobalt-dark-alpha-8: #2a6affcd;
|
||||
--cobalt-dark-alpha-9: #034cff;
|
||||
--cobalt-dark-alpha-10: #003bffed;
|
||||
--cobalt-dark-alpha-11: #89b5ff;
|
||||
--cobalt-dark-alpha-12: #cde2ff;
|
||||
--cobalt-light-8: #73a4ff;
|
||||
--cobalt-light-9: #034cff;
|
||||
--cobalt-light-10: #0443de;
|
||||
--cobalt-light-11: #1251ec;
|
||||
--cobalt-light-12: #0f2b6c;
|
||||
--cobalt-light-alpha-1: #0055ff03;
|
||||
--cobalt-light-alpha-2: #0080ff0a;
|
||||
--cobalt-light-alpha-3: #0062ff15;
|
||||
--cobalt-light-alpha-4: #006fff25;
|
||||
--cobalt-light-alpha-5: #0070ff37;
|
||||
--cobalt-light-alpha-6: #0167ff4b;
|
||||
--cobalt-light-alpha-7: #0061ff67;
|
||||
--cobalt-light-alpha-8: #005aff8c;
|
||||
--cobalt-light-alpha-9: #004afffc;
|
||||
--cobalt-light-alpha-10: #0040ddfb;
|
||||
--cobalt-light-alpha-11: #0044ebed;
|
||||
--cobalt-light-alpha-12: #001e63f0;
|
||||
--apple-dark-1: #0c140b;
|
||||
--apple-light-1: #fafefa;
|
||||
--apple-light-2: #f4fcf3;
|
||||
--apple-light-3: #e1fade;
|
||||
--apple-light-4: #cef6c9;
|
||||
--apple-light-5: #b9efb3;
|
||||
--apple-light-6: #9fe598;
|
||||
--apple-light-7: #7dd676;
|
||||
--apple-light-8: #43c23b;
|
||||
--apple-light-9: #12c905;
|
||||
--apple-light-10: #00bd00;
|
||||
--apple-light-11: #008600;
|
||||
--apple-light-12: #184115;
|
||||
--apple-dark-2: #121b11;
|
||||
--apple-dark-3: #152d13;
|
||||
--apple-dark-4: #123d0f;
|
||||
--apple-dark-5: #174b14;
|
||||
--apple-dark-6: #1d5b19;
|
||||
--apple-dark-7: #226c1e;
|
||||
--apple-dark-8: #267f20;
|
||||
--apple-dark-9: #12c905;
|
||||
--apple-dark-10: #17bb0d;
|
||||
--apple-dark-11: #37db2e;
|
||||
--apple-dark-12: #aff7a8;
|
||||
--apple-dark-alpha-1: #00d10004;
|
||||
--apple-dark-alpha-2: #29f9120b;
|
||||
--apple-dark-alpha-3: #33ff221e;
|
||||
--apple-dark-alpha-4: #17fb0730;
|
||||
--apple-dark-alpha-5: #2afc1e3f;
|
||||
--apple-dark-alpha-6: #37fd2b50;
|
||||
--apple-dark-alpha-7: #3efe3362;
|
||||
--apple-dark-alpha-8: #3fff3276;
|
||||
--apple-dark-alpha-9: #12fe02c6;
|
||||
--apple-dark-alpha-10: #19fe0cb7;
|
||||
--apple-dark-alpha-11: #3dfe33d9;
|
||||
--apple-dark-alpha-12: #b4feacf7;
|
||||
--apple-light-alpha-1: #00cc0005;
|
||||
--apple-light-alpha-2: #16c0000c;
|
||||
--apple-light-alpha-3: #18d90021;
|
||||
--apple-light-alpha-4: #18d50036;
|
||||
--apple-light-alpha-5: #15ca004c;
|
||||
--apple-light-alpha-6: #12bf0067;
|
||||
--apple-light-alpha-7: #0db30089;
|
||||
--apple-light-alpha-8: #0bb000c4;
|
||||
--apple-light-alpha-9: #0dc800fa;
|
||||
--apple-light-alpha-10: #00bd00;
|
||||
--apple-light-alpha-11: #008600;
|
||||
--apple-light-alpha-12: #033000ea;
|
||||
--ember-dark-1: #170f0d;
|
||||
--ember-dark-2: #201412;
|
||||
--ember-dark-3: #3c140d;
|
||||
--ember-dark-4: #530e05;
|
||||
--ember-dark-5: #631409;
|
||||
--ember-dark-6: #742216;
|
||||
--ember-dark-7: #8d3324;
|
||||
--ember-dark-8: #b64330;
|
||||
--ember-dark-9: #fc533a;
|
||||
--ember-dark-10: #ee462d;
|
||||
--ember-dark-11: #ff917b;
|
||||
--ember-dark-12: #ffd1c8;
|
||||
--ember-light-1: #fffcfb;
|
||||
--ember-light-2: #fff6f3;
|
||||
--ember-light-3: #ffe9e4;
|
||||
--ember-light-4: #ffd7cc;
|
||||
--ember-light-5: #ffc8ba;
|
||||
--ember-light-6: #ffb7a6;
|
||||
--ember-light-7: #ffa392;
|
||||
--ember-light-8: #f68975;
|
||||
--ember-light-9: #fc533a;
|
||||
--ember-light-10: #ef442a;
|
||||
--ember-light-11: #da3319;
|
||||
--ember-light-12: #5c281f;
|
||||
--ember-dark-alpha-1: #ec000007;
|
||||
--ember-dark-alpha-2: #f23e2011;
|
||||
--ember-dark-alpha-3: #fb22002f;
|
||||
--ember-dark-alpha-4: #ff070047;
|
||||
--ember-dark-alpha-5: #ff1a0058;
|
||||
--ember-dark-alpha-6: #fd3a1d6b;
|
||||
--ember-dark-alpha-7: #ff533685;
|
||||
--ember-dark-alpha-8: #ff5a3eb1;
|
||||
--ember-dark-alpha-9: #ff553bfc;
|
||||
--ember-dark-alpha-10: #ff4a2fed;
|
||||
--ember-dark-alpha-11: #ff917b;
|
||||
--ember-dark-alpha-12: #ffd1c8;
|
||||
--ember-light-alpha-1: #ff400004;
|
||||
--ember-light-alpha-2: #ff40000c;
|
||||
--ember-light-alpha-3: #ff30001b;
|
||||
--ember-light-alpha-4: #ff370033;
|
||||
--ember-light-alpha-5: #ff340045;
|
||||
--ember-light-alpha-6: #ff310059;
|
||||
--ember-light-alpha-7: #ff28006d;
|
||||
--ember-light-alpha-8: #ef25008a;
|
||||
--ember-light-alpha-9: #fb2200c5;
|
||||
--ember-light-alpha-10: #ec1f00d5;
|
||||
--ember-light-alpha-11: #d61d00e6;
|
||||
--ember-light-alpha-12: #460a00e0;
|
||||
--solaris-dark-1: #13110b;
|
||||
--solaris-dark-2: #1b180f;
|
||||
--solaris-dark-3: #2a2307;
|
||||
--solaris-dark-4: #382b00;
|
||||
--solaris-dark-5: #443500;
|
||||
--solaris-dark-6: #514307;
|
||||
--solaris-dark-7: #64551a;
|
||||
--solaris-dark-8: #7f6c25;
|
||||
--solaris-dark-9: #fcd53a;
|
||||
--solaris-dark-10: #f2cb2a;
|
||||
--solaris-dark-11: #fdd63c;
|
||||
--solaris-dark-12: #faebb5;
|
||||
--solaris-light-1: #fefdfa;
|
||||
--solaris-light-2: #fffbea;
|
||||
--solaris-light-3: #fff6be;
|
||||
--solaris-light-4: #ffee9c;
|
||||
--solaris-light-5: #ffe47c;
|
||||
--solaris-light-6: #f2d775;
|
||||
--solaris-light-7: #e0c76f;
|
||||
--solaris-light-8: #cdb047;
|
||||
--solaris-light-9: #ffdc17;
|
||||
--solaris-light-10: #fad337;
|
||||
--solaris-light-11: #917500;
|
||||
--solaris-light-12: #433c22;
|
||||
--solaris-dark-alpha-1: #bb110003;
|
||||
--solaris-dark-alpha-2: #f9b4000b;
|
||||
--solaris-dark-alpha-3: #febb001b;
|
||||
--solaris-dark-alpha-4: #feaf002a;
|
||||
--solaris-dark-alpha-5: #feb80037;
|
||||
--solaris-dark-alpha-6: #feca0045;
|
||||
--solaris-dark-alpha-7: #ffd42b59;
|
||||
--solaris-dark-alpha-8: #ffd63d76;
|
||||
--solaris-dark-alpha-9: #ffd83bfc;
|
||||
--solaris-dark-alpha-10: #fed52bf2;
|
||||
--solaris-dark-alpha-11: #ffd83cfd;
|
||||
--solaris-dark-alpha-12: #fff0b9fa;
|
||||
--solaris-light-alpha-1: #cc990005;
|
||||
--solaris-light-alpha-2: #ffcf0015;
|
||||
--solaris-light-alpha-3: #ffdc0041;
|
||||
--solaris-light-alpha-4: #ffd40063;
|
||||
--solaris-light-alpha-5: #ffcb0083;
|
||||
--solaris-light-alpha-6: #e7b6008a;
|
||||
--solaris-light-alpha-7: #c89c0090;
|
||||
--solaris-light-alpha-8: #ba9200b8;
|
||||
--solaris-light-alpha-9: #ffd900e8;
|
||||
--solaris-light-alpha-10: #f9c700c8;
|
||||
--solaris-light-alpha-11: #917500;
|
||||
--solaris-light-alpha-12: #261e00dd;
|
||||
--lilac-dark-1: #140f14;
|
||||
--lilac-dark-2: #1d141d;
|
||||
--lilac-dark-3: #2f1e31;
|
||||
--lilac-dark-4: #3e2440;
|
||||
--lilac-dark-5: #4a2c4c;
|
||||
--lilac-dark-6: #573859;
|
||||
--lilac-dark-7: #6c486e;
|
||||
--lilac-dark-8: #8a5e8d;
|
||||
--lilac-dark-9: #edb2f1;
|
||||
--lilac-dark-10: #e2a8e6;
|
||||
--lilac-dark-11: #dca2e0;
|
||||
--lilac-dark-12: #edd8ef;
|
||||
--lilac-light-1: #fffcff;
|
||||
--lilac-light-2: #fdf7fe;
|
||||
--lilac-light-3: #fceafd;
|
||||
--lilac-light-4: #faddfb;
|
||||
--lilac-light-5: #f5cff7;
|
||||
--lilac-light-6: #eebff1;
|
||||
--lilac-light-7: #e3a9e7;
|
||||
--lilac-light-8: #d78bdd;
|
||||
--lilac-light-9: #a753ae;
|
||||
--lilac-light-10: #9946a0;
|
||||
--lilac-light-11: #95429c;
|
||||
--lilac-light-12: #590b60;
|
||||
--lilac-dark-alpha-1: #d100d104;
|
||||
--lilac-dark-alpha-2: #fd4cfd0d;
|
||||
--lilac-dark-alpha-3: #ec70fb23;
|
||||
--lilac-dark-alpha-4: #f270fc33;
|
||||
--lilac-dark-alpha-5: #f57dfd40;
|
||||
--lilac-dark-alpha-6: #f691fd4e;
|
||||
--lilac-dark-alpha-7: #fa9eff64;
|
||||
--lilac-dark-alpha-8: #f9a5ff85;
|
||||
--lilac-dark-alpha-9: #fbbcfff0;
|
||||
--lilac-dark-alpha-10: #f9b9fee5;
|
||||
--lilac-dark-alpha-11: #fab8ffde;
|
||||
--lilac-dark-alpha-12: #fde6ffee;
|
||||
--lilac-light-alpha-1: #ff00ff03;
|
||||
--lilac-light-alpha-2: #c000e008;
|
||||
--lilac-light-alpha-3: #db00e715;
|
||||
--lilac-light-alpha-4: #da00e122;
|
||||
--lilac-light-alpha-5: #ca00d530;
|
||||
--lilac-light-alpha-6: #bc00c840;
|
||||
--lilac-light-alpha-7: #ac00b856;
|
||||
--lilac-light-alpha-8: #a800b574;
|
||||
--lilac-light-alpha-9: #7d0087ac;
|
||||
--lilac-light-alpha-10: #73007cb9;
|
||||
--lilac-light-alpha-11: #70007abd;
|
||||
--lilac-light-alpha-12: #520059f4;
|
||||
--coral-dark-1: #160f0e;
|
||||
--coral-light-1: #fffcfc;
|
||||
--coral-light-2: #fff8f7;
|
||||
--coral-light-3: #ffebe8;
|
||||
--coral-light-4: #ffdbd5;
|
||||
--coral-light-5: #ffcdc5;
|
||||
--coral-light-6: #f9beb5;
|
||||
--coral-light-7: #e9aea6;
|
||||
--coral-light-8: #d49b93;
|
||||
--coral-light-9: #af7871;
|
||||
--coral-light-10: #a26c65;
|
||||
--coral-light-11: #9c665f;
|
||||
--coral-light-12: #592a24;
|
||||
--coral-dark-2: #1f1413;
|
||||
--coral-dark-3: #391613;
|
||||
--coral-dark-4: #481b17;
|
||||
--coral-dark-5: #542621;
|
||||
--coral-dark-6: #63332d;
|
||||
--coral-dark-7: #77453f;
|
||||
--coral-dark-8: #935e57;
|
||||
--coral-dark-9: #ffd6d0;
|
||||
--coral-dark-10: #f5ccc6;
|
||||
--coral-dark-11: #e2a8a0;
|
||||
--coral-dark-12: #fcd3cd;
|
||||
--coral-dark-alpha-1: #e6000006;
|
||||
--coral-dark-alpha-2: #ff44330f;
|
||||
--coral-dark-alpha-3: #ff2f1d2b;
|
||||
--coral-dark-alpha-4: #ff3d2b3b;
|
||||
--coral-dark-alpha-5: #ff5c4a48;
|
||||
--coral-dark-alpha-6: #ff746358;
|
||||
--coral-dark-alpha-7: #fd897c6e;
|
||||
--coral-dark-alpha-8: #fe9d908c;
|
||||
--coral-dark-alpha-9: #ffd6d0;
|
||||
--coral-dark-alpha-10: #fed3cdf5;
|
||||
--coral-dark-alpha-11: #ffbdb4e0;
|
||||
--coral-dark-alpha-12: #ffd6cffc;
|
||||
--coral-light-alpha-1: #ff000003;
|
||||
--coral-light-alpha-2: #ff200008;
|
||||
--coral-light-alpha-3: #ff220017;
|
||||
--coral-light-alpha-4: #ff25002a;
|
||||
--coral-light-alpha-5: #ff24003a;
|
||||
--coral-light-alpha-6: #eb20014a;
|
||||
--coral-light-alpha-7: #c0170059;
|
||||
--coral-light-alpha-8: #9a13006c;
|
||||
--coral-light-alpha-9: #700d008e;
|
||||
--coral-light-alpha-10: #650c009a;
|
||||
--coral-light-alpha-11: #620b00a0;
|
||||
--coral-light-alpha-12: #3e0700db;
|
||||
--mint-dark-alpha-1: #00bb0003;
|
||||
--mint-dark-alpha-2: #2bf72b0a;
|
||||
--mint-dark-alpha-3: #66fe5d1b;
|
||||
--mint-dark-alpha-4: #63ff5d2c;
|
||||
--mint-dark-alpha-5: #6cff643b;
|
||||
--mint-dark-alpha-6: #71ff6a4b;
|
||||
--mint-dark-alpha-7: #74fd6f5d;
|
||||
--mint-dark-alpha-8: #74ff6f72;
|
||||
--mint-dark-alpha-9: #c8ffc4f5;
|
||||
--mint-dark-alpha-10: #c6fec2f5;
|
||||
--mint-dark-alpha-11: #b4ffafdc;
|
||||
--mint-dark-alpha-12: #c7ffc3fb;
|
||||
--mint-dark-1: #0d130c;
|
||||
--mint-dark-2: #121a12;
|
||||
--mint-dark-3: #1a2a19;
|
||||
--mint-dark-4: #1f3a1e;
|
||||
--mint-dark-5: #264824;
|
||||
--mint-dark-6: #2d572b;
|
||||
--mint-dark-7: #356733;
|
||||
--mint-dark-8: #3d7b3b;
|
||||
--mint-dark-9: #c8ffc4;
|
||||
--mint-dark-10: #bff5bb;
|
||||
--mint-dark-11: #9dde99;
|
||||
--mint-dark-12: #c4fbc0;
|
||||
--mint-light-1: #fafefa;
|
||||
--mint-light-2: #f4fcf3;
|
||||
--mint-light-3: #dbfdd8;
|
||||
--mint-light-4: #c3fabf;
|
||||
--mint-light-5: #adf2a8;
|
||||
--mint-light-6: #96e692;
|
||||
--mint-light-7: #81d47d;
|
||||
--mint-light-8: #6abc67;
|
||||
--mint-light-9: #9ff29a;
|
||||
--mint-light-10: #98e793;
|
||||
--mint-light-11: #318430;
|
||||
--mint-light-12: #1f461d;
|
||||
--mint-dark-alpha-1: #00bb0003;
|
||||
--mint-dark-alpha-2: #2bf72b09;
|
||||
--mint-dark-alpha-3: #66fe5d1b;
|
||||
--mint-dark-alpha-4: #63ff5d2b;
|
||||
--mint-dark-alpha-5: #6cff643b;
|
||||
--mint-dark-alpha-6: #71ff6a4a;
|
||||
--mint-dark-alpha-7: #74fd6f5c;
|
||||
--mint-dark-alpha-8: #74ff6f72;
|
||||
--mint-dark-alpha-9: #c8ffc4f5;
|
||||
--mint-dark-alpha-10: #c6fec2f5;
|
||||
--mint-dark-alpha-11: #b4ffafdb;
|
||||
--mint-dark-alpha-12: #c7ffc3fa;
|
||||
--mint-light-alpha-1: #00cc0005;
|
||||
--mint-light-alpha-2: #16c0000c;
|
||||
--mint-light-alpha-3: #14f20027;
|
||||
--mint-light-alpha-4: #10ec0040;
|
||||
--mint-light-alpha-5: #0fd90057;
|
||||
--mint-light-alpha-6: #0ac5006d;
|
||||
--mint-light-alpha-7: #08ab0082;
|
||||
--mint-light-alpha-8: #058f0098;
|
||||
--mint-light-alpha-9: #0ddf0065;
|
||||
--mint-light-alpha-10: #0cc7006c;
|
||||
--mint-light-alpha-11: #016800cf;
|
||||
--mint-light-alpha-12: #022e00e2;
|
||||
--grey-light-alpha-1: #ffffff;
|
||||
--grey-light-alpha-2: #ffffff;
|
||||
--grey-light-alpha-3: #ffffff;
|
||||
--grey-light-alpha-4: #ffffff;
|
||||
--grey-light-alpha-5: #ffffff;
|
||||
--grey-light-alpha-6: #ffffff;
|
||||
--grey-light-alpha-7: #ffffff;
|
||||
--grey-light-alpha-8: #ffffff;
|
||||
--grey-light-alpha-9: #ffffff;
|
||||
--grey-light-alpha-10: #ffffff;
|
||||
--grey-light-alpha-11: #ffffff;
|
||||
--grey-light-alpha-12: #ffffff;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
[data-component="button"] {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: var(--radius-md);
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--text-base--line-height);
|
||||
font-weight: var(--font-weight-normal);
|
||||
text-decoration: none;
|
||||
user-select: none;
|
||||
gap: calc(var(--spacing) * 2);
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&[data-variant="primary"] {
|
||||
border-color: var(--border-default-border);
|
||||
background-color: var(--surface-brand-surface-brand);
|
||||
color: var(--text-on-brand-text-strong-on-brand);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: var(--border-default-border-hover);
|
||||
background-color: var(--surface-brand-surface-brand-hover);
|
||||
}
|
||||
&:active:not(:disabled) {
|
||||
border-color: var(--border-default-border-active);
|
||||
background-color: var(--surface-brand-surface-brand-active);
|
||||
}
|
||||
&:focus:not(:disabled) {
|
||||
border-color: var(--border-default-border-focus);
|
||||
background-color: var(--surface-brand-surface-brand-focus);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-variant="secondary"] {
|
||||
border-color: var(--border-default-border);
|
||||
background-color: var(--surface-default-surface);
|
||||
color: var(--text-default-text);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: var(--border-default-border-hover);
|
||||
background-color: var(--surface-default-surface-hover);
|
||||
}
|
||||
&:active:not(:disabled) {
|
||||
border-color: var(--border-default-border-active);
|
||||
background-color: var(--surface-default-surface-active);
|
||||
}
|
||||
&:focus:not(:disabled) {
|
||||
border-color: var(--border-default-border-focus);
|
||||
background-color: var(--surface-default-surface-focus);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-variant="ghost"] {
|
||||
border-color: transparent;
|
||||
background-color: transparent;
|
||||
color: var(--text-default-text);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: var(--surface-default-surface-hover);
|
||||
}
|
||||
&:active:not(:disabled) {
|
||||
border-color: var(--border-default-border-active);
|
||||
background-color: var(--surface-default-surface-active);
|
||||
}
|
||||
&:focus:not(:disabled) {
|
||||
border-color: var(--border-default-border-focus);
|
||||
background-color: var(--surface-default-surface-focus);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-size="normal"] {
|
||||
padding: calc(var(--spacing) * 0.5) calc(var(--spacing) * 3);
|
||||
font-size: var(--text-xs);
|
||||
line-height: var(--text-sm--line-height);
|
||||
}
|
||||
|
||||
&[data-size="large"] {
|
||||
padding: calc(var(--spacing) * 1) calc(var(--spacing) * 4);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--text-sm--line-height);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
[data-component="icon"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
[data-component="select"] {
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px var(--color-primary);
|
||||
}
|
||||
|
||||
[data-slot="section"] {
|
||||
font-size: var(--text-xs);
|
||||
line-height: var(--text-xs--line-height);
|
||||
font-weight: var(--font-weight-light);
|
||||
text-transform: uppercase;
|
||||
color: var(--text-default-text-weak);
|
||||
opacity: 0.6;
|
||||
margin-top: calc(var(--spacing) * 3);
|
||||
margin-left: calc(var(--spacing) * 2);
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="item"] {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: calc(var(--spacing) * 2) calc(var(--spacing) * 2);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--text-xs);
|
||||
line-height: var(--text-xs--line-height);
|
||||
color: var(--text-default-text);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 0.2s ease-in-out,
|
||||
color 0.2s ease-in-out;
|
||||
outline: none;
|
||||
user-select: none;
|
||||
|
||||
&[data-highlighted] {
|
||||
background-color: var(--surface-default-surface);
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
[data-slot="item-indicator"] {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="trigger"] {
|
||||
[data-slot="value"] {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
[data-slot="icon"] {
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
flex-shrink: 0;
|
||||
color: var(--text-default-text-weak);
|
||||
transition: transform 0.1s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="select-content"] {
|
||||
min-width: 8rem;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-md);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: var(--border-default-border-weak);
|
||||
background-color: var(--surface-raised-surface-raised);
|
||||
padding: calc(var(--spacing) * 1);
|
||||
box-shadow: var(--shadow-md);
|
||||
z-index: 50;
|
||||
|
||||
&[data-closed] {
|
||||
animation: select-close 0.15s ease-out;
|
||||
}
|
||||
|
||||
&[data-expanded] {
|
||||
animation: select-open 0.15s ease-out;
|
||||
}
|
||||
|
||||
[data-slot="list"] {
|
||||
overflow-y: auto;
|
||||
max-height: 12rem;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes select-open {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes select-close {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
[data-component="tabs"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
& [data-slot="list"] {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--surface-default-surface);
|
||||
overflow-x: auto;
|
||||
|
||||
/* Hide scrollbar */
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Divider between tabs */
|
||||
& > [data-slot="trigger"]:not(:first-child) {
|
||||
border-left: 1px solid var(--border-default-border-weak);
|
||||
}
|
||||
|
||||
/* After element to fill remaining space */
|
||||
&::after {
|
||||
content: "";
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
height: calc(var(--spacing) * 8);
|
||||
border-left: 1px solid var(--border-default-border-weak);
|
||||
border-bottom: 1px solid var(--border-default-border-weak);
|
||||
}
|
||||
|
||||
&:empty::after {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
& [data-slot="trigger"] {
|
||||
position: relative;
|
||||
padding: 0 calc(var(--spacing) * 3);
|
||||
height: calc(var(--spacing) * 8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--text-default-text-weak);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid var(--border-default-border-weak);
|
||||
background-color: transparent;
|
||||
transition:
|
||||
background-color 0.15s ease,
|
||||
color 0.15s ease;
|
||||
|
||||
&:disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px var(--border-default-border-focus);
|
||||
}
|
||||
|
||||
&[data-selected] {
|
||||
color: var(--text-default-text);
|
||||
background-color: var(--surface-panel-surface);
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled):not([data-selected]) {
|
||||
color: var(--text-default-text);
|
||||
}
|
||||
}
|
||||
|
||||
& [data-slot="content"] {
|
||||
background-color: var(--surface-panel-surface);
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
|
||||
/* Hide scrollbar */
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px var(--border-default-border-focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
@layer theme, base, components, utilities;
|
||||
|
||||
@import "./color.css" layer(theme);
|
||||
@import "./theme.css" layer(theme);
|
||||
|
||||
@import "./base.css" layer(base);
|
||||
|
||||
@import "./components/button.css" layer(components);
|
||||
@import "./components/icon.css" layer(components);
|
||||
@import "./components/select.css" layer(components);
|
||||
@import "./components/tabs.css" layer(components);
|
||||
|
||||
@import "./utilities.css" layer(utilities);
|
||||
@@ -0,0 +1,536 @@
|
||||
:root {
|
||||
--font-sans:
|
||||
ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
|
||||
--spacing: 0.25rem;
|
||||
|
||||
--breakpoint-sm: 40rem;
|
||||
--breakpoint-md: 48rem;
|
||||
--breakpoint-lg: 64rem;
|
||||
--breakpoint-xl: 80rem;
|
||||
--breakpoint-2xl: 96rem;
|
||||
|
||||
--container-3xs: 16rem;
|
||||
--container-2xs: 18rem;
|
||||
--container-xs: 20rem;
|
||||
--container-sm: 24rem;
|
||||
--container-md: 28rem;
|
||||
--container-lg: 32rem;
|
||||
--container-xl: 36rem;
|
||||
--container-2xl: 42rem;
|
||||
--container-3xl: 48rem;
|
||||
--container-4xl: 56rem;
|
||||
--container-5xl: 64rem;
|
||||
--container-6xl: 72rem;
|
||||
--container-7xl: 80rem;
|
||||
|
||||
--text-xs: 0.75rem;
|
||||
--text-xs--line-height: calc(1 / 0.75);
|
||||
--text-sm: 0.875rem;
|
||||
--text-sm--line-height: calc(1.25 / 0.875);
|
||||
--text-base: 1rem;
|
||||
--text-base--line-height: calc(1.5 / 1);
|
||||
--text-lg: 1.125rem;
|
||||
--text-lg--line-height: calc(1.75 / 1.125);
|
||||
--text-xl: 1.25rem;
|
||||
--text-xl--line-height: calc(1.75 / 1.25);
|
||||
--text-2xl: 1.5rem;
|
||||
--text-2xl--line-height: calc(2 / 1.5);
|
||||
--text-3xl: 1.875rem;
|
||||
--text-3xl--line-height: calc(2.25 / 1.875);
|
||||
--text-4xl: 2.25rem;
|
||||
--text-4xl--line-height: calc(2.5 / 2.25);
|
||||
--text-5xl: 3rem;
|
||||
--text-5xl--line-height: 1;
|
||||
--text-6xl: 3.75rem;
|
||||
--text-6xl--line-height: 1;
|
||||
--text-7xl: 4.5rem;
|
||||
--text-7xl--line-height: 1;
|
||||
--text-8xl: 6rem;
|
||||
--text-8xl--line-height: 1;
|
||||
--text-9xl: 8rem;
|
||||
--text-9xl--line-height: 1;
|
||||
|
||||
--font-weight-thin: 100;
|
||||
--font-weight-extralight: 200;
|
||||
--font-weight-light: 300;
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
--font-weight-extrabold: 800;
|
||||
--font-weight-black: 900;
|
||||
|
||||
--tracking-tighter: -0.05em;
|
||||
--tracking-tight: -0.025em;
|
||||
--tracking-normal: 0em;
|
||||
--tracking-wide: 0.025em;
|
||||
--tracking-wider: 0.05em;
|
||||
--tracking-widest: 0.1em;
|
||||
|
||||
--leading-tight: 1.25;
|
||||
--leading-snug: 1.375;
|
||||
--leading-normal: 1.5;
|
||||
--leading-relaxed: 1.625;
|
||||
--leading-loose: 2;
|
||||
|
||||
--radius-xs: 0.125rem;
|
||||
--radius-sm: 0.25rem;
|
||||
--radius-md: 0.375rem;
|
||||
--radius-lg: 0.5rem;
|
||||
--radius-xl: 0.75rem;
|
||||
--radius-2xl: 1rem;
|
||||
--radius-3xl: 1.5rem;
|
||||
--radius-4xl: 2rem;
|
||||
|
||||
--shadow-2xs: 0 1px rgb(0 0 0 / 0.05);
|
||||
--shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
||||
|
||||
--inset-shadow-2xs: inset 0 1px rgb(0 0 0 / 0.05);
|
||||
--inset-shadow-xs: inset 0 1px 1px rgb(0 0 0 / 0.05);
|
||||
--inset-shadow-sm: inset 0 2px 4px rgb(0 0 0 / 0.05);
|
||||
|
||||
--drop-shadow-xs: 0 1px 1px rgb(0 0 0 / 0.05);
|
||||
--drop-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.15);
|
||||
--drop-shadow-md: 0 3px 3px rgb(0 0 0 / 0.12);
|
||||
--drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15);
|
||||
--drop-shadow-xl: 0 9px 7px rgb(0 0 0 / 0.1);
|
||||
--drop-shadow-2xl: 0 25px 25px rgb(0 0 0 / 0.15);
|
||||
|
||||
--text-shadow-2xs: 0px 1px 0px rgb(0 0 0 / 0.15);
|
||||
--text-shadow-xs: 0px 1px 1px rgb(0 0 0 / 0.2);
|
||||
--text-shadow-sm: 0px 1px 0px rgb(0 0 0 / 0.075), 0px 1px 1px rgb(0 0 0 / 0.075), 0px 2px 2px rgb(0 0 0 / 0.075);
|
||||
--text-shadow-md: 0px 1px 1px rgb(0 0 0 / 0.1), 0px 1px 2px rgb(0 0 0 / 0.1), 0px 2px 4px rgb(0 0 0 / 0.1);
|
||||
--text-shadow-lg: 0px 1px 2px rgb(0 0 0 / 0.1), 0px 3px 2px rgb(0 0 0 / 0.1), 0px 4px 8px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
:root {
|
||||
/* OC-1-Light */
|
||||
color-scheme: light;
|
||||
--background-background: var(--smoke-light-1);
|
||||
--background-background-weak: #f7f6f6;
|
||||
--background-background-weaker: #fcfcfc;
|
||||
--surface-default-surface: var(--smoke-light-2);
|
||||
--input-input: var(--smoke-light-1);
|
||||
--input-input-hover: var(--smoke-light-2);
|
||||
--input-input-active: var(--cobalt-light-1);
|
||||
--input-input-selected: var(--cobalt-light-4);
|
||||
--input-input-focus: var(--cobalt-light-1);
|
||||
--input-input-disabled: var(--smoke-light-4);
|
||||
--surface-default-surface-hover: var(--smoke-light-4);
|
||||
--surface-default-surface-active: var(--smoke-light-5);
|
||||
--surface-default-surface-selected: var(--cobalt-light-3);
|
||||
--surface-default-surface-disabled: var(--smoke-light-2);
|
||||
--surface-default-surface-focus: var(--cobalt-light-3);
|
||||
--surface-inset-surface-inset: var(--smoke-light-3);
|
||||
--surface-inset-surface-inset-hover: var(--smoke-light-4);
|
||||
--surface-inset-surface-inset-active: var(--smoke-light-6);
|
||||
--surface-inset-surface-inset-selected: var(--cobalt-light-4);
|
||||
--surface-inset-surface-inset-disabled: var(--smoke-light-3);
|
||||
--surface-inset-surface-inset-focus: var(--cobalt-light-4);
|
||||
--surface-raised-surface-raised: var(--white);
|
||||
--surface-raised-surface-raised-hover: var(--smoke-light-3);
|
||||
--surface-raised-surface-raised-active: var(--smoke-light-4);
|
||||
--surface-raised-surface-raised-selected: var(--cobalt-light-3);
|
||||
--surface-raised-surface-raised-disabled: var(--smoke-light-2);
|
||||
--surface-raised-surface-raised-focus: var(--cobalt-light-3);
|
||||
--surface-overlay-surface-overlay: var(--smoke-light-alpha-9);
|
||||
--surface-brand-surface-brand: var(--yuzu-light-9);
|
||||
--surface-brand-surface-brand-hover: var(--yuzu-light-10);
|
||||
--surface-interactive-surface-interactive: var(--cobalt-light-3);
|
||||
--surface-interactive-surface-interactive-hover: var(--cobalt-light-4);
|
||||
--surface-interactive-surface-interactive-weak: var(--cobalt-light-2);
|
||||
--surface-interactive-surface-interactive-weak-hover: var(--cobalt-light-3);
|
||||
--surface-success-surface-success: var(--apple-light-3);
|
||||
--surface-success-surface-success-weak: var(--apple-light-2);
|
||||
--surface-success-surface-success-strong: var(--apple-light-9);
|
||||
--surface-warning-surface-warning: var(--solaris-light-3);
|
||||
--surface-warning-surface-warning-weak: var(--solaris-light-2);
|
||||
--surface-warning-surface-warning-strong: var(--solaris-light-9);
|
||||
--surface-critical-surface-critical: var(--ember-light-3);
|
||||
--surface-critical-surface-critical-weak: var(--ember-light-2);
|
||||
--surface-critical-surface-critical-strong: var(--ember-light-9);
|
||||
--surface-info-surface-info: var(--lilac-light-3);
|
||||
--surface-info-surface-info-weak: var(--lilac-light-2);
|
||||
--surface-info-surface-info-strong: var(--lilac-light-9);
|
||||
--surface-diff-add-surface-diff-add: var(--coral-light-3);
|
||||
--surface-diff-add-surface-diff-add-weak: var(--coral-light-2);
|
||||
--surface-diff-add-surface-diff-add-weaker: var(--coral-light-1);
|
||||
--surface-diff-add-surface-diff-add-strong: var(--coral-light-9);
|
||||
--surface-diff-add-surface-diff-add-stronger: var(--coral-light-12);
|
||||
--surface-diff-delete-surface-diff-delete: var(--mint-light-3);
|
||||
--surface-on-background-weak-default-surface-on-background-weak: var(--smoke-light-1);
|
||||
--surface-on-background-weak-inset-surface-inset-on-background-weak: var(--smoke-light-1);
|
||||
--surface-on-background-weak-default-surface-on-background-weak-hover: var(--smoke-light-1);
|
||||
--surface-on-background-weak-default-surface-on-background-weak-active: var(--smoke-light-1);
|
||||
--surface-on-background-weak-default-surface-on-background-weak-selected: var(--smoke-light-1);
|
||||
--surface-on-background-weak-default-surface-on-background-weak-disabled: var(--smoke-light-1);
|
||||
--surface-on-background-weak-default-surface-on-background-weak-focus: var(--smoke-light-1);
|
||||
--surface-on-background-weak-inset-surface-inset-on-background-weak-hover: var(--smoke-light-1);
|
||||
--surface-on-background-weak-inset-surface-inset-on-background-weak-active: var(--smoke-light-1);
|
||||
--surface-on-background-weak-inset-surface-inset-on-background-weak-selected: var(--smoke-light-1);
|
||||
--surface-on-background-weak-inset-surface-inset-on-background-weak-disabled: var(--smoke-light-1);
|
||||
--surface-on-background-weak-inset-surface-inset-on-background-weak-focus: var(--smoke-light-1);
|
||||
--surface-diff-delete-surface-diff-delete-weak: var(--mint-light-2);
|
||||
--surface-diff-delete-surface-diff-delete-weaker: var(--mint-light-1);
|
||||
--surface-diff-delete-surface-diff-delete-strong: var(--mint-light-9);
|
||||
--surface-diff-delete-surface-diff-delete-stronger: var(--mint-light-11);
|
||||
--text-default-text: var(--smoke-light-11);
|
||||
--text-default-text-weak: var(--smoke-light-9);
|
||||
--text-default-text-weaker: var(--smoke-light-8);
|
||||
--text-default-text-strong: var(--smoke-light-12);
|
||||
--text-on-brand-text-on-brand: var(--smoke-light-alpha-11);
|
||||
--text-on-interactive-text-on-interactive: var(--smoke-light-alpha-11);
|
||||
--text-on-success-text-on-success: var(--smoke-dark-alpha-11);
|
||||
--text-on-warning-text-on-warning: var(--smoke-dark-alpha-11);
|
||||
--text-on-info-text-on-info: var(--smoke-dark-alpha-11);
|
||||
--text-on-diff-add-text-on-diff-add: var(--smoke-dark-alpha-11);
|
||||
--text-on-diff-delete-text-on-diff-delete: var(--smoke-dark-alpha-11);
|
||||
--text-on-diff-delete-text-weak-on-diff-delete: var(--smoke-dark-alpha-9);
|
||||
--text-on-diff-delete-text-strong-on-diff-delete: var(--smoke-dark-alpha-12);
|
||||
--text-on-diff-add-text-weak-on-diff-add: var(--smoke-dark-alpha-9);
|
||||
--text-on-diff-add-text-strong-on-diff-add: var(--smoke-dark-alpha-12);
|
||||
--text-on-info-text-weak-on-info: var(--smoke-dark-alpha-9);
|
||||
--text-on-info-text-strong-on-info: var(--smoke-dark-alpha-12);
|
||||
--text-on-warning-text-weak-on-warning: var(--smoke-dark-alpha-9);
|
||||
--text-on-warning-text-strong-on-warning: var(--smoke-dark-alpha-12);
|
||||
--text-on-success-text-weak-on-success: var(--smoke-dark-alpha-9);
|
||||
--text-on-success-text-strong-on-success: var(--smoke-dark-alpha-12);
|
||||
--text-on-interactive-text-weak-on-interactive: var(--smoke-light-alpha-9);
|
||||
--text-on-interactive-text-strong-on-interactive: var(--smoke-light-alpha-12);
|
||||
--text-on-brand-text-weak-on-brand: var(--smoke-light-alpha-9);
|
||||
--text-on-brand-text-weaker-on-brand: var(--smoke-light-alpha-8);
|
||||
--text-on-brand-text-strong-on-brand: var(--smoke-light-alpha-12);
|
||||
--border-default-border: var(--smoke-light-alpha-7);
|
||||
--border-default-border-hover: var(--smoke-light-alpha-8);
|
||||
--border-default-border-active: var(--smoke-light-alpha-9);
|
||||
--border-default-border-selected: var(--cobalt-light-alpha-9);
|
||||
--border-default-border-disabled: var(--smoke-light-alpha-8);
|
||||
--border-default-border-focus: var(--smoke-light-alpha-9);
|
||||
--border-default-border-weak: var(--smoke-light-alpha-5);
|
||||
--icon-default-icon: var(--smoke-light-9);
|
||||
--border-default-border-weak-hover: var(--smoke-light-alpha-6);
|
||||
--icon-default-icon-hover: var(--smoke-light-10);
|
||||
--border-default-border-weak-active: var(--smoke-light-alpha-7);
|
||||
--icon-default-icon-active: var(--smoke-light-11);
|
||||
--icon-default-icon-selected: var(--smoke-light-12);
|
||||
--icon-default-icon-disabled: var(--smoke-light-7);
|
||||
--icon-default-icon-focus: var(--smoke-light-12);
|
||||
--icon-default-icon-weak: var(--smoke-light-7);
|
||||
--icon-default-icon-weak-hover: var(--smoke-light-8);
|
||||
--icon-default-icon-weak-active: var(--smoke-light-9);
|
||||
--icon-default-icon-weak-selected: var(--smoke-light-10);
|
||||
--icon-default-icon-weak-disabled: var(--smoke-light-6);
|
||||
--icon-default-icon-weak-focus: var(--smoke-light-9);
|
||||
--icon-default-icon-strong: var(--smoke-light-12);
|
||||
--icon-default-icon-strong-hover: var(--smoke-light-12);
|
||||
--icon-default-icon-strong-active: var(--smoke-light-12);
|
||||
--icon-default-icon-strong-selected: var(--smoke-light-12);
|
||||
--icon-default-icon-strong-disabled: var(--smoke-light-8);
|
||||
--icon-default-icon-strong-focus: var(--smoke-light-12);
|
||||
--border-default-border-weak-selected: var(--cobalt-light-alpha-6);
|
||||
--border-default-border-weak-disabled: var(--smoke-light-alpha-6);
|
||||
--border-default-border-weak-focus: var(--smoke-light-alpha-7);
|
||||
--border-interactive-border-interactive: var(--cobalt-light-7);
|
||||
--border-interactive-border-interactive-hover: var(--cobalt-light-8);
|
||||
--border-interactive-border-interactive-active: var(--cobalt-light-9);
|
||||
--border-interactive-border-interactive-selected: var(--cobalt-light-9);
|
||||
--border-interactive-border-interactive-disabled: var(--smoke-light-8);
|
||||
--border-interactive-border-interactive-focus: var(--cobalt-light-9);
|
||||
--border-success-border-success: var(--apple-light-6);
|
||||
--border-success-border-success-hover: var(--apple-light-7);
|
||||
--border-success-border-success-selected: var(--apple-light-9);
|
||||
--border-warning-border-warning: var(--solaris-light-6);
|
||||
--border-warning-border-warning-hover: var(--solaris-light-7);
|
||||
--border-warning-border-warning-selected: var(--solaris-light-9);
|
||||
--border-critical-border-critical: var(--ember-light-6);
|
||||
--border-critical-border-critical-hover: var(--ember-light-7);
|
||||
--border-critical-border-critical-selected: var(--ember-light-9);
|
||||
--border-info-border-info: var(--lilac-light-6);
|
||||
--border-info-border-info-hover: var(--lilac-light-7);
|
||||
--border-info-border-info-selected: var(--lilac-light-9);
|
||||
--icon-brand-icon-brand: var(--smoke-light-12);
|
||||
--icon-success-icon-success: var(--apple-light-7);
|
||||
--icon-warning-icon-warning: var(--solaris-light-7);
|
||||
--icon-warning-icon-warning-hover: var(--solaris-light-8);
|
||||
--icon-warning-icon-warning-active: var(--solaris-light-11);
|
||||
--icon-success-icon-success-hover: var(--apple-light-8);
|
||||
--icon-success-icon-success-active: var(--apple-light-11);
|
||||
--icon-critical-icon-critical: var(--ember-light-7);
|
||||
--icon-critical-icon-critical-hover: var(--ember-light-8);
|
||||
--icon-critical-icon-critical-active: var(--ember-light-11);
|
||||
--icon-info-icon-info: var(--lilac-light-7);
|
||||
--icon-info-icon-info-hover: var(--lilac-light-8);
|
||||
--icon-info-icon-info-active: var(--lilac-light-11);
|
||||
--icon-on-brand-icon-on-brand: var(--smoke-light-alpha-11);
|
||||
--icon-on-brand-icon-on-brand-hover: var(--smoke-light-alpha-12);
|
||||
--icon-on-brand-icon-on-brand-selected: var(--smoke-light-alpha-12);
|
||||
--icon-on-interactive-icon-on-interactive: var(--smoke-light-alpha-9);
|
||||
--icon-on-interactive-icon-on-interactive-hover: var(--smoke-light-alpha-10);
|
||||
--icon-on-interactive-icon-on-interactive-selected: var(--smoke-light-alpha-11);
|
||||
--icon-on-success-icon-on-success: var(--apple-light-alpha-9);
|
||||
--icon-on-success-icon-on-success-hover: var(--apple-light-alpha-10);
|
||||
--icon-on-success-icon-on-success-selected: var(--apple-light-alpha-11);
|
||||
--icon-on-warning-icon-on-warning: var(--solaris-light-alpha-9);
|
||||
--icon-on-warning-icon-on-warning-hover: var(--solaris-light-alpha-10);
|
||||
--icon-on-warning-icon-on-warning-selected: var(--solaris-light-alpha-11);
|
||||
--icon-on-critical-icon-on-critical: var(--ember-light-alpha-9);
|
||||
--icon-on-critical-icon-on-critical-hover: var(--ember-light-alpha-10);
|
||||
--icon-on-critical-icon-on-critical-selected: var(--ember-light-alpha-11);
|
||||
--icon-on-info-icon-on-info: var(--lilac-light-9);
|
||||
--icon-on-diff-add-icon-on-diff-add: var(--mint-dark-alpha-9);
|
||||
--icon-on-diff-delete-icon-on-diff-delete: var(--coral-light-alpha-9);
|
||||
--icon-on-diff-delete-icon-on-diff-delete-hover: var(--coral-light-alpha-10);
|
||||
--icon-on-diff-delete-icon-on-diff-delete-active: var(--coral-light-alpha-11);
|
||||
--icon-on-diff-add-icon-on-diff-add-hover: var(--mint-dark-alpha-10);
|
||||
--icon-on-diff-add-icon-on-diff-add-active: var(--mint-dark-alpha-11);
|
||||
--icon-on-info-icon-on-info-hover: var(--lilac-light-alpha-10);
|
||||
--icon-on-info-icon-on-info-selected: var(--lilac-light-alpha-11);
|
||||
--syntax-syntax-comment: #ffffff;
|
||||
--syntax-syntax-string: #ffffff;
|
||||
--syntax-syntax-keyword: #ffffff;
|
||||
--syntax-syntax-function: #ffffff;
|
||||
--syntax-syntax-number: #ffffff;
|
||||
--syntax-syntax-operator: #ffffff;
|
||||
--syntax-syntax-variable: #ffffff;
|
||||
--syntax-syntax-type: #ffffff;
|
||||
--syntax-syntax-constant: #ffffff;
|
||||
--syntax-syntax-punctuation: #ffffff;
|
||||
--syntax-syntax-success: #ffffff;
|
||||
--syntax-syntax-warning: #ffffff;
|
||||
--syntax-syntax-critical: #ffffff;
|
||||
--syntax-syntax-info: #ffffff;
|
||||
--markdown-markdown-heading: #ffffff;
|
||||
--markdown-markdown-text: #ffffff;
|
||||
--markdown-markdown-link: #ffffff;
|
||||
--markdown-markdown-link-text: #ffffff;
|
||||
--markdown-markdown-code: #ffffff;
|
||||
--markdown-markdown-block-quote: #ffffff;
|
||||
--markdown-markdown-emph: #ffffff;
|
||||
--markdown-markdown-strong: #ffffff;
|
||||
--markdown-markdown-horizontal-rule: #ffffff;
|
||||
--markdown-markdown-list-item: #ffffff;
|
||||
--markdown-markdown-list-enumeration: #ffffff;
|
||||
--markdown-markdown-image: #ffffff;
|
||||
--markdown-markdown-image-text: #ffffff;
|
||||
--markdown-markdown-code-block: #ffffff;
|
||||
|
||||
.dark {
|
||||
/* OC-1-Dark */
|
||||
color-scheme: dark;
|
||||
--background-background: var(--smoke-dark-1);
|
||||
--background-background-weak: #151313;
|
||||
--background-background-weaker: #201c1c;
|
||||
--surface-default-surface: var(--smoke-dark-3);
|
||||
--input-input: var(--smoke-dark-2);
|
||||
--input-input-hover: var(--smoke-dark-2);
|
||||
--input-input-active: var(--cobalt-dark-1);
|
||||
--input-input-selected: var(--cobalt-dark-2);
|
||||
--input-input-focus: var(--cobalt-dark-1);
|
||||
--input-input-disabled: var(--smoke-dark-4);
|
||||
--surface-default-surface-hover: var(--smoke-dark-4);
|
||||
--surface-default-surface-active: var(--smoke-dark-5);
|
||||
--surface-default-surface-selected: var(--cobalt-dark-3);
|
||||
--surface-default-surface-disabled: var(--smoke-dark-2);
|
||||
--surface-default-surface-focus: var(--cobalt-dark-3);
|
||||
--surface-inset-surface-inset: var(--smoke-dark-4);
|
||||
--surface-inset-surface-inset-hover: var(--smoke-dark-5);
|
||||
--surface-inset-surface-inset-active: var(--smoke-dark-6);
|
||||
--surface-inset-surface-inset-selected: var(--cobalt-dark-4);
|
||||
--surface-inset-surface-inset-disabled: var(--smoke-dark-3);
|
||||
--surface-inset-surface-inset-focus: var(--cobalt-dark-4);
|
||||
--surface-raised-surface-raised: var(--smoke-dark-4);
|
||||
--surface-raised-surface-raised-hover: var(--smoke-dark-7);
|
||||
--surface-raised-surface-raised-active: var(--smoke-dark-8);
|
||||
--surface-raised-surface-raised-selected: var(--cobalt-dark-3);
|
||||
--surface-raised-surface-raised-disabled: var(--smoke-dark-3);
|
||||
--surface-raised-surface-raised-focus: var(--cobalt-dark-3);
|
||||
--surface-overlay-surface-overlay: var(--smoke-dark-alpha-2);
|
||||
--surface-brand-surface-brand: var(--yuzu-light-9);
|
||||
--surface-brand-surface-brand-hover: var(--yuzu-light-10);
|
||||
--surface-interactive-surface-interactive: var(--cobalt-light-3);
|
||||
--surface-interactive-surface-interactive-hover: var(--cobalt-light-4);
|
||||
--surface-interactive-surface-interactive-weak: var(--cobalt-light-2);
|
||||
--surface-interactive-surface-interactive-weak-hover: var(--cobalt-light-3);
|
||||
--surface-success-surface-success: var(--apple-light-3);
|
||||
--surface-success-surface-success-weak: var(--apple-light-2);
|
||||
--surface-success-surface-success-strong: var(--apple-light-9);
|
||||
--surface-warning-surface-warning: var(--solaris-light-3);
|
||||
--surface-warning-surface-warning-weak: var(--solaris-light-2);
|
||||
--surface-warning-surface-warning-strong: var(--solaris-light-9);
|
||||
--surface-critical-surface-critical: var(--ember-light-3);
|
||||
--surface-critical-surface-critical-weak: var(--ember-light-2);
|
||||
--surface-critical-surface-critical-strong: var(--ember-light-9);
|
||||
--surface-info-surface-info: var(--lilac-light-3);
|
||||
--surface-info-surface-info-weak: var(--lilac-light-2);
|
||||
--surface-info-surface-info-strong: var(--lilac-light-9);
|
||||
--surface-diff-add-surface-diff-add: var(--coral-light-3);
|
||||
--surface-diff-add-surface-diff-add-weak: var(--coral-light-2);
|
||||
--surface-diff-add-surface-diff-add-weaker: var(--coral-light-1);
|
||||
--surface-diff-add-surface-diff-add-strong: var(--coral-light-9);
|
||||
--surface-diff-add-surface-diff-add-stronger: var(--coral-light-12);
|
||||
--surface-diff-delete-surface-diff-delete: var(--mint-light-3);
|
||||
--surface-on-background-weak-default-surface-on-background-weak: var(--smoke-dark-1);
|
||||
--surface-on-background-weak-inset-surface-inset-on-background-weak: var(--smoke-light-1);
|
||||
--surface-on-background-weak-default-surface-on-background-weak-hover: var(--smoke-light-1);
|
||||
--surface-on-background-weak-default-surface-on-background-weak-active: var(--smoke-light-1);
|
||||
--surface-on-background-weak-default-surface-on-background-weak-selected: var(--smoke-light-1);
|
||||
--surface-on-background-weak-default-surface-on-background-weak-disabled: var(--smoke-light-1);
|
||||
--surface-on-background-weak-default-surface-on-background-weak-focus: var(--smoke-light-1);
|
||||
--surface-on-background-weak-inset-surface-inset-on-background-weak-hover: var(--smoke-light-1);
|
||||
--surface-on-background-weak-inset-surface-inset-on-background-weak-active: var(--smoke-light-1);
|
||||
--surface-on-background-weak-inset-surface-inset-on-background-weak-selected: var(--smoke-light-1);
|
||||
--surface-on-background-weak-inset-surface-inset-on-background-weak-disabled: var(--smoke-light-1);
|
||||
--surface-on-background-weak-inset-surface-inset-on-background-weak-focus: var(--smoke-light-1);
|
||||
--surface-diff-delete-surface-diff-delete-weak: var(--mint-light-2);
|
||||
--surface-diff-delete-surface-diff-delete-weaker: var(--mint-light-1);
|
||||
--surface-diff-delete-surface-diff-delete-strong: var(--mint-light-9);
|
||||
--surface-diff-delete-surface-diff-delete-stronger: var(--mint-light-11);
|
||||
--text-default-text: var(--smoke-dark-alpha-11);
|
||||
--text-default-text-weak: var(--smoke-dark-alpha-9);
|
||||
--text-default-text-weaker: var(--smoke-dark-alpha-8);
|
||||
--text-default-text-strong: var(--smoke-dark-alpha-12);
|
||||
--text-on-brand-text-on-brand: var(--smoke-dark-alpha-11);
|
||||
--text-on-interactive-text-on-interactive: var(--smoke-dark-alpha-11);
|
||||
--text-on-success-text-on-success: var(--smoke-dark-alpha-11);
|
||||
--text-on-warning-text-on-warning: var(--smoke-dark-alpha-11);
|
||||
--text-on-info-text-on-info: var(--smoke-dark-alpha-11);
|
||||
--text-on-diff-add-text-on-diff-add: var(--smoke-dark-alpha-11);
|
||||
--text-on-diff-delete-text-on-diff-delete: var(--smoke-dark-alpha-11);
|
||||
--text-on-diff-delete-text-weak-on-diff-delete: var(--smoke-dark-alpha-9);
|
||||
--text-on-diff-delete-text-strong-on-diff-delete: var(--smoke-dark-alpha-12);
|
||||
--text-on-diff-add-text-weak-on-diff-add: var(--smoke-dark-alpha-9);
|
||||
--text-on-diff-add-text-strong-on-diff-add: var(--smoke-dark-alpha-12);
|
||||
--text-on-info-text-weak-on-info: var(--smoke-dark-alpha-9);
|
||||
--text-on-info-text-strong-on-info: var(--smoke-dark-alpha-12);
|
||||
--text-on-warning-text-weak-on-warning: var(--smoke-dark-alpha-9);
|
||||
--text-on-warning-text-strong-on-warning: var(--smoke-dark-alpha-12);
|
||||
--text-on-success-text-weak-on-success: var(--smoke-dark-alpha-9);
|
||||
--text-on-success-text-strong-on-success: var(--smoke-dark-alpha-12);
|
||||
--text-on-interactive-text-weak-on-interactive: var(--smoke-dark-alpha-9);
|
||||
--text-on-interactive-text-strong-on-interactive: var(--smoke-dark-alpha-12);
|
||||
--text-on-brand-text-weak-on-brand: var(--smoke-dark-alpha-9);
|
||||
--text-on-brand-text-weaker-on-brand: var(--smoke-dark-alpha-8);
|
||||
--text-on-brand-text-strong-on-brand: var(--smoke-dark-alpha-12);
|
||||
--border-default-border: var(--smoke-dark-alpha-7);
|
||||
--border-default-border-hover: var(--smoke-dark-alpha-8);
|
||||
--border-default-border-active: var(--smoke-dark-alpha-9);
|
||||
--border-default-border-selected: var(--cobalt-dark-alpha-9);
|
||||
--border-default-border-disabled: var(--smoke-dark-alpha-8);
|
||||
--border-default-border-focus: var(--smoke-dark-alpha-9);
|
||||
--border-default-border-weak: var(--smoke-dark-alpha-6);
|
||||
--icon-default-icon: var(--smoke-light-9);
|
||||
--border-default-border-weak-hover: var(--smoke-dark-alpha-7);
|
||||
--icon-default-icon-hover: var(--smoke-light-10);
|
||||
--border-default-border-weak-active: var(--smoke-dark-alpha-8);
|
||||
--icon-default-icon-active: var(--smoke-light-11);
|
||||
--icon-default-icon-selected: var(--smoke-light-12);
|
||||
--icon-default-icon-disabled: var(--smoke-light-7);
|
||||
--icon-default-icon-focus: var(--smoke-light-12);
|
||||
--icon-default-icon-weak: var(--smoke-dark-6);
|
||||
--icon-default-icon-weak-hover: var(--smoke-light-7);
|
||||
--icon-default-icon-weak-active: var(--smoke-light-8);
|
||||
--icon-default-icon-weak-selected: var(--smoke-light-9);
|
||||
--icon-default-icon-weak-disabled: var(--smoke-light-4);
|
||||
--icon-default-icon-weak-focus: var(--smoke-light-9);
|
||||
--icon-default-icon-strong: var(--smoke-dark-12);
|
||||
--icon-default-icon-strong-hover: var(--smoke-light-12);
|
||||
--icon-default-icon-strong-active: var(--smoke-light-12);
|
||||
--icon-default-icon-strong-selected: var(--smoke-light-12);
|
||||
--icon-default-icon-strong-disabled: var(--smoke-light-8);
|
||||
--icon-default-icon-strong-focus: var(--smoke-light-12);
|
||||
--border-default-border-weak-selected: var(--cobalt-dark-alpha-6);
|
||||
--border-default-border-weak-disabled: var(--smoke-dark-alpha-6);
|
||||
--border-default-border-weak-focus: var(--smoke-dark-alpha-8);
|
||||
--border-interactive-border-interactive: var(--cobalt-light-7);
|
||||
--border-interactive-border-interactive-hover: var(--cobalt-light-8);
|
||||
--border-interactive-border-interactive-active: var(--cobalt-light-9);
|
||||
--border-interactive-border-interactive-selected: var(--cobalt-light-9);
|
||||
--border-interactive-border-interactive-disabled: var(--smoke-light-8);
|
||||
--border-interactive-border-interactive-focus: var(--cobalt-light-9);
|
||||
--border-success-border-success: var(--apple-light-6);
|
||||
--border-success-border-success-hover: var(--apple-light-7);
|
||||
--border-success-border-success-selected: var(--apple-light-9);
|
||||
--border-warning-border-warning: var(--solaris-light-6);
|
||||
--border-warning-border-warning-hover: var(--solaris-light-7);
|
||||
--border-warning-border-warning-selected: var(--solaris-light-9);
|
||||
--border-critical-border-critical: var(--ember-light-6);
|
||||
--border-critical-border-critical-hover: var(--ember-light-7);
|
||||
--border-critical-border-critical-selected: var(--ember-light-9);
|
||||
--border-info-border-info: var(--lilac-light-6);
|
||||
--border-info-border-info-hover: var(--lilac-light-7);
|
||||
--border-info-border-info-selected: var(--lilac-light-9);
|
||||
--icon-brand-icon-brand: var(--white);
|
||||
--icon-success-icon-success: var(--apple-light-7);
|
||||
--icon-warning-icon-warning: var(--solaris-light-7);
|
||||
--icon-warning-icon-warning-hover: var(--solaris-light-8);
|
||||
--icon-warning-icon-warning-active: var(--solaris-light-11);
|
||||
--icon-success-icon-success-hover: var(--apple-light-8);
|
||||
--icon-success-icon-success-active: var(--apple-light-11);
|
||||
--icon-critical-icon-critical: var(--ember-light-7);
|
||||
--icon-critical-icon-critical-hover: var(--ember-light-8);
|
||||
--icon-critical-icon-critical-active: var(--ember-light-11);
|
||||
--icon-info-icon-info: var(--lilac-light-7);
|
||||
--icon-info-icon-info-hover: var(--lilac-light-8);
|
||||
--icon-info-icon-info-active: var(--lilac-light-11);
|
||||
--icon-on-brand-icon-on-brand: var(--smoke-light-alpha-11);
|
||||
--icon-on-brand-icon-on-brand-hover: var(--smoke-light-alpha-12);
|
||||
--icon-on-brand-icon-on-brand-selected: var(--smoke-light-alpha-12);
|
||||
--icon-on-interactive-icon-on-interactive: var(--smoke-dark-alpha-9);
|
||||
--icon-on-interactive-icon-on-interactive-hover: var(--smoke-dark-alpha-10);
|
||||
--icon-on-interactive-icon-on-interactive-selected: var(--smoke-dark-alpha-11);
|
||||
--icon-on-success-icon-on-success: var(--apple-light-alpha-9);
|
||||
--icon-on-success-icon-on-success-hover: var(--apple-light-alpha-10);
|
||||
--icon-on-success-icon-on-success-selected: var(--apple-light-alpha-11);
|
||||
--icon-on-warning-icon-on-warning: var(--solaris-light-alpha-9);
|
||||
--icon-on-warning-icon-on-warning-hover: var(--solaris-light-alpha-10);
|
||||
--icon-on-warning-icon-on-warning-selected: var(--solaris-light-alpha-11);
|
||||
--icon-on-critical-icon-on-critical: var(--ember-light-alpha-9);
|
||||
--icon-on-critical-icon-on-critical-hover: var(--ember-light-alpha-10);
|
||||
--icon-on-critical-icon-on-critical-selected: var(--ember-light-alpha-11);
|
||||
--icon-on-info-icon-on-info: var(--lilac-light-9);
|
||||
--icon-on-diff-add-icon-on-diff-add: var(--mint-dark-alpha-9);
|
||||
--icon-on-diff-delete-icon-on-diff-delete: var(--coral-light-alpha-9);
|
||||
--icon-on-diff-delete-icon-on-diff-delete-hover: var(--coral-light-alpha-10);
|
||||
--icon-on-diff-delete-icon-on-diff-delete-active: var(--coral-light-alpha-11);
|
||||
--icon-on-diff-add-icon-on-diff-add-hover: var(--mint-dark-alpha-10);
|
||||
--icon-on-diff-add-icon-on-diff-add-active: var(--mint-dark-alpha-11);
|
||||
--icon-on-info-icon-on-info-hover: var(--lilac-light-alpha-10);
|
||||
--icon-on-info-icon-on-info-selected: var(--lilac-light-alpha-11);
|
||||
--syntax-syntax-comment: #ffffff;
|
||||
--syntax-syntax-string: #ffffff;
|
||||
--syntax-syntax-keyword: #ffffff;
|
||||
--syntax-syntax-function: #ffffff;
|
||||
--syntax-syntax-number: #ffffff;
|
||||
--syntax-syntax-operator: #ffffff;
|
||||
--syntax-syntax-variable: #ffffff;
|
||||
--syntax-syntax-type: #ffffff;
|
||||
--syntax-syntax-constant: #ffffff;
|
||||
--syntax-syntax-punctuation: #ffffff;
|
||||
--syntax-syntax-success: #ffffff;
|
||||
--syntax-syntax-warning: #ffffff;
|
||||
--syntax-syntax-critical: #ffffff;
|
||||
--syntax-syntax-info: #ffffff;
|
||||
--markdown-markdown-heading: #ffffff;
|
||||
--markdown-markdown-text: #ffffff;
|
||||
--markdown-markdown-link: #ffffff;
|
||||
--markdown-markdown-link-text: #ffffff;
|
||||
--markdown-markdown-code: #ffffff;
|
||||
--markdown-markdown-block-quote: #ffffff;
|
||||
--markdown-markdown-emph: #ffffff;
|
||||
--markdown-markdown-strong: #ffffff;
|
||||
--markdown-markdown-horizontal-rule: #ffffff;
|
||||
--markdown-markdown-list-item: #ffffff;
|
||||
--markdown-markdown-list-enumeration: #ffffff;
|
||||
--markdown-markdown-image: #ffffff;
|
||||
--markdown-markdown-image-text: #ffffff;
|
||||
--markdown-markdown-code-block: #ffffff;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
:root {
|
||||
interpolate-size: allow-keywords;
|
||||
}
|
||||
|
||||
.no-scrollbar {
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
& {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "0.14.7",
|
||||
"version": "0.15.5",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -8,17 +8,19 @@
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"typecheck": "tsgo --noEmit"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"@tailwindcss/vite": "catalog:",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vite": "^6.0.0",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"vite": "catalog:",
|
||||
"vite-plugin-icons-spritesheet": "3.0.1",
|
||||
"vite-plugin-solid": "^2.11.6"
|
||||
"vite-plugin-solid": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -29,16 +31,17 @@
|
||||
"@solid-primitives/scroll": "2.1.3",
|
||||
"@solidjs/router": "0.15.3",
|
||||
"@thisbeyond/solid-dnd": "0.7.5",
|
||||
"diff": "8.0.2",
|
||||
"diff": "catalog:",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"fuzzysort": "catalog:",
|
||||
"luxon": "catalog:",
|
||||
"marked": "16.2.0",
|
||||
"marked-shiki": "1.2.1",
|
||||
"remeda": "catalog:",
|
||||
"shiki": "3.9.2",
|
||||
"solid-js": "catalog:",
|
||||
"shiki": "3.9.2",
|
||||
"solid-list": "0.3.0",
|
||||
"tailwindcss": "4.1.11",
|
||||
"tailwindcss": "catalog:",
|
||||
"virtua": "0.42.3"
|
||||
},
|
||||
"prettier": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user