Merge branch 'dev' into feat/canceled-prompts-in-history
This commit is contained in:
commit
716f40b128
494 changed files with 96091 additions and 51215 deletions
2
.github/VOUCHED.td
vendored
2
.github/VOUCHED.td
vendored
|
|
@ -8,7 +8,9 @@
|
||||||
# - Denounce with minus prefix: -username or -platform:username.
|
# - Denounce with minus prefix: -username or -platform:username.
|
||||||
# - Optional details after a space following the handle.
|
# - Optional details after a space following the handle.
|
||||||
adamdotdevin
|
adamdotdevin
|
||||||
|
-agusbasari29 AI PR slop
|
||||||
ariane-emory
|
ariane-emory
|
||||||
|
edemaine
|
||||||
-florianleibert
|
-florianleibert
|
||||||
fwang
|
fwang
|
||||||
iamdavidhill
|
iamdavidhill
|
||||||
|
|
|
||||||
69
.github/actions/setup-bun/action.yml
vendored
69
.github/actions/setup-bun/action.yml
vendored
|
|
@ -1,5 +1,10 @@
|
||||||
name: "Setup Bun"
|
name: "Setup Bun"
|
||||||
description: "Setup Bun with caching and install dependencies"
|
description: "Setup Bun with caching and install dependencies"
|
||||||
|
inputs:
|
||||||
|
cross-compile:
|
||||||
|
description: "Pre-cache canary cross-compile binaries for all targets"
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -11,10 +16,72 @@ runs:
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-bun-
|
${{ runner.os }}-bun-
|
||||||
|
|
||||||
|
- name: Get baseline download URL
|
||||||
|
id: bun-url
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ "$RUNNER_ARCH" = "X64" ]; then
|
||||||
|
case "$RUNNER_OS" in
|
||||||
|
macOS) OS=darwin ;;
|
||||||
|
Linux) OS=linux ;;
|
||||||
|
Windows) OS=windows ;;
|
||||||
|
esac
|
||||||
|
echo "url=https://github.com/oven-sh/bun/releases/download/canary/bun-${OS}-x64-baseline.zip" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version-file: package.json
|
bun-version-file: ${{ !steps.bun-url.outputs.url && 'package.json' || '' }}
|
||||||
|
bun-download-url: ${{ steps.bun-url.outputs.url }}
|
||||||
|
|
||||||
|
- name: Pre-cache canary cross-compile binaries
|
||||||
|
if: inputs.cross-compile == 'true'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
BUN_VERSION=$(bun --revision)
|
||||||
|
if echo "$BUN_VERSION" | grep -q "canary"; then
|
||||||
|
SEMVER=$(echo "$BUN_VERSION" | sed 's/^\([0-9]*\.[0-9]*\.[0-9]*\).*/\1/')
|
||||||
|
echo "Bun version: $BUN_VERSION (semver: $SEMVER)"
|
||||||
|
CACHE_DIR="$HOME/.bun/install/cache"
|
||||||
|
mkdir -p "$CACHE_DIR"
|
||||||
|
TMP_DIR=$(mktemp -d)
|
||||||
|
for TARGET in linux-aarch64 linux-x64 linux-x64-baseline linux-aarch64-musl linux-x64-musl linux-x64-musl-baseline darwin-aarch64 darwin-x64 windows-x64 windows-x64-baseline; do
|
||||||
|
DEST="$CACHE_DIR/bun-${TARGET}-v${SEMVER}"
|
||||||
|
if [ -f "$DEST" ]; then
|
||||||
|
echo "Already cached: $DEST"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
URL="https://github.com/oven-sh/bun/releases/download/canary/bun-${TARGET}.zip"
|
||||||
|
echo "Downloading $TARGET from $URL"
|
||||||
|
if curl -sfL -o "$TMP_DIR/bun.zip" "$URL"; then
|
||||||
|
unzip -qo "$TMP_DIR/bun.zip" -d "$TMP_DIR"
|
||||||
|
if echo "$TARGET" | grep -q "windows"; then
|
||||||
|
BIN_NAME="bun.exe"
|
||||||
|
else
|
||||||
|
BIN_NAME="bun"
|
||||||
|
fi
|
||||||
|
mv "$TMP_DIR/bun-${TARGET}/$BIN_NAME" "$DEST"
|
||||||
|
chmod +x "$DEST"
|
||||||
|
rm -rf "$TMP_DIR/bun-${TARGET}" "$TMP_DIR/bun.zip"
|
||||||
|
echo "Cached: $DEST"
|
||||||
|
# baseline bun resolves "bun-darwin-x64" to the baseline cache key
|
||||||
|
# so copy the modern binary there too
|
||||||
|
if [ "$TARGET" = "darwin-x64" ]; then
|
||||||
|
BASELINE_DEST="$CACHE_DIR/bun-darwin-x64-baseline-v${SEMVER}"
|
||||||
|
if [ ! -f "$BASELINE_DEST" ]; then
|
||||||
|
cp "$DEST" "$BASELINE_DEST"
|
||||||
|
echo "Cached (baseline alias): $BASELINE_DEST"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Skipped: $TARGET (not available)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
else
|
||||||
|
echo "Not a canary build ($BUN_VERSION), skipping pre-cache"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install
|
run: bun install
|
||||||
|
|
|
||||||
4
.github/workflows/beta.yml
vendored
4
.github/workflows/beta.yml
vendored
|
|
@ -27,7 +27,11 @@ jobs:
|
||||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||||
|
|
||||||
|
- name: Install OpenCode
|
||||||
|
run: bun i -g opencode-ai
|
||||||
|
|
||||||
- name: Sync beta branch
|
- name: Sync beta branch
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }}
|
GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }}
|
||||||
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
run: bun script/beta.ts
|
run: bun script/beta.ts
|
||||||
|
|
|
||||||
9
.github/workflows/compliance-close.yml
vendored
9
.github/workflows/compliance-close.yml
vendored
|
|
@ -65,6 +65,15 @@ jobs:
|
||||||
body: closeMessage,
|
body: closeMessage,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await github.rest.issues.removeLabel({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: item.number,
|
||||||
|
name: 'needs:compliance',
|
||||||
|
});
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
if (isPR) {
|
if (isPR) {
|
||||||
await github.rest.pulls.update({
|
await github.rest.pulls.update({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
|
|
||||||
59
.github/workflows/docs-locale-sync.yml
vendored
59
.github/workflows/docs-locale-sync.yml
vendored
|
|
@ -12,13 +12,14 @@ jobs:
|
||||||
if: github.actor != 'opencode-agent[bot]'
|
if: github.actor != 'opencode-agent[bot]'
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
persist-credentials: false
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
ref: ${{ github.ref_name }}
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: ./.github/actions/setup-bun
|
uses: ./.github/actions/setup-bun
|
||||||
|
|
@ -51,9 +52,54 @@ jobs:
|
||||||
uses: sst/opencode/github@latest
|
uses: sst/opencode/github@latest
|
||||||
env:
|
env:
|
||||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||||
|
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||||
|
OPENCODE_CONFIG_CONTENT: |
|
||||||
|
{
|
||||||
|
"permission": {
|
||||||
|
"*": "deny",
|
||||||
|
"read": {
|
||||||
|
"*": "deny",
|
||||||
|
"packages/web/src/content/docs": "allow",
|
||||||
|
"packages/web/src/content/docs/*": "allow",
|
||||||
|
"packages/web/src/content/docs/*.mdx": "allow",
|
||||||
|
"packages/web/src/content/docs/*/*.mdx": "allow",
|
||||||
|
".opencode": "allow",
|
||||||
|
".opencode/agent": "allow",
|
||||||
|
".opencode/glossary": "allow",
|
||||||
|
".opencode/agent/translator.md": "allow",
|
||||||
|
".opencode/glossary/*.md": "allow"
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"*": "deny",
|
||||||
|
"packages/web/src/content/docs/*/*.mdx": "allow"
|
||||||
|
},
|
||||||
|
"glob": {
|
||||||
|
"*": "deny",
|
||||||
|
"packages/web/src/content/docs*": "allow",
|
||||||
|
".opencode/glossary*": "allow"
|
||||||
|
},
|
||||||
|
"task": {
|
||||||
|
"*": "deny",
|
||||||
|
"translator": "allow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"agent": {
|
||||||
|
"translator": {
|
||||||
|
"permission": {
|
||||||
|
"*": "deny",
|
||||||
|
"read": {
|
||||||
|
"*": "deny",
|
||||||
|
".opencode/agent/translator.md": "allow",
|
||||||
|
".opencode/glossary/*.md": "allow"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
with:
|
with:
|
||||||
model: opencode/gpt-5.2
|
model: opencode/gpt-5.3-codex
|
||||||
agent: docs
|
agent: docs
|
||||||
|
use_github_token: true
|
||||||
prompt: |
|
prompt: |
|
||||||
Update localized docs to match the latest English docs changes.
|
Update localized docs to match the latest English docs changes.
|
||||||
|
|
||||||
|
|
@ -67,10 +113,11 @@ jobs:
|
||||||
2. You MUST use the Task tool for translation work and launch subagents with subagent_type `translator` (defined in .opencode/agent/translator.md).
|
2. You MUST use the Task tool for translation work and launch subagents with subagent_type `translator` (defined in .opencode/agent/translator.md).
|
||||||
3. Do not translate directly in the primary agent. Use translator subagent output as the source for locale text updates.
|
3. Do not translate directly in the primary agent. Use translator subagent output as the source for locale text updates.
|
||||||
4. Run translator subagent Task calls in parallel whenever file/locale translation work is independent.
|
4. Run translator subagent Task calls in parallel whenever file/locale translation work is independent.
|
||||||
5. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update.
|
5. Use only the minimum tools needed for this task (read/glob, file edits, and translator Task). Do not use shell, web, search, or GitHub tools for translation work.
|
||||||
6. Keep locale docs structure aligned with their corresponding English pages.
|
6. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update.
|
||||||
7. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
|
7. Keep locale docs structure aligned with their corresponding English pages.
|
||||||
8. If no locale updates are needed, make no changes.
|
8. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
|
||||||
|
9. If no locale updates are needed, make no changes.
|
||||||
|
|
||||||
- name: Commit and push locale docs updates
|
- name: Commit and push locale docs updates
|
||||||
if: steps.changes.outputs.has_changes == 'true'
|
if: steps.changes.outputs.has_changes == 'true'
|
||||||
|
|
|
||||||
12
.github/workflows/pr-standards.yml
vendored
12
.github/workflows/pr-standards.yml
vendored
|
|
@ -108,11 +108,11 @@ jobs:
|
||||||
|
|
||||||
await removeLabel('needs:title');
|
await removeLabel('needs:title');
|
||||||
|
|
||||||
// Step 2: Check for linked issue (skip for docs/refactor PRs)
|
// Step 2: Check for linked issue (skip for docs/refactor/feat PRs)
|
||||||
const skipIssueCheck = /^(docs|refactor)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
|
const skipIssueCheck = /^(docs|refactor|feat)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
|
||||||
if (skipIssueCheck) {
|
if (skipIssueCheck) {
|
||||||
await removeLabel('needs:issue');
|
await removeLabel('needs:issue');
|
||||||
console.log('Skipping issue check for docs/refactor PR');
|
console.log('Skipping issue check for docs/refactor/feat PR');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const query = `
|
const query = `
|
||||||
|
|
@ -189,7 +189,7 @@ jobs:
|
||||||
|
|
||||||
const body = pr.body || '';
|
const body = pr.body || '';
|
||||||
const title = pr.title;
|
const title = pr.title;
|
||||||
const isDocsOrRefactor = /^(docs|refactor)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
|
const isDocsRefactorOrFeat = /^(docs|refactor|feat)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
|
||||||
|
|
||||||
const issues = [];
|
const issues = [];
|
||||||
|
|
||||||
|
|
@ -225,8 +225,8 @@ jobs:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check: issue reference (skip for docs/refactor)
|
// Check: issue reference (skip for docs/refactor/feat)
|
||||||
if (!isDocsOrRefactor && hasIssueSection) {
|
if (!isDocsRefactorOrFeat && hasIssueSection) {
|
||||||
const issueMatch = body.match(/### Issue for this PR\s*\n([\s\S]*?)(?=###|$)/);
|
const issueMatch = body.match(/### Issue for this PR\s*\n([\s\S]*?)(?=###|$)/);
|
||||||
const issueContent = issueMatch ? issueMatch[1].trim() : '';
|
const issueContent = issueMatch ? issueMatch[1].trim() : '';
|
||||||
const hasIssueRef = /(closes|fixes|resolves)\s+#\d+/i.test(issueContent) || /#\d+/.test(issueContent);
|
const hasIssueRef = /(closes|fixes|resolves)\s+#\d+/i.test(issueContent) || /#\d+/.test(issueContent);
|
||||||
|
|
|
||||||
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
|
|
@ -77,6 +77,8 @@ jobs:
|
||||||
fetch-tags: true
|
fetch-tags: true
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-bun
|
- uses: ./.github/actions/setup-bun
|
||||||
|
with:
|
||||||
|
cross-compile: "true"
|
||||||
|
|
||||||
- name: Setup git committer
|
- name: Setup git committer
|
||||||
id: committer
|
id: committer
|
||||||
|
|
@ -88,7 +90,7 @@ jobs:
|
||||||
- name: Build
|
- name: Build
|
||||||
id: build
|
id: build
|
||||||
run: |
|
run: |
|
||||||
./packages/opencode/script/build.ts
|
./packages/opencode/script/build.ts --all
|
||||||
env:
|
env:
|
||||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||||
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||||
|
|
|
||||||
4
.github/workflows/sign-cli.yml
vendored
4
.github/workflows/sign-cli.yml
vendored
|
|
@ -20,10 +20,12 @@ jobs:
|
||||||
fetch-tags: true
|
fetch-tags: true
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-bun
|
- uses: ./.github/actions/setup-bun
|
||||||
|
with:
|
||||||
|
cross-compile: "true"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
./packages/opencode/script/build.ts
|
./packages/opencode/script/build.ts --all
|
||||||
|
|
||||||
- name: Upload unsigned Windows CLI
|
- name: Upload unsigned Windows CLI
|
||||||
id: upload_unsigned_windows_cli
|
id: upload_unsigned_windows_cli
|
||||||
|
|
|
||||||
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
|
|
@ -8,8 +8,16 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
jobs:
|
jobs:
|
||||||
unit:
|
unit:
|
||||||
name: unit (linux)
|
name: unit (${{ matrix.settings.name }})
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
settings:
|
||||||
|
- name: linux
|
||||||
|
host: blacksmith-4vcpu-ubuntu-2404
|
||||||
|
- name: windows
|
||||||
|
host: blacksmith-4vcpu-windows-2025
|
||||||
|
runs-on: ${{ matrix.settings.host }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
|
||||||
58
.github/workflows/vouch-check-issue.yml
vendored
58
.github/workflows/vouch-check-issue.yml
vendored
|
|
@ -42,15 +42,17 @@ jobs:
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the .td file for denounced users
|
// Parse the .td file for vouched and denounced users
|
||||||
|
const vouched = new Set();
|
||||||
const denounced = new Map();
|
const denounced = new Map();
|
||||||
for (const line of content.split('\n')) {
|
for (const line of content.split('\n')) {
|
||||||
const trimmed = line.trim();
|
const trimmed = line.trim();
|
||||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||||
if (!trimmed.startsWith('-')) continue;
|
|
||||||
|
|
||||||
const rest = trimmed.slice(1).trim();
|
const isDenounced = trimmed.startsWith('-');
|
||||||
|
const rest = isDenounced ? trimmed.slice(1).trim() : trimmed;
|
||||||
if (!rest) continue;
|
if (!rest) continue;
|
||||||
|
|
||||||
const spaceIdx = rest.indexOf(' ');
|
const spaceIdx = rest.indexOf(' ');
|
||||||
const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
|
const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
|
||||||
const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
|
const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
|
||||||
|
|
@ -65,32 +67,50 @@ jobs:
|
||||||
const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
|
const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
|
||||||
if (!username) continue;
|
if (!username) continue;
|
||||||
|
|
||||||
denounced.set(username.toLowerCase(), reason);
|
if (isDenounced) {
|
||||||
|
denounced.set(username.toLowerCase(), reason);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
vouched.add(username.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the author is denounced
|
// Check if the author is denounced
|
||||||
const reason = denounced.get(author.toLowerCase());
|
const reason = denounced.get(author.toLowerCase());
|
||||||
if (reason === undefined) {
|
if (reason !== undefined) {
|
||||||
core.info(`User ${author} is not denounced. Allowing issue.`);
|
// Author is denounced — close the issue
|
||||||
|
const body = 'This issue has been automatically closed.';
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
await github.rest.issues.update({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
state: 'closed',
|
||||||
|
state_reason: 'not_planned',
|
||||||
|
});
|
||||||
|
|
||||||
|
core.info(`Closed issue #${issueNumber} from denounced user ${author}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Author is denounced — close the issue
|
// Author is positively vouched — add label
|
||||||
const body = 'This issue has been automatically closed.';
|
if (!vouched.has(author.toLowerCase())) {
|
||||||
|
core.info(`User ${author} is not denounced or vouched. Allowing issue.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await github.rest.issues.createComment({
|
await github.rest.issues.addLabels({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
issue_number: issueNumber,
|
issue_number: issueNumber,
|
||||||
body,
|
labels: ['Vouched'],
|
||||||
});
|
});
|
||||||
|
|
||||||
await github.rest.issues.update({
|
core.info(`Added vouched label to issue #${issueNumber} from ${author}`);
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: issueNumber,
|
|
||||||
state: 'closed',
|
|
||||||
state_reason: 'not_planned',
|
|
||||||
});
|
|
||||||
|
|
||||||
core.info(`Closed issue #${issueNumber} from denounced user ${author}`);
|
|
||||||
|
|
|
||||||
55
.github/workflows/vouch-check-pr.yml
vendored
55
.github/workflows/vouch-check-pr.yml
vendored
|
|
@ -6,6 +6,7 @@ on:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
@ -42,15 +43,17 @@ jobs:
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the .td file for denounced users
|
// Parse the .td file for vouched and denounced users
|
||||||
|
const vouched = new Set();
|
||||||
const denounced = new Map();
|
const denounced = new Map();
|
||||||
for (const line of content.split('\n')) {
|
for (const line of content.split('\n')) {
|
||||||
const trimmed = line.trim();
|
const trimmed = line.trim();
|
||||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||||
if (!trimmed.startsWith('-')) continue;
|
|
||||||
|
|
||||||
const rest = trimmed.slice(1).trim();
|
const isDenounced = trimmed.startsWith('-');
|
||||||
|
const rest = isDenounced ? trimmed.slice(1).trim() : trimmed;
|
||||||
if (!rest) continue;
|
if (!rest) continue;
|
||||||
|
|
||||||
const spaceIdx = rest.indexOf(' ');
|
const spaceIdx = rest.indexOf(' ');
|
||||||
const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
|
const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
|
||||||
const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
|
const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
|
||||||
|
|
@ -65,29 +68,47 @@ jobs:
|
||||||
const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
|
const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
|
||||||
if (!username) continue;
|
if (!username) continue;
|
||||||
|
|
||||||
denounced.set(username.toLowerCase(), reason);
|
if (isDenounced) {
|
||||||
|
denounced.set(username.toLowerCase(), reason);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
vouched.add(username.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the author is denounced
|
// Check if the author is denounced
|
||||||
const reason = denounced.get(author.toLowerCase());
|
const reason = denounced.get(author.toLowerCase());
|
||||||
if (reason === undefined) {
|
if (reason !== undefined) {
|
||||||
core.info(`User ${author} is not denounced. Allowing PR.`);
|
// Author is denounced — close the PR
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
body: 'This pull request has been automatically closed.',
|
||||||
|
});
|
||||||
|
|
||||||
|
await github.rest.pulls.update({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
pull_number: prNumber,
|
||||||
|
state: 'closed',
|
||||||
|
});
|
||||||
|
|
||||||
|
core.info(`Closed PR #${prNumber} from denounced user ${author}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Author is denounced — close the PR
|
// Author is positively vouched — add label
|
||||||
await github.rest.issues.createComment({
|
if (!vouched.has(author.toLowerCase())) {
|
||||||
|
core.info(`User ${author} is not denounced or vouched. Allowing PR.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
issue_number: prNumber,
|
issue_number: prNumber,
|
||||||
body: 'This pull request has been automatically closed.',
|
labels: ['Vouched'],
|
||||||
});
|
});
|
||||||
|
|
||||||
await github.rest.pulls.update({
|
core.info(`Added vouched label to PR #${prNumber} from ${author}`);
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
pull_number: prNumber,
|
|
||||||
state: 'closed',
|
|
||||||
});
|
|
||||||
|
|
||||||
core.info(`Closed PR #${prNumber} from denounced user ${author}`);
|
|
||||||
|
|
|
||||||
1
.github/workflows/vouch-manage-by-issue.yml
vendored
1
.github/workflows/vouch-manage-by-issue.yml
vendored
|
|
@ -33,5 +33,6 @@ jobs:
|
||||||
with:
|
with:
|
||||||
issue-id: ${{ github.event.issue.number }}
|
issue-id: ${{ github.event.issue.number }}
|
||||||
comment-id: ${{ github.event.comment.id }}
|
comment-id: ${{ github.event.comment.id }}
|
||||||
|
roles: admin,maintain
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,25 @@ Requirements:
|
||||||
- Preserve meaning, intent, tone, and formatting (including Markdown/MDX structure).
|
- Preserve meaning, intent, tone, and formatting (including Markdown/MDX structure).
|
||||||
- Preserve all technical terms and artifacts exactly: product/company names, API names, identifiers, code, commands/flags, file paths, URLs, versions, error messages, config keys/values, and anything inside inline code or code blocks.
|
- Preserve all technical terms and artifacts exactly: product/company names, API names, identifiers, code, commands/flags, file paths, URLs, versions, error messages, config keys/values, and anything inside inline code or code blocks.
|
||||||
- Also preserve every term listed in the Do-Not-Translate glossary below.
|
- Also preserve every term listed in the Do-Not-Translate glossary below.
|
||||||
|
- Also apply locale-specific guidance from `.opencode/glossary/<locale>.md` when available (for example, `zh-cn.md`).
|
||||||
- Do not modify fenced code blocks.
|
- Do not modify fenced code blocks.
|
||||||
- Output ONLY the translation (no commentary).
|
- Output ONLY the translation (no commentary).
|
||||||
|
|
||||||
If the target locale is missing, ask the user to provide it.
|
If the target locale is missing, ask the user to provide it.
|
||||||
|
If no locale-specific glossary exists, use the global glossary only.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Locale-Specific Glossaries
|
||||||
|
|
||||||
|
When a locale glossary exists, use it to:
|
||||||
|
|
||||||
|
- Apply preferred wording for recurring UI/docs terms in that locale
|
||||||
|
- Preserve locale-specific do-not-translate terms and casing decisions
|
||||||
|
- Prefer natural phrasing over literal translation when the locale file calls it out
|
||||||
|
- If the repo uses a locale alias slug, apply that file too (for example, `pt-BR` maps to `br.md` in this repo)
|
||||||
|
|
||||||
|
Locale guidance does not override code/command preservation rules or the global Do-Not-Translate glossary below.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
63
.opencode/glossary/README.md
Normal file
63
.opencode/glossary/README.md
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
# Locale Glossaries
|
||||||
|
|
||||||
|
Use this folder for locale-specific translation guidance that supplements `.opencode/agent/translator.md`.
|
||||||
|
|
||||||
|
The global glossary in `translator.md` remains the source of truth for shared do-not-translate terms (commands, code, paths, product names, etc.). These locale files capture community learnings about phrasing and terminology preferences.
|
||||||
|
|
||||||
|
## File Naming
|
||||||
|
|
||||||
|
- One file per locale
|
||||||
|
- Use lowercase locale slugs that match docs locales when possible (for example, `zh-cn.md`, `zh-tw.md`)
|
||||||
|
- If only language-level guidance exists, use the language code (for example, `fr.md`)
|
||||||
|
- Some repo locale slugs may be aliases/non-BCP47 for consistency (for example, `br` for Brazilian Portuguese / `pt-BR`)
|
||||||
|
|
||||||
|
## What To Put In A Locale File
|
||||||
|
|
||||||
|
- **Sources**: PRs/issues/discussions that motivated the guidance
|
||||||
|
- **Do Not Translate (Locale Additions)**: locale-specific terms or casing decisions
|
||||||
|
- **Preferred Terms**: recurring UI/docs words with preferred translations
|
||||||
|
- **Guidance**: tone, style, and consistency notes
|
||||||
|
- **Avoid** (optional): common literal translations or wording we should avoid
|
||||||
|
- If the repo uses a locale alias slug, document the alias in **Guidance** (for example, prose may mention `pt-BR` while config/examples use `br`)
|
||||||
|
|
||||||
|
Prefer guidance that is:
|
||||||
|
|
||||||
|
- Repeated across multiple docs/screens
|
||||||
|
- Easy to apply consistently
|
||||||
|
- Backed by a community contribution or review discussion
|
||||||
|
|
||||||
|
## Template
|
||||||
|
|
||||||
|
```md
|
||||||
|
# <locale> Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #12345: https://github.com/anomalyco/opencode/pull/12345
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
| English | Preferred | Notes |
|
||||||
|
| ------- | --------- | --------- |
|
||||||
|
| prompt | ... | preferred |
|
||||||
|
| session | ... | preferred |
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Prefer natural phrasing over literal translation
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid ... when ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contribution Notes
|
||||||
|
|
||||||
|
- Mark entries as preferred when they may evolve
|
||||||
|
- Keep examples short
|
||||||
|
- Add or update the `Sources` section whenever you add a new rule
|
||||||
|
- Prefer PR-backed guidance over invented term mappings; start with general guidance if no term-level corrections exist yet
|
||||||
28
.opencode/glossary/ar.md
Normal file
28
.opencode/glossary/ar.md
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# ar Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #9947: https://github.com/anomalyco/opencode/pull/9947
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Prefer natural Arabic phrasing over literal translation
|
||||||
|
- Keep tone clear and direct in UI labels and docs prose
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||||
|
- For RTL text, treat code, commands, and paths as LTR artifacts and keep their character order unchanged
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid translating product and protocol names that are fixed identifiers
|
||||||
|
- Avoid mixing multiple Arabic terms for the same recurring UI action once a preferred term is established
|
||||||
34
.opencode/glossary/br.md
Normal file
34
.opencode/glossary/br.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# br Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #10086: https://github.com/anomalyco/opencode/pull/10086
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- Locale code `br` in repo config, code, and paths (repo alias for Brazilian Portuguese)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
These are PR-backed locale naming preferences and may evolve.
|
||||||
|
|
||||||
|
| English / Context | Preferred | Notes |
|
||||||
|
| ---------------------------------------- | ------------------------------ | ------------------------------------------------------------- |
|
||||||
|
| Brazilian Portuguese (prose locale name) | `pt-BR` | Use standard locale naming in prose when helpful |
|
||||||
|
| Repo locale slug (code/config) | `br` | PR #10086 uses `br` for consistency/simplicity |
|
||||||
|
| Browser locale detection | `pt`, `pt-br`, `pt-BR` -> `br` | Preserve this mapping in docs/examples about locale detection |
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- This file covers Brazilian Portuguese (`pt-BR`), but the repo locale code is `br`
|
||||||
|
- Use natural Brazilian Portuguese phrasing over literal translation
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||||
|
- Keep repo locale identifiers as implemented in code/config (`br`) even when prose mentions `pt-BR`
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid changing repo locale code references from `br` to `pt-br` in code snippets, paths, or config examples
|
||||||
|
- Avoid mixing Portuguese variants when a Brazilian Portuguese form is established
|
||||||
33
.opencode/glossary/bs.md
Normal file
33
.opencode/glossary/bs.md
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# bs Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #12283: https://github.com/anomalyco/opencode/pull/12283
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
These are PR-backed locale naming preferences and may evolve.
|
||||||
|
|
||||||
|
| English / Context | Preferred | Notes |
|
||||||
|
| ---------------------------------- | ---------- | ------------------------------------------------- |
|
||||||
|
| Bosnian language label (UI) | `Bosanski` | PR #12283 tested switching language to `Bosanski` |
|
||||||
|
| Repo locale slug (code/config) | `bs` | Preserve in code, config, paths, and examples |
|
||||||
|
| Browser locale detection (Bosnian) | `bs` | PR #12283 added `bs` locale auto-detection |
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Use natural Bosnian phrasing over literal translation
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||||
|
- Keep repo locale references as `bs` in code/config, and use `Bosanski` for the user-facing language name when applicable
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid changing repo locale references from `bs` to another slug in code snippets or config examples
|
||||||
|
- Avoid translating product and protocol names that are fixed identifiers
|
||||||
27
.opencode/glossary/da.md
Normal file
27
.opencode/glossary/da.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# da Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #9821: https://github.com/anomalyco/opencode/pull/9821
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Prefer natural Danish phrasing over literal translation
|
||||||
|
- Keep tone clear and direct in UI labels and docs prose
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid translating product and protocol names that are fixed identifiers
|
||||||
|
- Avoid mixing multiple Danish terms for the same recurring UI action once a preferred term is established
|
||||||
27
.opencode/glossary/de.md
Normal file
27
.opencode/glossary/de.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# de Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #9817: https://github.com/anomalyco/opencode/pull/9817
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Prefer natural German phrasing over literal translation
|
||||||
|
- Keep tone clear and direct in UI labels and docs prose
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid translating product and protocol names that are fixed identifiers
|
||||||
|
- Avoid mixing multiple German terms for the same recurring UI action once a preferred term is established
|
||||||
27
.opencode/glossary/es.md
Normal file
27
.opencode/glossary/es.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# es Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #9817: https://github.com/anomalyco/opencode/pull/9817
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Prefer natural Spanish phrasing over literal translation
|
||||||
|
- Keep tone clear and direct in UI labels and docs prose
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid translating product and protocol names that are fixed identifiers
|
||||||
|
- Avoid mixing multiple Spanish terms for the same recurring UI action once a preferred term is established
|
||||||
27
.opencode/glossary/fr.md
Normal file
27
.opencode/glossary/fr.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# fr Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #9821: https://github.com/anomalyco/opencode/pull/9821
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Prefer natural French phrasing over literal translation
|
||||||
|
- Keep tone clear and direct in UI labels and docs prose
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid translating product and protocol names that are fixed identifiers
|
||||||
|
- Avoid mixing multiple French terms for the same recurring UI action once a preferred term is established
|
||||||
33
.opencode/glossary/ja.md
Normal file
33
.opencode/glossary/ja.md
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# ja Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #9821: https://github.com/anomalyco/opencode/pull/9821
|
||||||
|
- PR #13160: https://github.com/anomalyco/opencode/pull/13160
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
These are PR-backed wording preferences and may evolve.
|
||||||
|
|
||||||
|
| English / Context | Preferred | Notes |
|
||||||
|
| --------------------------- | ----------------------- | ------------------------------------- |
|
||||||
|
| WSL integration (UI label) | `WSL連携` | PR #13160 prefers this over `WSL統合` |
|
||||||
|
| WSL integration description | `WindowsのWSL環境で...` | PR #13160 improved phrasing naturally |
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Prefer natural Japanese phrasing over literal translation
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||||
|
- In WSL integration text, follow PR #13160 wording direction for more natural Japanese phrasing
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid `WSL統合` in the WSL integration UI context where `WSL連携` is the reviewed wording
|
||||||
|
- Avoid translating product and protocol names that are fixed identifiers
|
||||||
27
.opencode/glossary/ko.md
Normal file
27
.opencode/glossary/ko.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# ko Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #9817: https://github.com/anomalyco/opencode/pull/9817
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Prefer natural Korean phrasing over literal translation
|
||||||
|
- Keep tone clear and direct in UI labels and docs prose
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid translating product and protocol names that are fixed identifiers
|
||||||
|
- Avoid mixing multiple Korean terms for the same recurring UI action once a preferred term is established
|
||||||
38
.opencode/glossary/no.md
Normal file
38
.opencode/glossary/no.md
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# no Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #10018: https://github.com/anomalyco/opencode/pull/10018
|
||||||
|
- PR #12935: https://github.com/anomalyco/opencode/pull/12935
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- Sound names (PR #10018 notes these were intentionally left untranslated)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
These are PR-backed corrections and may evolve.
|
||||||
|
|
||||||
|
| English / Context | Preferred | Notes |
|
||||||
|
| ----------------------------------- | ------------ | ----------------------------- |
|
||||||
|
| Save (data persistence action) | `Lagre` | Prefer over `Spare` |
|
||||||
|
| Disabled (feature/state) | `deaktivert` | Prefer over `funksjonshemmet` |
|
||||||
|
| API keys | `API Nøkler` | Prefer over `API Taster` |
|
||||||
|
| Cost (noun) | `Kostnad` | Prefer over verb form `Koste` |
|
||||||
|
| Show/View (imperative button label) | `Vis` | Prefer over `Utsikt` |
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Prefer natural Norwegian Bokmal (Bokmål) wording over literal translation
|
||||||
|
- Keep tone clear and practical in UI labels
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||||
|
- Keep recurring UI terms consistent once a preferred term is chosen
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid `Spare` for save actions in persistence contexts
|
||||||
|
- Avoid `funksjonshemmet` for disabled feature states
|
||||||
|
- Avoid `API Taster`, `Koste`, and `Utsikt` in the corrected contexts above
|
||||||
27
.opencode/glossary/pl.md
Normal file
27
.opencode/glossary/pl.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# pl Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #9884: https://github.com/anomalyco/opencode/pull/9884
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Prefer natural Polish phrasing over literal translation
|
||||||
|
- Keep tone clear and direct in UI labels and docs prose
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid translating product and protocol names that are fixed identifiers
|
||||||
|
- Avoid mixing multiple Polish terms for the same recurring UI action once a preferred term is established
|
||||||
27
.opencode/glossary/ru.md
Normal file
27
.opencode/glossary/ru.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# ru Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #9882: https://github.com/anomalyco/opencode/pull/9882
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Prefer natural Russian phrasing over literal translation
|
||||||
|
- Keep tone clear and direct in UI labels and docs prose
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid translating product and protocol names that are fixed identifiers
|
||||||
|
- Avoid mixing multiple Russian terms for the same recurring UI action once a preferred term is established
|
||||||
34
.opencode/glossary/th.md
Normal file
34
.opencode/glossary/th.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# th Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #10809: https://github.com/anomalyco/opencode/pull/10809
|
||||||
|
- PR #11496: https://github.com/anomalyco/opencode/pull/11496
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
These are PR-backed preferences and may evolve.
|
||||||
|
|
||||||
|
| English / Context | Preferred | Notes |
|
||||||
|
| ------------------------------------- | --------------------- | -------------------------------------------------------------------------------- |
|
||||||
|
| Thai language label in language lists | `ไทย` | PR #10809 standardized this across locales |
|
||||||
|
| Language names in language pickers | Native names (static) | PR #11496: keep names like `English`, `Deutsch`, `ไทย` consistent across locales |
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Prefer natural Thai phrasing over literal translation
|
||||||
|
- Keep tone short and clear for buttons and labels
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||||
|
- Keep language names static/native in language pickers instead of translating them per current locale (PR #11496)
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid translating language names differently per current locale in language lists
|
||||||
|
- Avoid changing `ไทย` to another display form for the Thai language option unless the product standard changes
|
||||||
42
.opencode/glossary/zh-cn.md
Normal file
42
.opencode/glossary/zh-cn.md
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# zh-cn Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #13942: https://github.com/anomalyco/opencode/pull/13942
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only when it is part of commands, package names, paths, or code)
|
||||||
|
- `OpenCode Zen`
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- `Model Context Protocol` (prefer the English expansion when introducing `MCP`)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
These are preferred terms for docs/UI prose and may evolve.
|
||||||
|
|
||||||
|
| English | Preferred | Notes |
|
||||||
|
| ----------------------- | --------- | ------------------------------------------- |
|
||||||
|
| prompt | 提示词 | Keep `--prompt` unchanged in flags/code |
|
||||||
|
| session | 会话 | |
|
||||||
|
| provider | 提供商 | |
|
||||||
|
| share link / shared URL | 分享链接 | Prefer `分享` for user-facing share actions |
|
||||||
|
| headless (server) | 无界面 | Docs wording |
|
||||||
|
| authentication | 认证 | Prefer in auth/OAuth contexts |
|
||||||
|
| cache | 缓存 | |
|
||||||
|
| keybind / shortcut | 快捷键 | User-facing docs wording |
|
||||||
|
| workflow | 工作流 | e.g. GitHub Actions workflow |
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Prefer natural, concise phrasing over literal translation
|
||||||
|
- Keep the tone direct and friendly (PR #13942 consistently moved wording in this direction)
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, inline code, URLs, file paths, model IDs
|
||||||
|
- Keep enum-like values in English when they are literals (for example, `default`, `json`)
|
||||||
|
- Prefer consistent terminology across pages once a term is chosen (`会话`, `提供商`, `提示词`, etc.)
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid `opencode` in prose when referring to the product name; use `OpenCode`
|
||||||
|
- Avoid mixing alternative terms for the same concept across docs when a preferred term is already established
|
||||||
42
.opencode/glossary/zh-tw.md
Normal file
42
.opencode/glossary/zh-tw.md
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# zh-tw Glossary
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- PR #13942: https://github.com/anomalyco/opencode/pull/13942
|
||||||
|
|
||||||
|
## Do Not Translate (Locale Additions)
|
||||||
|
|
||||||
|
- `OpenCode` (preserve casing in prose; keep `opencode` only when it is part of commands, package names, paths, or code)
|
||||||
|
- `OpenCode Zen`
|
||||||
|
- `OpenCode CLI`
|
||||||
|
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||||
|
- `Model Context Protocol` (prefer the English expansion when introducing `MCP`)
|
||||||
|
|
||||||
|
## Preferred Terms
|
||||||
|
|
||||||
|
These are preferred terms for docs/UI prose and may evolve.
|
||||||
|
|
||||||
|
| English | Preferred | Notes |
|
||||||
|
| ----------------------- | --------- | ------------------------------------------- |
|
||||||
|
| prompt | 提示詞 | Keep `--prompt` unchanged in flags/code |
|
||||||
|
| session | 工作階段 | |
|
||||||
|
| provider | 供應商 | |
|
||||||
|
| share link / shared URL | 分享連結 | Prefer `分享` for user-facing share actions |
|
||||||
|
| headless (server) | 無介面 | Docs wording |
|
||||||
|
| authentication | 認證 | Prefer in auth/OAuth contexts |
|
||||||
|
| cache | 快取 | |
|
||||||
|
| keybind / shortcut | 快捷鍵 | User-facing docs wording |
|
||||||
|
| workflow | 工作流程 | e.g. GitHub Actions workflow |
|
||||||
|
|
||||||
|
## Guidance
|
||||||
|
|
||||||
|
- Prefer natural, concise phrasing over literal translation
|
||||||
|
- Keep the tone direct and friendly (PR #13942 consistently moved wording in this direction)
|
||||||
|
- Preserve technical artifacts exactly: commands, flags, code, inline code, URLs, file paths, model IDs
|
||||||
|
- Keep enum-like values in English when they are literals (for example, `default`, `json`)
|
||||||
|
- Prefer consistent terminology across pages once a term is chosen (`工作階段`, `供應商`, `提示詞`, etc.)
|
||||||
|
|
||||||
|
## Avoid
|
||||||
|
|
||||||
|
- Avoid `opencode` in prose when referring to the product name; use `OpenCode`
|
||||||
|
- Avoid mixing alternative terms for the same concept across docs when a preferred term is already established
|
||||||
52
bun.lock
52
bun.lock
|
|
@ -25,7 +25,7 @@
|
||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
|
|
@ -109,7 +109,7 @@
|
||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
|
|
@ -136,7 +136,7 @@
|
||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "2.0.0",
|
"@ai-sdk/anthropic": "2.0.0",
|
||||||
"@ai-sdk/openai": "2.0.2",
|
"@ai-sdk/openai": "2.0.2",
|
||||||
|
|
@ -160,7 +160,7 @@
|
||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
|
|
@ -184,7 +184,7 @@
|
||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/app": "workspace:*",
|
"@opencode-ai/app": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
|
|
@ -217,7 +217,7 @@
|
||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
|
|
@ -246,7 +246,7 @@
|
||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
|
|
@ -262,7 +262,7 @@
|
||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
|
|
@ -304,8 +304,8 @@
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
"@openrouter/ai-sdk-provider": "1.5.4",
|
"@openrouter/ai-sdk-provider": "1.5.4",
|
||||||
"@opentui/core": "0.1.79",
|
"@opentui/core": "0.1.81",
|
||||||
"@opentui/solid": "0.1.79",
|
"@opentui/solid": "0.1.81",
|
||||||
"@parcel/watcher": "2.5.1",
|
"@parcel/watcher": "2.5.1",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
"@solid-primitives/event-bus": "1.1.2",
|
"@solid-primitives/event-bus": "1.1.2",
|
||||||
|
|
@ -376,7 +376,7 @@
|
||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
|
|
@ -396,7 +396,7 @@
|
||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.90.10",
|
"@hey-api/openapi-ts": "0.90.10",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
|
|
@ -407,7 +407,7 @@
|
||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
|
|
@ -420,7 +420,7 @@
|
||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
|
|
@ -462,7 +462,7 @@
|
||||||
},
|
},
|
||||||
"packages/util": {
|
"packages/util": {
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
|
|
@ -473,7 +473,7 @@
|
||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
|
|
@ -1314,21 +1314,21 @@
|
||||||
|
|
||||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||||
|
|
||||||
"@opentui/core": ["@opentui/core@0.1.79", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.79", "@opentui/core-darwin-x64": "0.1.79", "@opentui/core-linux-arm64": "0.1.79", "@opentui/core-linux-x64": "0.1.79", "@opentui/core-win32-arm64": "0.1.79", "@opentui/core-win32-x64": "0.1.79", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-job/t09w8A/aHb/WuaVbimu5fIffyN+PCuVO5cYhXEg/NkOkC/WdFi80B8bwncR/DBPyLAh6oJ3EG86grOVo5g=="],
|
"@opentui/core": ["@opentui/core@0.1.81", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.81", "@opentui/core-darwin-x64": "0.1.81", "@opentui/core-linux-arm64": "0.1.81", "@opentui/core-linux-x64": "0.1.81", "@opentui/core-win32-arm64": "0.1.81", "@opentui/core-win32-x64": "0.1.81", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-ooFjkkQ80DDC4X5eLvH8dBcLAtWwGp9RTaWsaeWet3GOv4N0SDcN8mi1XGhYnUlTuxmofby5eQrPegjtWHODlA=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.79", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kgsGniV+DM5G1P3GideyJhvfnthNKcVCAm2mPTIr9InQ3L0gS/Feh7zgwOS/jxDvdlQbOWGKMk2Z3JApeC1MLw=="],
|
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.81", "", { "os": "darwin", "cpu": "arm64" }, "sha512-I3Ry5JbkSQXs2g1me8yYr0v3CUcIIfLHzbWz9WMFla8kQDSa+HOr8IpZbqZDeIFgOVzolAXBmZhg0VJI3bZ7MA=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.79", "", { "os": "darwin", "cpu": "x64" }, "sha512-OpyAmFqAAKQ2CeFmf/oLWcNksmP6Ryx/3R5dbKXThOudMCeQvfvInJTRbc2jTn9VFpf+Qj4BgHkJg1h90tf/EA=="],
|
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.81", "", { "os": "darwin", "cpu": "x64" }, "sha512-CrtNKu41D6+bOQdUOmDX4Q3hTL6p+sT55wugPzbDq7cdqFZabCeguBAyOlvRl2g2aJ93kmOWW6MXG0bPPklEFg=="],
|
||||||
|
|
||||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.79", "", { "os": "linux", "cpu": "arm64" }, "sha512-DCa5YaknS4bWhFt8TMEGH+qmTinyzuY8hoZbO4crtWXAxofPP7Pas76Cwxlvis/PyLffA+pPxAl1l5sUZpsvqw=="],
|
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.81", "", { "os": "linux", "cpu": "arm64" }, "sha512-FJw9zmJop9WiMvtT07nSrfBLPLqskxL6xfV3GNft0mSYV+C3hdJ0qkiczGSHUX/6V7fmouM84RWwmY53Rb6hYQ=="],
|
||||||
|
|
||||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.79", "", { "os": "linux", "cpu": "x64" }, "sha512-V6xjvFfHh3NGvsuuDae1KHPRZXHMEE8XL0A/GM6v4I4OCC23kDmkK60Vn6OptQwAzwwbz0X0IX+Ut/GQU9qGgA=="],
|
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.81", "", { "os": "linux", "cpu": "x64" }, "sha512-Rj2AFIiuWI0BEMIvh/Jeuxty9Gp5ZhLuQU7ZHJJhojKo/mpBpMs9X+5kwZPZya/tyR8uVDAVyB6AOLkhdRW5lw=="],
|
||||||
|
|
||||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.79", "", { "os": "win32", "cpu": "arm64" }, "sha512-sPRKnVzOdT5szI59tte7pxwwkYA+07EQN+6miFAvkFuiLmRUngONUD8HVjL7nCnxcPFqxaU4Rvl1y40ST86g8g=="],
|
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.81", "", { "os": "win32", "cpu": "arm64" }, "sha512-AiZB+mZ1cVr8plAPrPT98e3kw6D0OdOSe2CQYLgJRbfRlPqq3jl26lHPzDb3ZO2OR0oVGRPJvXraus939mvoiQ=="],
|
||||||
|
|
||||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.79", "", { "os": "win32", "cpu": "x64" }, "sha512-vmQcFTvKf9fqajnDtgU6/uAsiTGwx8//khqHVBmiTEXUsiT792Ki9l8sgNughbuldqG5iZOiF6IaAWU1H67UpA=="],
|
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.81", "", { "os": "win32", "cpu": "x64" }, "sha512-l8R2Ni1CR4eHi3DTmSkEL/EjHAtOZ/sndYs3VVw+Ej2esL3Mf0W7qSO5S0YNBanz2VXZhbkmM6ERm9keH8RD3w=="],
|
||||||
|
|
||||||
"@opentui/solid": ["@opentui/solid@0.1.79", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.79", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-c5+0jexKxb8GwRDDkQ/U6isZZqClAzHccXmYiLYmSnqdoQQp2lIGHLartL+K8lfIQrsKClzP2ZHumN6nexRfRg=="],
|
"@opentui/solid": ["@opentui/solid@0.1.81", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.81", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-QRjS0wPuIhBRdY8tpG3yprCM4ZnOxWWHTuaZ4hhia2wFZygf7Ome6EuZnLXmtuOQjkjCwu0if8Yik6toc6QylA=="],
|
||||||
|
|
||||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||||
|
|
||||||
|
|
@ -2226,7 +2226,7 @@
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
||||||
|
|
||||||
"bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="],
|
"bun-webgpu": ["bun-webgpu@0.1.5", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.5", "bun-webgpu-darwin-x64": "^0.1.5", "bun-webgpu-linux-x64": "^0.1.5", "bun-webgpu-win32-x64": "^0.1.5" } }, "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA=="],
|
||||||
|
|
||||||
"bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-qM7W5IaFpWYGPDcNiQ8DOng3noQ97gxpH2MFH1mGsdKwI0T4oy++egSh5Z7s6AQx8WKgc9GzAsTUM4KZkFdacw=="],
|
"bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-qM7W5IaFpWYGPDcNiQ8DOng3noQ97gxpH2MFH1mGsdKwI0T4oy++egSh5Z7s6AQx8WKgc9GzAsTUM4KZkFdacw=="],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,26 +100,46 @@ export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint",
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
const zenProduct = new stripe.Product("ZenBlack", {
|
const zenLiteProduct = new stripe.Product("ZenLite", {
|
||||||
|
name: "OpenCode Go",
|
||||||
|
})
|
||||||
|
const zenLitePrice = new stripe.Price("ZenLitePrice", {
|
||||||
|
product: zenLiteProduct.id,
|
||||||
|
currency: "usd",
|
||||||
|
recurring: {
|
||||||
|
interval: "month",
|
||||||
|
intervalCount: 1,
|
||||||
|
},
|
||||||
|
unitAmount: 1000,
|
||||||
|
})
|
||||||
|
const ZEN_LITE_PRICE = new sst.Linkable("ZEN_LITE_PRICE", {
|
||||||
|
properties: {
|
||||||
|
product: zenLiteProduct.id,
|
||||||
|
price: zenLitePrice.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const ZEN_LITE_LIMITS = new sst.Secret("ZEN_LITE_LIMITS")
|
||||||
|
|
||||||
|
const zenBlackProduct = new stripe.Product("ZenBlack", {
|
||||||
name: "OpenCode Black",
|
name: "OpenCode Black",
|
||||||
})
|
})
|
||||||
const zenPriceProps = {
|
const zenBlackPriceProps = {
|
||||||
product: zenProduct.id,
|
product: zenBlackProduct.id,
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
recurring: {
|
recurring: {
|
||||||
interval: "month",
|
interval: "month",
|
||||||
intervalCount: 1,
|
intervalCount: 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const zenPrice200 = new stripe.Price("ZenBlackPrice", { ...zenPriceProps, unitAmount: 20000 })
|
const zenBlackPrice200 = new stripe.Price("ZenBlackPrice", { ...zenBlackPriceProps, unitAmount: 20000 })
|
||||||
const zenPrice100 = new stripe.Price("ZenBlack100Price", { ...zenPriceProps, unitAmount: 10000 })
|
const zenBlackPrice100 = new stripe.Price("ZenBlack100Price", { ...zenBlackPriceProps, unitAmount: 10000 })
|
||||||
const zenPrice20 = new stripe.Price("ZenBlack20Price", { ...zenPriceProps, unitAmount: 2000 })
|
const zenBlackPrice20 = new stripe.Price("ZenBlack20Price", { ...zenBlackPriceProps, unitAmount: 2000 })
|
||||||
const ZEN_BLACK_PRICE = new sst.Linkable("ZEN_BLACK_PRICE", {
|
const ZEN_BLACK_PRICE = new sst.Linkable("ZEN_BLACK_PRICE", {
|
||||||
properties: {
|
properties: {
|
||||||
product: zenProduct.id,
|
product: zenBlackProduct.id,
|
||||||
plan200: zenPrice200.id,
|
plan200: zenBlackPrice200.id,
|
||||||
plan100: zenPrice100.id,
|
plan100: zenBlackPrice100.id,
|
||||||
plan20: zenPrice20.id,
|
plan20: zenBlackPrice20.id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const ZEN_BLACK_LIMITS = new sst.Secret("ZEN_BLACK_LIMITS")
|
const ZEN_BLACK_LIMITS = new sst.Secret("ZEN_BLACK_LIMITS")
|
||||||
|
|
@ -196,6 +216,8 @@ new sst.cloudflare.x.SolidStart("Console", {
|
||||||
AWS_SES_SECRET_ACCESS_KEY,
|
AWS_SES_SECRET_ACCESS_KEY,
|
||||||
ZEN_BLACK_PRICE,
|
ZEN_BLACK_PRICE,
|
||||||
ZEN_BLACK_LIMITS,
|
ZEN_BLACK_LIMITS,
|
||||||
|
ZEN_LITE_PRICE,
|
||||||
|
ZEN_LITE_LIMITS,
|
||||||
new sst.Secret("ZEN_SESSION_SECRET"),
|
new sst.Secret("ZEN_SESSION_SECRET"),
|
||||||
...ZEN_MODELS,
|
...ZEN_MODELS,
|
||||||
...($dev
|
...($dev
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": {
|
||||||
"x86_64-linux": "sha256-fjrvCgQ2PHYxzw8NsiEHOcor46qN95/cfilFHFqCp/k=",
|
"x86_64-linux": "sha256-3hfy6nfEnGq4J6inH0pXANw05oas+81iuayn7J0pj9c=",
|
||||||
"aarch64-linux": "sha256-xWp4LLJrbrCPFL1F6SSbProq/t/az4CqhTcymPvjOBQ=",
|
"aarch64-linux": "sha256-dxWaLtzSeI5NfHwB6u0K10yxoA0ESz/r+zTEQ3FdKFY=",
|
||||||
"aarch64-darwin": "sha256-Wbfyy/bruFHKUWsyJ2aiPXAzLkk5MNBfN6QdGPQwZS0=",
|
"aarch64-darwin": "sha256-kkK4rj4g0j2jJFXVmVH7CJcXlI8Dj/KmL/VC3iE4Z+8=",
|
||||||
"x86_64-darwin": "sha256-wDnMbiaBCRj5STkaLoVCZTdXVde+/YKfwWzwJZ1AJXQ="
|
"x86_64-darwin": "sha256-jt51irxZd48kb0BItd8InP7lfsELUh0unVYO2es+a98="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { test, expect } from "../fixtures"
|
import { test, expect } from "../fixtures"
|
||||||
import { clearSessionDockSeed, seedSessionPermission, seedSessionQuestion, seedSessionTodos } from "../actions"
|
import { clearSessionDockSeed, seedSessionQuestion, seedSessionTodos } from "../actions"
|
||||||
import {
|
import {
|
||||||
permissionDockSelector,
|
permissionDockSelector,
|
||||||
promptSelector,
|
promptSelector,
|
||||||
|
|
@ -11,11 +11,23 @@ import {
|
||||||
} from "../selectors"
|
} from "../selectors"
|
||||||
|
|
||||||
type Sdk = Parameters<typeof clearSessionDockSeed>[0]
|
type Sdk = Parameters<typeof clearSessionDockSeed>[0]
|
||||||
|
type PermissionRule = { permission: string; pattern: string; action: "allow" | "deny" | "ask" }
|
||||||
|
|
||||||
async function withDockSession<T>(sdk: Sdk, title: string, fn: (session: { id: string; title: string }) => Promise<T>) {
|
async function withDockSession<T>(
|
||||||
const session = await sdk.session.create({ title }).then((r) => r.data)
|
sdk: Sdk,
|
||||||
|
title: string,
|
||||||
|
fn: (session: { id: string; title: string }) => Promise<T>,
|
||||||
|
opts?: { permission?: PermissionRule[] },
|
||||||
|
) {
|
||||||
|
const session = await sdk.session
|
||||||
|
.create(opts?.permission ? { title, permission: opts.permission } : { title })
|
||||||
|
.then((r) => r.data)
|
||||||
if (!session?.id) throw new Error("Session create did not return an id")
|
if (!session?.id) throw new Error("Session create did not return an id")
|
||||||
return fn(session)
|
try {
|
||||||
|
return await fn(session)
|
||||||
|
} finally {
|
||||||
|
await sdk.session.delete({ sessionID: session.id }).catch(() => undefined)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test.setTimeout(120_000)
|
test.setTimeout(120_000)
|
||||||
|
|
@ -28,6 +40,85 @@ async function withDockSeed<T>(sdk: Sdk, sessionID: string, fn: () => Promise<T>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function clearPermissionDock(page: any, label: RegExp) {
|
||||||
|
const dock = page.locator(permissionDockSelector)
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const count = await dock.count()
|
||||||
|
if (count === 0) return
|
||||||
|
await dock.getByRole("button", { name: label }).click()
|
||||||
|
await page.waitForTimeout(150)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function withMockPermission<T>(
|
||||||
|
page: any,
|
||||||
|
request: {
|
||||||
|
id: string
|
||||||
|
sessionID: string
|
||||||
|
permission: string
|
||||||
|
patterns: string[]
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
always?: string[]
|
||||||
|
},
|
||||||
|
opts: { child?: any } | undefined,
|
||||||
|
fn: () => Promise<T>,
|
||||||
|
) {
|
||||||
|
let pending = [
|
||||||
|
{
|
||||||
|
...request,
|
||||||
|
always: request.always ?? ["*"],
|
||||||
|
metadata: request.metadata ?? {},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const list = async (route: any) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify(pending),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const reply = async (route: any) => {
|
||||||
|
const url = new URL(route.request().url())
|
||||||
|
const id = url.pathname.split("/").pop()
|
||||||
|
pending = pending.filter((item) => item.id !== id)
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify(true),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.route("**/permission", list)
|
||||||
|
await page.route("**/session/*/permissions/*", reply)
|
||||||
|
|
||||||
|
const sessionList = opts?.child
|
||||||
|
? async (route: any) => {
|
||||||
|
const res = await route.fetch()
|
||||||
|
const json = await res.json()
|
||||||
|
const list = Array.isArray(json) ? json : Array.isArray(json?.data) ? json.data : undefined
|
||||||
|
if (Array.isArray(list) && !list.some((item) => item?.id === opts.child?.id)) list.push(opts.child)
|
||||||
|
await route.fulfill({
|
||||||
|
status: res.status(),
|
||||||
|
headers: res.headers(),
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify(json),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
if (sessionList) await page.route("**/session?*", sessionList)
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await fn()
|
||||||
|
} finally {
|
||||||
|
await page.unroute("**/permission", list)
|
||||||
|
await page.unroute("**/session/*/permissions/*", reply)
|
||||||
|
if (sessionList) await page.unroute("**/session?*", sessionList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test("default dock shows prompt input", async ({ page, sdk, gotoSession }) => {
|
test("default dock shows prompt input", async ({ page, sdk, gotoSession }) => {
|
||||||
await withDockSession(sdk, "e2e composer dock default", async (session) => {
|
await withDockSession(sdk, "e2e composer dock default", async (session) => {
|
||||||
await gotoSession(session.id)
|
await gotoSession(session.id)
|
||||||
|
|
@ -76,72 +167,175 @@ test("blocked question flow unblocks after submit", async ({ page, sdk, gotoSess
|
||||||
|
|
||||||
test("blocked permission flow supports allow once", async ({ page, sdk, gotoSession }) => {
|
test("blocked permission flow supports allow once", async ({ page, sdk, gotoSession }) => {
|
||||||
await withDockSession(sdk, "e2e composer dock permission once", async (session) => {
|
await withDockSession(sdk, "e2e composer dock permission once", async (session) => {
|
||||||
await withDockSeed(sdk, session.id, async () => {
|
await gotoSession(session.id)
|
||||||
await gotoSession(session.id)
|
await withMockPermission(
|
||||||
|
page,
|
||||||
await seedSessionPermission(sdk, {
|
{
|
||||||
|
id: "per_e2e_once",
|
||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
permission: "bash",
|
permission: "bash",
|
||||||
patterns: ["README.md"],
|
patterns: ["/tmp/opencode-e2e-perm-once"],
|
||||||
description: "Need permission for command",
|
metadata: { description: "Need permission for command" },
|
||||||
})
|
},
|
||||||
|
undefined,
|
||||||
|
async () => {
|
||||||
|
await page.goto(page.url())
|
||||||
|
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
|
||||||
|
await expect(page.locator(promptSelector)).toHaveCount(0)
|
||||||
|
|
||||||
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
|
await clearPermissionDock(page, /allow once/i)
|
||||||
await expect(page.locator(promptSelector)).toHaveCount(0)
|
await page.goto(page.url())
|
||||||
|
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
|
||||||
await page
|
await expect(page.locator(promptSelector)).toBeVisible()
|
||||||
.locator(permissionDockSelector)
|
},
|
||||||
.getByRole("button", { name: /allow once/i })
|
)
|
||||||
.click()
|
|
||||||
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
|
|
||||||
await expect(page.locator(promptSelector)).toBeVisible()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("blocked permission flow supports reject", async ({ page, sdk, gotoSession }) => {
|
test("blocked permission flow supports reject", async ({ page, sdk, gotoSession }) => {
|
||||||
await withDockSession(sdk, "e2e composer dock permission reject", async (session) => {
|
await withDockSession(sdk, "e2e composer dock permission reject", async (session) => {
|
||||||
await withDockSeed(sdk, session.id, async () => {
|
await gotoSession(session.id)
|
||||||
await gotoSession(session.id)
|
await withMockPermission(
|
||||||
|
page,
|
||||||
await seedSessionPermission(sdk, {
|
{
|
||||||
|
id: "per_e2e_reject",
|
||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
permission: "bash",
|
permission: "bash",
|
||||||
patterns: ["REJECT.md"],
|
patterns: ["/tmp/opencode-e2e-perm-reject"],
|
||||||
})
|
},
|
||||||
|
undefined,
|
||||||
|
async () => {
|
||||||
|
await page.goto(page.url())
|
||||||
|
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
|
||||||
|
await expect(page.locator(promptSelector)).toHaveCount(0)
|
||||||
|
|
||||||
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
|
await clearPermissionDock(page, /deny/i)
|
||||||
await expect(page.locator(promptSelector)).toHaveCount(0)
|
await page.goto(page.url())
|
||||||
|
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
|
||||||
await page.locator(permissionDockSelector).getByRole("button", { name: /deny/i }).click()
|
await expect(page.locator(promptSelector)).toBeVisible()
|
||||||
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
|
},
|
||||||
await expect(page.locator(promptSelector)).toBeVisible()
|
)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("blocked permission flow supports allow always", async ({ page, sdk, gotoSession }) => {
|
test("blocked permission flow supports allow always", async ({ page, sdk, gotoSession }) => {
|
||||||
await withDockSession(sdk, "e2e composer dock permission always", async (session) => {
|
await withDockSession(sdk, "e2e composer dock permission always", async (session) => {
|
||||||
await withDockSeed(sdk, session.id, async () => {
|
await gotoSession(session.id)
|
||||||
await gotoSession(session.id)
|
await withMockPermission(
|
||||||
|
page,
|
||||||
await seedSessionPermission(sdk, {
|
{
|
||||||
|
id: "per_e2e_always",
|
||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
permission: "bash",
|
permission: "bash",
|
||||||
patterns: ["README.md"],
|
patterns: ["/tmp/opencode-e2e-perm-always"],
|
||||||
description: "Need permission for command",
|
metadata: { description: "Need permission for command" },
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
async () => {
|
||||||
|
await page.goto(page.url())
|
||||||
|
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
|
||||||
|
await expect(page.locator(promptSelector)).toHaveCount(0)
|
||||||
|
|
||||||
|
await clearPermissionDock(page, /allow always/i)
|
||||||
|
await page.goto(page.url())
|
||||||
|
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
|
||||||
|
await expect(page.locator(promptSelector)).toBeVisible()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("child session question request blocks parent dock and unblocks after submit", async ({
|
||||||
|
page,
|
||||||
|
sdk,
|
||||||
|
gotoSession,
|
||||||
|
}) => {
|
||||||
|
await withDockSession(sdk, "e2e composer dock child question parent", async (session) => {
|
||||||
|
await gotoSession(session.id)
|
||||||
|
|
||||||
|
const child = await sdk.session
|
||||||
|
.create({
|
||||||
|
title: "e2e composer dock child question",
|
||||||
|
parentID: session.id,
|
||||||
})
|
})
|
||||||
|
.then((r) => r.data)
|
||||||
|
if (!child?.id) throw new Error("Child session create did not return an id")
|
||||||
|
|
||||||
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(1)
|
try {
|
||||||
await expect(page.locator(promptSelector)).toHaveCount(0)
|
await withDockSeed(sdk, child.id, async () => {
|
||||||
|
await seedSessionQuestion(sdk, {
|
||||||
|
sessionID: child.id,
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
header: "Child input",
|
||||||
|
question: "Pick one child option",
|
||||||
|
options: [
|
||||||
|
{ label: "Continue", description: "Continue child" },
|
||||||
|
{ label: "Stop", description: "Stop child" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
await page
|
const dock = page.locator(questionDockSelector)
|
||||||
.locator(permissionDockSelector)
|
await expect.poll(() => dock.count(), { timeout: 10_000 }).toBe(1)
|
||||||
.getByRole("button", { name: /allow always/i })
|
await expect(page.locator(promptSelector)).toHaveCount(0)
|
||||||
.click()
|
|
||||||
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
|
await dock.locator('[data-slot="question-option"]').first().click()
|
||||||
await expect(page.locator(promptSelector)).toBeVisible()
|
await dock.getByRole("button", { name: /submit/i }).click()
|
||||||
})
|
|
||||||
|
await expect.poll(() => page.locator(questionDockSelector).count(), { timeout: 10_000 }).toBe(0)
|
||||||
|
await expect(page.locator(promptSelector)).toBeVisible()
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
await sdk.session.delete({ sessionID: child.id }).catch(() => undefined)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("child session permission request blocks parent dock and supports allow once", async ({
|
||||||
|
page,
|
||||||
|
sdk,
|
||||||
|
gotoSession,
|
||||||
|
}) => {
|
||||||
|
await withDockSession(sdk, "e2e composer dock child permission parent", async (session) => {
|
||||||
|
await gotoSession(session.id)
|
||||||
|
|
||||||
|
const child = await sdk.session
|
||||||
|
.create({
|
||||||
|
title: "e2e composer dock child permission",
|
||||||
|
parentID: session.id,
|
||||||
|
})
|
||||||
|
.then((r) => r.data)
|
||||||
|
if (!child?.id) throw new Error("Child session create did not return an id")
|
||||||
|
|
||||||
|
try {
|
||||||
|
await withMockPermission(
|
||||||
|
page,
|
||||||
|
{
|
||||||
|
id: "per_e2e_child",
|
||||||
|
sessionID: child.id,
|
||||||
|
permission: "bash",
|
||||||
|
patterns: ["/tmp/opencode-e2e-perm-child"],
|
||||||
|
metadata: { description: "Need child permission" },
|
||||||
|
},
|
||||||
|
{ child },
|
||||||
|
async () => {
|
||||||
|
await page.goto(page.url())
|
||||||
|
const dock = page.locator(permissionDockSelector)
|
||||||
|
await expect.poll(() => dock.count(), { timeout: 10_000 }).toBe(1)
|
||||||
|
await expect(page.locator(promptSelector)).toHaveCount(0)
|
||||||
|
|
||||||
|
await clearPermissionDock(page, /allow once/i)
|
||||||
|
await page.goto(page.url())
|
||||||
|
|
||||||
|
await expect.poll(() => page.locator(permissionDockSelector).count(), { timeout: 10_000 }).toBe(0)
|
||||||
|
await expect(page.locator(promptSelector)).toBeVisible()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
await sdk.session.delete({ sessionID: child.id }).catch(() => undefined)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||||
import { base64Encode } from "@opencode-ai/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
|
|
||||||
export const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "localhost"
|
export const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"
|
||||||
export const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
|
export const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
|
||||||
|
|
||||||
export const serverUrl = `http://${serverHost}:${serverPort}`
|
export const serverUrl = `http://${serverHost}:${serverPort}`
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { defineConfig, devices } from "@playwright/test"
|
import { defineConfig, devices } from "@playwright/test"
|
||||||
|
|
||||||
const port = Number(process.env.PLAYWRIGHT_PORT ?? 3000)
|
const port = Number(process.env.PLAYWRIGHT_PORT ?? 3000)
|
||||||
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? `http://localhost:${port}`
|
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? `http://127.0.0.1:${port}`
|
||||||
const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "localhost"
|
const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"
|
||||||
const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
|
const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
|
||||||
const command = `bun run dev -- --host 0.0.0.0 --port ${port}`
|
const command = `bun run dev -- --host 0.0.0.0 --port ${port}`
|
||||||
const reuse = !process.env.CI
|
const reuse = !process.env.CI
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { encodeFilePath } from "@/context/file/path"
|
||||||
import { Collapsible } from "@opencode-ai/ui/collapsible"
|
import { Collapsible } from "@opencode-ai/ui/collapsible"
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|
||||||
import {
|
import {
|
||||||
createEffect,
|
createEffect,
|
||||||
createMemo,
|
createMemo,
|
||||||
|
|
@ -192,59 +191,6 @@ const FileTreeNode = (
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileTreeNodeTooltip = (props: { enabled: boolean; node: FileNode; kind?: Kind; children: JSXElement }) => {
|
|
||||||
if (!props.enabled) return props.children
|
|
||||||
|
|
||||||
const parts = props.node.path.split("/")
|
|
||||||
const leaf = parts[parts.length - 1] ?? props.node.path
|
|
||||||
const head = parts.slice(0, -1).join("/")
|
|
||||||
const prefix = head ? `${head}/` : ""
|
|
||||||
const label =
|
|
||||||
props.kind === "add"
|
|
||||||
? "Additions"
|
|
||||||
: props.kind === "del"
|
|
||||||
? "Deletions"
|
|
||||||
: props.kind === "mix"
|
|
||||||
? "Modifications"
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
openDelay={2000}
|
|
||||||
placement="bottom-start"
|
|
||||||
class="w-full"
|
|
||||||
contentStyle={{ "max-width": "480px", width: "fit-content" }}
|
|
||||||
value={
|
|
||||||
<div class="flex items-center min-w-0 whitespace-nowrap text-12-regular">
|
|
||||||
<span
|
|
||||||
class="min-w-0 truncate text-text-invert-base"
|
|
||||||
style={{ direction: "rtl", "unicode-bidi": "plaintext" }}
|
|
||||||
>
|
|
||||||
{prefix}
|
|
||||||
</span>
|
|
||||||
<span class="shrink-0 text-text-invert-strong">{leaf}</span>
|
|
||||||
<Show when={label}>
|
|
||||||
{(text) => (
|
|
||||||
<>
|
|
||||||
<span class="mx-1 font-bold text-text-invert-strong">•</span>
|
|
||||||
<span class="shrink-0 text-text-invert-strong">{text()}</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
<Show when={props.node.type === "directory" && props.node.ignored}>
|
|
||||||
<>
|
|
||||||
<span class="mx-1 font-bold text-text-invert-strong">•</span>
|
|
||||||
<span class="shrink-0 text-text-invert-strong">Ignored</span>
|
|
||||||
</>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function FileTree(props: {
|
export default function FileTree(props: {
|
||||||
path: string
|
path: string
|
||||||
class?: string
|
class?: string
|
||||||
|
|
@ -255,7 +201,6 @@ export default function FileTree(props: {
|
||||||
modified?: readonly string[]
|
modified?: readonly string[]
|
||||||
kinds?: ReadonlyMap<string, Kind>
|
kinds?: ReadonlyMap<string, Kind>
|
||||||
draggable?: boolean
|
draggable?: boolean
|
||||||
tooltip?: boolean
|
|
||||||
onFileClick?: (file: FileNode) => void
|
onFileClick?: (file: FileNode) => void
|
||||||
|
|
||||||
_filter?: Filter
|
_filter?: Filter
|
||||||
|
|
@ -267,7 +212,6 @@ export default function FileTree(props: {
|
||||||
const file = useFile()
|
const file = useFile()
|
||||||
const level = props.level ?? 0
|
const level = props.level ?? 0
|
||||||
const draggable = () => props.draggable ?? true
|
const draggable = () => props.draggable ?? true
|
||||||
const tooltip = () => props.tooltip ?? true
|
|
||||||
|
|
||||||
const key = (p: string) =>
|
const key = (p: string) =>
|
||||||
file
|
file
|
||||||
|
|
@ -467,21 +411,19 @@ export default function FileTree(props: {
|
||||||
onOpenChange={(open) => (open ? file.tree.expand(node.path) : file.tree.collapse(node.path))}
|
onOpenChange={(open) => (open ? file.tree.expand(node.path) : file.tree.collapse(node.path))}
|
||||||
>
|
>
|
||||||
<Collapsible.Trigger>
|
<Collapsible.Trigger>
|
||||||
<FileTreeNodeTooltip enabled={tooltip()} node={node} kind={kind()}>
|
<FileTreeNode
|
||||||
<FileTreeNode
|
node={node}
|
||||||
node={node}
|
level={level}
|
||||||
level={level}
|
active={props.active}
|
||||||
active={props.active}
|
nodeClass={props.nodeClass}
|
||||||
nodeClass={props.nodeClass}
|
draggable={draggable()}
|
||||||
draggable={draggable()}
|
kinds={kinds()}
|
||||||
kinds={kinds()}
|
marks={marks()}
|
||||||
marks={marks()}
|
>
|
||||||
>
|
<div class="size-4 flex items-center justify-center text-icon-weak">
|
||||||
<div class="size-4 flex items-center justify-center text-icon-weak">
|
<Icon name={expanded() ? "chevron-down" : "chevron-right"} size="small" />
|
||||||
<Icon name={expanded() ? "chevron-down" : "chevron-right"} size="small" />
|
</div>
|
||||||
</div>
|
</FileTreeNode>
|
||||||
</FileTreeNode>
|
|
||||||
</FileTreeNodeTooltip>
|
|
||||||
</Collapsible.Trigger>
|
</Collapsible.Trigger>
|
||||||
<Collapsible.Content class="relative pt-0.5">
|
<Collapsible.Content class="relative pt-0.5">
|
||||||
<div
|
<div
|
||||||
|
|
@ -504,7 +446,6 @@ export default function FileTree(props: {
|
||||||
kinds={props.kinds}
|
kinds={props.kinds}
|
||||||
active={props.active}
|
active={props.active}
|
||||||
draggable={props.draggable}
|
draggable={props.draggable}
|
||||||
tooltip={props.tooltip}
|
|
||||||
onFileClick={props.onFileClick}
|
onFileClick={props.onFileClick}
|
||||||
_filter={filter()}
|
_filter={filter()}
|
||||||
_marks={marks()}
|
_marks={marks()}
|
||||||
|
|
@ -517,53 +458,51 @@ export default function FileTree(props: {
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={node.type === "file"}>
|
<Match when={node.type === "file"}>
|
||||||
<FileTreeNodeTooltip enabled={tooltip()} node={node} kind={kind()}>
|
<FileTreeNode
|
||||||
<FileTreeNode
|
node={node}
|
||||||
node={node}
|
level={level}
|
||||||
level={level}
|
active={props.active}
|
||||||
active={props.active}
|
nodeClass={props.nodeClass}
|
||||||
nodeClass={props.nodeClass}
|
draggable={draggable()}
|
||||||
draggable={draggable()}
|
kinds={kinds()}
|
||||||
kinds={kinds()}
|
marks={marks()}
|
||||||
marks={marks()}
|
as="button"
|
||||||
as="button"
|
type="button"
|
||||||
type="button"
|
onClick={() => props.onFileClick?.(node)}
|
||||||
onClick={() => props.onFileClick?.(node)}
|
>
|
||||||
>
|
<div class="w-4 shrink-0" />
|
||||||
<div class="w-4 shrink-0" />
|
<Switch>
|
||||||
<Switch>
|
<Match when={node.ignored}>
|
||||||
<Match when={node.ignored}>
|
<FileIcon
|
||||||
|
node={node}
|
||||||
|
class="size-4 filetree-icon filetree-icon--mono"
|
||||||
|
style="color: var(--icon-weak-base)"
|
||||||
|
mono
|
||||||
|
/>
|
||||||
|
</Match>
|
||||||
|
<Match when={active()}>
|
||||||
|
<FileIcon
|
||||||
|
node={node}
|
||||||
|
class="size-4 filetree-icon filetree-icon--mono"
|
||||||
|
style={kindTextColor(kind()!)}
|
||||||
|
mono
|
||||||
|
/>
|
||||||
|
</Match>
|
||||||
|
<Match when={!node.ignored}>
|
||||||
|
<span class="filetree-iconpair size-4">
|
||||||
<FileIcon
|
<FileIcon
|
||||||
node={node}
|
node={node}
|
||||||
class="size-4 filetree-icon filetree-icon--mono"
|
class="size-4 filetree-icon filetree-icon--color opacity-0 group-hover/filetree:opacity-100"
|
||||||
style="color: var(--icon-weak-base)"
|
|
||||||
mono
|
|
||||||
/>
|
/>
|
||||||
</Match>
|
|
||||||
<Match when={active()}>
|
|
||||||
<FileIcon
|
<FileIcon
|
||||||
node={node}
|
node={node}
|
||||||
class="size-4 filetree-icon filetree-icon--mono"
|
class="size-4 filetree-icon filetree-icon--mono group-hover/filetree:opacity-0"
|
||||||
style={kindTextColor(kind()!)}
|
|
||||||
mono
|
mono
|
||||||
/>
|
/>
|
||||||
</Match>
|
</span>
|
||||||
<Match when={!node.ignored}>
|
</Match>
|
||||||
<span class="filetree-iconpair size-4">
|
</Switch>
|
||||||
<FileIcon
|
</FileTreeNode>
|
||||||
node={node}
|
|
||||||
class="size-4 filetree-icon filetree-icon--color opacity-0 group-hover/filetree:opacity-100"
|
|
||||||
/>
|
|
||||||
<FileIcon
|
|
||||||
node={node}
|
|
||||||
class="size-4 filetree-icon filetree-icon--mono group-hover/filetree:opacity-0"
|
|
||||||
mono
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</FileTreeNode>
|
|
||||||
</FileTreeNodeTooltip>
|
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
|
import { AppIcon } from "@opencode-ai/ui/app-icon"
|
||||||
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
|
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||||
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
|
import { Keybind } from "@opencode-ai/ui/keybind"
|
||||||
|
import { Popover } from "@opencode-ai/ui/popover"
|
||||||
|
import { Spinner } from "@opencode-ai/ui/spinner"
|
||||||
|
import { TextField } from "@opencode-ai/ui/text-field"
|
||||||
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
|
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||||
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
|
import { useParams } from "@solidjs/router"
|
||||||
import { createEffect, createMemo, For, onCleanup, Show } from "solid-js"
|
import { createEffect, createMemo, For, onCleanup, Show } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { Portal } from "solid-js/web"
|
import { Portal } from "solid-js/web"
|
||||||
import { useParams } from "@solidjs/router"
|
|
||||||
import { useLayout } from "@/context/layout"
|
|
||||||
import { useCommand } from "@/context/command"
|
import { useCommand } from "@/context/command"
|
||||||
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { useLayout } from "@/context/layout"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { useServer } from "@/context/server"
|
import { useServer } from "@/context/server"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { useGlobalSDK } from "@/context/global-sdk"
|
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
|
||||||
import { decode64 } from "@/utils/base64"
|
import { decode64 } from "@/utils/base64"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
|
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
|
||||||
import { AppIcon } from "@opencode-ai/ui/app-icon"
|
|
||||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
|
||||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
|
||||||
import { Popover } from "@opencode-ai/ui/popover"
|
|
||||||
import { TextField } from "@opencode-ai/ui/text-field"
|
|
||||||
import { Keybind } from "@opencode-ai/ui/keybind"
|
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
|
||||||
import { StatusPopover } from "../status-popover"
|
import { StatusPopover } from "../status-popover"
|
||||||
|
|
||||||
const OPEN_APPS = [
|
const OPEN_APPS = [
|
||||||
|
|
@ -45,32 +45,67 @@ type OpenApp = (typeof OPEN_APPS)[number]
|
||||||
type OS = "macos" | "windows" | "linux" | "unknown"
|
type OS = "macos" | "windows" | "linux" | "unknown"
|
||||||
|
|
||||||
const MAC_APPS = [
|
const MAC_APPS = [
|
||||||
{ id: "vscode", label: "VS Code", icon: "vscode", openWith: "Visual Studio Code" },
|
{
|
||||||
|
id: "vscode",
|
||||||
|
label: "VS Code",
|
||||||
|
icon: "vscode",
|
||||||
|
openWith: "Visual Studio Code",
|
||||||
|
},
|
||||||
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "Cursor" },
|
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "Cursor" },
|
||||||
{ id: "zed", label: "Zed", icon: "zed", openWith: "Zed" },
|
{ id: "zed", label: "Zed", icon: "zed", openWith: "Zed" },
|
||||||
{ id: "textmate", label: "TextMate", icon: "textmate", openWith: "TextMate" },
|
{ id: "textmate", label: "TextMate", icon: "textmate", openWith: "TextMate" },
|
||||||
{ id: "antigravity", label: "Antigravity", icon: "antigravity", openWith: "Antigravity" },
|
{
|
||||||
|
id: "antigravity",
|
||||||
|
label: "Antigravity",
|
||||||
|
icon: "antigravity",
|
||||||
|
openWith: "Antigravity",
|
||||||
|
},
|
||||||
{ id: "terminal", label: "Terminal", icon: "terminal", openWith: "Terminal" },
|
{ id: "terminal", label: "Terminal", icon: "terminal", openWith: "Terminal" },
|
||||||
{ id: "iterm2", label: "iTerm2", icon: "iterm2", openWith: "iTerm" },
|
{ id: "iterm2", label: "iTerm2", icon: "iterm2", openWith: "iTerm" },
|
||||||
{ id: "ghostty", label: "Ghostty", icon: "ghostty", openWith: "Ghostty" },
|
{ id: "ghostty", label: "Ghostty", icon: "ghostty", openWith: "Ghostty" },
|
||||||
{ id: "xcode", label: "Xcode", icon: "xcode", openWith: "Xcode" },
|
{ id: "xcode", label: "Xcode", icon: "xcode", openWith: "Xcode" },
|
||||||
{ id: "android-studio", label: "Android Studio", icon: "android-studio", openWith: "Android Studio" },
|
{
|
||||||
{ id: "sublime-text", label: "Sublime Text", icon: "sublime-text", openWith: "Sublime Text" },
|
id: "android-studio",
|
||||||
|
label: "Android Studio",
|
||||||
|
icon: "android-studio",
|
||||||
|
openWith: "Android Studio",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "sublime-text",
|
||||||
|
label: "Sublime Text",
|
||||||
|
icon: "sublime-text",
|
||||||
|
openWith: "Sublime Text",
|
||||||
|
},
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
const WINDOWS_APPS = [
|
const WINDOWS_APPS = [
|
||||||
{ id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" },
|
{ id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" },
|
||||||
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" },
|
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" },
|
||||||
{ id: "zed", label: "Zed", icon: "zed", openWith: "zed" },
|
{ id: "zed", label: "Zed", icon: "zed", openWith: "zed" },
|
||||||
{ id: "powershell", label: "PowerShell", icon: "powershell", openWith: "powershell" },
|
{
|
||||||
{ id: "sublime-text", label: "Sublime Text", icon: "sublime-text", openWith: "Sublime Text" },
|
id: "powershell",
|
||||||
|
label: "PowerShell",
|
||||||
|
icon: "powershell",
|
||||||
|
openWith: "powershell",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "sublime-text",
|
||||||
|
label: "Sublime Text",
|
||||||
|
icon: "sublime-text",
|
||||||
|
openWith: "Sublime Text",
|
||||||
|
},
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
const LINUX_APPS = [
|
const LINUX_APPS = [
|
||||||
{ id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" },
|
{ id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" },
|
||||||
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" },
|
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" },
|
||||||
{ id: "zed", label: "Zed", icon: "zed", openWith: "zed" },
|
{ id: "zed", label: "Zed", icon: "zed", openWith: "zed" },
|
||||||
{ id: "sublime-text", label: "Sublime Text", icon: "sublime-text", openWith: "Sublime Text" },
|
{
|
||||||
|
id: "sublime-text",
|
||||||
|
label: "Sublime Text",
|
||||||
|
icon: "sublime-text",
|
||||||
|
openWith: "Sublime Text",
|
||||||
|
},
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
type OpenOption = (typeof MAC_APPS)[number] | (typeof WINDOWS_APPS)[number] | (typeof LINUX_APPS)[number]
|
type OpenOption = (typeof MAC_APPS)[number] | (typeof WINDOWS_APPS)[number] | (typeof LINUX_APPS)[number]
|
||||||
|
|
@ -213,7 +248,9 @@ export function SessionHeader() {
|
||||||
const view = createMemo(() => layout.view(sessionKey))
|
const view = createMemo(() => layout.view(sessionKey))
|
||||||
const os = createMemo(() => detectOS(platform))
|
const os = createMemo(() => detectOS(platform))
|
||||||
|
|
||||||
const [exists, setExists] = createStore<Partial<Record<OpenApp, boolean>>>({ finder: true })
|
const [exists, setExists] = createStore<Partial<Record<OpenApp, boolean>>>({
|
||||||
|
finder: true,
|
||||||
|
})
|
||||||
|
|
||||||
const apps = createMemo(() => {
|
const apps = createMemo(() => {
|
||||||
if (os() === "macos") return MAC_APPS
|
if (os() === "macos") return MAC_APPS
|
||||||
|
|
@ -259,18 +296,34 @@ export function SessionHeader() {
|
||||||
|
|
||||||
const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp }))
|
const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp }))
|
||||||
const [menu, setMenu] = createStore({ open: false })
|
const [menu, setMenu] = createStore({ open: false })
|
||||||
|
const [openRequest, setOpenRequest] = createStore({
|
||||||
|
app: undefined as OpenApp | undefined,
|
||||||
|
})
|
||||||
|
|
||||||
const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal())
|
const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal())
|
||||||
const current = createMemo(() => options().find((o) => o.id === prefs.app) ?? options()[0])
|
const current = createMemo(() => options().find((o) => o.id === prefs.app) ?? options()[0])
|
||||||
|
const opening = createMemo(() => openRequest.app !== undefined)
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const value = prefs.app
|
||||||
|
if (options().some((o) => o.id === value)) return
|
||||||
|
setPrefs("app", options()[0]?.id ?? "finder")
|
||||||
|
})
|
||||||
|
|
||||||
const openDir = (app: OpenApp) => {
|
const openDir = (app: OpenApp) => {
|
||||||
|
if (opening() || !canOpen() || !platform.openPath) return
|
||||||
const directory = projectDirectory()
|
const directory = projectDirectory()
|
||||||
if (!directory) return
|
if (!directory) return
|
||||||
if (!canOpen()) return
|
|
||||||
|
|
||||||
const item = options().find((o) => o.id === app)
|
const item = options().find((o) => o.id === app)
|
||||||
const openWith = item && "openWith" in item ? item.openWith : undefined
|
const openWith = item && "openWith" in item ? item.openWith : undefined
|
||||||
Promise.resolve(platform.openPath?.(directory, openWith)).catch((err: unknown) => showRequestError(language, err))
|
setOpenRequest("app", app)
|
||||||
|
platform
|
||||||
|
.openPath(directory, openWith)
|
||||||
|
.catch((err: unknown) => showRequestError(language, err))
|
||||||
|
.finally(() => {
|
||||||
|
setOpenRequest("app", undefined)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyPath = () => {
|
const copyPath = () => {
|
||||||
|
|
@ -315,7 +368,9 @@ export function SessionHeader() {
|
||||||
<div class="flex min-w-0 flex-1 items-center gap-1.5 overflow-visible">
|
<div class="flex min-w-0 flex-1 items-center gap-1.5 overflow-visible">
|
||||||
<Icon name="magnifying-glass" size="small" class="icon-base shrink-0 size-4" />
|
<Icon name="magnifying-glass" size="small" class="icon-base shrink-0 size-4" />
|
||||||
<span class="flex-1 min-w-0 text-12-regular text-text-weak truncate text-left">
|
<span class="flex-1 min-w-0 text-12-regular text-text-weak truncate text-left">
|
||||||
{language.t("session.header.search.placeholder", { project: name() })}
|
{language.t("session.header.search.placeholder", {
|
||||||
|
project: name(),
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -357,12 +412,21 @@ export function SessionHeader() {
|
||||||
<div class="flex h-[24px] box-border items-center rounded-md border border-border-weak-base bg-surface-panel overflow-hidden">
|
<div class="flex h-[24px] box-border items-center rounded-md border border-border-weak-base bg-surface-panel overflow-hidden">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="rounded-none h-full py-0 pr-3 pl-0.5 gap-1.5 border-none shadow-none"
|
class="rounded-none h-full py-0 pr-3 pl-0.5 gap-1.5 border-none shadow-none disabled:!cursor-default"
|
||||||
|
classList={{
|
||||||
|
"bg-surface-raised-base-active": opening(),
|
||||||
|
}}
|
||||||
onClick={() => openDir(current().id)}
|
onClick={() => openDir(current().id)}
|
||||||
|
disabled={opening()}
|
||||||
aria-label={language.t("session.header.open.ariaLabel", { app: current().label })}
|
aria-label={language.t("session.header.open.ariaLabel", { app: current().label })}
|
||||||
>
|
>
|
||||||
<div class="flex size-5 shrink-0 items-center justify-center">
|
<div class="flex size-5 shrink-0 items-center justify-center">
|
||||||
<AppIcon id={current().icon} class="size-4" />
|
<Show
|
||||||
|
when={opening()}
|
||||||
|
fallback={<AppIcon id={current().icon} class={openIconSize(current().icon)} />}
|
||||||
|
>
|
||||||
|
<Spinner class="size-3.5 text-icon-base" />
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-12-regular text-text-strong">Open</span>
|
<span class="text-12-regular text-text-strong">Open</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -377,7 +441,11 @@ export function SessionHeader() {
|
||||||
as={IconButton}
|
as={IconButton}
|
||||||
icon="chevron-down"
|
icon="chevron-down"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-hover"
|
disabled={opening()}
|
||||||
|
class="rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-active disabled:!cursor-default"
|
||||||
|
classList={{
|
||||||
|
"bg-surface-raised-base-active": opening(),
|
||||||
|
}}
|
||||||
aria-label={language.t("session.header.open.menu")}
|
aria-label={language.t("session.header.open.menu")}
|
||||||
/>
|
/>
|
||||||
<DropdownMenu.Portal>
|
<DropdownMenu.Portal>
|
||||||
|
|
@ -395,6 +463,7 @@ export function SessionHeader() {
|
||||||
{(o) => (
|
{(o) => (
|
||||||
<DropdownMenu.RadioItem
|
<DropdownMenu.RadioItem
|
||||||
value={o.id}
|
value={o.id}
|
||||||
|
disabled={opening()}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
setMenu("open", false)
|
setMenu("open", false)
|
||||||
openDir(o.id)
|
openDir(o.id)
|
||||||
|
|
|
||||||
|
|
@ -267,18 +267,50 @@ export const SettingsGeneral: Component = () => {
|
||||||
)}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const FeedSection = () => (
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.feed")}</h3>
|
||||||
|
|
||||||
|
<div class="bg-surface-raised-base px-4 rounded-lg">
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
title={language.t("settings.general.row.reasoningSummaries.title")}
|
title={language.t("settings.general.row.reasoningSummaries.title")}
|
||||||
description={language.t("settings.general.row.reasoningSummaries.description")}
|
description={language.t("settings.general.row.reasoningSummaries.description")}
|
||||||
>
|
>
|
||||||
<div data-action="settings-reasoning-summaries">
|
<div data-action="settings-feed-reasoning-summaries">
|
||||||
<Switch
|
<Switch
|
||||||
checked={settings.general.showReasoningSummaries()}
|
checked={settings.general.showReasoningSummaries()}
|
||||||
onChange={(checked) => settings.general.setShowReasoningSummaries(checked)}
|
onChange={(checked) => settings.general.setShowReasoningSummaries(checked)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
|
|
||||||
|
<SettingsRow
|
||||||
|
title={language.t("settings.general.row.shellToolPartsExpanded.title")}
|
||||||
|
description={language.t("settings.general.row.shellToolPartsExpanded.description")}
|
||||||
|
>
|
||||||
|
<div data-action="settings-feed-shell-tool-parts-expanded">
|
||||||
|
<Switch
|
||||||
|
checked={settings.general.shellToolPartsExpanded()}
|
||||||
|
onChange={(checked) => settings.general.setShellToolPartsExpanded(checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SettingsRow>
|
||||||
|
|
||||||
|
<SettingsRow
|
||||||
|
title={language.t("settings.general.row.editToolPartsExpanded.title")}
|
||||||
|
description={language.t("settings.general.row.editToolPartsExpanded.description")}
|
||||||
|
>
|
||||||
|
<div data-action="settings-feed-edit-tool-parts-expanded">
|
||||||
|
<Switch
|
||||||
|
checked={settings.general.editToolPartsExpanded()}
|
||||||
|
onChange={(checked) => settings.general.setEditToolPartsExpanded(checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SettingsRow>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -435,6 +467,8 @@ export const SettingsGeneral: Component = () => {
|
||||||
<div class="flex flex-col gap-8 w-full">
|
<div class="flex flex-col gap-8 w-full">
|
||||||
<AppearanceSection />
|
<AppearanceSection />
|
||||||
|
|
||||||
|
<FeedSection />
|
||||||
|
|
||||||
<NotificationsSection />
|
<NotificationsSection />
|
||||||
|
|
||||||
<SoundsSection />
|
<SoundsSection />
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,14 @@ describe("file path helpers", () => {
|
||||||
expect(path.pathFromTab("other://src/app.ts")).toBeUndefined()
|
expect(path.pathFromTab("other://src/app.ts")).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("normalizes Windows absolute paths with mixed separators", () => {
|
||||||
|
const path = createPathHelpers(() => "C:\\repo")
|
||||||
|
expect(path.normalize("C:\\repo\\src\\app.ts")).toBe("src\\app.ts")
|
||||||
|
expect(path.normalize("C:/repo/src/app.ts")).toBe("src/app.ts")
|
||||||
|
expect(path.normalize("file://C:/repo/src/app.ts")).toBe("src/app.ts")
|
||||||
|
expect(path.normalize("c:\\repo\\src\\app.ts")).toBe("src\\app.ts")
|
||||||
|
})
|
||||||
|
|
||||||
test("keeps query/hash stripping behavior stable", () => {
|
test("keeps query/hash stripping behavior stable", () => {
|
||||||
expect(stripQueryAndHash("a/b.ts#L12?x=1")).toBe("a/b.ts")
|
expect(stripQueryAndHash("a/b.ts#L12?x=1")).toBe("a/b.ts")
|
||||||
expect(stripQueryAndHash("a/b.ts?x=1#L12")).toBe("a/b.ts")
|
expect(stripQueryAndHash("a/b.ts?x=1#L12")).toBe("a/b.ts")
|
||||||
|
|
|
||||||
|
|
@ -104,26 +104,29 @@ export function encodeFilePath(filepath: string): string {
|
||||||
export function createPathHelpers(scope: () => string) {
|
export function createPathHelpers(scope: () => string) {
|
||||||
const normalize = (input: string) => {
|
const normalize = (input: string) => {
|
||||||
const root = scope()
|
const root = scope()
|
||||||
const prefix = root.endsWith("/") ? root : root + "/"
|
|
||||||
|
|
||||||
let path = unquoteGitPath(decodeFilePath(stripQueryAndHash(stripFileProtocol(input))))
|
let path = unquoteGitPath(decodeFilePath(stripQueryAndHash(stripFileProtocol(input))))
|
||||||
|
|
||||||
if (path.startsWith(prefix)) {
|
// Separator-agnostic prefix stripping for Cygwin/native Windows compatibility
|
||||||
path = path.slice(prefix.length)
|
// Only case-insensitive on Windows (drive letter or UNC paths)
|
||||||
}
|
const windows = /^[A-Za-z]:/.test(root) || root.startsWith("\\\\")
|
||||||
|
const canonRoot = windows ? root.replace(/\\/g, "/").toLowerCase() : root.replace(/\\/g, "/")
|
||||||
if (path.startsWith(root)) {
|
const canonPath = windows ? path.replace(/\\/g, "/").toLowerCase() : path.replace(/\\/g, "/")
|
||||||
|
if (
|
||||||
|
canonPath.startsWith(canonRoot) &&
|
||||||
|
(canonRoot.endsWith("/") || canonPath === canonRoot || canonPath[canonRoot.length] === "/")
|
||||||
|
) {
|
||||||
|
// Slice from original path to preserve native separators
|
||||||
path = path.slice(root.length)
|
path = path.slice(root.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.startsWith("./")) {
|
if (path.startsWith("./") || path.startsWith(".\\")) {
|
||||||
path = path.slice(2)
|
path = path.slice(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.startsWith("/")) {
|
if (path.startsWith("/") || path.startsWith("\\")) {
|
||||||
path = path.slice(1)
|
path = path.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,12 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
||||||
let queue: Queued[] = []
|
let queue: Queued[] = []
|
||||||
let buffer: Queued[] = []
|
let buffer: Queued[] = []
|
||||||
const coalesced = new Map<string, number>()
|
const coalesced = new Map<string, number>()
|
||||||
|
const staleDeltas = new Set<string>()
|
||||||
let timer: ReturnType<typeof setTimeout> | undefined
|
let timer: ReturnType<typeof setTimeout> | undefined
|
||||||
let last = 0
|
let last = 0
|
||||||
|
|
||||||
|
const deltaKey = (directory: string, messageID: string, partID: string) => `${directory}:${messageID}:${partID}`
|
||||||
|
|
||||||
const key = (directory: string, payload: Event) => {
|
const key = (directory: string, payload: Event) => {
|
||||||
if (payload.type === "session.status") return `session.status:${directory}:${payload.properties.sessionID}`
|
if (payload.type === "session.status") return `session.status:${directory}:${payload.properties.sessionID}`
|
||||||
if (payload.type === "lsp.updated") return `lsp.updated:${directory}`
|
if (payload.type === "lsp.updated") return `lsp.updated:${directory}`
|
||||||
|
|
@ -68,14 +71,20 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
||||||
if (queue.length === 0) return
|
if (queue.length === 0) return
|
||||||
|
|
||||||
const events = queue
|
const events = queue
|
||||||
|
const skip = staleDeltas.size > 0 ? new Set(staleDeltas) : undefined
|
||||||
queue = buffer
|
queue = buffer
|
||||||
buffer = events
|
buffer = events
|
||||||
queue.length = 0
|
queue.length = 0
|
||||||
coalesced.clear()
|
coalesced.clear()
|
||||||
|
staleDeltas.clear()
|
||||||
|
|
||||||
last = Date.now()
|
last = Date.now()
|
||||||
batch(() => {
|
batch(() => {
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
|
if (skip && event.payload.type === "message.part.delta") {
|
||||||
|
const props = event.payload.properties
|
||||||
|
if (skip.has(deltaKey(event.directory, props.messageID, props.partID))) continue
|
||||||
|
}
|
||||||
emitter.emit(event.directory, event.payload)
|
emitter.emit(event.directory, event.payload)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -144,6 +153,10 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
|
||||||
const i = coalesced.get(k)
|
const i = coalesced.get(k)
|
||||||
if (i !== undefined) {
|
if (i !== undefined) {
|
||||||
queue[i] = { directory, payload }
|
queue[i] = { directory, payload }
|
||||||
|
if (payload.type === "message.part.updated") {
|
||||||
|
const part = payload.properties.part
|
||||||
|
staleDeltas.add(deltaKey(directory, part.messageID, part.id))
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
coalesced.set(k, queue.length)
|
coalesced.set(k, queue.length)
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import type { ProjectMeta } from "./global-sync/types"
|
||||||
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
|
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
|
||||||
import { sanitizeProject } from "./global-sync/utils"
|
import { sanitizeProject } from "./global-sync/utils"
|
||||||
import { usePlatform } from "./platform"
|
import { usePlatform } from "./platform"
|
||||||
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
|
|
||||||
type GlobalStore = {
|
type GlobalStore = {
|
||||||
ready: boolean
|
ready: boolean
|
||||||
|
|
@ -51,12 +52,6 @@ type GlobalStore = {
|
||||||
reload: undefined | "pending" | "complete"
|
reload: undefined | "pending" | "complete"
|
||||||
}
|
}
|
||||||
|
|
||||||
function errorMessage(error: unknown) {
|
|
||||||
if (error instanceof Error && error.message) return error.message
|
|
||||||
if (typeof error === "string" && error) return error
|
|
||||||
return "Unknown error"
|
|
||||||
}
|
|
||||||
|
|
||||||
function createGlobalSync() {
|
function createGlobalSync() {
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
|
|
@ -207,8 +202,9 @@ function createGlobalSync() {
|
||||||
console.error("Failed to load sessions", err)
|
console.error("Failed to load sessions", err)
|
||||||
const project = getFilename(directory)
|
const project = getFilename(directory)
|
||||||
showToast({
|
showToast({
|
||||||
|
variant: "error",
|
||||||
title: language.t("toast.session.listFailed.title", { project }),
|
title: language.t("toast.session.listFailed.title", { project }),
|
||||||
description: errorMessage(err),
|
description: formatServerError(err),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { batch } from "solid-js"
|
||||||
import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
|
import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
|
||||||
import type { State, VcsCache } from "./types"
|
import type { State, VcsCache } from "./types"
|
||||||
import { cmp, normalizeProviderList } from "./utils"
|
import { cmp, normalizeProviderList } from "./utils"
|
||||||
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
|
|
||||||
type GlobalStore = {
|
type GlobalStore = {
|
||||||
ready: boolean
|
ready: boolean
|
||||||
|
|
@ -133,8 +134,11 @@ export async function bootstrapDirectory(input: {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to bootstrap instance", err)
|
console.error("Failed to bootstrap instance", err)
|
||||||
const project = getFilename(input.directory)
|
const project = getFilename(input.directory)
|
||||||
const message = err instanceof Error ? err.message : String(err)
|
showToast({
|
||||||
showToast({ title: `Failed to reload ${project}`, description: message })
|
variant: "error",
|
||||||
|
title: `Failed to reload ${project}`,
|
||||||
|
description: formatServerError(err),
|
||||||
|
})
|
||||||
input.setStore("status", "partial")
|
input.setStore("status", "partial")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ export interface Settings {
|
||||||
autoSave: boolean
|
autoSave: boolean
|
||||||
releaseNotes: boolean
|
releaseNotes: boolean
|
||||||
showReasoningSummaries: boolean
|
showReasoningSummaries: boolean
|
||||||
|
shellToolPartsExpanded: boolean
|
||||||
|
editToolPartsExpanded: boolean
|
||||||
}
|
}
|
||||||
updates: {
|
updates: {
|
||||||
startup: boolean
|
startup: boolean
|
||||||
|
|
@ -44,6 +46,8 @@ const defaultSettings: Settings = {
|
||||||
autoSave: true,
|
autoSave: true,
|
||||||
releaseNotes: true,
|
releaseNotes: true,
|
||||||
showReasoningSummaries: false,
|
showReasoningSummaries: false,
|
||||||
|
shellToolPartsExpanded: true,
|
||||||
|
editToolPartsExpanded: false,
|
||||||
},
|
},
|
||||||
updates: {
|
updates: {
|
||||||
startup: true,
|
startup: true,
|
||||||
|
|
@ -129,6 +133,20 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
||||||
setShowReasoningSummaries(value: boolean) {
|
setShowReasoningSummaries(value: boolean) {
|
||||||
setStore("general", "showReasoningSummaries", value)
|
setStore("general", "showReasoningSummaries", value)
|
||||||
},
|
},
|
||||||
|
shellToolPartsExpanded: withFallback(
|
||||||
|
() => store.general?.shellToolPartsExpanded,
|
||||||
|
defaultSettings.general.shellToolPartsExpanded,
|
||||||
|
),
|
||||||
|
setShellToolPartsExpanded(value: boolean) {
|
||||||
|
setStore("general", "shellToolPartsExpanded", value)
|
||||||
|
},
|
||||||
|
editToolPartsExpanded: withFallback(
|
||||||
|
() => store.general?.editToolPartsExpanded,
|
||||||
|
defaultSettings.general.editToolPartsExpanded,
|
||||||
|
),
|
||||||
|
setEditToolPartsExpanded(value: boolean) {
|
||||||
|
setStore("general", "editToolPartsExpanded", value)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
updates: {
|
updates: {
|
||||||
startup: withFallback(() => store.updates?.startup, defaultSettings.updates.startup),
|
startup: withFallback(() => store.updates?.startup, defaultSettings.updates.startup),
|
||||||
|
|
|
||||||
|
|
@ -529,6 +529,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "إشعارات النظام",
|
"settings.general.section.notifications": "إشعارات النظام",
|
||||||
"settings.general.section.updates": "التحديثات",
|
"settings.general.section.updates": "التحديثات",
|
||||||
"settings.general.section.sounds": "المؤثرات الصوتية",
|
"settings.general.section.sounds": "المؤثرات الصوتية",
|
||||||
|
"settings.general.section.feed": "الخلاصة",
|
||||||
"settings.general.section.display": "شاشة العرض",
|
"settings.general.section.display": "شاشة العرض",
|
||||||
"settings.general.row.language.title": "اللغة",
|
"settings.general.row.language.title": "اللغة",
|
||||||
"settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode",
|
"settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode",
|
||||||
|
|
@ -538,6 +539,12 @@ export const dict = {
|
||||||
"settings.general.row.theme.description": "تخصيص سمة OpenCode.",
|
"settings.general.row.theme.description": "تخصيص سمة OpenCode.",
|
||||||
"settings.general.row.font.title": "الخط",
|
"settings.general.row.font.title": "الخط",
|
||||||
"settings.general.row.font.description": "تخصيص الخط الأحادي المستخدم في كتل التعليمات البرمجية",
|
"settings.general.row.font.description": "تخصيص الخط الأحادي المستخدم في كتل التعليمات البرمجية",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "توسيع أجزاء أداة shell",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description":
|
||||||
|
"إظهار أجزاء أداة shell موسعة بشكل افتراضي في الشريط الزمني",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "توسيع أجزاء أداة edit",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
|
"إظهار أجزاء أدوات edit و write و patch موسعة بشكل افتراضي في الشريط الزمني",
|
||||||
"settings.general.row.wayland.title": "استخدام Wayland الأصلي",
|
"settings.general.row.wayland.title": "استخدام Wayland الأصلي",
|
||||||
"settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.",
|
"settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
|
|
|
||||||
|
|
@ -535,6 +535,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "Notificações do sistema",
|
"settings.general.section.notifications": "Notificações do sistema",
|
||||||
"settings.general.section.updates": "Atualizações",
|
"settings.general.section.updates": "Atualizações",
|
||||||
"settings.general.section.sounds": "Efeitos sonoros",
|
"settings.general.section.sounds": "Efeitos sonoros",
|
||||||
|
"settings.general.section.feed": "Feed",
|
||||||
"settings.general.section.display": "Tela",
|
"settings.general.section.display": "Tela",
|
||||||
"settings.general.row.language.title": "Idioma",
|
"settings.general.row.language.title": "Idioma",
|
||||||
"settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode",
|
"settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode",
|
||||||
|
|
@ -544,6 +545,12 @@ export const dict = {
|
||||||
"settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
|
"settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
|
||||||
"settings.general.row.font.title": "Fonte",
|
"settings.general.row.font.title": "Fonte",
|
||||||
"settings.general.row.font.description": "Personalize a fonte monoespaçada usada em blocos de código",
|
"settings.general.row.font.description": "Personalize a fonte monoespaçada usada em blocos de código",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "Expandir partes da ferramenta shell",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description":
|
||||||
|
"Mostrar partes da ferramenta shell expandidas por padrão na linha do tempo",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "Expandir partes da ferramenta de edição",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
|
"Mostrar partes das ferramentas de edição, escrita e patch expandidas por padrão na linha do tempo",
|
||||||
"settings.general.row.wayland.title": "Usar Wayland nativo",
|
"settings.general.row.wayland.title": "Usar Wayland nativo",
|
||||||
"settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.",
|
"settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ export const dict = {
|
||||||
"dialog.provider.tag.recommended": "Preporučeno",
|
"dialog.provider.tag.recommended": "Preporučeno",
|
||||||
"dialog.provider.opencode.note": "Kurirani modeli uključujući Claude, GPT, Gemini i druge",
|
"dialog.provider.opencode.note": "Kurirani modeli uključujući Claude, GPT, Gemini i druge",
|
||||||
"dialog.provider.anthropic.note": "Direktan pristup Claude modelima, uključujući Pro i Max",
|
"dialog.provider.anthropic.note": "Direktan pristup Claude modelima, uključujući Pro i Max",
|
||||||
"dialog.provider.copilot.note": "Claude modeli za pomoć pri kodiranju",
|
"dialog.provider.copilot.note": "AI modeli za pomoć pri kodiranju putem GitHub Copilot",
|
||||||
"dialog.provider.openai.note": "GPT modeli za brze, sposobne opšte AI zadatke",
|
"dialog.provider.openai.note": "GPT modeli za brze, sposobne opšte AI zadatke",
|
||||||
"dialog.provider.google.note": "Gemini modeli za brze, strukturirane odgovore",
|
"dialog.provider.google.note": "Gemini modeli za brze, strukturirane odgovore",
|
||||||
"dialog.provider.openrouter.note": "Pristup svim podržanim modelima preko jednog provajdera",
|
"dialog.provider.openrouter.note": "Pristup svim podržanim modelima preko jednog provajdera",
|
||||||
|
|
@ -599,6 +599,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "Sistemske obavijesti",
|
"settings.general.section.notifications": "Sistemske obavijesti",
|
||||||
"settings.general.section.updates": "Ažuriranja",
|
"settings.general.section.updates": "Ažuriranja",
|
||||||
"settings.general.section.sounds": "Zvučni efekti",
|
"settings.general.section.sounds": "Zvučni efekti",
|
||||||
|
"settings.general.section.feed": "Feed",
|
||||||
"settings.general.section.display": "Prikaz",
|
"settings.general.section.display": "Prikaz",
|
||||||
|
|
||||||
"settings.general.row.language.title": "Jezik",
|
"settings.general.row.language.title": "Jezik",
|
||||||
|
|
@ -610,6 +611,12 @@ export const dict = {
|
||||||
"settings.general.row.font.title": "Font",
|
"settings.general.row.font.title": "Font",
|
||||||
"settings.general.row.font.description": "Prilagodi monospace font koji se koristi u blokovima koda",
|
"settings.general.row.font.description": "Prilagodi monospace font koji se koristi u blokovima koda",
|
||||||
|
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "Proširi dijelove shell alata",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description":
|
||||||
|
"Prikaži dijelove shell alata podrazumijevano proširene na vremenskoj traci",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "Proširi dijelove alata za uređivanje",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
|
"Prikaži dijelove alata za uređivanje, pisanje i patch podrazumijevano proširene na vremenskoj traci",
|
||||||
"settings.general.row.wayland.title": "Koristi nativni Wayland",
|
"settings.general.row.wayland.title": "Koristi nativni Wayland",
|
||||||
"settings.general.row.wayland.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.",
|
"settings.general.row.wayland.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ export const dict = {
|
||||||
"dialog.provider.tag.recommended": "Anbefalet",
|
"dialog.provider.tag.recommended": "Anbefalet",
|
||||||
"dialog.provider.opencode.note": "Udvalgte modeller inklusive Claude, GPT, Gemini og flere",
|
"dialog.provider.opencode.note": "Udvalgte modeller inklusive Claude, GPT, Gemini og flere",
|
||||||
"dialog.provider.anthropic.note": "Direkte adgang til Claude-modeller, inklusive Pro og Max",
|
"dialog.provider.anthropic.note": "Direkte adgang til Claude-modeller, inklusive Pro og Max",
|
||||||
"dialog.provider.copilot.note": "Claude-modeller til kodningsassistance",
|
"dialog.provider.copilot.note": "AI-modeller til kodningsassistance via GitHub Copilot",
|
||||||
"dialog.provider.openai.note": "GPT-modeller til hurtige, kompetente generelle AI-opgaver",
|
"dialog.provider.openai.note": "GPT-modeller til hurtige, kompetente generelle AI-opgaver",
|
||||||
"dialog.provider.google.note": "Gemini-modeller til hurtige, strukturerede svar",
|
"dialog.provider.google.note": "Gemini-modeller til hurtige, strukturerede svar",
|
||||||
"dialog.provider.openrouter.note": "Få adgang til alle understøttede modeller fra én udbyder",
|
"dialog.provider.openrouter.note": "Få adgang til alle understøttede modeller fra én udbyder",
|
||||||
|
|
@ -594,6 +594,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "Systemmeddelelser",
|
"settings.general.section.notifications": "Systemmeddelelser",
|
||||||
"settings.general.section.updates": "Opdateringer",
|
"settings.general.section.updates": "Opdateringer",
|
||||||
"settings.general.section.sounds": "Lydeffekter",
|
"settings.general.section.sounds": "Lydeffekter",
|
||||||
|
"settings.general.section.feed": "Feed",
|
||||||
"settings.general.section.display": "Skærm",
|
"settings.general.section.display": "Skærm",
|
||||||
|
|
||||||
"settings.general.row.language.title": "Sprog",
|
"settings.general.row.language.title": "Sprog",
|
||||||
|
|
@ -605,6 +606,11 @@ export const dict = {
|
||||||
"settings.general.row.font.title": "Skrifttype",
|
"settings.general.row.font.title": "Skrifttype",
|
||||||
"settings.general.row.font.description": "Tilpas mono-skrifttypen brugt i kodeblokke",
|
"settings.general.row.font.description": "Tilpas mono-skrifttypen brugt i kodeblokke",
|
||||||
|
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "Udvid shell-værktøjsdele",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description": "Vis shell-værktøjsdele udvidet som standard i tidslinjen",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "Udvid edit-værktøjsdele",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
|
"Vis edit-, write- og patch-værktøjsdele udvidet som standard i tidslinjen",
|
||||||
"settings.general.row.wayland.title": "Brug native Wayland",
|
"settings.general.row.wayland.title": "Brug native Wayland",
|
||||||
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.",
|
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
|
|
|
||||||
|
|
@ -544,6 +544,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "Systembenachrichtigungen",
|
"settings.general.section.notifications": "Systembenachrichtigungen",
|
||||||
"settings.general.section.updates": "Updates",
|
"settings.general.section.updates": "Updates",
|
||||||
"settings.general.section.sounds": "Soundeffekte",
|
"settings.general.section.sounds": "Soundeffekte",
|
||||||
|
"settings.general.section.feed": "Feed",
|
||||||
"settings.general.section.display": "Anzeige",
|
"settings.general.section.display": "Anzeige",
|
||||||
"settings.general.row.language.title": "Sprache",
|
"settings.general.row.language.title": "Sprache",
|
||||||
"settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern",
|
"settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern",
|
||||||
|
|
@ -553,6 +554,12 @@ export const dict = {
|
||||||
"settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
|
"settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
|
||||||
"settings.general.row.font.title": "Schriftart",
|
"settings.general.row.font.title": "Schriftart",
|
||||||
"settings.general.row.font.description": "Die in Codeblöcken verwendete Monospace-Schriftart anpassen",
|
"settings.general.row.font.description": "Die in Codeblöcken verwendete Monospace-Schriftart anpassen",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "Shell-Tool-Abschnitte ausklappen",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description":
|
||||||
|
"Shell-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "Edit-Tool-Abschnitte ausklappen",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
|
"Edit-, Write- und Patch-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen",
|
||||||
"settings.general.row.wayland.title": "Natives Wayland verwenden",
|
"settings.general.row.wayland.title": "Natives Wayland verwenden",
|
||||||
"settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.",
|
"settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ export const dict = {
|
||||||
"dialog.provider.tag.recommended": "Recommended",
|
"dialog.provider.tag.recommended": "Recommended",
|
||||||
"dialog.provider.opencode.note": "Curated models including Claude, GPT, Gemini and more",
|
"dialog.provider.opencode.note": "Curated models including Claude, GPT, Gemini and more",
|
||||||
"dialog.provider.anthropic.note": "Direct access to Claude models, including Pro and Max",
|
"dialog.provider.anthropic.note": "Direct access to Claude models, including Pro and Max",
|
||||||
"dialog.provider.copilot.note": "Claude models for coding assistance",
|
"dialog.provider.copilot.note": "AI models for coding assistance via GitHub Copilot",
|
||||||
"dialog.provider.openai.note": "GPT models for fast, capable general AI tasks",
|
"dialog.provider.openai.note": "GPT models for fast, capable general AI tasks",
|
||||||
"dialog.provider.google.note": "Gemini models for fast, structured responses",
|
"dialog.provider.google.note": "Gemini models for fast, structured responses",
|
||||||
"dialog.provider.openrouter.note": "Access all supported models from one provider",
|
"dialog.provider.openrouter.note": "Access all supported models from one provider",
|
||||||
|
|
@ -495,6 +495,7 @@ export const dict = {
|
||||||
"session.review.change.other": "Changes",
|
"session.review.change.other": "Changes",
|
||||||
"session.review.loadingChanges": "Loading changes...",
|
"session.review.loadingChanges": "Loading changes...",
|
||||||
"session.review.empty": "No changes in this session yet",
|
"session.review.empty": "No changes in this session yet",
|
||||||
|
"session.review.noVcs": "No git VCS detected, so session changes will not be detected",
|
||||||
"session.review.noChanges": "No changes",
|
"session.review.noChanges": "No changes",
|
||||||
|
|
||||||
"session.files.selectToOpen": "Select a file to open",
|
"session.files.selectToOpen": "Select a file to open",
|
||||||
|
|
@ -600,6 +601,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "System notifications",
|
"settings.general.section.notifications": "System notifications",
|
||||||
"settings.general.section.updates": "Updates",
|
"settings.general.section.updates": "Updates",
|
||||||
"settings.general.section.sounds": "Sound effects",
|
"settings.general.section.sounds": "Sound effects",
|
||||||
|
"settings.general.section.feed": "Feed",
|
||||||
"settings.general.section.display": "Display",
|
"settings.general.section.display": "Display",
|
||||||
|
|
||||||
"settings.general.row.language.title": "Language",
|
"settings.general.row.language.title": "Language",
|
||||||
|
|
@ -612,6 +614,12 @@ export const dict = {
|
||||||
"settings.general.row.font.description": "Customise the mono font used in code blocks",
|
"settings.general.row.font.description": "Customise the mono font used in code blocks",
|
||||||
"settings.general.row.reasoningSummaries.title": "Show reasoning summaries",
|
"settings.general.row.reasoningSummaries.title": "Show reasoning summaries",
|
||||||
"settings.general.row.reasoningSummaries.description": "Display model reasoning summaries in the timeline",
|
"settings.general.row.reasoningSummaries.description": "Display model reasoning summaries in the timeline",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "Expand shell tool parts",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description":
|
||||||
|
"Show shell tool parts expanded by default in the timeline",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "Expand edit tool parts",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
|
"Show edit, write, and patch tool parts expanded by default in the timeline",
|
||||||
|
|
||||||
"settings.general.row.wayland.title": "Use native Wayland",
|
"settings.general.row.wayland.title": "Use native Wayland",
|
||||||
"settings.general.row.wayland.description": "Disable X11 fallback on Wayland. Requires restart.",
|
"settings.general.row.wayland.description": "Disable X11 fallback on Wayland. Requires restart.",
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ export const dict = {
|
||||||
"dialog.provider.tag.recommended": "Recomendado",
|
"dialog.provider.tag.recommended": "Recomendado",
|
||||||
"dialog.provider.opencode.note": "Modelos seleccionados incluyendo Claude, GPT, Gemini y más",
|
"dialog.provider.opencode.note": "Modelos seleccionados incluyendo Claude, GPT, Gemini y más",
|
||||||
"dialog.provider.anthropic.note": "Acceso directo a modelos Claude, incluyendo Pro y Max",
|
"dialog.provider.anthropic.note": "Acceso directo a modelos Claude, incluyendo Pro y Max",
|
||||||
"dialog.provider.copilot.note": "Modelos Claude para asistencia de codificación",
|
"dialog.provider.copilot.note": "Modelos de IA para asistencia de codificación a través de GitHub Copilot",
|
||||||
"dialog.provider.openai.note": "Modelos GPT para tareas de IA generales rápidas y capaces",
|
"dialog.provider.openai.note": "Modelos GPT para tareas de IA generales rápidas y capaces",
|
||||||
"dialog.provider.google.note": "Modelos Gemini para respuestas rápidas y estructuradas",
|
"dialog.provider.google.note": "Modelos Gemini para respuestas rápidas y estructuradas",
|
||||||
"dialog.provider.openrouter.note": "Accede a todos los modelos soportados desde un solo proveedor",
|
"dialog.provider.openrouter.note": "Accede a todos los modelos soportados desde un solo proveedor",
|
||||||
|
|
@ -602,6 +602,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "Notificaciones del sistema",
|
"settings.general.section.notifications": "Notificaciones del sistema",
|
||||||
"settings.general.section.updates": "Actualizaciones",
|
"settings.general.section.updates": "Actualizaciones",
|
||||||
"settings.general.section.sounds": "Efectos de sonido",
|
"settings.general.section.sounds": "Efectos de sonido",
|
||||||
|
"settings.general.section.feed": "Feed",
|
||||||
"settings.general.section.display": "Pantalla",
|
"settings.general.section.display": "Pantalla",
|
||||||
|
|
||||||
"settings.general.row.language.title": "Idioma",
|
"settings.general.row.language.title": "Idioma",
|
||||||
|
|
@ -613,6 +614,12 @@ export const dict = {
|
||||||
"settings.general.row.font.title": "Fuente",
|
"settings.general.row.font.title": "Fuente",
|
||||||
"settings.general.row.font.description": "Personaliza la fuente monoespaciada usada en bloques de código",
|
"settings.general.row.font.description": "Personaliza la fuente monoespaciada usada en bloques de código",
|
||||||
|
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "Expandir partes de la herramienta shell",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description":
|
||||||
|
"Mostrar las partes de la herramienta shell expandidas por defecto en la línea de tiempo",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "Expandir partes de la herramienta de edición",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
|
"Mostrar las partes de las herramientas de edición, escritura y parcheado expandidas por defecto en la línea de tiempo",
|
||||||
"settings.general.row.wayland.title": "Usar Wayland nativo",
|
"settings.general.row.wayland.title": "Usar Wayland nativo",
|
||||||
"settings.general.row.wayland.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.",
|
"settings.general.row.wayland.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
|
|
|
||||||
|
|
@ -543,6 +543,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "Notifications système",
|
"settings.general.section.notifications": "Notifications système",
|
||||||
"settings.general.section.updates": "Mises à jour",
|
"settings.general.section.updates": "Mises à jour",
|
||||||
"settings.general.section.sounds": "Effets sonores",
|
"settings.general.section.sounds": "Effets sonores",
|
||||||
|
"settings.general.section.feed": "Flux",
|
||||||
"settings.general.section.display": "Affichage",
|
"settings.general.section.display": "Affichage",
|
||||||
"settings.general.row.language.title": "Langue",
|
"settings.general.row.language.title": "Langue",
|
||||||
"settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode",
|
"settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode",
|
||||||
|
|
@ -552,6 +553,12 @@ export const dict = {
|
||||||
"settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
|
"settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
|
||||||
"settings.general.row.font.title": "Police",
|
"settings.general.row.font.title": "Police",
|
||||||
"settings.general.row.font.description": "Personnaliser la police mono utilisée dans les blocs de code",
|
"settings.general.row.font.description": "Personnaliser la police mono utilisée dans les blocs de code",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "Développer les parties de l'outil shell",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description":
|
||||||
|
"Afficher les parties de l'outil shell développées par défaut dans la chronologie",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "Développer les parties de l'outil edit",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
|
"Afficher les parties des outils edit, write et patch développées par défaut dans la chronologie",
|
||||||
"settings.general.row.wayland.title": "Utiliser Wayland natif",
|
"settings.general.row.wayland.title": "Utiliser Wayland natif",
|
||||||
"settings.general.row.wayland.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.",
|
"settings.general.row.wayland.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
|
|
|
||||||
|
|
@ -533,6 +533,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "システム通知",
|
"settings.general.section.notifications": "システム通知",
|
||||||
"settings.general.section.updates": "アップデート",
|
"settings.general.section.updates": "アップデート",
|
||||||
"settings.general.section.sounds": "効果音",
|
"settings.general.section.sounds": "効果音",
|
||||||
|
"settings.general.section.feed": "フィード",
|
||||||
"settings.general.section.display": "ディスプレイ",
|
"settings.general.section.display": "ディスプレイ",
|
||||||
"settings.general.row.language.title": "言語",
|
"settings.general.row.language.title": "言語",
|
||||||
"settings.general.row.language.description": "OpenCodeの表示言語を変更します",
|
"settings.general.row.language.description": "OpenCodeの表示言語を変更します",
|
||||||
|
|
@ -542,6 +543,12 @@ export const dict = {
|
||||||
"settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
|
"settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
|
||||||
"settings.general.row.font.title": "フォント",
|
"settings.general.row.font.title": "フォント",
|
||||||
"settings.general.row.font.description": "コードブロックで使用する等幅フォントをカスタマイズします",
|
"settings.general.row.font.description": "コードブロックで使用する等幅フォントをカスタマイズします",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "shell ツールパーツを展開",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description":
|
||||||
|
"タイムラインで shell ツールパーツをデフォルトで展開して表示します",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "edit ツールパーツを展開",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
|
"タイムラインで edit、write、patch ツールパーツをデフォルトで展開して表示します",
|
||||||
"settings.general.row.wayland.title": "ネイティブWaylandを使用",
|
"settings.general.row.wayland.title": "ネイティブWaylandを使用",
|
||||||
"settings.general.row.wayland.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。",
|
"settings.general.row.wayland.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
|
|
|
||||||
|
|
@ -534,6 +534,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "시스템 알림",
|
"settings.general.section.notifications": "시스템 알림",
|
||||||
"settings.general.section.updates": "업데이트",
|
"settings.general.section.updates": "업데이트",
|
||||||
"settings.general.section.sounds": "효과음",
|
"settings.general.section.sounds": "효과음",
|
||||||
|
"settings.general.section.feed": "피드",
|
||||||
"settings.general.section.display": "디스플레이",
|
"settings.general.section.display": "디스플레이",
|
||||||
"settings.general.row.language.title": "언어",
|
"settings.general.row.language.title": "언어",
|
||||||
"settings.general.row.language.description": "OpenCode 표시 언어 변경",
|
"settings.general.row.language.description": "OpenCode 표시 언어 변경",
|
||||||
|
|
@ -543,6 +544,12 @@ export const dict = {
|
||||||
"settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
|
"settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
|
||||||
"settings.general.row.font.title": "글꼴",
|
"settings.general.row.font.title": "글꼴",
|
||||||
"settings.general.row.font.description": "코드 블록에 사용되는 고정폭 글꼴 사용자 지정",
|
"settings.general.row.font.description": "코드 블록에 사용되는 고정폭 글꼴 사용자 지정",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "shell 도구 파트 펼치기",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description":
|
||||||
|
"타임라인에서 기본적으로 shell 도구 파트를 펼친 상태로 표시합니다",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "edit 도구 파트 펼치기",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
|
"타임라인에서 기본적으로 edit, write, patch 도구 파트를 펼친 상태로 표시합니다",
|
||||||
"settings.general.row.wayland.title": "네이티브 Wayland 사용",
|
"settings.general.row.wayland.title": "네이티브 Wayland 사용",
|
||||||
"settings.general.row.wayland.description": "Wayland에서 X11 폴백을 비활성화합니다. 다시 시작해야 합니다.",
|
"settings.general.row.wayland.description": "Wayland에서 X11 폴백을 비활성화합니다. 다시 시작해야 합니다.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ export const dict = {
|
||||||
"dialog.provider.tag.recommended": "Anbefalt",
|
"dialog.provider.tag.recommended": "Anbefalt",
|
||||||
"dialog.provider.opencode.note": "Utvalgte modeller inkludert Claude, GPT, Gemini og mer",
|
"dialog.provider.opencode.note": "Utvalgte modeller inkludert Claude, GPT, Gemini og mer",
|
||||||
"dialog.provider.anthropic.note": "Direkte tilgang til Claude-modeller, inkludert Pro og Max",
|
"dialog.provider.anthropic.note": "Direkte tilgang til Claude-modeller, inkludert Pro og Max",
|
||||||
"dialog.provider.copilot.note": "Claude-modeller for kodeassistanse",
|
"dialog.provider.copilot.note": "AI-modeller for kodeassistanse via GitHub Copilot",
|
||||||
"dialog.provider.openai.note": "GPT-modeller for raske, dyktige generelle AI-oppgaver",
|
"dialog.provider.openai.note": "GPT-modeller for raske, dyktige generelle AI-oppgaver",
|
||||||
"dialog.provider.google.note": "Gemini-modeller for raske, strukturerte svar",
|
"dialog.provider.google.note": "Gemini-modeller for raske, strukturerte svar",
|
||||||
"dialog.provider.openrouter.note": "Tilgang til alle støttede modeller fra én leverandør",
|
"dialog.provider.openrouter.note": "Tilgang til alle støttede modeller fra én leverandør",
|
||||||
|
|
@ -602,6 +602,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "Systemvarsler",
|
"settings.general.section.notifications": "Systemvarsler",
|
||||||
"settings.general.section.updates": "Oppdateringer",
|
"settings.general.section.updates": "Oppdateringer",
|
||||||
"settings.general.section.sounds": "Lydeffekter",
|
"settings.general.section.sounds": "Lydeffekter",
|
||||||
|
"settings.general.section.feed": "Feed",
|
||||||
"settings.general.section.display": "Skjerm",
|
"settings.general.section.display": "Skjerm",
|
||||||
|
|
||||||
"settings.general.row.language.title": "Språk",
|
"settings.general.row.language.title": "Språk",
|
||||||
|
|
@ -613,6 +614,11 @@ export const dict = {
|
||||||
"settings.general.row.font.title": "Skrift",
|
"settings.general.row.font.title": "Skrift",
|
||||||
"settings.general.row.font.description": "Tilpass mono-skriften som brukes i kodeblokker",
|
"settings.general.row.font.description": "Tilpass mono-skriften som brukes i kodeblokker",
|
||||||
|
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "Utvid shell-verktøydeler",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description": "Vis shell-verktøydeler utvidet som standard i tidslinjen",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "Utvid edit-verktøydeler",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
|
"Vis edit-, write- og patch-verktøydeler utvidet som standard i tidslinjen",
|
||||||
"settings.general.row.wayland.title": "Bruk innebygd Wayland",
|
"settings.general.row.wayland.title": "Bruk innebygd Wayland",
|
||||||
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Krever omstart.",
|
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Krever omstart.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ export const dict = {
|
||||||
"dialog.provider.tag.recommended": "Zalecane",
|
"dialog.provider.tag.recommended": "Zalecane",
|
||||||
"dialog.provider.opencode.note": "Wyselekcjonowane modele, w tym Claude, GPT, Gemini i inne",
|
"dialog.provider.opencode.note": "Wyselekcjonowane modele, w tym Claude, GPT, Gemini i inne",
|
||||||
"dialog.provider.anthropic.note": "Bezpośredni dostęp do modeli Claude, w tym Pro i Max",
|
"dialog.provider.anthropic.note": "Bezpośredni dostęp do modeli Claude, w tym Pro i Max",
|
||||||
"dialog.provider.copilot.note": "Modele Claude do pomocy w kodowaniu",
|
"dialog.provider.copilot.note": "Modele AI do pomocy w kodowaniu przez GitHub Copilot",
|
||||||
"dialog.provider.openai.note": "Modele GPT do szybkich i wszechstronnych zadań AI",
|
"dialog.provider.openai.note": "Modele GPT do szybkich i wszechstronnych zadań AI",
|
||||||
"dialog.provider.google.note": "Modele Gemini do szybkich i ustrukturyzowanych odpowiedzi",
|
"dialog.provider.google.note": "Modele Gemini do szybkich i ustrukturyzowanych odpowiedzi",
|
||||||
"dialog.provider.openrouter.note": "Dostęp do wszystkich obsługiwanych modeli od jednego dostawcy",
|
"dialog.provider.openrouter.note": "Dostęp do wszystkich obsługiwanych modeli od jednego dostawcy",
|
||||||
|
|
@ -534,6 +534,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "Powiadomienia systemowe",
|
"settings.general.section.notifications": "Powiadomienia systemowe",
|
||||||
"settings.general.section.updates": "Aktualizacje",
|
"settings.general.section.updates": "Aktualizacje",
|
||||||
"settings.general.section.sounds": "Efekty dźwiękowe",
|
"settings.general.section.sounds": "Efekty dźwiękowe",
|
||||||
|
"settings.general.section.feed": "Kanał",
|
||||||
"settings.general.section.display": "Ekran",
|
"settings.general.section.display": "Ekran",
|
||||||
"settings.general.row.language.title": "Język",
|
"settings.general.row.language.title": "Język",
|
||||||
"settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode",
|
"settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode",
|
||||||
|
|
@ -543,6 +544,12 @@ export const dict = {
|
||||||
"settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
|
"settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
|
||||||
"settings.general.row.font.title": "Czcionka",
|
"settings.general.row.font.title": "Czcionka",
|
||||||
"settings.general.row.font.description": "Dostosuj czcionkę mono używaną w blokach kodu",
|
"settings.general.row.font.description": "Dostosuj czcionkę mono używaną w blokach kodu",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "Rozwijaj elementy narzędzia shell",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description":
|
||||||
|
"Domyślnie pokazuj rozwinięte elementy narzędzia shell na osi czasu",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "Rozwijaj elementy narzędzia edit",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
|
"Domyślnie pokazuj rozwinięte elementy narzędzi edit, write i patch na osi czasu",
|
||||||
"settings.general.row.wayland.title": "Użyj natywnego Wayland",
|
"settings.general.row.wayland.title": "Użyj natywnego Wayland",
|
||||||
"settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.",
|
"settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ export const dict = {
|
||||||
"dialog.provider.tag.recommended": "Рекомендуемые",
|
"dialog.provider.tag.recommended": "Рекомендуемые",
|
||||||
"dialog.provider.opencode.note": "Отобранные модели, включая Claude, GPT, Gemini и другие",
|
"dialog.provider.opencode.note": "Отобранные модели, включая Claude, GPT, Gemini и другие",
|
||||||
"dialog.provider.anthropic.note": "Прямой доступ к моделям Claude, включая Pro и Max",
|
"dialog.provider.anthropic.note": "Прямой доступ к моделям Claude, включая Pro и Max",
|
||||||
"dialog.provider.copilot.note": "Модели Claude для помощи в кодировании",
|
"dialog.provider.copilot.note": "ИИ-модели для помощи в кодировании через GitHub Copilot",
|
||||||
"dialog.provider.openai.note": "Модели GPT для быстрых и мощных задач общего ИИ",
|
"dialog.provider.openai.note": "Модели GPT для быстрых и мощных задач общего ИИ",
|
||||||
"dialog.provider.google.note": "Модели Gemini для быстрых и структурированных ответов",
|
"dialog.provider.google.note": "Модели Gemini для быстрых и структурированных ответов",
|
||||||
"dialog.provider.openrouter.note": "Доступ ко всем поддерживаемым моделям через одного провайдера",
|
"dialog.provider.openrouter.note": "Доступ ко всем поддерживаемым моделям через одного провайдера",
|
||||||
|
|
@ -600,6 +600,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "Системные уведомления",
|
"settings.general.section.notifications": "Системные уведомления",
|
||||||
"settings.general.section.updates": "Обновления",
|
"settings.general.section.updates": "Обновления",
|
||||||
"settings.general.section.sounds": "Звуковые эффекты",
|
"settings.general.section.sounds": "Звуковые эффекты",
|
||||||
|
"settings.general.section.feed": "Лента",
|
||||||
"settings.general.section.display": "Дисплей",
|
"settings.general.section.display": "Дисплей",
|
||||||
|
|
||||||
"settings.general.row.language.title": "Язык",
|
"settings.general.row.language.title": "Язык",
|
||||||
|
|
@ -611,6 +612,12 @@ export const dict = {
|
||||||
"settings.general.row.font.title": "Шрифт",
|
"settings.general.row.font.title": "Шрифт",
|
||||||
"settings.general.row.font.description": "Настройте моноширинный шрифт для блоков кода",
|
"settings.general.row.font.description": "Настройте моноширинный шрифт для блоков кода",
|
||||||
|
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "Разворачивать элементы инструмента shell",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description":
|
||||||
|
"Показывать элементы инструмента shell в ленте развернутыми по умолчанию",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "Разворачивать элементы инструмента edit",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
|
"Показывать элементы инструментов edit, write и patch в ленте развернутыми по умолчанию",
|
||||||
"settings.general.row.wayland.title": "Использовать нативный Wayland",
|
"settings.general.row.wayland.title": "Использовать нативный Wayland",
|
||||||
"settings.general.row.wayland.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.",
|
"settings.general.row.wayland.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ export const dict = {
|
||||||
"dialog.provider.tag.recommended": "แนะนำ",
|
"dialog.provider.tag.recommended": "แนะนำ",
|
||||||
"dialog.provider.opencode.note": "โมเดลที่คัดสรร รวมถึง Claude, GPT, Gemini และอื่น ๆ",
|
"dialog.provider.opencode.note": "โมเดลที่คัดสรร รวมถึง Claude, GPT, Gemini และอื่น ๆ",
|
||||||
"dialog.provider.anthropic.note": "เข้าถึงโมเดล Claude โดยตรง รวมถึง Pro และ Max",
|
"dialog.provider.anthropic.note": "เข้าถึงโมเดล Claude โดยตรง รวมถึง Pro และ Max",
|
||||||
"dialog.provider.copilot.note": "โมเดล Claude สำหรับการช่วยเหลือในการเขียนโค้ด",
|
"dialog.provider.copilot.note": "โมเดล AI สำหรับการช่วยเหลือในการเขียนโค้ดผ่าน GitHub Copilot",
|
||||||
"dialog.provider.openai.note": "โมเดล GPT สำหรับงาน AI ทั่วไปที่รวดเร็วและมีความสามารถ",
|
"dialog.provider.openai.note": "โมเดล GPT สำหรับงาน AI ทั่วไปที่รวดเร็วและมีความสามารถ",
|
||||||
"dialog.provider.google.note": "โมเดล Gemini สำหรับการตอบสนองที่รวดเร็วและมีโครงสร้าง",
|
"dialog.provider.google.note": "โมเดล Gemini สำหรับการตอบสนองที่รวดเร็วและมีโครงสร้าง",
|
||||||
"dialog.provider.openrouter.note": "เข้าถึงโมเดลที่รองรับทั้งหมดจากผู้ให้บริการเดียว",
|
"dialog.provider.openrouter.note": "เข้าถึงโมเดลที่รองรับทั้งหมดจากผู้ให้บริการเดียว",
|
||||||
|
|
@ -594,6 +594,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "การแจ้งเตือนระบบ",
|
"settings.general.section.notifications": "การแจ้งเตือนระบบ",
|
||||||
"settings.general.section.updates": "การอัปเดต",
|
"settings.general.section.updates": "การอัปเดต",
|
||||||
"settings.general.section.sounds": "เสียงเอฟเฟกต์",
|
"settings.general.section.sounds": "เสียงเอฟเฟกต์",
|
||||||
|
"settings.general.section.feed": "ฟีด",
|
||||||
"settings.general.section.display": "การแสดงผล",
|
"settings.general.section.display": "การแสดงผล",
|
||||||
|
|
||||||
"settings.general.row.language.title": "ภาษา",
|
"settings.general.row.language.title": "ภาษา",
|
||||||
|
|
@ -605,6 +606,11 @@ export const dict = {
|
||||||
"settings.general.row.font.title": "ฟอนต์",
|
"settings.general.row.font.title": "ฟอนต์",
|
||||||
"settings.general.row.font.description": "ปรับแต่งฟอนต์โมโนที่ใช้ในบล็อกโค้ด",
|
"settings.general.row.font.description": "ปรับแต่งฟอนต์โมโนที่ใช้ในบล็อกโค้ด",
|
||||||
|
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "ขยายส่วนเครื่องมือ shell",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description": "แสดงส่วนเครื่องมือ shell แบบขยายตามค่าเริ่มต้นในไทม์ไลน์",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "ขยายส่วนเครื่องมือ edit",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
|
"แสดงส่วนเครื่องมือ edit, write และ patch แบบขยายตามค่าเริ่มต้นในไทม์ไลน์",
|
||||||
"settings.general.row.wayland.title": "ใช้ Wayland แบบเนทีฟ",
|
"settings.general.row.wayland.title": "ใช้ Wayland แบบเนทีฟ",
|
||||||
"settings.general.row.wayland.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท",
|
"settings.general.row.wayland.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท",
|
||||||
"settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า",
|
"settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า",
|
||||||
|
|
|
||||||
|
|
@ -595,6 +595,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "系统通知",
|
"settings.general.section.notifications": "系统通知",
|
||||||
"settings.general.section.updates": "更新",
|
"settings.general.section.updates": "更新",
|
||||||
"settings.general.section.sounds": "音效",
|
"settings.general.section.sounds": "音效",
|
||||||
|
"settings.general.section.feed": "动态",
|
||||||
"settings.general.section.display": "显示",
|
"settings.general.section.display": "显示",
|
||||||
"settings.general.row.language.title": "语言",
|
"settings.general.row.language.title": "语言",
|
||||||
"settings.general.row.language.description": "更改 OpenCode 的显示语言",
|
"settings.general.row.language.description": "更改 OpenCode 的显示语言",
|
||||||
|
|
@ -604,6 +605,10 @@ export const dict = {
|
||||||
"settings.general.row.theme.description": "自定义 OpenCode 的主题。",
|
"settings.general.row.theme.description": "自定义 OpenCode 的主题。",
|
||||||
"settings.general.row.font.title": "字体",
|
"settings.general.row.font.title": "字体",
|
||||||
"settings.general.row.font.description": "自定义代码块使用的等宽字体",
|
"settings.general.row.font.description": "自定义代码块使用的等宽字体",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "展开 shell 工具部分",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description": "默认在时间线中展开 shell 工具部分",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "展开编辑工具部分",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description": "默认在时间线中展开 edit、write 和 patch 工具部分",
|
||||||
"settings.general.row.wayland.title": "使用原生 Wayland",
|
"settings.general.row.wayland.title": "使用原生 Wayland",
|
||||||
"settings.general.row.wayland.description": "在 Wayland 上禁用 X11 回退。需要重启。",
|
"settings.general.row.wayland.description": "在 Wayland 上禁用 X11 回退。需要重启。",
|
||||||
"settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。",
|
"settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。",
|
||||||
|
|
|
||||||
|
|
@ -589,6 +589,7 @@ export const dict = {
|
||||||
"settings.general.section.notifications": "系統通知",
|
"settings.general.section.notifications": "系統通知",
|
||||||
"settings.general.section.updates": "更新",
|
"settings.general.section.updates": "更新",
|
||||||
"settings.general.section.sounds": "音效",
|
"settings.general.section.sounds": "音效",
|
||||||
|
"settings.general.section.feed": "資訊流",
|
||||||
"settings.general.section.display": "顯示",
|
"settings.general.section.display": "顯示",
|
||||||
|
|
||||||
"settings.general.row.language.title": "語言",
|
"settings.general.row.language.title": "語言",
|
||||||
|
|
@ -600,6 +601,10 @@ export const dict = {
|
||||||
"settings.general.row.font.title": "字型",
|
"settings.general.row.font.title": "字型",
|
||||||
"settings.general.row.font.description": "自訂程式碼區塊使用的等寬字型",
|
"settings.general.row.font.description": "自訂程式碼區塊使用的等寬字型",
|
||||||
|
|
||||||
|
"settings.general.row.shellToolPartsExpanded.title": "展開 shell 工具區塊",
|
||||||
|
"settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊",
|
||||||
|
"settings.general.row.editToolPartsExpanded.title": "展開 edit 工具區塊",
|
||||||
|
"settings.general.row.editToolPartsExpanded.description": "在時間軸中預設展開 edit、write 和 patch 工具區塊",
|
||||||
"settings.general.row.wayland.title": "使用原生 Wayland",
|
"settings.general.row.wayland.title": "使用原生 Wayland",
|
||||||
"settings.general.row.wayland.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。",
|
"settings.general.row.wayland.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。",
|
||||||
"settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。",
|
"settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。",
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { createEffect, createMemo, Show, type ParentProps } from "solid-js"
|
import { createEffect, createMemo, Show, type ParentProps } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useNavigate, useParams } from "@solidjs/router"
|
import { useNavigate, useParams } from "@solidjs/router"
|
||||||
import { SDKProvider, useSDK } from "@/context/sdk"
|
import { SDKProvider } from "@/context/sdk"
|
||||||
import { SyncProvider, useSync } from "@/context/sync"
|
import { SyncProvider, useSync } from "@/context/sync"
|
||||||
import { LocalProvider } from "@/context/local"
|
import { LocalProvider } from "@/context/local"
|
||||||
|
|
||||||
import { DataProvider } from "@opencode-ai/ui/context"
|
import { DataProvider } from "@opencode-ai/ui/context"
|
||||||
import type { QuestionAnswer } from "@opencode-ai/sdk/v2"
|
|
||||||
import { decode64 } from "@/utils/base64"
|
import { decode64 } from "@/utils/base64"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
|
@ -15,19 +14,11 @@ function DirectoryDataProvider(props: ParentProps<{ directory: string }>) {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const sdk = useSDK()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataProvider
|
<DataProvider
|
||||||
data={sync.data}
|
data={sync.data}
|
||||||
directory={props.directory}
|
directory={props.directory}
|
||||||
onPermissionRespond={(input: {
|
|
||||||
sessionID: string
|
|
||||||
permissionID: string
|
|
||||||
response: "once" | "always" | "reject"
|
|
||||||
}) => sdk.client.permission.respond(input)}
|
|
||||||
onQuestionReply={(input: { requestID: string; answers: QuestionAnswer[] }) => sdk.client.question.reply(input)}
|
|
||||||
onQuestionReject={(input: { requestID: string }) => sdk.client.question.reject(input)}
|
|
||||||
onNavigateToSession={(sessionID: string) => navigate(`/${params.dir}/session/${sessionID}`)}
|
onNavigateToSession={(sessionID: string) => navigate(`/${params.dir}/session/${sessionID}`)}
|
||||||
onSessionHref={(sessionID: string) => `/${params.dir}/session/${sessionID}`}
|
onSessionHref={(sessionID: string) => `/${params.dir}/session/${sessionID}`}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -254,12 +254,13 @@ export default function Page() {
|
||||||
const msgs = visibleUserMessages()
|
const msgs = visibleUserMessages()
|
||||||
if (msgs.length === 0) return
|
if (msgs.length === 0) return
|
||||||
|
|
||||||
const current = activeMessage()
|
const current = store.messageId
|
||||||
const currentIndex = current ? msgs.findIndex((m) => m.id === current.id) : -1
|
const base = current ? msgs.findIndex((m) => m.id === current) : msgs.length
|
||||||
const targetIndex = currentIndex === -1 ? (offset > 0 ? 0 : msgs.length - 1) : currentIndex + offset
|
const currentIndex = base === -1 ? msgs.length : base
|
||||||
if (targetIndex < 0 || targetIndex >= msgs.length) return
|
const targetIndex = currentIndex + offset
|
||||||
|
if (targetIndex < 0 || targetIndex > msgs.length) return
|
||||||
|
|
||||||
if (targetIndex === msgs.length - 1) {
|
if (targetIndex === msgs.length) {
|
||||||
resumeScroll()
|
resumeScroll()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -274,6 +275,11 @@ export default function Page() {
|
||||||
if (!hasReview()) return true
|
if (!hasReview()) return true
|
||||||
return sync.data.session_diff[id] !== undefined
|
return sync.data.session_diff[id] !== undefined
|
||||||
})
|
})
|
||||||
|
const reviewEmptyKey = createMemo(() => {
|
||||||
|
const project = sync.project
|
||||||
|
if (!project || project.vcs) return "session.review.empty"
|
||||||
|
return "session.review.noVcs"
|
||||||
|
})
|
||||||
|
|
||||||
let inputRef!: HTMLDivElement
|
let inputRef!: HTMLDivElement
|
||||||
let promptDock: HTMLDivElement | undefined
|
let promptDock: HTMLDivElement | undefined
|
||||||
|
|
@ -531,7 +537,7 @@ export default function Page() {
|
||||||
) : (
|
) : (
|
||||||
<div class={input.emptyClass}>
|
<div class={input.emptyClass}>
|
||||||
<Mark class="w-14 opacity-10" />
|
<Mark class="w-14 opacity-10" />
|
||||||
<div class="text-14-regular text-text-weak max-w-56">{language.t("session.review.empty")}</div>
|
<div class="text-14-regular text-text-weak max-w-56">{language.t(reviewEmptyKey())}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import type { PermissionRequest, QuestionRequest, Session } from "@opencode-ai/sdk/v2/client"
|
||||||
|
import { sessionPermissionRequest, sessionQuestionRequest } from "./session-request-tree"
|
||||||
|
|
||||||
|
const session = (input: { id: string; parentID?: string }) =>
|
||||||
|
({
|
||||||
|
id: input.id,
|
||||||
|
parentID: input.parentID,
|
||||||
|
}) as Session
|
||||||
|
|
||||||
|
const permission = (id: string, sessionID: string) =>
|
||||||
|
({
|
||||||
|
id,
|
||||||
|
sessionID,
|
||||||
|
}) as PermissionRequest
|
||||||
|
|
||||||
|
const question = (id: string, sessionID: string) =>
|
||||||
|
({
|
||||||
|
id,
|
||||||
|
sessionID,
|
||||||
|
questions: [],
|
||||||
|
}) as QuestionRequest
|
||||||
|
|
||||||
|
describe("sessionPermissionRequest", () => {
|
||||||
|
test("prefers the current session permission", () => {
|
||||||
|
const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" })]
|
||||||
|
const permissions = {
|
||||||
|
root: [permission("perm-root", "root")],
|
||||||
|
child: [permission("perm-child", "child")],
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(sessionPermissionRequest(sessions, permissions, "root")?.id).toBe("perm-root")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns a nested child permission", () => {
|
||||||
|
const sessions = [
|
||||||
|
session({ id: "root" }),
|
||||||
|
session({ id: "child", parentID: "root" }),
|
||||||
|
session({ id: "grand", parentID: "child" }),
|
||||||
|
session({ id: "other" }),
|
||||||
|
]
|
||||||
|
const permissions = {
|
||||||
|
grand: [permission("perm-grand", "grand")],
|
||||||
|
other: [permission("perm-other", "other")],
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(sessionPermissionRequest(sessions, permissions, "root")?.id).toBe("perm-grand")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns undefined without a matching tree permission", () => {
|
||||||
|
const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" })]
|
||||||
|
const permissions = {
|
||||||
|
other: [permission("perm-other", "other")],
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(sessionPermissionRequest(sessions, permissions, "root")).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("sessionQuestionRequest", () => {
|
||||||
|
test("prefers the current session question", () => {
|
||||||
|
const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" })]
|
||||||
|
const questions = {
|
||||||
|
root: [question("q-root", "root")],
|
||||||
|
child: [question("q-child", "child")],
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(sessionQuestionRequest(sessions, questions, "root")?.id).toBe("q-root")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns a nested child question", () => {
|
||||||
|
const sessions = [
|
||||||
|
session({ id: "root" }),
|
||||||
|
session({ id: "child", parentID: "root" }),
|
||||||
|
session({ id: "grand", parentID: "child" }),
|
||||||
|
]
|
||||||
|
const questions = {
|
||||||
|
grand: [question("q-grand", "grand")],
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(sessionQuestionRequest(sessions, questions, "root")?.id).toBe("q-grand")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -7,14 +7,20 @@ import { useGlobalSync } from "@/context/global-sync"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useSDK } from "@/context/sdk"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
|
import { sessionPermissionRequest, sessionQuestionRequest } from "./session-request-tree"
|
||||||
|
|
||||||
export function createSessionComposerBlocked() {
|
export function createSessionComposerBlocked() {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
|
const permissionRequest = createMemo(() =>
|
||||||
|
sessionPermissionRequest(sync.data.session, sync.data.permission, params.id),
|
||||||
|
)
|
||||||
|
const questionRequest = createMemo(() => sessionQuestionRequest(sync.data.session, sync.data.question, params.id))
|
||||||
|
|
||||||
return createMemo(() => {
|
return createMemo(() => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return false
|
if (!id) return false
|
||||||
return !!sync.data.permission[id]?.[0] || !!sync.data.question[id]?.[0]
|
return !!permissionRequest() || !!questionRequest()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,18 +32,18 @@ export function createSessionComposerState() {
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
const questionRequest = createMemo((): QuestionRequest | undefined => {
|
const questionRequest = createMemo((): QuestionRequest | undefined => {
|
||||||
const id = params.id
|
return sessionQuestionRequest(sync.data.session, sync.data.question, params.id)
|
||||||
if (!id) return
|
|
||||||
return sync.data.question[id]?.[0]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const permissionRequest = createMemo((): PermissionRequest | undefined => {
|
const permissionRequest = createMemo((): PermissionRequest | undefined => {
|
||||||
const id = params.id
|
return sessionPermissionRequest(sync.data.session, sync.data.permission, params.id)
|
||||||
if (!id) return
|
|
||||||
return sync.data.permission[id]?.[0]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const blocked = createSessionComposerBlocked()
|
const blocked = createMemo(() => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return false
|
||||||
|
return !!permissionRequest() || !!questionRequest()
|
||||||
|
})
|
||||||
|
|
||||||
const todos = createMemo((): Todo[] => {
|
const todos = createMemo((): Todo[] => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import type { PermissionRequest, QuestionRequest, Session } from "@opencode-ai/sdk/v2/client"
|
||||||
|
|
||||||
|
function sessionTreeRequest<T>(session: Session[], request: Record<string, T[] | undefined>, sessionID?: string) {
|
||||||
|
if (!sessionID) return
|
||||||
|
|
||||||
|
const map = session.reduce((acc, item) => {
|
||||||
|
if (!item.parentID) return acc
|
||||||
|
const list = acc.get(item.parentID)
|
||||||
|
if (list) list.push(item.id)
|
||||||
|
if (!list) acc.set(item.parentID, [item.id])
|
||||||
|
return acc
|
||||||
|
}, new Map<string, string[]>())
|
||||||
|
|
||||||
|
const seen = new Set([sessionID])
|
||||||
|
const ids = [sessionID]
|
||||||
|
for (const id of ids) {
|
||||||
|
const list = map.get(id)
|
||||||
|
if (!list) continue
|
||||||
|
for (const child of list) {
|
||||||
|
if (seen.has(child)) continue
|
||||||
|
seen.add(child)
|
||||||
|
ids.push(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = ids.find((id) => !!request[id]?.[0])
|
||||||
|
if (!id) return
|
||||||
|
return request[id]?.[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sessionPermissionRequest(
|
||||||
|
session: Session[],
|
||||||
|
request: Record<string, PermissionRequest[] | undefined>,
|
||||||
|
sessionID?: string,
|
||||||
|
) {
|
||||||
|
return sessionTreeRequest(session, request, sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sessionQuestionRequest(
|
||||||
|
session: Session[],
|
||||||
|
request: Record<string, QuestionRequest[] | undefined>,
|
||||||
|
sessionID?: string,
|
||||||
|
) {
|
||||||
|
return sessionTreeRequest(session, request, sessionID)
|
||||||
|
}
|
||||||
|
|
@ -87,7 +87,7 @@ export function SessionTodoDock(props: { todos: Todo[]; title: string; collapseL
|
||||||
icon="chevron-down"
|
icon="chevron-down"
|
||||||
size="normal"
|
size="normal"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
classList={{ "rotate-180": !store.collapsed }}
|
classList={{ "rotate-180": store.collapsed }}
|
||||||
onMouseDown={(event) => {
|
onMouseDown={(event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
|
||||||
|
|
@ -371,6 +371,12 @@ export function FileTabContent(props: { tab: string }) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cancelCommenting = () => {
|
||||||
|
const p = path()
|
||||||
|
if (p) file.setSelectedLines(p, null)
|
||||||
|
setNote("commenting", null)
|
||||||
|
}
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => state()?.loaded,
|
() => state()?.loaded,
|
||||||
|
|
@ -484,7 +490,7 @@ export function FileTabContent(props: { tab: string }) {
|
||||||
value={note.draft}
|
value={note.draft}
|
||||||
selection={formatCommentLabel(range())}
|
selection={formatCommentLabel(range())}
|
||||||
onInput={(value) => setNote("draft", value)}
|
onInput={(value) => setNote("draft", value)}
|
||||||
onCancel={() => setCommenting(null)}
|
onCancel={cancelCommenting}
|
||||||
onSubmit={(value) => {
|
onSubmit={(value) => {
|
||||||
const p = path()
|
const p = path()
|
||||||
if (!p) return
|
if (!p) return
|
||||||
|
|
@ -498,7 +504,7 @@ export function FileTabContent(props: { tab: string }) {
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!document.activeElement || !current.contains(document.activeElement)) {
|
if (!document.activeElement || !current.contains(document.activeElement)) {
|
||||||
setCommenting(null)
|
cancelCommenting()
|
||||||
}
|
}
|
||||||
}, 0)
|
}, 0)
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ describe("createOpenReviewFile", () => {
|
||||||
|
|
||||||
openReviewFile("src/a.ts")
|
openReviewFile("src/a.ts")
|
||||||
|
|
||||||
expect(calls).toEqual(["show", "tab:src/a.ts", "open:file://src/a.ts", "load:src/a.ts"])
|
expect(calls).toEqual(["show", "load:src/a.ts", "tab:src/a.ts", "open:file://src/a.ts"])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,15 @@ export const createOpenReviewFile = (input: {
|
||||||
showAllFiles: () => void
|
showAllFiles: () => void
|
||||||
tabForPath: (path: string) => string
|
tabForPath: (path: string) => string
|
||||||
openTab: (tab: string) => void
|
openTab: (tab: string) => void
|
||||||
loadFile: (path: string) => void
|
loadFile: (path: string) => any | Promise<void>
|
||||||
}) => {
|
}) => {
|
||||||
return (path: string) => {
|
return (path: string) => {
|
||||||
batch(() => {
|
batch(() => {
|
||||||
input.showAllFiles()
|
input.showAllFiles()
|
||||||
input.openTab(input.tabForPath(path))
|
const maybePromise = input.loadFile(path)
|
||||||
input.loadFile(path)
|
const openTab = () => input.openTab(input.tabForPath(path))
|
||||||
|
if (maybePromise instanceof Promise) maybePromise.then(openTab)
|
||||||
|
else openTab()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -376,6 +376,7 @@ export function MessageTimeline(props: {
|
||||||
>
|
>
|
||||||
<Show when={showHeader()}>
|
<Show when={showHeader()}>
|
||||||
<div
|
<div
|
||||||
|
data-session-title
|
||||||
classList={{
|
classList={{
|
||||||
"sticky top-0 z-30 bg-[linear-gradient(to_bottom,var(--background-stronger)_48px,transparent)]": true,
|
"sticky top-0 z-30 bg-[linear-gradient(to_bottom,var(--background-stronger)_48px,transparent)]": true,
|
||||||
"w-full": true,
|
"w-full": true,
|
||||||
|
|
@ -539,6 +540,8 @@ export function MessageTimeline(props: {
|
||||||
messageID={message.id}
|
messageID={message.id}
|
||||||
lastUserMessageID={props.lastUserMessageID}
|
lastUserMessageID={props.lastUserMessageID}
|
||||||
showReasoningSummaries={settings.general.showReasoningSummaries()}
|
showReasoningSummaries={settings.general.showReasoningSummaries()}
|
||||||
|
shellToolDefaultOpen={settings.general.shellToolPartsExpanded()}
|
||||||
|
editToolDefaultOpen={settings.general.editToolPartsExpanded()}
|
||||||
classes={{
|
classes={{
|
||||||
root: "min-w-0 w-full relative",
|
root: "min-w-0 w-full relative",
|
||||||
content: "flex flex-col justify-between !overflow-visible",
|
content: "flex flex-col justify-between !overflow-visible",
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,9 @@ export const useSessionHashScroll = (input: {
|
||||||
|
|
||||||
const a = el.getBoundingClientRect()
|
const a = el.getBoundingClientRect()
|
||||||
const b = root.getBoundingClientRect()
|
const b = root.getBoundingClientRect()
|
||||||
const top = a.top - b.top + root.scrollTop
|
const sticky = root.querySelector("[data-session-title]")
|
||||||
|
const inset = sticky instanceof HTMLElement ? sticky.offsetHeight : 0
|
||||||
|
const top = Math.max(0, a.top - b.top + root.scrollTop - inset)
|
||||||
root.scrollTo({ top, behavior })
|
root.scrollTo({ top, behavior })
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
69
packages/app/src/utils/server-errors.test.ts
Normal file
69
packages/app/src/utils/server-errors.test.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import type { ConfigInvalidError } from "./server-errors"
|
||||||
|
import { formatServerError, parseReabaleConfigInvalidError } from "./server-errors"
|
||||||
|
|
||||||
|
describe("parseReabaleConfigInvalidError", () => {
|
||||||
|
test("formats issues with file path", () => {
|
||||||
|
const error = {
|
||||||
|
name: "ConfigInvalidError",
|
||||||
|
data: {
|
||||||
|
path: "opencode.config.ts",
|
||||||
|
issues: [
|
||||||
|
{ path: ["settings", "host"], message: "Required" },
|
||||||
|
{ path: ["mode"], message: "Invalid" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} satisfies ConfigInvalidError
|
||||||
|
|
||||||
|
const result = parseReabaleConfigInvalidError(error)
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
["Invalid configuration", "opencode.config.ts", "settings.host: Required", "mode: Invalid"].join("\n"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("uses trimmed message when issues are missing", () => {
|
||||||
|
const error = {
|
||||||
|
name: "ConfigInvalidError",
|
||||||
|
data: {
|
||||||
|
path: "config",
|
||||||
|
message: " Bad value ",
|
||||||
|
},
|
||||||
|
} satisfies ConfigInvalidError
|
||||||
|
|
||||||
|
const result = parseReabaleConfigInvalidError(error)
|
||||||
|
|
||||||
|
expect(result).toBe(["Invalid configuration", "Bad value"].join("\n"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("formatServerError", () => {
|
||||||
|
test("formats config invalid errors", () => {
|
||||||
|
const error = {
|
||||||
|
name: "ConfigInvalidError",
|
||||||
|
data: {
|
||||||
|
message: "Missing host",
|
||||||
|
},
|
||||||
|
} satisfies ConfigInvalidError
|
||||||
|
|
||||||
|
const result = formatServerError(error)
|
||||||
|
|
||||||
|
expect(result).toBe(["Invalid configuration", "Missing host"].join("\n"))
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns error messages", () => {
|
||||||
|
expect(formatServerError(new Error("Request failed with status 503"))).toBe("Request failed with status 503")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns provided string errors", () => {
|
||||||
|
expect(formatServerError("Failed to connect to server")).toBe("Failed to connect to server")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("falls back to unknown", () => {
|
||||||
|
expect(formatServerError(0)).toBe("Unknown error")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("falls back for unknown error objects and names", () => {
|
||||||
|
expect(formatServerError({ name: "ServerTimeoutError", data: { seconds: 30 } })).toBe("Unknown error")
|
||||||
|
})
|
||||||
|
})
|
||||||
32
packages/app/src/utils/server-errors.ts
Normal file
32
packages/app/src/utils/server-errors.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
export type ConfigInvalidError = {
|
||||||
|
name: "ConfigInvalidError"
|
||||||
|
data: {
|
||||||
|
path?: string
|
||||||
|
message?: string
|
||||||
|
issues?: Array<{ message: string; path: string[] }>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatServerError(error: unknown) {
|
||||||
|
if (isConfigInvalidErrorLike(error)) return parseReabaleConfigInvalidError(error)
|
||||||
|
if (error instanceof Error && error.message) return error.message
|
||||||
|
if (typeof error === "string" && error) return error
|
||||||
|
return "Unknown error"
|
||||||
|
}
|
||||||
|
|
||||||
|
function isConfigInvalidErrorLike(error: unknown): error is ConfigInvalidError {
|
||||||
|
if (typeof error !== "object" || error === null) return false
|
||||||
|
const o = error as Record<string, unknown>
|
||||||
|
return o.name === "ConfigInvalidError" && typeof o.data === "object" && o.data !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseReabaleConfigInvalidError(errorInput: ConfigInvalidError) {
|
||||||
|
const head = "Invalid configuration"
|
||||||
|
const file = errorInput.data.path && errorInput.data.path !== "config" ? errorInput.data.path : ""
|
||||||
|
const detail = errorInput.data.message?.trim() ?? ""
|
||||||
|
const issues = (errorInput.data.issues ?? []).map((issue) => {
|
||||||
|
return `${issue.path.join(".")}: ${issue.message}`
|
||||||
|
})
|
||||||
|
if (issues.length) return [head, file, "", ...issues].filter(Boolean).join("\n")
|
||||||
|
return [head, file, detail].filter(Boolean).join("\n")
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.2.10",
|
"version": "1.2.14",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsgo --noEmit",
|
"typecheck": "tsgo --noEmit",
|
||||||
"dev": "vite dev --host 0.0.0.0",
|
"dev": "vite dev --host 0.0.0.0",
|
||||||
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai VITE_STRIPE_PUBLISHABLE_KEY=pk_test_51RtuLNE7fOCwHSD4mewwzFejyytjdGoSDK7CAvhbffwaZnPbNb2rwJICw6LTOXCmWO320fSNXvb5NzI08RZVkAxd00syfqrW7t bun sst shell --stage=dev bun dev",
|
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai VITE_STRIPE_PUBLISHABLE_KEY=pk_test_51RtuLNE7fOCwHSD4mewwzFejyytjdGoSDK7CAvhbffwaZnPbNb2rwJICw6LTOXCmWO320fSNXvb5NzI08RZVkAxd00syfqrW7t bun sst shell --stage=dev bun dev",
|
||||||
"build": "./script/generate-sitemap.ts && vite build && ../../opencode/script/schema.ts ./.output/public/config.json",
|
"build": "bun ./script/generate-sitemap.ts && vite build && bun ../../opencode/script/schema.ts ./.output/public/config.json ./.output/public/tui.json",
|
||||||
"start": "vite start"
|
"start": "vite start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,7 @@ export const dict = {
|
||||||
"black.hero.title": "الوصول إلى أفضل نماذج البرمجة في العالم",
|
"black.hero.title": "الوصول إلى أفضل نماذج البرمجة في العالم",
|
||||||
"black.hero.subtitle": "بما في ذلك Claude، GPT، Gemini والمزيد",
|
"black.hero.subtitle": "بما في ذلك Claude، GPT، Gemini والمزيد",
|
||||||
"black.title": "OpenCode Black | الأسعار",
|
"black.title": "OpenCode Black | الأسعار",
|
||||||
|
"black.paused": "التسجيل في خطة Black متوقف مؤقتًا.",
|
||||||
"black.plan.icon20": "خطة Black 20",
|
"black.plan.icon20": "خطة Black 20",
|
||||||
"black.plan.icon100": "خطة Black 100",
|
"black.plan.icon100": "خطة Black 100",
|
||||||
"black.plan.icon200": "خطة Black 200",
|
"black.plan.icon200": "خطة Black 200",
|
||||||
|
|
@ -337,12 +338,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "الدخل",
|
"workspace.usage.table.input": "الدخل",
|
||||||
"workspace.usage.table.output": "الخرج",
|
"workspace.usage.table.output": "الخرج",
|
||||||
"workspace.usage.table.cost": "التكلفة",
|
"workspace.usage.table.cost": "التكلفة",
|
||||||
|
"workspace.usage.table.session": "الجلسة",
|
||||||
"workspace.usage.breakdown.input": "الدخل",
|
"workspace.usage.breakdown.input": "الدخل",
|
||||||
"workspace.usage.breakdown.cacheRead": "قراءة الكاش",
|
"workspace.usage.breakdown.cacheRead": "قراءة الكاش",
|
||||||
"workspace.usage.breakdown.cacheWrite": "كتابة الكاش",
|
"workspace.usage.breakdown.cacheWrite": "كتابة الكاش",
|
||||||
"workspace.usage.breakdown.output": "الخرج",
|
"workspace.usage.breakdown.output": "الخرج",
|
||||||
"workspace.usage.breakdown.reasoning": "المنطق",
|
"workspace.usage.breakdown.reasoning": "المنطق",
|
||||||
"workspace.usage.subscription": "الاشتراك (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "التكلفة",
|
"workspace.cost.title": "التكلفة",
|
||||||
"workspace.cost.subtitle": "تكاليف الاستخدام مقسمة حسب النموذج.",
|
"workspace.cost.subtitle": "تكاليف الاستخدام مقسمة حسب النموذج.",
|
||||||
|
|
@ -351,6 +355,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(محذوف)",
|
"workspace.cost.deletedSuffix": "(محذوف)",
|
||||||
"workspace.cost.empty": "لا توجد بيانات استخدام متاحة للفترة المحددة.",
|
"workspace.cost.empty": "لا توجد بيانات استخدام متاحة للفترة المحددة.",
|
||||||
"workspace.cost.subscriptionShort": "اشتراك",
|
"workspace.cost.subscriptionShort": "اشتراك",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "مفاتيح API",
|
"workspace.keys.title": "مفاتيح API",
|
||||||
"workspace.keys.subtitle": "إدارة مفاتيح API الخاصة بك للوصول إلى خدمات opencode.",
|
"workspace.keys.subtitle": "إدارة مفاتيح API الخاصة بك للوصول إلى خدمات opencode.",
|
||||||
|
|
@ -478,6 +483,36 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrolled": "مسجل",
|
"workspace.black.waitlist.enrolled": "مسجل",
|
||||||
"workspace.black.waitlist.enrollNote": 'عند النقر فوق "تسجيل"، يبدأ اشتراكك على الفور وسيتم خصم الرسوم من بطاقتك.',
|
"workspace.black.waitlist.enrollNote": 'عند النقر فوق "تسجيل"، يبدأ اشتراكك على الفور وسيتم خصم الرسوم من بطاقتك.',
|
||||||
|
|
||||||
|
"workspace.lite.loading": "جارٍ التحميل...",
|
||||||
|
"workspace.lite.time.day": "يوم",
|
||||||
|
"workspace.lite.time.days": "أيام",
|
||||||
|
"workspace.lite.time.hour": "ساعة",
|
||||||
|
"workspace.lite.time.hours": "ساعات",
|
||||||
|
"workspace.lite.time.minute": "دقيقة",
|
||||||
|
"workspace.lite.time.minutes": "دقائق",
|
||||||
|
"workspace.lite.time.fewSeconds": "بضع ثوان",
|
||||||
|
"workspace.lite.subscription.title": "اشتراك Go",
|
||||||
|
"workspace.lite.subscription.message": "أنت مشترك في OpenCode Go.",
|
||||||
|
"workspace.lite.subscription.manage": "إدارة الاشتراك",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "الاستخدام المتجدد",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "الاستخدام الأسبوعي",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "الاستخدام الشهري",
|
||||||
|
"workspace.lite.subscription.resetsIn": "إعادة تعيين في",
|
||||||
|
"workspace.lite.subscription.useBalance": "استخدم رصيدك المتوفر بعد الوصول إلى حدود الاستخدام",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
'اختر "OpenCode Go" كمزود في إعدادات opencode الخاصة بك لاستخدام نماذج Go.',
|
||||||
|
"workspace.lite.other.title": "اشتراك Go",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"عضو آخر في مساحة العمل هذه مشترك بالفعل في OpenCode Go. يمكن لعضو واحد فقط لكل مساحة عمل الاشتراك.",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go هو اشتراك بسعر $10 شهريًا يوفر وصولاً موثوقًا إلى نماذج البرمجة المفتوحة الشائعة مع حدود استخدام سخية.",
|
||||||
|
"workspace.lite.promo.modelsTitle": "ما يتضمنه",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"تم تصميم الخطة بشكل أساسي للمستخدمين الدوليين، مع استضافة النماذج في الولايات المتحدة والاتحاد الأوروبي وسنغافورة للحصول على وصول عالمي مستقر. قد تتغير الأسعار وحدود الاستخدام بناءً على تعلمنا من الاستخدام المبكر والملاحظات.",
|
||||||
|
"workspace.lite.promo.subscribe": "الاشتراك في Go",
|
||||||
|
"workspace.lite.promo.subscribing": "جارٍ إعادة التوجيه...",
|
||||||
|
|
||||||
"download.title": "OpenCode | تنزيل",
|
"download.title": "OpenCode | تنزيل",
|
||||||
"download.meta.description": "نزّل OpenCode لـ macOS، Windows، وLinux",
|
"download.meta.description": "نزّل OpenCode لـ macOS، Windows، وLinux",
|
||||||
"download.hero.title": "تنزيل OpenCode",
|
"download.hero.title": "تنزيل OpenCode",
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,7 @@ export const dict = {
|
||||||
"black.hero.title": "Acesse os melhores modelos de codificação do mundo",
|
"black.hero.title": "Acesse os melhores modelos de codificação do mundo",
|
||||||
"black.hero.subtitle": "Incluindo Claude, GPT, Gemini e mais",
|
"black.hero.subtitle": "Incluindo Claude, GPT, Gemini e mais",
|
||||||
"black.title": "OpenCode Black | Preços",
|
"black.title": "OpenCode Black | Preços",
|
||||||
|
"black.paused": "A inscrição no plano Black está temporariamente pausada.",
|
||||||
"black.plan.icon20": "Plano Black 20",
|
"black.plan.icon20": "Plano Black 20",
|
||||||
"black.plan.icon100": "Plano Black 100",
|
"black.plan.icon100": "Plano Black 100",
|
||||||
"black.plan.icon200": "Plano Black 200",
|
"black.plan.icon200": "Plano Black 200",
|
||||||
|
|
@ -342,12 +343,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "Entrada",
|
"workspace.usage.table.input": "Entrada",
|
||||||
"workspace.usage.table.output": "Saída",
|
"workspace.usage.table.output": "Saída",
|
||||||
"workspace.usage.table.cost": "Custo",
|
"workspace.usage.table.cost": "Custo",
|
||||||
|
"workspace.usage.table.session": "Sessão",
|
||||||
"workspace.usage.breakdown.input": "Entrada",
|
"workspace.usage.breakdown.input": "Entrada",
|
||||||
"workspace.usage.breakdown.cacheRead": "Leitura de Cache",
|
"workspace.usage.breakdown.cacheRead": "Leitura de Cache",
|
||||||
"workspace.usage.breakdown.cacheWrite": "Escrita em Cache",
|
"workspace.usage.breakdown.cacheWrite": "Escrita em Cache",
|
||||||
"workspace.usage.breakdown.output": "Saída",
|
"workspace.usage.breakdown.output": "Saída",
|
||||||
"workspace.usage.breakdown.reasoning": "Raciocínio",
|
"workspace.usage.breakdown.reasoning": "Raciocínio",
|
||||||
"workspace.usage.subscription": "assinatura (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "Custo",
|
"workspace.cost.title": "Custo",
|
||||||
"workspace.cost.subtitle": "Custos de uso discriminados por modelo.",
|
"workspace.cost.subtitle": "Custos de uso discriminados por modelo.",
|
||||||
|
|
@ -356,6 +360,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(excluído)",
|
"workspace.cost.deletedSuffix": "(excluído)",
|
||||||
"workspace.cost.empty": "Nenhum dado de uso disponível para o período selecionado.",
|
"workspace.cost.empty": "Nenhum dado de uso disponível para o período selecionado.",
|
||||||
"workspace.cost.subscriptionShort": "ass",
|
"workspace.cost.subscriptionShort": "ass",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "Chaves de API",
|
"workspace.keys.title": "Chaves de API",
|
||||||
"workspace.keys.subtitle": "Gerencie suas chaves de API para acessar os serviços opencode.",
|
"workspace.keys.subtitle": "Gerencie suas chaves de API para acessar os serviços opencode.",
|
||||||
|
|
@ -484,6 +489,36 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrollNote":
|
"workspace.black.waitlist.enrollNote":
|
||||||
"Ao clicar em Inscrever-se, sua assinatura começará imediatamente e seu cartão será cobrado.",
|
"Ao clicar em Inscrever-se, sua assinatura começará imediatamente e seu cartão será cobrado.",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "Carregando...",
|
||||||
|
"workspace.lite.time.day": "dia",
|
||||||
|
"workspace.lite.time.days": "dias",
|
||||||
|
"workspace.lite.time.hour": "hora",
|
||||||
|
"workspace.lite.time.hours": "horas",
|
||||||
|
"workspace.lite.time.minute": "minuto",
|
||||||
|
"workspace.lite.time.minutes": "minutos",
|
||||||
|
"workspace.lite.time.fewSeconds": "alguns segundos",
|
||||||
|
"workspace.lite.subscription.title": "Assinatura Go",
|
||||||
|
"workspace.lite.subscription.message": "Você assina o OpenCode Go.",
|
||||||
|
"workspace.lite.subscription.manage": "Gerenciar Assinatura",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "Uso Contínuo",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "Uso Semanal",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "Uso Mensal",
|
||||||
|
"workspace.lite.subscription.resetsIn": "Reinicia em",
|
||||||
|
"workspace.lite.subscription.useBalance": "Use seu saldo disponível após atingir os limites de uso",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
'Selecione "OpenCode Go" como provedor na sua configuração do opencode para usar os modelos Go.',
|
||||||
|
"workspace.lite.other.title": "Assinatura Go",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"Outro membro neste workspace já assina o OpenCode Go. Apenas um membro por workspace pode assinar.",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"O OpenCode Go é uma assinatura de $10 por mês que fornece acesso confiável a modelos abertos de codificação populares com limites de uso generosos.",
|
||||||
|
"workspace.lite.promo.modelsTitle": "O que está incluído",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"O plano é projetado principalmente para usuários internacionais, com modelos hospedados nos EUA, UE e Singapura para acesso global estável. Preços e limites de uso podem mudar conforme aprendemos com o uso inicial e feedback.",
|
||||||
|
"workspace.lite.promo.subscribe": "Assinar Go",
|
||||||
|
"workspace.lite.promo.subscribing": "Redirecionando...",
|
||||||
|
|
||||||
"download.title": "OpenCode | Baixar",
|
"download.title": "OpenCode | Baixar",
|
||||||
"download.meta.description": "Baixe o OpenCode para macOS, Windows e Linux",
|
"download.meta.description": "Baixe o OpenCode para macOS, Windows e Linux",
|
||||||
"download.hero.title": "Baixar OpenCode",
|
"download.hero.title": "Baixar OpenCode",
|
||||||
|
|
|
||||||
|
|
@ -245,6 +245,7 @@ export const dict = {
|
||||||
"black.hero.title": "Få adgang til verdens bedste kodningsmodeller",
|
"black.hero.title": "Få adgang til verdens bedste kodningsmodeller",
|
||||||
"black.hero.subtitle": "Inklusive Claude, GPT, Gemini og mere",
|
"black.hero.subtitle": "Inklusive Claude, GPT, Gemini og mere",
|
||||||
"black.title": "OpenCode Black | Priser",
|
"black.title": "OpenCode Black | Priser",
|
||||||
|
"black.paused": "Black-plantilmelding er midlertidigt sat på pause.",
|
||||||
"black.plan.icon20": "Black 20-plan",
|
"black.plan.icon20": "Black 20-plan",
|
||||||
"black.plan.icon100": "Black 100-plan",
|
"black.plan.icon100": "Black 100-plan",
|
||||||
"black.plan.icon200": "Black 200-plan",
|
"black.plan.icon200": "Black 200-plan",
|
||||||
|
|
@ -340,12 +341,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "Input",
|
"workspace.usage.table.input": "Input",
|
||||||
"workspace.usage.table.output": "Output",
|
"workspace.usage.table.output": "Output",
|
||||||
"workspace.usage.table.cost": "Omkostning",
|
"workspace.usage.table.cost": "Omkostning",
|
||||||
|
"workspace.usage.table.session": "Session",
|
||||||
"workspace.usage.breakdown.input": "Input",
|
"workspace.usage.breakdown.input": "Input",
|
||||||
"workspace.usage.breakdown.cacheRead": "Cache læst",
|
"workspace.usage.breakdown.cacheRead": "Cache læst",
|
||||||
"workspace.usage.breakdown.cacheWrite": "Cache skriv",
|
"workspace.usage.breakdown.cacheWrite": "Cache skriv",
|
||||||
"workspace.usage.breakdown.output": "Output",
|
"workspace.usage.breakdown.output": "Output",
|
||||||
"workspace.usage.breakdown.reasoning": "Ræsonnement",
|
"workspace.usage.breakdown.reasoning": "Ræsonnement",
|
||||||
"workspace.usage.subscription": "abonnement (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "Omkostninger",
|
"workspace.cost.title": "Omkostninger",
|
||||||
"workspace.cost.subtitle": "Brugsomkostninger opdelt efter model.",
|
"workspace.cost.subtitle": "Brugsomkostninger opdelt efter model.",
|
||||||
|
|
@ -354,6 +358,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(slettet)",
|
"workspace.cost.deletedSuffix": "(slettet)",
|
||||||
"workspace.cost.empty": "Ingen brugsdata tilgængelige for den valgte periode.",
|
"workspace.cost.empty": "Ingen brugsdata tilgængelige for den valgte periode.",
|
||||||
"workspace.cost.subscriptionShort": "sub",
|
"workspace.cost.subscriptionShort": "sub",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "API-nøgler",
|
"workspace.keys.title": "API-nøgler",
|
||||||
"workspace.keys.subtitle": "Administrer dine API-nøgler for at få adgang til opencode-tjenester.",
|
"workspace.keys.subtitle": "Administrer dine API-nøgler for at få adgang til opencode-tjenester.",
|
||||||
|
|
@ -482,6 +487,36 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrollNote":
|
"workspace.black.waitlist.enrollNote":
|
||||||
"Når du klikker på Tilmeld, starter dit abonnement med det samme, og dit kort vil blive debiteret.",
|
"Når du klikker på Tilmeld, starter dit abonnement med det samme, og dit kort vil blive debiteret.",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "Indlæser...",
|
||||||
|
"workspace.lite.time.day": "dag",
|
||||||
|
"workspace.lite.time.days": "dage",
|
||||||
|
"workspace.lite.time.hour": "time",
|
||||||
|
"workspace.lite.time.hours": "timer",
|
||||||
|
"workspace.lite.time.minute": "minut",
|
||||||
|
"workspace.lite.time.minutes": "minutter",
|
||||||
|
"workspace.lite.time.fewSeconds": "et par sekunder",
|
||||||
|
"workspace.lite.subscription.title": "Go-abonnement",
|
||||||
|
"workspace.lite.subscription.message": "Du abonnerer på OpenCode Go.",
|
||||||
|
"workspace.lite.subscription.manage": "Administrer abonnement",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "Løbende forbrug",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "Ugentligt forbrug",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "Månedligt forbrug",
|
||||||
|
"workspace.lite.subscription.resetsIn": "Nulstiller i",
|
||||||
|
"workspace.lite.subscription.useBalance": "Brug din tilgængelige saldo, når du har nået forbrugsgrænserne",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
'Vælg "OpenCode Go" som udbyder i din opencode-konfiguration for at bruge Go-modeller.',
|
||||||
|
"workspace.lite.other.title": "Go-abonnement",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"Et andet medlem i dette workspace abonnerer allerede på OpenCode Go. Kun ét medlem pr. workspace kan abonnere.",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go er et abonnement til $10 om måneden, der giver pålidelig adgang til populære åbne kodningsmodeller med generøse forbrugsgrænser.",
|
||||||
|
"workspace.lite.promo.modelsTitle": "Hvad er inkluderet",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"Planen er primært designet til internationale brugere, med modeller hostet i USA, EU og Singapore for stabil global adgang. Priser og forbrugsgrænser kan ændre sig, efterhånden som vi lærer af tidlig brug og feedback.",
|
||||||
|
"workspace.lite.promo.subscribe": "Abonner på Go",
|
||||||
|
"workspace.lite.promo.subscribing": "Omdirigerer...",
|
||||||
|
|
||||||
"download.title": "OpenCode | Download",
|
"download.title": "OpenCode | Download",
|
||||||
"download.meta.description": "Download OpenCode til macOS, Windows og Linux",
|
"download.meta.description": "Download OpenCode til macOS, Windows og Linux",
|
||||||
"download.hero.title": "Download OpenCode",
|
"download.hero.title": "Download OpenCode",
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,7 @@ export const dict = {
|
||||||
"black.hero.title": "Zugriff auf die weltweit besten Coding-Modelle",
|
"black.hero.title": "Zugriff auf die weltweit besten Coding-Modelle",
|
||||||
"black.hero.subtitle": "Einschließlich Claude, GPT, Gemini und mehr",
|
"black.hero.subtitle": "Einschließlich Claude, GPT, Gemini und mehr",
|
||||||
"black.title": "OpenCode Black | Preise",
|
"black.title": "OpenCode Black | Preise",
|
||||||
|
"black.paused": "Die Anmeldung zum Black-Plan ist vorübergehend pausiert.",
|
||||||
"black.plan.icon20": "Black 20 Plan",
|
"black.plan.icon20": "Black 20 Plan",
|
||||||
"black.plan.icon100": "Black 100 Plan",
|
"black.plan.icon100": "Black 100 Plan",
|
||||||
"black.plan.icon200": "Black 200 Plan",
|
"black.plan.icon200": "Black 200 Plan",
|
||||||
|
|
@ -342,12 +343,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "Input",
|
"workspace.usage.table.input": "Input",
|
||||||
"workspace.usage.table.output": "Output",
|
"workspace.usage.table.output": "Output",
|
||||||
"workspace.usage.table.cost": "Kosten",
|
"workspace.usage.table.cost": "Kosten",
|
||||||
|
"workspace.usage.table.session": "Sitzung",
|
||||||
"workspace.usage.breakdown.input": "Input",
|
"workspace.usage.breakdown.input": "Input",
|
||||||
"workspace.usage.breakdown.cacheRead": "Cache Read",
|
"workspace.usage.breakdown.cacheRead": "Cache Read",
|
||||||
"workspace.usage.breakdown.cacheWrite": "Cache Write",
|
"workspace.usage.breakdown.cacheWrite": "Cache Write",
|
||||||
"workspace.usage.breakdown.output": "Output",
|
"workspace.usage.breakdown.output": "Output",
|
||||||
"workspace.usage.breakdown.reasoning": "Reasoning",
|
"workspace.usage.breakdown.reasoning": "Reasoning",
|
||||||
"workspace.usage.subscription": "Abonnement (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "Kosten",
|
"workspace.cost.title": "Kosten",
|
||||||
"workspace.cost.subtitle": "Nutzungskosten aufgeschlüsselt nach Modell.",
|
"workspace.cost.subtitle": "Nutzungskosten aufgeschlüsselt nach Modell.",
|
||||||
|
|
@ -356,6 +360,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(gelöscht)",
|
"workspace.cost.deletedSuffix": "(gelöscht)",
|
||||||
"workspace.cost.empty": "Keine Nutzungsdaten für den gewählten Zeitraum verfügbar.",
|
"workspace.cost.empty": "Keine Nutzungsdaten für den gewählten Zeitraum verfügbar.",
|
||||||
"workspace.cost.subscriptionShort": "Abo",
|
"workspace.cost.subscriptionShort": "Abo",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "API Keys",
|
"workspace.keys.title": "API Keys",
|
||||||
"workspace.keys.subtitle": "Verwalte deine API Keys für den Zugriff auf OpenCode-Dienste.",
|
"workspace.keys.subtitle": "Verwalte deine API Keys für den Zugriff auf OpenCode-Dienste.",
|
||||||
|
|
@ -484,6 +489,36 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrollNote":
|
"workspace.black.waitlist.enrollNote":
|
||||||
"Wenn du auf Einschreiben klickst, startet dein Abo sofort und deine Karte wird belastet.",
|
"Wenn du auf Einschreiben klickst, startet dein Abo sofort und deine Karte wird belastet.",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "Lade...",
|
||||||
|
"workspace.lite.time.day": "Tag",
|
||||||
|
"workspace.lite.time.days": "Tage",
|
||||||
|
"workspace.lite.time.hour": "Stunde",
|
||||||
|
"workspace.lite.time.hours": "Stunden",
|
||||||
|
"workspace.lite.time.minute": "Minute",
|
||||||
|
"workspace.lite.time.minutes": "Minuten",
|
||||||
|
"workspace.lite.time.fewSeconds": "einige Sekunden",
|
||||||
|
"workspace.lite.subscription.title": "Go-Abonnement",
|
||||||
|
"workspace.lite.subscription.message": "Du hast OpenCode Go abonniert.",
|
||||||
|
"workspace.lite.subscription.manage": "Abo verwalten",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "Fortlaufende Nutzung",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "Wöchentliche Nutzung",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "Monatliche Nutzung",
|
||||||
|
"workspace.lite.subscription.resetsIn": "Setzt zurück in",
|
||||||
|
"workspace.lite.subscription.useBalance": "Nutze dein verfügbares Guthaben, nachdem die Nutzungslimits erreicht sind",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
'Wähle "OpenCode Go" als Anbieter in deiner opencode-Konfiguration, um Go-Modelle zu verwenden.',
|
||||||
|
"workspace.lite.other.title": "Go-Abonnement",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"Ein anderes Mitglied in diesem Workspace hat OpenCode Go bereits abonniert. Nur ein Mitglied pro Workspace kann abonnieren.",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go ist ein Abonnement für $10 pro Monat, das zuverlässigen Zugriff auf beliebte offene Coding-Modelle mit großzügigen Nutzungslimits bietet.",
|
||||||
|
"workspace.lite.promo.modelsTitle": "Was enthalten ist",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"Der Plan wurde hauptsächlich für internationale Nutzer entwickelt, wobei die Modelle in den USA, der EU und Singapur gehostet werden, um einen stabilen weltweiten Zugriff zu gewährleisten. Preise und Nutzungslimits können sich ändern, während wir aus der frühen Nutzung und dem Feedback lernen.",
|
||||||
|
"workspace.lite.promo.subscribe": "Go abonnieren",
|
||||||
|
"workspace.lite.promo.subscribing": "Leite weiter...",
|
||||||
|
|
||||||
"download.title": "OpenCode | Download",
|
"download.title": "OpenCode | Download",
|
||||||
"download.meta.description": "Lade OpenCode für macOS, Windows und Linux herunter",
|
"download.meta.description": "Lade OpenCode für macOS, Windows und Linux herunter",
|
||||||
"download.hero.title": "OpenCode herunterladen",
|
"download.hero.title": "OpenCode herunterladen",
|
||||||
|
|
|
||||||
|
|
@ -239,6 +239,7 @@ export const dict = {
|
||||||
"black.hero.title": "Access all the world's best coding models",
|
"black.hero.title": "Access all the world's best coding models",
|
||||||
"black.hero.subtitle": "Including Claude, GPT, Gemini and more",
|
"black.hero.subtitle": "Including Claude, GPT, Gemini and more",
|
||||||
"black.title": "OpenCode Black | Pricing",
|
"black.title": "OpenCode Black | Pricing",
|
||||||
|
"black.paused": "Black plan enrollment is temporarily paused.",
|
||||||
"black.plan.icon20": "Black 20 plan",
|
"black.plan.icon20": "Black 20 plan",
|
||||||
"black.plan.icon100": "Black 100 plan",
|
"black.plan.icon100": "Black 100 plan",
|
||||||
"black.plan.icon200": "Black 200 plan",
|
"black.plan.icon200": "Black 200 plan",
|
||||||
|
|
@ -334,12 +335,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "Input",
|
"workspace.usage.table.input": "Input",
|
||||||
"workspace.usage.table.output": "Output",
|
"workspace.usage.table.output": "Output",
|
||||||
"workspace.usage.table.cost": "Cost",
|
"workspace.usage.table.cost": "Cost",
|
||||||
|
"workspace.usage.table.session": "Session",
|
||||||
"workspace.usage.breakdown.input": "Input",
|
"workspace.usage.breakdown.input": "Input",
|
||||||
"workspace.usage.breakdown.cacheRead": "Cache Read",
|
"workspace.usage.breakdown.cacheRead": "Cache Read",
|
||||||
"workspace.usage.breakdown.cacheWrite": "Cache Write",
|
"workspace.usage.breakdown.cacheWrite": "Cache Write",
|
||||||
"workspace.usage.breakdown.output": "Output",
|
"workspace.usage.breakdown.output": "Output",
|
||||||
"workspace.usage.breakdown.reasoning": "Reasoning",
|
"workspace.usage.breakdown.reasoning": "Reasoning",
|
||||||
"workspace.usage.subscription": "subscription (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "Cost",
|
"workspace.cost.title": "Cost",
|
||||||
"workspace.cost.subtitle": "Usage costs broken down by model.",
|
"workspace.cost.subtitle": "Usage costs broken down by model.",
|
||||||
|
|
@ -348,6 +352,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(deleted)",
|
"workspace.cost.deletedSuffix": "(deleted)",
|
||||||
"workspace.cost.empty": "No usage data available for the selected period.",
|
"workspace.cost.empty": "No usage data available for the selected period.",
|
||||||
"workspace.cost.subscriptionShort": "sub",
|
"workspace.cost.subscriptionShort": "sub",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "API Keys",
|
"workspace.keys.title": "API Keys",
|
||||||
"workspace.keys.subtitle": "Manage your API keys for accessing opencode services.",
|
"workspace.keys.subtitle": "Manage your API keys for accessing opencode services.",
|
||||||
|
|
@ -476,6 +481,36 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrollNote":
|
"workspace.black.waitlist.enrollNote":
|
||||||
"When you click Enroll, your subscription starts immediately and your card will be charged.",
|
"When you click Enroll, your subscription starts immediately and your card will be charged.",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "Loading...",
|
||||||
|
"workspace.lite.time.day": "day",
|
||||||
|
"workspace.lite.time.days": "days",
|
||||||
|
"workspace.lite.time.hour": "hour",
|
||||||
|
"workspace.lite.time.hours": "hours",
|
||||||
|
"workspace.lite.time.minute": "minute",
|
||||||
|
"workspace.lite.time.minutes": "minutes",
|
||||||
|
"workspace.lite.time.fewSeconds": "a few seconds",
|
||||||
|
"workspace.lite.subscription.title": "Go Subscription",
|
||||||
|
"workspace.lite.subscription.message": "You are subscribed to OpenCode Go.",
|
||||||
|
"workspace.lite.subscription.manage": "Manage Subscription",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "Rolling Usage",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "Weekly Usage",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "Monthly Usage",
|
||||||
|
"workspace.lite.subscription.resetsIn": "Resets in",
|
||||||
|
"workspace.lite.subscription.useBalance": "Use your available balance after reaching the usage limits",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
'Select "OpenCode Go" as the provider in your opencode configuration to use Go models.',
|
||||||
|
"workspace.lite.other.title": "Go Subscription",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"Another member in this workspace is already subscribed to OpenCode Go. Only one member per workspace can subscribe.",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go is a $10 per month subscription that provides reliable access to popular open coding models with generous usage limits.",
|
||||||
|
"workspace.lite.promo.modelsTitle": "What's Included",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"The plan is designed primarily for international users, with models hosted in the US, EU, and Singapore for stable global access. Pricing and usage limits may change as we learn from early usage and feedback.",
|
||||||
|
"workspace.lite.promo.subscribe": "Subscribe to Go",
|
||||||
|
"workspace.lite.promo.subscribing": "Redirecting...",
|
||||||
|
|
||||||
"download.title": "OpenCode | Download",
|
"download.title": "OpenCode | Download",
|
||||||
"download.meta.description": "Download OpenCode for macOS, Windows, and Linux",
|
"download.meta.description": "Download OpenCode for macOS, Windows, and Linux",
|
||||||
"download.hero.title": "Download OpenCode",
|
"download.hero.title": "Download OpenCode",
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,7 @@ export const dict = {
|
||||||
"black.hero.title": "Accede a los mejores modelos de codificación del mundo",
|
"black.hero.title": "Accede a los mejores modelos de codificación del mundo",
|
||||||
"black.hero.subtitle": "Incluyendo Claude, GPT, Gemini y más",
|
"black.hero.subtitle": "Incluyendo Claude, GPT, Gemini y más",
|
||||||
"black.title": "OpenCode Black | Precios",
|
"black.title": "OpenCode Black | Precios",
|
||||||
|
"black.paused": "La inscripción al plan Black está temporalmente pausada.",
|
||||||
"black.plan.icon20": "Plan Black 20",
|
"black.plan.icon20": "Plan Black 20",
|
||||||
"black.plan.icon100": "Plan Black 100",
|
"black.plan.icon100": "Plan Black 100",
|
||||||
"black.plan.icon200": "Plan Black 200",
|
"black.plan.icon200": "Plan Black 200",
|
||||||
|
|
@ -343,12 +344,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "Entrada",
|
"workspace.usage.table.input": "Entrada",
|
||||||
"workspace.usage.table.output": "Salida",
|
"workspace.usage.table.output": "Salida",
|
||||||
"workspace.usage.table.cost": "Costo",
|
"workspace.usage.table.cost": "Costo",
|
||||||
|
"workspace.usage.table.session": "Sesión",
|
||||||
"workspace.usage.breakdown.input": "Entrada",
|
"workspace.usage.breakdown.input": "Entrada",
|
||||||
"workspace.usage.breakdown.cacheRead": "Lectura de Caché",
|
"workspace.usage.breakdown.cacheRead": "Lectura de Caché",
|
||||||
"workspace.usage.breakdown.cacheWrite": "Escritura de Caché",
|
"workspace.usage.breakdown.cacheWrite": "Escritura de Caché",
|
||||||
"workspace.usage.breakdown.output": "Salida",
|
"workspace.usage.breakdown.output": "Salida",
|
||||||
"workspace.usage.breakdown.reasoning": "Razonamiento",
|
"workspace.usage.breakdown.reasoning": "Razonamiento",
|
||||||
"workspace.usage.subscription": "suscripción (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "Costo",
|
"workspace.cost.title": "Costo",
|
||||||
"workspace.cost.subtitle": "Costos de uso desglosados por modelo.",
|
"workspace.cost.subtitle": "Costos de uso desglosados por modelo.",
|
||||||
|
|
@ -357,6 +361,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(eliminado)",
|
"workspace.cost.deletedSuffix": "(eliminado)",
|
||||||
"workspace.cost.empty": "No hay datos de uso disponibles para el periodo seleccionado.",
|
"workspace.cost.empty": "No hay datos de uso disponibles para el periodo seleccionado.",
|
||||||
"workspace.cost.subscriptionShort": "sub",
|
"workspace.cost.subscriptionShort": "sub",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "Claves API",
|
"workspace.keys.title": "Claves API",
|
||||||
"workspace.keys.subtitle": "Gestiona tus claves API para acceder a los servicios de opencode.",
|
"workspace.keys.subtitle": "Gestiona tus claves API para acceder a los servicios de opencode.",
|
||||||
|
|
@ -485,6 +490,36 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrollNote":
|
"workspace.black.waitlist.enrollNote":
|
||||||
"Cuando haces clic en Inscribirse, tu suscripción comienza inmediatamente y se cargará a tu tarjeta.",
|
"Cuando haces clic en Inscribirse, tu suscripción comienza inmediatamente y se cargará a tu tarjeta.",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "Cargando...",
|
||||||
|
"workspace.lite.time.day": "día",
|
||||||
|
"workspace.lite.time.days": "días",
|
||||||
|
"workspace.lite.time.hour": "hora",
|
||||||
|
"workspace.lite.time.hours": "horas",
|
||||||
|
"workspace.lite.time.minute": "minuto",
|
||||||
|
"workspace.lite.time.minutes": "minutos",
|
||||||
|
"workspace.lite.time.fewSeconds": "unos pocos segundos",
|
||||||
|
"workspace.lite.subscription.title": "Suscripción Go",
|
||||||
|
"workspace.lite.subscription.message": "Estás suscrito a OpenCode Go.",
|
||||||
|
"workspace.lite.subscription.manage": "Gestionar Suscripción",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "Uso Continuo",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "Uso Semanal",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "Uso Mensual",
|
||||||
|
"workspace.lite.subscription.resetsIn": "Se reinicia en",
|
||||||
|
"workspace.lite.subscription.useBalance": "Usa tu saldo disponible después de alcanzar los límites de uso",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
'Selecciona "OpenCode Go" como proveedor en tu configuración de opencode para usar los modelos Go.',
|
||||||
|
"workspace.lite.other.title": "Suscripción Go",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"Otro miembro de este espacio de trabajo ya está suscrito a OpenCode Go. Solo un miembro por espacio de trabajo puede suscribirse.",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go es una suscripción de $10 al mes que proporciona acceso confiable a modelos de codificación abiertos populares con generosos límites de uso.",
|
||||||
|
"workspace.lite.promo.modelsTitle": "Qué incluye",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"El plan está diseñado principalmente para usuarios internacionales, con modelos alojados en EE. UU., la UE y Singapur para un acceso global estable. Los precios y los límites de uso pueden cambiar a medida que aprendemos del uso inicial y los comentarios.",
|
||||||
|
"workspace.lite.promo.subscribe": "Suscribirse a Go",
|
||||||
|
"workspace.lite.promo.subscribing": "Redirigiendo...",
|
||||||
|
|
||||||
"download.title": "OpenCode | Descargar",
|
"download.title": "OpenCode | Descargar",
|
||||||
"download.meta.description": "Descarga OpenCode para macOS, Windows y Linux",
|
"download.meta.description": "Descarga OpenCode para macOS, Windows y Linux",
|
||||||
"download.hero.title": "Descargar OpenCode",
|
"download.hero.title": "Descargar OpenCode",
|
||||||
|
|
|
||||||
|
|
@ -251,6 +251,7 @@ export const dict = {
|
||||||
"black.hero.title": "Accédez aux meilleurs modèles de code au monde",
|
"black.hero.title": "Accédez aux meilleurs modèles de code au monde",
|
||||||
"black.hero.subtitle": "Y compris Claude, GPT, Gemini et plus",
|
"black.hero.subtitle": "Y compris Claude, GPT, Gemini et plus",
|
||||||
"black.title": "OpenCode Black | Tarification",
|
"black.title": "OpenCode Black | Tarification",
|
||||||
|
"black.paused": "L'inscription au plan Black est temporairement suspendue.",
|
||||||
"black.plan.icon20": "Forfait Black 20",
|
"black.plan.icon20": "Forfait Black 20",
|
||||||
"black.plan.icon100": "Forfait Black 100",
|
"black.plan.icon100": "Forfait Black 100",
|
||||||
"black.plan.icon200": "Forfait Black 200",
|
"black.plan.icon200": "Forfait Black 200",
|
||||||
|
|
@ -348,12 +349,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "Entrée",
|
"workspace.usage.table.input": "Entrée",
|
||||||
"workspace.usage.table.output": "Sortie",
|
"workspace.usage.table.output": "Sortie",
|
||||||
"workspace.usage.table.cost": "Coût",
|
"workspace.usage.table.cost": "Coût",
|
||||||
|
"workspace.usage.table.session": "Session",
|
||||||
"workspace.usage.breakdown.input": "Entrée",
|
"workspace.usage.breakdown.input": "Entrée",
|
||||||
"workspace.usage.breakdown.cacheRead": "Lecture cache",
|
"workspace.usage.breakdown.cacheRead": "Lecture cache",
|
||||||
"workspace.usage.breakdown.cacheWrite": "Écriture cache",
|
"workspace.usage.breakdown.cacheWrite": "Écriture cache",
|
||||||
"workspace.usage.breakdown.output": "Sortie",
|
"workspace.usage.breakdown.output": "Sortie",
|
||||||
"workspace.usage.breakdown.reasoning": "Raisonnement",
|
"workspace.usage.breakdown.reasoning": "Raisonnement",
|
||||||
"workspace.usage.subscription": "abonnement ({{amount}} $)",
|
"workspace.usage.subscription": "Black ({{amount}} $)",
|
||||||
|
"workspace.usage.lite": "Go ({{amount}} $)",
|
||||||
|
"workspace.usage.byok": "BYOK ({{amount}} $)",
|
||||||
|
|
||||||
"workspace.cost.title": "Coût",
|
"workspace.cost.title": "Coût",
|
||||||
"workspace.cost.subtitle": "Coûts d'utilisation répartis par modèle.",
|
"workspace.cost.subtitle": "Coûts d'utilisation répartis par modèle.",
|
||||||
|
|
@ -362,6 +366,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(supprimé)",
|
"workspace.cost.deletedSuffix": "(supprimé)",
|
||||||
"workspace.cost.empty": "Aucune donnée d'utilisation disponible pour la période sélectionnée.",
|
"workspace.cost.empty": "Aucune donnée d'utilisation disponible pour la période sélectionnée.",
|
||||||
"workspace.cost.subscriptionShort": "abo",
|
"workspace.cost.subscriptionShort": "abo",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "Clés API",
|
"workspace.keys.title": "Clés API",
|
||||||
"workspace.keys.subtitle": "Gérez vos clés API pour accéder aux services OpenCode.",
|
"workspace.keys.subtitle": "Gérez vos clés API pour accéder aux services OpenCode.",
|
||||||
|
|
@ -493,6 +498,37 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrollNote":
|
"workspace.black.waitlist.enrollNote":
|
||||||
"Lorsque vous cliquez sur S'inscrire, votre abonnement démarre immédiatement et votre carte sera débitée.",
|
"Lorsque vous cliquez sur S'inscrire, votre abonnement démarre immédiatement et votre carte sera débitée.",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "Chargement...",
|
||||||
|
"workspace.lite.time.day": "jour",
|
||||||
|
"workspace.lite.time.days": "jours",
|
||||||
|
"workspace.lite.time.hour": "heure",
|
||||||
|
"workspace.lite.time.hours": "heures",
|
||||||
|
"workspace.lite.time.minute": "minute",
|
||||||
|
"workspace.lite.time.minutes": "minutes",
|
||||||
|
"workspace.lite.time.fewSeconds": "quelques secondes",
|
||||||
|
"workspace.lite.subscription.title": "Abonnement Go",
|
||||||
|
"workspace.lite.subscription.message": "Vous êtes abonné à OpenCode Go.",
|
||||||
|
"workspace.lite.subscription.manage": "Gérer l'abonnement",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "Utilisation glissante",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "Utilisation hebdomadaire",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "Utilisation mensuelle",
|
||||||
|
"workspace.lite.subscription.resetsIn": "Réinitialisation dans",
|
||||||
|
"workspace.lite.subscription.useBalance":
|
||||||
|
"Utilisez votre solde disponible après avoir atteint les limites d'utilisation",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
'Sélectionnez "OpenCode Go" comme fournisseur dans votre configuration opencode pour utiliser les modèles Go.',
|
||||||
|
"workspace.lite.other.title": "Abonnement Go",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"Un autre membre de cet espace de travail est déjà abonné à OpenCode Go. Un seul membre par espace de travail peut s'abonner.",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go est un abonnement à 10 $ par mois qui offre un accès fiable aux modèles de codage ouverts populaires avec des limites d'utilisation généreuses.",
|
||||||
|
"workspace.lite.promo.modelsTitle": "Ce qui est inclus",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"Le plan est conçu principalement pour les utilisateurs internationaux, avec des modèles hébergés aux États-Unis, dans l'UE et à Singapour pour un accès mondial stable. Les tarifs et les limites d'utilisation peuvent changer à mesure que nous apprenons des premières utilisations et des commentaires.",
|
||||||
|
"workspace.lite.promo.subscribe": "S'abonner à Go",
|
||||||
|
"workspace.lite.promo.subscribing": "Redirection...",
|
||||||
|
|
||||||
"download.title": "OpenCode | Téléchargement",
|
"download.title": "OpenCode | Téléchargement",
|
||||||
"download.meta.description": "Téléchargez OpenCode pour macOS, Windows et Linux",
|
"download.meta.description": "Téléchargez OpenCode pour macOS, Windows et Linux",
|
||||||
"download.hero.title": "Télécharger OpenCode",
|
"download.hero.title": "Télécharger OpenCode",
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,7 @@ export const dict = {
|
||||||
"black.hero.title": "Accedi ai migliori modelli di coding al mondo",
|
"black.hero.title": "Accedi ai migliori modelli di coding al mondo",
|
||||||
"black.hero.subtitle": "Inclusi Claude, GPT, Gemini e altri",
|
"black.hero.subtitle": "Inclusi Claude, GPT, Gemini e altri",
|
||||||
"black.title": "OpenCode Black | Prezzi",
|
"black.title": "OpenCode Black | Prezzi",
|
||||||
|
"black.paused": "L'iscrizione al piano Black è temporaneamente sospesa.",
|
||||||
"black.plan.icon20": "Piano Black 20",
|
"black.plan.icon20": "Piano Black 20",
|
||||||
"black.plan.icon100": "Piano Black 100",
|
"black.plan.icon100": "Piano Black 100",
|
||||||
"black.plan.icon200": "Piano Black 200",
|
"black.plan.icon200": "Piano Black 200",
|
||||||
|
|
@ -342,12 +343,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "Input",
|
"workspace.usage.table.input": "Input",
|
||||||
"workspace.usage.table.output": "Output",
|
"workspace.usage.table.output": "Output",
|
||||||
"workspace.usage.table.cost": "Costo",
|
"workspace.usage.table.cost": "Costo",
|
||||||
|
"workspace.usage.table.session": "Sessione",
|
||||||
"workspace.usage.breakdown.input": "Input",
|
"workspace.usage.breakdown.input": "Input",
|
||||||
"workspace.usage.breakdown.cacheRead": "Lettura Cache",
|
"workspace.usage.breakdown.cacheRead": "Lettura Cache",
|
||||||
"workspace.usage.breakdown.cacheWrite": "Scrittura Cache",
|
"workspace.usage.breakdown.cacheWrite": "Scrittura Cache",
|
||||||
"workspace.usage.breakdown.output": "Output",
|
"workspace.usage.breakdown.output": "Output",
|
||||||
"workspace.usage.breakdown.reasoning": "Reasoning",
|
"workspace.usage.breakdown.reasoning": "Reasoning",
|
||||||
"workspace.usage.subscription": "abbonamento (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "Costo",
|
"workspace.cost.title": "Costo",
|
||||||
"workspace.cost.subtitle": "Costi di utilizzo suddivisi per modello.",
|
"workspace.cost.subtitle": "Costi di utilizzo suddivisi per modello.",
|
||||||
|
|
@ -356,6 +360,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(eliminato)",
|
"workspace.cost.deletedSuffix": "(eliminato)",
|
||||||
"workspace.cost.empty": "Nessun dato di utilizzo disponibile per il periodo selezionato.",
|
"workspace.cost.empty": "Nessun dato di utilizzo disponibile per il periodo selezionato.",
|
||||||
"workspace.cost.subscriptionShort": "sub",
|
"workspace.cost.subscriptionShort": "sub",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "Chiavi API",
|
"workspace.keys.title": "Chiavi API",
|
||||||
"workspace.keys.subtitle": "Gestisci le tue chiavi API per accedere ai servizi opencode.",
|
"workspace.keys.subtitle": "Gestisci le tue chiavi API per accedere ai servizi opencode.",
|
||||||
|
|
@ -484,6 +489,36 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrollNote":
|
"workspace.black.waitlist.enrollNote":
|
||||||
"Quando clicchi su Iscriviti, il tuo abbonamento inizia immediatamente e la tua carta verrà addebitata.",
|
"Quando clicchi su Iscriviti, il tuo abbonamento inizia immediatamente e la tua carta verrà addebitata.",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "Caricamento...",
|
||||||
|
"workspace.lite.time.day": "giorno",
|
||||||
|
"workspace.lite.time.days": "giorni",
|
||||||
|
"workspace.lite.time.hour": "ora",
|
||||||
|
"workspace.lite.time.hours": "ore",
|
||||||
|
"workspace.lite.time.minute": "minuto",
|
||||||
|
"workspace.lite.time.minutes": "minuti",
|
||||||
|
"workspace.lite.time.fewSeconds": "pochi secondi",
|
||||||
|
"workspace.lite.subscription.title": "Abbonamento Go",
|
||||||
|
"workspace.lite.subscription.message": "Sei abbonato a OpenCode Go.",
|
||||||
|
"workspace.lite.subscription.manage": "Gestisci Abbonamento",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "Utilizzo Continuativo",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "Utilizzo Settimanale",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "Utilizzo Mensile",
|
||||||
|
"workspace.lite.subscription.resetsIn": "Si resetta tra",
|
||||||
|
"workspace.lite.subscription.useBalance": "Usa il tuo saldo disponibile dopo aver raggiunto i limiti di utilizzo",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
'Seleziona "OpenCode Go" come provider nella tua configurazione opencode per utilizzare i modelli Go.',
|
||||||
|
"workspace.lite.other.title": "Abbonamento Go",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"Un altro membro in questo workspace è già abbonato a OpenCode Go. Solo un membro per workspace può abbonarsi.",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go è un abbonamento a $10 al mese che fornisce un accesso affidabile a popolari modelli di coding aperti con generosi limiti di utilizzo.",
|
||||||
|
"workspace.lite.promo.modelsTitle": "Cosa è incluso",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"Il piano è progettato principalmente per gli utenti internazionali, con modelli ospitati in US, EU e Singapore per un accesso globale stabile. I prezzi e i limiti di utilizzo potrebbero cambiare man mano che impariamo dall'utilizzo iniziale e dal feedback.",
|
||||||
|
"workspace.lite.promo.subscribe": "Abbonati a Go",
|
||||||
|
"workspace.lite.promo.subscribing": "Reindirizzamento...",
|
||||||
|
|
||||||
"download.title": "OpenCode | Download",
|
"download.title": "OpenCode | Download",
|
||||||
"download.meta.description": "Scarica OpenCode per macOS, Windows e Linux",
|
"download.meta.description": "Scarica OpenCode per macOS, Windows e Linux",
|
||||||
"download.hero.title": "Scarica OpenCode",
|
"download.hero.title": "Scarica OpenCode",
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,7 @@ export const dict = {
|
||||||
"black.hero.title": "世界最高峰のコーディングモデルすべてにアクセス",
|
"black.hero.title": "世界最高峰のコーディングモデルすべてにアクセス",
|
||||||
"black.hero.subtitle": "Claude、GPT、Gemini などを含む",
|
"black.hero.subtitle": "Claude、GPT、Gemini などを含む",
|
||||||
"black.title": "OpenCode Black | 料金",
|
"black.title": "OpenCode Black | 料金",
|
||||||
|
"black.paused": "Blackプランの登録は一時的に停止しています。",
|
||||||
"black.plan.icon20": "Black 20 プラン",
|
"black.plan.icon20": "Black 20 プラン",
|
||||||
"black.plan.icon100": "Black 100 プラン",
|
"black.plan.icon100": "Black 100 プラン",
|
||||||
"black.plan.icon200": "Black 200 プラン",
|
"black.plan.icon200": "Black 200 プラン",
|
||||||
|
|
@ -339,12 +340,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "入力",
|
"workspace.usage.table.input": "入力",
|
||||||
"workspace.usage.table.output": "出力",
|
"workspace.usage.table.output": "出力",
|
||||||
"workspace.usage.table.cost": "コスト",
|
"workspace.usage.table.cost": "コスト",
|
||||||
|
"workspace.usage.table.session": "セッション",
|
||||||
"workspace.usage.breakdown.input": "入力",
|
"workspace.usage.breakdown.input": "入力",
|
||||||
"workspace.usage.breakdown.cacheRead": "キャッシュ読み取り",
|
"workspace.usage.breakdown.cacheRead": "キャッシュ読み取り",
|
||||||
"workspace.usage.breakdown.cacheWrite": "キャッシュ書き込み",
|
"workspace.usage.breakdown.cacheWrite": "キャッシュ書き込み",
|
||||||
"workspace.usage.breakdown.output": "出力",
|
"workspace.usage.breakdown.output": "出力",
|
||||||
"workspace.usage.breakdown.reasoning": "推論",
|
"workspace.usage.breakdown.reasoning": "推論",
|
||||||
"workspace.usage.subscription": "サブスクリプション (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "コスト",
|
"workspace.cost.title": "コスト",
|
||||||
"workspace.cost.subtitle": "モデルごとの使用料金の内訳。",
|
"workspace.cost.subtitle": "モデルごとの使用料金の内訳。",
|
||||||
|
|
@ -353,6 +357,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(削除済み)",
|
"workspace.cost.deletedSuffix": "(削除済み)",
|
||||||
"workspace.cost.empty": "選択した期間の使用状況データはありません。",
|
"workspace.cost.empty": "選択した期間の使用状況データはありません。",
|
||||||
"workspace.cost.subscriptionShort": "サブ",
|
"workspace.cost.subscriptionShort": "サブ",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "APIキー",
|
"workspace.keys.title": "APIキー",
|
||||||
"workspace.keys.subtitle": "OpenCodeサービスにアクセスするためのAPIキーを管理します。",
|
"workspace.keys.subtitle": "OpenCodeサービスにアクセスするためのAPIキーを管理します。",
|
||||||
|
|
@ -482,6 +487,36 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrollNote":
|
"workspace.black.waitlist.enrollNote":
|
||||||
"「登録する」をクリックすると、サブスクリプションがすぐに開始され、カードに請求されます。",
|
"「登録する」をクリックすると、サブスクリプションがすぐに開始され、カードに請求されます。",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "読み込み中...",
|
||||||
|
"workspace.lite.time.day": "日",
|
||||||
|
"workspace.lite.time.days": "日",
|
||||||
|
"workspace.lite.time.hour": "時間",
|
||||||
|
"workspace.lite.time.hours": "時間",
|
||||||
|
"workspace.lite.time.minute": "分",
|
||||||
|
"workspace.lite.time.minutes": "分",
|
||||||
|
"workspace.lite.time.fewSeconds": "数秒",
|
||||||
|
"workspace.lite.subscription.title": "Goサブスクリプション",
|
||||||
|
"workspace.lite.subscription.message": "あなたは OpenCode Go を購読しています。",
|
||||||
|
"workspace.lite.subscription.manage": "サブスクリプションの管理",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "ローリング利用量",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "週間利用量",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "月間利用量",
|
||||||
|
"workspace.lite.subscription.resetsIn": "リセットまで",
|
||||||
|
"workspace.lite.subscription.useBalance": "利用限度額に達したら利用可能な残高を使用する",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
"Go モデルを使用するには、opencode の設定で「OpenCode Go」をプロバイダーとして選択してください。",
|
||||||
|
"workspace.lite.other.title": "Goサブスクリプション",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"このワークスペースの別のメンバーが既に OpenCode Go を購読しています。ワークスペースにつき1人のメンバーのみが購読できます。",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Goは月額$10のサブスクリプションプランで、人気のオープンコーディングモデルへの安定したアクセスを十分な利用枠で提供します。",
|
||||||
|
"workspace.lite.promo.modelsTitle": "含まれるもの",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"このプランは主にグローバルユーザー向けに設計されており、米国、EU、シンガポールでホストされたモデルにより安定したグローバルアクセスを提供します。料金と利用制限は、初期の利用状況やフィードバックに基づいて変更される可能性があります。",
|
||||||
|
"workspace.lite.promo.subscribe": "Goを購読する",
|
||||||
|
"workspace.lite.promo.subscribing": "リダイレクト中...",
|
||||||
|
|
||||||
"download.title": "OpenCode | ダウンロード",
|
"download.title": "OpenCode | ダウンロード",
|
||||||
"download.meta.description": "OpenCode を macOS、Windows、Linux 向けにダウンロード",
|
"download.meta.description": "OpenCode を macOS、Windows、Linux 向けにダウンロード",
|
||||||
"download.hero.title": "OpenCode をダウンロード",
|
"download.hero.title": "OpenCode をダウンロード",
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,7 @@ export const dict = {
|
||||||
"black.hero.title": "세계 최고의 코딩 모델에 액세스하세요",
|
"black.hero.title": "세계 최고의 코딩 모델에 액세스하세요",
|
||||||
"black.hero.subtitle": "Claude, GPT, Gemini 등 포함",
|
"black.hero.subtitle": "Claude, GPT, Gemini 등 포함",
|
||||||
"black.title": "OpenCode Black | 가격",
|
"black.title": "OpenCode Black | 가격",
|
||||||
|
"black.paused": "Black 플랜 등록이 일시적으로 중단되었습니다.",
|
||||||
"black.plan.icon20": "Black 20 플랜",
|
"black.plan.icon20": "Black 20 플랜",
|
||||||
"black.plan.icon100": "Black 100 플랜",
|
"black.plan.icon100": "Black 100 플랜",
|
||||||
"black.plan.icon200": "Black 200 플랜",
|
"black.plan.icon200": "Black 200 플랜",
|
||||||
|
|
@ -336,12 +337,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "입력",
|
"workspace.usage.table.input": "입력",
|
||||||
"workspace.usage.table.output": "출력",
|
"workspace.usage.table.output": "출력",
|
||||||
"workspace.usage.table.cost": "비용",
|
"workspace.usage.table.cost": "비용",
|
||||||
|
"workspace.usage.table.session": "세션",
|
||||||
"workspace.usage.breakdown.input": "입력",
|
"workspace.usage.breakdown.input": "입력",
|
||||||
"workspace.usage.breakdown.cacheRead": "캐시 읽기",
|
"workspace.usage.breakdown.cacheRead": "캐시 읽기",
|
||||||
"workspace.usage.breakdown.cacheWrite": "캐시 쓰기",
|
"workspace.usage.breakdown.cacheWrite": "캐시 쓰기",
|
||||||
"workspace.usage.breakdown.output": "출력",
|
"workspace.usage.breakdown.output": "출력",
|
||||||
"workspace.usage.breakdown.reasoning": "추론",
|
"workspace.usage.breakdown.reasoning": "추론",
|
||||||
"workspace.usage.subscription": "구독 (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "비용",
|
"workspace.cost.title": "비용",
|
||||||
"workspace.cost.subtitle": "모델별 사용 비용 내역.",
|
"workspace.cost.subtitle": "모델별 사용 비용 내역.",
|
||||||
|
|
@ -350,6 +354,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(삭제됨)",
|
"workspace.cost.deletedSuffix": "(삭제됨)",
|
||||||
"workspace.cost.empty": "선택한 기간에 사용 데이터가 없습니다.",
|
"workspace.cost.empty": "선택한 기간에 사용 데이터가 없습니다.",
|
||||||
"workspace.cost.subscriptionShort": "구독",
|
"workspace.cost.subscriptionShort": "구독",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "API 키",
|
"workspace.keys.title": "API 키",
|
||||||
"workspace.keys.subtitle": "OpenCode 서비스 액세스를 위한 API 키를 관리하세요.",
|
"workspace.keys.subtitle": "OpenCode 서비스 액세스를 위한 API 키를 관리하세요.",
|
||||||
|
|
@ -477,6 +482,36 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrolled": "등록됨",
|
"workspace.black.waitlist.enrolled": "등록됨",
|
||||||
"workspace.black.waitlist.enrollNote": "등록을 클릭하면 구독이 즉시 시작되며 카드에 요금이 청구됩니다.",
|
"workspace.black.waitlist.enrollNote": "등록을 클릭하면 구독이 즉시 시작되며 카드에 요금이 청구됩니다.",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "로드 중...",
|
||||||
|
"workspace.lite.time.day": "일",
|
||||||
|
"workspace.lite.time.days": "일",
|
||||||
|
"workspace.lite.time.hour": "시간",
|
||||||
|
"workspace.lite.time.hours": "시간",
|
||||||
|
"workspace.lite.time.minute": "분",
|
||||||
|
"workspace.lite.time.minutes": "분",
|
||||||
|
"workspace.lite.time.fewSeconds": "몇 초",
|
||||||
|
"workspace.lite.subscription.title": "Go 구독",
|
||||||
|
"workspace.lite.subscription.message": "현재 OpenCode Go를 구독 중입니다.",
|
||||||
|
"workspace.lite.subscription.manage": "구독 관리",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "롤링 사용량",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "주간 사용량",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "월간 사용량",
|
||||||
|
"workspace.lite.subscription.resetsIn": "초기화까지 남은 시간:",
|
||||||
|
"workspace.lite.subscription.useBalance": "사용 한도 도달 후에는 보유 잔액 사용",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
'Go 모델을 사용하려면 opencode 설정에서 "OpenCode Go"를 공급자로 선택하세요.',
|
||||||
|
"workspace.lite.other.title": "Go 구독",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"이 워크스페이스의 다른 멤버가 이미 OpenCode Go를 구독 중입니다. 워크스페이스당 한 명의 멤버만 구독할 수 있습니다.",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go는 넉넉한 사용 한도와 함께 인기 있는 오픈 코딩 모델에 대한 안정적인 액세스를 제공하는 월 $10의 구독입니다.",
|
||||||
|
"workspace.lite.promo.modelsTitle": "포함 내역",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"이 플랜은 주로 글로벌 사용자를 위해 설계되었으며, 안정적인 글로벌 액세스를 위해 미국, EU 및 싱가포르에 모델이 호스팅되어 있습니다. 가격 및 사용 한도는 초기 사용을 통해 학습하고 피드백을 수집함에 따라 변경될 수 있습니다.",
|
||||||
|
"workspace.lite.promo.subscribe": "Go 구독하기",
|
||||||
|
"workspace.lite.promo.subscribing": "리디렉션 중...",
|
||||||
|
|
||||||
"download.title": "OpenCode | 다운로드",
|
"download.title": "OpenCode | 다운로드",
|
||||||
"download.meta.description": "macOS, Windows, Linux용 OpenCode 다운로드",
|
"download.meta.description": "macOS, Windows, Linux용 OpenCode 다운로드",
|
||||||
"download.hero.title": "OpenCode 다운로드",
|
"download.hero.title": "OpenCode 다운로드",
|
||||||
|
|
|
||||||
|
|
@ -245,6 +245,7 @@ export const dict = {
|
||||||
"black.hero.title": "Få tilgang til verdens beste kodemodeller",
|
"black.hero.title": "Få tilgang til verdens beste kodemodeller",
|
||||||
"black.hero.subtitle": "Inkludert Claude, GPT, Gemini og mer",
|
"black.hero.subtitle": "Inkludert Claude, GPT, Gemini og mer",
|
||||||
"black.title": "OpenCode Black | Priser",
|
"black.title": "OpenCode Black | Priser",
|
||||||
|
"black.paused": "Black-planregistrering er midlertidig satt på pause.",
|
||||||
"black.plan.icon20": "Black 20-plan",
|
"black.plan.icon20": "Black 20-plan",
|
||||||
"black.plan.icon100": "Black 100-plan",
|
"black.plan.icon100": "Black 100-plan",
|
||||||
"black.plan.icon200": "Black 200-plan",
|
"black.plan.icon200": "Black 200-plan",
|
||||||
|
|
@ -340,12 +341,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "Input",
|
"workspace.usage.table.input": "Input",
|
||||||
"workspace.usage.table.output": "Output",
|
"workspace.usage.table.output": "Output",
|
||||||
"workspace.usage.table.cost": "Kostnad",
|
"workspace.usage.table.cost": "Kostnad",
|
||||||
|
"workspace.usage.table.session": "Økt",
|
||||||
"workspace.usage.breakdown.input": "Input",
|
"workspace.usage.breakdown.input": "Input",
|
||||||
"workspace.usage.breakdown.cacheRead": "Cache Lest",
|
"workspace.usage.breakdown.cacheRead": "Cache Lest",
|
||||||
"workspace.usage.breakdown.cacheWrite": "Cache Skrevet",
|
"workspace.usage.breakdown.cacheWrite": "Cache Skrevet",
|
||||||
"workspace.usage.breakdown.output": "Output",
|
"workspace.usage.breakdown.output": "Output",
|
||||||
"workspace.usage.breakdown.reasoning": "Resonnering",
|
"workspace.usage.breakdown.reasoning": "Resonnering",
|
||||||
"workspace.usage.subscription": "abonnement (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "Kostnad",
|
"workspace.cost.title": "Kostnad",
|
||||||
"workspace.cost.subtitle": "Brukskostnader fordelt på modell.",
|
"workspace.cost.subtitle": "Brukskostnader fordelt på modell.",
|
||||||
|
|
@ -354,6 +358,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(slettet)",
|
"workspace.cost.deletedSuffix": "(slettet)",
|
||||||
"workspace.cost.empty": "Ingen bruksdata tilgjengelig for den valgte perioden.",
|
"workspace.cost.empty": "Ingen bruksdata tilgjengelig for den valgte perioden.",
|
||||||
"workspace.cost.subscriptionShort": "sub",
|
"workspace.cost.subscriptionShort": "sub",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "API-nøkler",
|
"workspace.keys.title": "API-nøkler",
|
||||||
"workspace.keys.subtitle": "Administrer API-nøklene dine for å få tilgang til opencode-tjenester.",
|
"workspace.keys.subtitle": "Administrer API-nøklene dine for å få tilgang til opencode-tjenester.",
|
||||||
|
|
@ -482,6 +487,36 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrollNote":
|
"workspace.black.waitlist.enrollNote":
|
||||||
"Når du klikker på Meld på, starter abonnementet umiddelbart og kortet ditt belastes.",
|
"Når du klikker på Meld på, starter abonnementet umiddelbart og kortet ditt belastes.",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "Laster...",
|
||||||
|
"workspace.lite.time.day": "dag",
|
||||||
|
"workspace.lite.time.days": "dager",
|
||||||
|
"workspace.lite.time.hour": "time",
|
||||||
|
"workspace.lite.time.hours": "timer",
|
||||||
|
"workspace.lite.time.minute": "minutt",
|
||||||
|
"workspace.lite.time.minutes": "minutter",
|
||||||
|
"workspace.lite.time.fewSeconds": "noen få sekunder",
|
||||||
|
"workspace.lite.subscription.title": "Go-abonnement",
|
||||||
|
"workspace.lite.subscription.message": "Du abonnerer på OpenCode Go.",
|
||||||
|
"workspace.lite.subscription.manage": "Administrer abonnement",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "Løpende bruk",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "Ukentlig bruk",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "Månedlig bruk",
|
||||||
|
"workspace.lite.subscription.resetsIn": "Nullstilles om",
|
||||||
|
"workspace.lite.subscription.useBalance": "Bruk din tilgjengelige saldo etter å ha nådd bruksgrensene",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
'Velg "OpenCode Go" som leverandør i opencode-konfigurasjonen din for å bruke Go-modeller.',
|
||||||
|
"workspace.lite.other.title": "Go-abonnement",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"Et annet medlem i dette arbeidsområdet abonnerer allerede på OpenCode Go. Kun ett medlem per arbeidsområde kan abonnere.",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go er et abonnement til $10 per måned som gir pålitelig tilgang til populære åpne kodemodeller med rause bruksgrenser.",
|
||||||
|
"workspace.lite.promo.modelsTitle": "Hva som er inkludert",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"Planen er primært designet for internasjonale brukere, med modeller driftet i USA, EU og Singapore for stabil global tilgang. Priser og bruksgrenser kan endres etter hvert som vi lærer fra tidlig bruk og tilbakemeldinger.",
|
||||||
|
"workspace.lite.promo.subscribe": "Abonner på Go",
|
||||||
|
"workspace.lite.promo.subscribing": "Omdirigerer...",
|
||||||
|
|
||||||
"download.title": "OpenCode | Last ned",
|
"download.title": "OpenCode | Last ned",
|
||||||
"download.meta.description": "Last ned OpenCode for macOS, Windows og Linux",
|
"download.meta.description": "Last ned OpenCode for macOS, Windows og Linux",
|
||||||
"download.hero.title": "Last ned OpenCode",
|
"download.hero.title": "Last ned OpenCode",
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,7 @@ export const dict = {
|
||||||
"black.hero.title": "Dostęp do najlepszych na świecie modeli kodujących",
|
"black.hero.title": "Dostęp do najlepszych na świecie modeli kodujących",
|
||||||
"black.hero.subtitle": "W tym Claude, GPT, Gemini i inne",
|
"black.hero.subtitle": "W tym Claude, GPT, Gemini i inne",
|
||||||
"black.title": "OpenCode Black | Cennik",
|
"black.title": "OpenCode Black | Cennik",
|
||||||
|
"black.paused": "Rejestracja planu Black jest tymczasowo wstrzymana.",
|
||||||
"black.plan.icon20": "Plan Black 20",
|
"black.plan.icon20": "Plan Black 20",
|
||||||
"black.plan.icon100": "Plan Black 100",
|
"black.plan.icon100": "Plan Black 100",
|
||||||
"black.plan.icon200": "Plan Black 200",
|
"black.plan.icon200": "Plan Black 200",
|
||||||
|
|
@ -341,12 +342,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "Wejście",
|
"workspace.usage.table.input": "Wejście",
|
||||||
"workspace.usage.table.output": "Wyjście",
|
"workspace.usage.table.output": "Wyjście",
|
||||||
"workspace.usage.table.cost": "Koszt",
|
"workspace.usage.table.cost": "Koszt",
|
||||||
|
"workspace.usage.table.session": "Sesja",
|
||||||
"workspace.usage.breakdown.input": "Wejście",
|
"workspace.usage.breakdown.input": "Wejście",
|
||||||
"workspace.usage.breakdown.cacheRead": "Odczyt Cache",
|
"workspace.usage.breakdown.cacheRead": "Odczyt Cache",
|
||||||
"workspace.usage.breakdown.cacheWrite": "Zapis Cache",
|
"workspace.usage.breakdown.cacheWrite": "Zapis Cache",
|
||||||
"workspace.usage.breakdown.output": "Wyjście",
|
"workspace.usage.breakdown.output": "Wyjście",
|
||||||
"workspace.usage.breakdown.reasoning": "Rozumowanie",
|
"workspace.usage.breakdown.reasoning": "Rozumowanie",
|
||||||
"workspace.usage.subscription": "subskrypcja (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "Koszt",
|
"workspace.cost.title": "Koszt",
|
||||||
"workspace.cost.subtitle": "Koszty użycia w podziale na modele.",
|
"workspace.cost.subtitle": "Koszty użycia w podziale na modele.",
|
||||||
|
|
@ -355,6 +359,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(usunięte)",
|
"workspace.cost.deletedSuffix": "(usunięte)",
|
||||||
"workspace.cost.empty": "Brak danych o użyciu dla wybranego okresu.",
|
"workspace.cost.empty": "Brak danych o użyciu dla wybranego okresu.",
|
||||||
"workspace.cost.subscriptionShort": "sub",
|
"workspace.cost.subscriptionShort": "sub",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "Klucze API",
|
"workspace.keys.title": "Klucze API",
|
||||||
"workspace.keys.subtitle": "Zarządzaj kluczami API do usług opencode.",
|
"workspace.keys.subtitle": "Zarządzaj kluczami API do usług opencode.",
|
||||||
|
|
@ -483,6 +488,36 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrollNote":
|
"workspace.black.waitlist.enrollNote":
|
||||||
"Po kliknięciu Zapisz się, Twoja subskrypcja rozpocznie się natychmiast, a karta zostanie obciążona.",
|
"Po kliknięciu Zapisz się, Twoja subskrypcja rozpocznie się natychmiast, a karta zostanie obciążona.",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "Ładowanie...",
|
||||||
|
"workspace.lite.time.day": "dzień",
|
||||||
|
"workspace.lite.time.days": "dni",
|
||||||
|
"workspace.lite.time.hour": "godzina",
|
||||||
|
"workspace.lite.time.hours": "godzin(y)",
|
||||||
|
"workspace.lite.time.minute": "minuta",
|
||||||
|
"workspace.lite.time.minutes": "minut(y)",
|
||||||
|
"workspace.lite.time.fewSeconds": "kilka sekund",
|
||||||
|
"workspace.lite.subscription.title": "Subskrypcja Go",
|
||||||
|
"workspace.lite.subscription.message": "Subskrybujesz OpenCode Go.",
|
||||||
|
"workspace.lite.subscription.manage": "Zarządzaj subskrypcją",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "Użycie kroczące",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "Użycie tygodniowe",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "Użycie miesięczne",
|
||||||
|
"workspace.lite.subscription.resetsIn": "Resetuje się za",
|
||||||
|
"workspace.lite.subscription.useBalance": "Użyj dostępnego salda po osiągnięciu limitów użycia",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
'Wybierz "OpenCode Go" jako dostawcę w konfiguracji opencode, aby używać modeli Go.',
|
||||||
|
"workspace.lite.other.title": "Subskrypcja Go",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"Inny członek tego obszaru roboczego już subskrybuje OpenCode Go. Tylko jeden członek na obszar roboczy może subskrybować.",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go to subskrypcja za $10 miesięcznie, która zapewnia niezawodny dostęp do popularnych otwartych modeli do kodowania z hojnymi limitami użycia.",
|
||||||
|
"workspace.lite.promo.modelsTitle": "Co zawiera",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"Plan został zaprojektowany głównie dla użytkowników międzynarodowych, z modelami hostowanymi w USA, UE i Singapurze, aby zapewnić stabilny globalny dostęp. Ceny i limity użycia mogą ulec zmianie w miarę analizy wczesnego użycia i zbierania opinii.",
|
||||||
|
"workspace.lite.promo.subscribe": "Subskrybuj Go",
|
||||||
|
"workspace.lite.promo.subscribing": "Przekierowywanie...",
|
||||||
|
|
||||||
"download.title": "OpenCode | Pobierz",
|
"download.title": "OpenCode | Pobierz",
|
||||||
"download.meta.description": "Pobierz OpenCode na macOS, Windows i Linux",
|
"download.meta.description": "Pobierz OpenCode na macOS, Windows i Linux",
|
||||||
"download.hero.title": "Pobierz OpenCode",
|
"download.hero.title": "Pobierz OpenCode",
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,7 @@ export const dict = {
|
||||||
"black.hero.title": "Доступ к лучшим моделям для кодинга в мире",
|
"black.hero.title": "Доступ к лучшим моделям для кодинга в мире",
|
||||||
"black.hero.subtitle": "Включая Claude, GPT, Gemini и другие",
|
"black.hero.subtitle": "Включая Claude, GPT, Gemini и другие",
|
||||||
"black.title": "OpenCode Black | Цены",
|
"black.title": "OpenCode Black | Цены",
|
||||||
|
"black.paused": "Регистрация на план Black временно приостановлена.",
|
||||||
"black.plan.icon20": "План Black 20",
|
"black.plan.icon20": "План Black 20",
|
||||||
"black.plan.icon100": "План Black 100",
|
"black.plan.icon100": "План Black 100",
|
||||||
"black.plan.icon200": "План Black 200",
|
"black.plan.icon200": "План Black 200",
|
||||||
|
|
@ -346,12 +347,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "Вход",
|
"workspace.usage.table.input": "Вход",
|
||||||
"workspace.usage.table.output": "Выход",
|
"workspace.usage.table.output": "Выход",
|
||||||
"workspace.usage.table.cost": "Стоимость",
|
"workspace.usage.table.cost": "Стоимость",
|
||||||
|
"workspace.usage.table.session": "Сессия",
|
||||||
"workspace.usage.breakdown.input": "Вход",
|
"workspace.usage.breakdown.input": "Вход",
|
||||||
"workspace.usage.breakdown.cacheRead": "Чтение кэша",
|
"workspace.usage.breakdown.cacheRead": "Чтение кэша",
|
||||||
"workspace.usage.breakdown.cacheWrite": "Запись кэша",
|
"workspace.usage.breakdown.cacheWrite": "Запись кэша",
|
||||||
"workspace.usage.breakdown.output": "Выход",
|
"workspace.usage.breakdown.output": "Выход",
|
||||||
"workspace.usage.breakdown.reasoning": "Reasoning (рассуждения)",
|
"workspace.usage.breakdown.reasoning": "Reasoning (рассуждения)",
|
||||||
"workspace.usage.subscription": "подписка (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "Расходы",
|
"workspace.cost.title": "Расходы",
|
||||||
"workspace.cost.subtitle": "Расходы на использование с разбивкой по моделям.",
|
"workspace.cost.subtitle": "Расходы на использование с разбивкой по моделям.",
|
||||||
|
|
@ -360,6 +364,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(удалено)",
|
"workspace.cost.deletedSuffix": "(удалено)",
|
||||||
"workspace.cost.empty": "Нет данных об использовании за выбранный период.",
|
"workspace.cost.empty": "Нет данных об использовании за выбранный период.",
|
||||||
"workspace.cost.subscriptionShort": "подписка",
|
"workspace.cost.subscriptionShort": "подписка",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "API Ключи",
|
"workspace.keys.title": "API Ключи",
|
||||||
"workspace.keys.subtitle": "Управляйте вашими API ключами для доступа к сервисам opencode.",
|
"workspace.keys.subtitle": "Управляйте вашими API ключами для доступа к сервисам opencode.",
|
||||||
|
|
@ -488,6 +493,36 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrollNote":
|
"workspace.black.waitlist.enrollNote":
|
||||||
"Когда вы нажмете Подключиться, ваша подписка начнется немедленно, и с карты будет списана оплата.",
|
"Когда вы нажмете Подключиться, ваша подписка начнется немедленно, и с карты будет списана оплата.",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "Загрузка...",
|
||||||
|
"workspace.lite.time.day": "день",
|
||||||
|
"workspace.lite.time.days": "дней",
|
||||||
|
"workspace.lite.time.hour": "час",
|
||||||
|
"workspace.lite.time.hours": "часов",
|
||||||
|
"workspace.lite.time.minute": "минута",
|
||||||
|
"workspace.lite.time.minutes": "минут",
|
||||||
|
"workspace.lite.time.fewSeconds": "несколько секунд",
|
||||||
|
"workspace.lite.subscription.title": "Подписка Go",
|
||||||
|
"workspace.lite.subscription.message": "Вы подписаны на OpenCode Go.",
|
||||||
|
"workspace.lite.subscription.manage": "Управление подпиской",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "Скользящее использование",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "Недельное использование",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "Ежемесячное использование",
|
||||||
|
"workspace.lite.subscription.resetsIn": "Сброс через",
|
||||||
|
"workspace.lite.subscription.useBalance": "Использовать доступный баланс после достижения лимитов",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
'Выберите "OpenCode Go" в качестве провайдера в настройках opencode для использования моделей Go.',
|
||||||
|
"workspace.lite.other.title": "Подписка Go",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"Другой участник в этом рабочем пространстве уже подписан на OpenCode Go. Только один участник в рабочем пространстве может оформить подписку.",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go — это подписка за $10 в месяц, которая предоставляет надежный доступ к популярным открытым моделям для кодинга с щедрыми лимитами использования.",
|
||||||
|
"workspace.lite.promo.modelsTitle": "Что включено",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"План предназначен в первую очередь для международных пользователей. Модели размещены в США, ЕС и Сингапуре для стабильного глобального доступа. Цены и лимиты использования могут меняться по мере того, как мы изучаем раннее использование и собираем отзывы.",
|
||||||
|
"workspace.lite.promo.subscribe": "Подписаться на Go",
|
||||||
|
"workspace.lite.promo.subscribing": "Перенаправление...",
|
||||||
|
|
||||||
"download.title": "OpenCode | Скачать",
|
"download.title": "OpenCode | Скачать",
|
||||||
"download.meta.description": "Скачать OpenCode для macOS, Windows и Linux",
|
"download.meta.description": "Скачать OpenCode для macOS, Windows и Linux",
|
||||||
"download.hero.title": "Скачать OpenCode",
|
"download.hero.title": "Скачать OpenCode",
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,7 @@ export const dict = {
|
||||||
"black.hero.title": "เข้าถึงโมเดลเขียนโค้ดที่ดีที่สุดในโลก",
|
"black.hero.title": "เข้าถึงโมเดลเขียนโค้ดที่ดีที่สุดในโลก",
|
||||||
"black.hero.subtitle": "รวมถึง Claude, GPT, Gemini และอื่นๆ อีกมากมาย",
|
"black.hero.subtitle": "รวมถึง Claude, GPT, Gemini และอื่นๆ อีกมากมาย",
|
||||||
"black.title": "OpenCode Black | ราคา",
|
"black.title": "OpenCode Black | ราคา",
|
||||||
|
"black.paused": "การสมัครแผน Black หยุดชั่วคราว",
|
||||||
"black.plan.icon20": "แผน Black 20",
|
"black.plan.icon20": "แผน Black 20",
|
||||||
"black.plan.icon100": "แผน Black 100",
|
"black.plan.icon100": "แผน Black 100",
|
||||||
"black.plan.icon200": "แผน Black 200",
|
"black.plan.icon200": "แผน Black 200",
|
||||||
|
|
@ -339,12 +340,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "Input",
|
"workspace.usage.table.input": "Input",
|
||||||
"workspace.usage.table.output": "Output",
|
"workspace.usage.table.output": "Output",
|
||||||
"workspace.usage.table.cost": "ค่าใช้จ่าย",
|
"workspace.usage.table.cost": "ค่าใช้จ่าย",
|
||||||
|
"workspace.usage.table.session": "เซสชัน",
|
||||||
"workspace.usage.breakdown.input": "Input",
|
"workspace.usage.breakdown.input": "Input",
|
||||||
"workspace.usage.breakdown.cacheRead": "Cache Read",
|
"workspace.usage.breakdown.cacheRead": "Cache Read",
|
||||||
"workspace.usage.breakdown.cacheWrite": "Cache Write",
|
"workspace.usage.breakdown.cacheWrite": "Cache Write",
|
||||||
"workspace.usage.breakdown.output": "Output",
|
"workspace.usage.breakdown.output": "Output",
|
||||||
"workspace.usage.breakdown.reasoning": "Reasoning",
|
"workspace.usage.breakdown.reasoning": "Reasoning",
|
||||||
"workspace.usage.subscription": "สมัครสมาชิก (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "ค่าใช้จ่าย",
|
"workspace.cost.title": "ค่าใช้จ่าย",
|
||||||
"workspace.cost.subtitle": "ต้นทุนการใช้งานแยกตามโมเดล",
|
"workspace.cost.subtitle": "ต้นทุนการใช้งานแยกตามโมเดล",
|
||||||
|
|
@ -353,6 +357,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(ลบแล้ว)",
|
"workspace.cost.deletedSuffix": "(ลบแล้ว)",
|
||||||
"workspace.cost.empty": "ไม่มีข้อมูลการใช้งานในช่วงเวลาที่เลือก",
|
"workspace.cost.empty": "ไม่มีข้อมูลการใช้งานในช่วงเวลาที่เลือก",
|
||||||
"workspace.cost.subscriptionShort": "sub",
|
"workspace.cost.subscriptionShort": "sub",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "API Keys",
|
"workspace.keys.title": "API Keys",
|
||||||
"workspace.keys.subtitle": "จัดการ API keys ของคุณสำหรับการเข้าถึงบริการ OpenCode",
|
"workspace.keys.subtitle": "จัดการ API keys ของคุณสำหรับการเข้าถึงบริการ OpenCode",
|
||||||
|
|
@ -481,6 +486,36 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrollNote":
|
"workspace.black.waitlist.enrollNote":
|
||||||
"เมื่อคุณคลิกลงทะเบียน การสมัครสมาชิกของคุณจะเริ่มต้นทันทีและบัตรของคุณจะถูกเรียกเก็บเงิน",
|
"เมื่อคุณคลิกลงทะเบียน การสมัครสมาชิกของคุณจะเริ่มต้นทันทีและบัตรของคุณจะถูกเรียกเก็บเงิน",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "กำลังโหลด...",
|
||||||
|
"workspace.lite.time.day": "วัน",
|
||||||
|
"workspace.lite.time.days": "วัน",
|
||||||
|
"workspace.lite.time.hour": "ชั่วโมง",
|
||||||
|
"workspace.lite.time.hours": "ชั่วโมง",
|
||||||
|
"workspace.lite.time.minute": "นาที",
|
||||||
|
"workspace.lite.time.minutes": "นาที",
|
||||||
|
"workspace.lite.time.fewSeconds": "ไม่กี่วินาที",
|
||||||
|
"workspace.lite.subscription.title": "การสมัครสมาชิก Go",
|
||||||
|
"workspace.lite.subscription.message": "คุณได้สมัครสมาชิก OpenCode Go แล้ว",
|
||||||
|
"workspace.lite.subscription.manage": "จัดการการสมัครสมาชิก",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "การใช้งานแบบหมุนเวียน",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "การใช้งานรายสัปดาห์",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "การใช้งานรายเดือน",
|
||||||
|
"workspace.lite.subscription.resetsIn": "รีเซ็ตใน",
|
||||||
|
"workspace.lite.subscription.useBalance": "ใช้ยอดคงเหลือของคุณหลังจากถึงขีดจำกัดการใช้งาน",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
'เลือก "OpenCode Go" เป็นผู้ให้บริการในการตั้งค่า opencode ของคุณเพื่อใช้โมเดล Go',
|
||||||
|
"workspace.lite.other.title": "การสมัครสมาชิก Go",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"สมาชิกคนอื่นใน Workspace นี้ได้สมัคร OpenCode Go แล้ว สามารถสมัครได้เพียงหนึ่งคนต่อหนึ่ง Workspace เท่านั้น",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go เป็นการสมัครสมาชิกราคา 10 ดอลลาร์ต่อเดือน ที่ให้การเข้าถึงโมเดลโอเพนโค้ดดิงยอดนิยมได้อย่างเสถียร ด้วยขีดจำกัดการใช้งานที่ครอบคลุม",
|
||||||
|
"workspace.lite.promo.modelsTitle": "สิ่งที่รวมอยู่ด้วย",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"แผนนี้ออกแบบมาสำหรับผู้ใช้งานต่างประเทศเป็นหลัก โดยมีโมเดลโฮสต์อยู่ในสหรัฐอเมริกา สหภาพยุโรป และสิงคโปร์ เพื่อการเข้าถึงที่เสถียรทั่วโลก ราคาและขีดจำกัดการใช้งานอาจมีการเปลี่ยนแปลงตามที่เราได้เรียนรู้จากการใช้งานในช่วงแรกและข้อเสนอแนะ",
|
||||||
|
"workspace.lite.promo.subscribe": "สมัครสมาชิก Go",
|
||||||
|
"workspace.lite.promo.subscribing": "กำลังเปลี่ยนเส้นทาง...",
|
||||||
|
|
||||||
"download.title": "OpenCode | ดาวน์โหลด",
|
"download.title": "OpenCode | ดาวน์โหลด",
|
||||||
"download.meta.description": "ดาวน์โหลด OpenCode สำหรับ macOS, Windows และ Linux",
|
"download.meta.description": "ดาวน์โหลด OpenCode สำหรับ macOS, Windows และ Linux",
|
||||||
"download.hero.title": "ดาวน์โหลด OpenCode",
|
"download.hero.title": "ดาวน์โหลด OpenCode",
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,7 @@ export const dict = {
|
||||||
"black.hero.title": "Dünyanın en iyi kodlama modellerine erişin",
|
"black.hero.title": "Dünyanın en iyi kodlama modellerine erişin",
|
||||||
"black.hero.subtitle": "Claude, GPT, Gemini ve daha fazlası dahil",
|
"black.hero.subtitle": "Claude, GPT, Gemini ve daha fazlası dahil",
|
||||||
"black.title": "OpenCode Black | Fiyatlandırma",
|
"black.title": "OpenCode Black | Fiyatlandırma",
|
||||||
|
"black.paused": "Black plan kaydı geçici olarak duraklatıldı.",
|
||||||
"black.plan.icon20": "Black 20 planı",
|
"black.plan.icon20": "Black 20 planı",
|
||||||
"black.plan.icon100": "Black 100 planı",
|
"black.plan.icon100": "Black 100 planı",
|
||||||
"black.plan.icon200": "Black 200 planı",
|
"black.plan.icon200": "Black 200 planı",
|
||||||
|
|
@ -342,12 +343,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "Giriş",
|
"workspace.usage.table.input": "Giriş",
|
||||||
"workspace.usage.table.output": "Çıkış",
|
"workspace.usage.table.output": "Çıkış",
|
||||||
"workspace.usage.table.cost": "Maliyet",
|
"workspace.usage.table.cost": "Maliyet",
|
||||||
|
"workspace.usage.table.session": "Oturum",
|
||||||
"workspace.usage.breakdown.input": "Giriş",
|
"workspace.usage.breakdown.input": "Giriş",
|
||||||
"workspace.usage.breakdown.cacheRead": "Önbellek Okuması",
|
"workspace.usage.breakdown.cacheRead": "Önbellek Okuması",
|
||||||
"workspace.usage.breakdown.cacheWrite": "Önbellek Yazma",
|
"workspace.usage.breakdown.cacheWrite": "Önbellek Yazma",
|
||||||
"workspace.usage.breakdown.output": "Çıkış",
|
"workspace.usage.breakdown.output": "Çıkış",
|
||||||
"workspace.usage.breakdown.reasoning": "Muhakeme",
|
"workspace.usage.breakdown.reasoning": "Muhakeme",
|
||||||
"workspace.usage.subscription": "abonelik (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "Maliyet",
|
"workspace.cost.title": "Maliyet",
|
||||||
"workspace.cost.subtitle": "Modele göre ayrılmış kullanım maliyetleri.",
|
"workspace.cost.subtitle": "Modele göre ayrılmış kullanım maliyetleri.",
|
||||||
|
|
@ -356,6 +360,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(silindi)",
|
"workspace.cost.deletedSuffix": "(silindi)",
|
||||||
"workspace.cost.empty": "Seçilen döneme ait kullanım verisi yok.",
|
"workspace.cost.empty": "Seçilen döneme ait kullanım verisi yok.",
|
||||||
"workspace.cost.subscriptionShort": "abonelik",
|
"workspace.cost.subscriptionShort": "abonelik",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "API Anahtarları",
|
"workspace.keys.title": "API Anahtarları",
|
||||||
"workspace.keys.subtitle": "opencode hizmetlerine erişim için API anahtarlarınızı yönetin.",
|
"workspace.keys.subtitle": "opencode hizmetlerine erişim için API anahtarlarınızı yönetin.",
|
||||||
|
|
@ -484,6 +489,36 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrollNote":
|
"workspace.black.waitlist.enrollNote":
|
||||||
"Kayıt Ol'a tıkladığınızda aboneliğiniz hemen başlar ve kartınızdan çekim yapılır.",
|
"Kayıt Ol'a tıkladığınızda aboneliğiniz hemen başlar ve kartınızdan çekim yapılır.",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "Yükleniyor...",
|
||||||
|
"workspace.lite.time.day": "gün",
|
||||||
|
"workspace.lite.time.days": "gün",
|
||||||
|
"workspace.lite.time.hour": "saat",
|
||||||
|
"workspace.lite.time.hours": "saat",
|
||||||
|
"workspace.lite.time.minute": "dakika",
|
||||||
|
"workspace.lite.time.minutes": "dakika",
|
||||||
|
"workspace.lite.time.fewSeconds": "birkaç saniye",
|
||||||
|
"workspace.lite.subscription.title": "Go Aboneliği",
|
||||||
|
"workspace.lite.subscription.message": "OpenCode Go abonesisiniz.",
|
||||||
|
"workspace.lite.subscription.manage": "Aboneliği Yönet",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "Devam Eden Kullanım",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "Haftalık Kullanım",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "Aylık Kullanım",
|
||||||
|
"workspace.lite.subscription.resetsIn": "Sıfırlama süresi",
|
||||||
|
"workspace.lite.subscription.useBalance": "Kullanım limitlerine ulaştıktan sonra mevcut bakiyenizi kullanın",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
'Go modellerini kullanmak için opencode yapılandırmanızda "OpenCode Go"\'yu sağlayıcı olarak seçin.',
|
||||||
|
"workspace.lite.other.title": "Go Aboneliği",
|
||||||
|
"workspace.lite.other.message":
|
||||||
|
"Bu çalışma alanındaki başka bir üye zaten OpenCode Go abonesi. Çalışma alanı başına yalnızca bir üye abone olabilir.",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go, cömert kullanım limitleriyle popüler açık kodlama modellerine güvenilir erişim sağlayan aylık 10$'lık bir aboneliktir.",
|
||||||
|
"workspace.lite.promo.modelsTitle": "Neler Dahil",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"Plan öncelikle uluslararası kullanıcılar için tasarlanmıştır; modeller istikrarlı küresel erişim için ABD, AB ve Singapur'da barındırılmaktadır. Erken kullanımdan öğrendikçe ve geri bildirim topladıkça fiyatlandırma ve kullanım limitleri değişebilir.",
|
||||||
|
"workspace.lite.promo.subscribe": "Go'ya Abone Ol",
|
||||||
|
"workspace.lite.promo.subscribing": "Yönlendiriliyor...",
|
||||||
|
|
||||||
"download.title": "OpenCode | İndir",
|
"download.title": "OpenCode | İndir",
|
||||||
"download.meta.description": "OpenCode'u macOS, Windows ve Linux için indirin",
|
"download.meta.description": "OpenCode'u macOS, Windows ve Linux için indirin",
|
||||||
"download.hero.title": "OpenCode'u İndir",
|
"download.hero.title": "OpenCode'u İndir",
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,7 @@ export const dict = {
|
||||||
"black.hero.title": "访问全球顶尖编程模型",
|
"black.hero.title": "访问全球顶尖编程模型",
|
||||||
"black.hero.subtitle": "包括 Claude, GPT, Gemini 等",
|
"black.hero.subtitle": "包括 Claude, GPT, Gemini 等",
|
||||||
"black.title": "OpenCode Black | 定价",
|
"black.title": "OpenCode Black | 定价",
|
||||||
|
"black.paused": "Black 订阅已暂时暂停注册。",
|
||||||
"black.plan.icon20": "Black 20 计划",
|
"black.plan.icon20": "Black 20 计划",
|
||||||
"black.plan.icon100": "Black 100 计划",
|
"black.plan.icon100": "Black 100 计划",
|
||||||
"black.plan.icon200": "Black 200 计划",
|
"black.plan.icon200": "Black 200 计划",
|
||||||
|
|
@ -327,12 +328,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "输入",
|
"workspace.usage.table.input": "输入",
|
||||||
"workspace.usage.table.output": "输出",
|
"workspace.usage.table.output": "输出",
|
||||||
"workspace.usage.table.cost": "成本",
|
"workspace.usage.table.cost": "成本",
|
||||||
|
"workspace.usage.table.session": "会话",
|
||||||
"workspace.usage.breakdown.input": "输入",
|
"workspace.usage.breakdown.input": "输入",
|
||||||
"workspace.usage.breakdown.cacheRead": "缓存读取",
|
"workspace.usage.breakdown.cacheRead": "缓存读取",
|
||||||
"workspace.usage.breakdown.cacheWrite": "缓存写入",
|
"workspace.usage.breakdown.cacheWrite": "缓存写入",
|
||||||
"workspace.usage.breakdown.output": "输出",
|
"workspace.usage.breakdown.output": "输出",
|
||||||
"workspace.usage.breakdown.reasoning": "推理",
|
"workspace.usage.breakdown.reasoning": "推理",
|
||||||
"workspace.usage.subscription": "订阅 (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "成本",
|
"workspace.cost.title": "成本",
|
||||||
"workspace.cost.subtitle": "按模型细分的使用成本。",
|
"workspace.cost.subtitle": "按模型细分的使用成本。",
|
||||||
|
|
@ -341,6 +345,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(已删除)",
|
"workspace.cost.deletedSuffix": "(已删除)",
|
||||||
"workspace.cost.empty": "所选期间无可用使用数据。",
|
"workspace.cost.empty": "所选期间无可用使用数据。",
|
||||||
"workspace.cost.subscriptionShort": "订阅",
|
"workspace.cost.subscriptionShort": "订阅",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "API 密钥",
|
"workspace.keys.title": "API 密钥",
|
||||||
"workspace.keys.subtitle": "管理访问 OpenCode 服务的 API 密钥。",
|
"workspace.keys.subtitle": "管理访问 OpenCode 服务的 API 密钥。",
|
||||||
|
|
@ -468,6 +473,35 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrolled": "已加入",
|
"workspace.black.waitlist.enrolled": "已加入",
|
||||||
"workspace.black.waitlist.enrollNote": "点击加入后,您的订阅将立即开始,并将从您的卡中扣费。",
|
"workspace.black.waitlist.enrollNote": "点击加入后,您的订阅将立即开始,并将从您的卡中扣费。",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "加载中...",
|
||||||
|
"workspace.lite.time.day": "天",
|
||||||
|
"workspace.lite.time.days": "天",
|
||||||
|
"workspace.lite.time.hour": "小时",
|
||||||
|
"workspace.lite.time.hours": "小时",
|
||||||
|
"workspace.lite.time.minute": "分钟",
|
||||||
|
"workspace.lite.time.minutes": "分钟",
|
||||||
|
"workspace.lite.time.fewSeconds": "几秒钟",
|
||||||
|
"workspace.lite.subscription.title": "Go 订阅",
|
||||||
|
"workspace.lite.subscription.message": "您已订阅 OpenCode Go。",
|
||||||
|
"workspace.lite.subscription.manage": "管理订阅",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "滚动用量",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "每周用量",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "每月用量",
|
||||||
|
"workspace.lite.subscription.resetsIn": "重置于",
|
||||||
|
"workspace.lite.subscription.useBalance": "达到使用限额后使用您的可用余额",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
"在你的 opencode 配置中选择「OpenCode Go」作为提供商,即可使用 Go 模型。",
|
||||||
|
"workspace.lite.other.title": "Go 订阅",
|
||||||
|
"workspace.lite.other.message": "此工作区中的另一位成员已经订阅了 OpenCode Go。每个工作区只有一名成员可以订阅。",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go 是一个每月 $10 的订阅计划,提供对主流开源编码模型的稳定访问,并配备充足的使用额度。",
|
||||||
|
"workspace.lite.promo.modelsTitle": "包含模型",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"该计划主要面向国际用户设计,模型部署在美国、欧盟和新加坡,以确保全球范围内的稳定访问体验。定价和使用额度可能会根据早期用户的使用情况和反馈持续调整与优化。",
|
||||||
|
"workspace.lite.promo.subscribe": "订阅 Go",
|
||||||
|
"workspace.lite.promo.subscribing": "正在重定向...",
|
||||||
|
|
||||||
"download.title": "OpenCode | 下载",
|
"download.title": "OpenCode | 下载",
|
||||||
"download.meta.description": "下载适用于 macOS, Windows, 和 Linux 的 OpenCode",
|
"download.meta.description": "下载适用于 macOS, Windows, 和 Linux 的 OpenCode",
|
||||||
"download.hero.title": "下载 OpenCode",
|
"download.hero.title": "下载 OpenCode",
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,7 @@ export const dict = {
|
||||||
"black.hero.title": "存取全球最佳編碼模型",
|
"black.hero.title": "存取全球最佳編碼模型",
|
||||||
"black.hero.subtitle": "包括 Claude、GPT、Gemini 等",
|
"black.hero.subtitle": "包括 Claude、GPT、Gemini 等",
|
||||||
"black.title": "OpenCode Black | 定價",
|
"black.title": "OpenCode Black | 定價",
|
||||||
|
"black.paused": "Black 訂閱暫時暫停註冊。",
|
||||||
"black.plan.icon20": "Black 20 方案",
|
"black.plan.icon20": "Black 20 方案",
|
||||||
"black.plan.icon100": "Black 100 方案",
|
"black.plan.icon100": "Black 100 方案",
|
||||||
"black.plan.icon200": "Black 200 方案",
|
"black.plan.icon200": "Black 200 方案",
|
||||||
|
|
@ -327,12 +328,15 @@ export const dict = {
|
||||||
"workspace.usage.table.input": "輸入",
|
"workspace.usage.table.input": "輸入",
|
||||||
"workspace.usage.table.output": "輸出",
|
"workspace.usage.table.output": "輸出",
|
||||||
"workspace.usage.table.cost": "成本",
|
"workspace.usage.table.cost": "成本",
|
||||||
|
"workspace.usage.table.session": "會話",
|
||||||
"workspace.usage.breakdown.input": "輸入",
|
"workspace.usage.breakdown.input": "輸入",
|
||||||
"workspace.usage.breakdown.cacheRead": "快取讀取",
|
"workspace.usage.breakdown.cacheRead": "快取讀取",
|
||||||
"workspace.usage.breakdown.cacheWrite": "快取寫入",
|
"workspace.usage.breakdown.cacheWrite": "快取寫入",
|
||||||
"workspace.usage.breakdown.output": "輸出",
|
"workspace.usage.breakdown.output": "輸出",
|
||||||
"workspace.usage.breakdown.reasoning": "推理",
|
"workspace.usage.breakdown.reasoning": "推理",
|
||||||
"workspace.usage.subscription": "訂閱 (${{amount}})",
|
"workspace.usage.subscription": "Black (${{amount}})",
|
||||||
|
"workspace.usage.lite": "Go (${{amount}})",
|
||||||
|
"workspace.usage.byok": "BYOK (${{amount}})",
|
||||||
|
|
||||||
"workspace.cost.title": "成本",
|
"workspace.cost.title": "成本",
|
||||||
"workspace.cost.subtitle": "按模型細分的使用成本。",
|
"workspace.cost.subtitle": "按模型細分的使用成本。",
|
||||||
|
|
@ -341,6 +345,7 @@ export const dict = {
|
||||||
"workspace.cost.deletedSuffix": "(已刪除)",
|
"workspace.cost.deletedSuffix": "(已刪除)",
|
||||||
"workspace.cost.empty": "所選期間沒有可用的使用資料。",
|
"workspace.cost.empty": "所選期間沒有可用的使用資料。",
|
||||||
"workspace.cost.subscriptionShort": "訂",
|
"workspace.cost.subscriptionShort": "訂",
|
||||||
|
"workspace.cost.liteShort": "lite",
|
||||||
|
|
||||||
"workspace.keys.title": "API 金鑰",
|
"workspace.keys.title": "API 金鑰",
|
||||||
"workspace.keys.subtitle": "管理你的 API 金鑰以存取 OpenCode 服務。",
|
"workspace.keys.subtitle": "管理你的 API 金鑰以存取 OpenCode 服務。",
|
||||||
|
|
@ -468,6 +473,35 @@ export const dict = {
|
||||||
"workspace.black.waitlist.enrolled": "已加入",
|
"workspace.black.waitlist.enrolled": "已加入",
|
||||||
"workspace.black.waitlist.enrollNote": "當你點選「加入」後,你的訂閱將立即開始,並且將從你的卡片中扣款。",
|
"workspace.black.waitlist.enrollNote": "當你點選「加入」後,你的訂閱將立即開始,並且將從你的卡片中扣款。",
|
||||||
|
|
||||||
|
"workspace.lite.loading": "載入中...",
|
||||||
|
"workspace.lite.time.day": "天",
|
||||||
|
"workspace.lite.time.days": "天",
|
||||||
|
"workspace.lite.time.hour": "小時",
|
||||||
|
"workspace.lite.time.hours": "小時",
|
||||||
|
"workspace.lite.time.minute": "分鐘",
|
||||||
|
"workspace.lite.time.minutes": "分鐘",
|
||||||
|
"workspace.lite.time.fewSeconds": "幾秒",
|
||||||
|
"workspace.lite.subscription.title": "Go 訂閱",
|
||||||
|
"workspace.lite.subscription.message": "您已訂閱 OpenCode Go。",
|
||||||
|
"workspace.lite.subscription.manage": "管理訂閱",
|
||||||
|
"workspace.lite.subscription.rollingUsage": "滾動使用量",
|
||||||
|
"workspace.lite.subscription.weeklyUsage": "每週使用量",
|
||||||
|
"workspace.lite.subscription.monthlyUsage": "每月使用量",
|
||||||
|
"workspace.lite.subscription.resetsIn": "重置時間:",
|
||||||
|
"workspace.lite.subscription.useBalance": "達到使用限制後使用您的可用餘額",
|
||||||
|
"workspace.lite.subscription.selectProvider":
|
||||||
|
"在您的 opencode 設定中選擇「OpenCode Go」作為提供商,即可使用 Go 模型。",
|
||||||
|
"workspace.lite.other.title": "Go 訂閱",
|
||||||
|
"workspace.lite.other.message": "此工作區中的另一位成員已訂閱 OpenCode Go。每個工作區只能有一位成員訂閱。",
|
||||||
|
"workspace.lite.promo.title": "OpenCode Go",
|
||||||
|
"workspace.lite.promo.description":
|
||||||
|
"OpenCode Go 是一個每月 $10 的訂閱方案,提供對主流開放原始碼編碼模型的穩定存取,並配備充足的使用額度。",
|
||||||
|
"workspace.lite.promo.modelsTitle": "包含模型",
|
||||||
|
"workspace.lite.promo.footer":
|
||||||
|
"該計畫主要面向國際用戶設計,模型部署在美國、歐盟和新加坡,以確保全球範圍內的穩定存取體驗。定價和使用額度可能會根據早期用戶的使用情況和回饋持續調整與優化。",
|
||||||
|
"workspace.lite.promo.subscribe": "訂閱 Go",
|
||||||
|
"workspace.lite.promo.subscribing": "重新導向中...",
|
||||||
|
|
||||||
"download.title": "OpenCode | 下載",
|
"download.title": "OpenCode | 下載",
|
||||||
"download.meta.description": "下載適用於 macOS、Windows 與 Linux 的 OpenCode",
|
"download.meta.description": "下載適用於 macOS、Windows 與 Linux 的 OpenCode",
|
||||||
"download.hero.title": "下載 OpenCode",
|
"download.hero.title": "下載 OpenCode",
|
||||||
|
|
|
||||||
|
|
@ -335,6 +335,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-slot="paused"] {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: rgba(255, 255, 255, 0.59);
|
||||||
|
font-size: 18px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 160%;
|
||||||
|
padding: 120px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot="pricing-card"] {
|
[data-slot="pricing-card"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,21 @@
|
||||||
import { A, useSearchParams } from "@solidjs/router"
|
import { A, createAsync, query, useSearchParams } from "@solidjs/router"
|
||||||
import { Title } from "@solidjs/meta"
|
import { Title } from "@solidjs/meta"
|
||||||
import { createMemo, createSignal, For, Match, onMount, Show, Switch } from "solid-js"
|
import { createMemo, createSignal, For, Match, onMount, Show, Switch } from "solid-js"
|
||||||
import { PlanIcon, plans } from "./common"
|
import { PlanIcon, plans } from "./common"
|
||||||
import { useI18n } from "~/context/i18n"
|
import { useI18n } from "~/context/i18n"
|
||||||
import { useLanguage } from "~/context/language"
|
import { useLanguage } from "~/context/language"
|
||||||
|
import { Resource } from "@opencode-ai/console-resource"
|
||||||
|
|
||||||
|
const getPaused = query(async () => {
|
||||||
|
"use server"
|
||||||
|
return Resource.App.stage === "production"
|
||||||
|
}, "black.paused")
|
||||||
|
|
||||||
export default function Black() {
|
export default function Black() {
|
||||||
const [params] = useSearchParams()
|
const [params] = useSearchParams()
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
const paused = createAsync(() => getPaused())
|
||||||
const [selected, setSelected] = createSignal<string | null>((params.plan as string) || null)
|
const [selected, setSelected] = createSignal<string | null>((params.plan as string) || null)
|
||||||
const [mounted, setMounted] = createSignal(false)
|
const [mounted, setMounted] = createSignal(false)
|
||||||
const selectedPlan = createMemo(() => plans.find((p) => p.id === selected()))
|
const selectedPlan = createMemo(() => plans.find((p) => p.id === selected()))
|
||||||
|
|
@ -42,72 +49,76 @@ export default function Black() {
|
||||||
<>
|
<>
|
||||||
<Title>{i18n.t("black.title")}</Title>
|
<Title>{i18n.t("black.title")}</Title>
|
||||||
<section data-slot="cta">
|
<section data-slot="cta">
|
||||||
<Switch>
|
<Show when={!paused()} fallback={<p data-slot="paused">{i18n.t("black.paused")}</p>}>
|
||||||
<Match when={!selected()}>
|
<Switch>
|
||||||
<div data-slot="pricing">
|
<Match when={!selected()}>
|
||||||
<For each={plans}>
|
<div data-slot="pricing">
|
||||||
{(plan) => (
|
<For each={plans}>
|
||||||
<button
|
{(plan) => (
|
||||||
type="button"
|
<button
|
||||||
onClick={() => select(plan.id)}
|
type="button"
|
||||||
data-slot="pricing-card"
|
onClick={() => select(plan.id)}
|
||||||
style={{ "view-transition-name": `card-${plan.id}` }}
|
data-slot="pricing-card"
|
||||||
>
|
style={{ "view-transition-name": `card-${plan.id}` }}
|
||||||
|
>
|
||||||
|
<div data-slot="icon">
|
||||||
|
<PlanIcon plan={plan.id} />
|
||||||
|
</div>
|
||||||
|
<p data-slot="price">
|
||||||
|
<span data-slot="amount">${plan.id}</span>{" "}
|
||||||
|
<span data-slot="period">{i18n.t("black.price.perMonth")}</span>
|
||||||
|
<Show when={plan.multiplier}>
|
||||||
|
{(multiplier) => <span data-slot="multiplier">{i18n.t(multiplier())}</span>}
|
||||||
|
</Show>
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</Match>
|
||||||
|
<Match when={selectedPlan()}>
|
||||||
|
{(plan) => (
|
||||||
|
<div data-slot="selected-plan">
|
||||||
|
<div data-slot="selected-card" style={{ "view-transition-name": `card-${plan().id}` }}>
|
||||||
<div data-slot="icon">
|
<div data-slot="icon">
|
||||||
<PlanIcon plan={plan.id} />
|
<PlanIcon plan={plan().id} />
|
||||||
</div>
|
</div>
|
||||||
<p data-slot="price">
|
<p data-slot="price">
|
||||||
<span data-slot="amount">${plan.id}</span>{" "}
|
<span data-slot="amount">${plan().id}</span>{" "}
|
||||||
<span data-slot="period">{i18n.t("black.price.perMonth")}</span>
|
<span data-slot="period">{i18n.t("black.price.perPersonBilledMonthly")}</span>
|
||||||
<Show when={plan.multiplier}>
|
<Show when={plan().multiplier}>
|
||||||
{(multiplier) => <span data-slot="multiplier">{i18n.t(multiplier())}</span>}
|
{(multiplier) => <span data-slot="multiplier">{i18n.t(multiplier())}</span>}
|
||||||
</Show>
|
</Show>
|
||||||
</p>
|
</p>
|
||||||
</button>
|
<ul data-slot="terms" style={{ "view-transition-name": `terms-${plan().id}` }}>
|
||||||
)}
|
<li>{i18n.t("black.terms.1")}</li>
|
||||||
</For>
|
<li>{i18n.t("black.terms.2")}</li>
|
||||||
</div>
|
<li>{i18n.t("black.terms.3")}</li>
|
||||||
</Match>
|
<li>{i18n.t("black.terms.4")}</li>
|
||||||
<Match when={selectedPlan()}>
|
<li>{i18n.t("black.terms.5")}</li>
|
||||||
{(plan) => (
|
<li>{i18n.t("black.terms.6")}</li>
|
||||||
<div data-slot="selected-plan">
|
<li>{i18n.t("black.terms.7")}</li>
|
||||||
<div data-slot="selected-card" style={{ "view-transition-name": `card-${plan().id}` }}>
|
</ul>
|
||||||
<div data-slot="icon">
|
<div data-slot="actions" style={{ "view-transition-name": `actions-${plan().id}` }}>
|
||||||
<PlanIcon plan={plan().id} />
|
<button type="button" onClick={() => cancel()} data-slot="cancel">
|
||||||
</div>
|
{i18n.t("common.cancel")}
|
||||||
<p data-slot="price">
|
</button>
|
||||||
<span data-slot="amount">${plan().id}</span>{" "}
|
<a href={`/black/subscribe/${plan().id}`} data-slot="continue">
|
||||||
<span data-slot="period">{i18n.t("black.price.perPersonBilledMonthly")}</span>
|
{i18n.t("black.action.continue")}
|
||||||
<Show when={plan().multiplier}>
|
</a>
|
||||||
{(multiplier) => <span data-slot="multiplier">{i18n.t(multiplier())}</span>}
|
</div>
|
||||||
</Show>
|
|
||||||
</p>
|
|
||||||
<ul data-slot="terms" style={{ "view-transition-name": `terms-${plan().id}` }}>
|
|
||||||
<li>{i18n.t("black.terms.1")}</li>
|
|
||||||
<li>{i18n.t("black.terms.2")}</li>
|
|
||||||
<li>{i18n.t("black.terms.3")}</li>
|
|
||||||
<li>{i18n.t("black.terms.4")}</li>
|
|
||||||
<li>{i18n.t("black.terms.5")}</li>
|
|
||||||
<li>{i18n.t("black.terms.6")}</li>
|
|
||||||
<li>{i18n.t("black.terms.7")}</li>
|
|
||||||
</ul>
|
|
||||||
<div data-slot="actions" style={{ "view-transition-name": `actions-${plan().id}` }}>
|
|
||||||
<button type="button" onClick={() => cancel()} data-slot="cancel">
|
|
||||||
{i18n.t("common.cancel")}
|
|
||||||
</button>
|
|
||||||
<a href={`/black/subscribe/${plan().id}`} data-slot="continue">
|
|
||||||
{i18n.t("black.action.continue")}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</Match>
|
||||||
</Match>
|
</Switch>
|
||||||
</Switch>
|
</Show>
|
||||||
<p data-slot="fine-print" style={{ "view-transition-name": "fine-print" }}>
|
<Show when={!paused()}>
|
||||||
{i18n.t("black.finePrint.beforeTerms")} ·{" "}
|
<p data-slot="fine-print" style={{ "view-transition-name": "fine-print" }}>
|
||||||
<A href={language.route("/legal/terms-of-service")}>{i18n.t("black.finePrint.terms")}</A>
|
{i18n.t("black.finePrint.beforeTerms")} ·{" "}
|
||||||
</p>
|
<A href={language.route("/legal/terms-of-service")}>{i18n.t("black.finePrint.terms")}</A>
|
||||||
|
</p>
|
||||||
|
</Show>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,12 @@ import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||||
import { useI18n } from "~/context/i18n"
|
import { useI18n } from "~/context/i18n"
|
||||||
import { useLanguage } from "~/context/language"
|
import { useLanguage } from "~/context/language"
|
||||||
import { formError } from "~/lib/form-error"
|
import { formError } from "~/lib/form-error"
|
||||||
|
import { Resource } from "@opencode-ai/console-resource"
|
||||||
|
|
||||||
|
const getEnabled = query(async () => {
|
||||||
|
"use server"
|
||||||
|
return Resource.App.stage !== "production"
|
||||||
|
}, "black.subscribe.enabled")
|
||||||
|
|
||||||
const plansMap = Object.fromEntries(plans.map((p) => [p.id, p])) as Record<PlanID, (typeof plans)[number]>
|
const plansMap = Object.fromEntries(plans.map((p) => [p.id, p])) as Record<PlanID, (typeof plans)[number]>
|
||||||
const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY!)
|
const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY!)
|
||||||
|
|
@ -269,6 +275,7 @@ export default function BlackSubscribe() {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
const enabled = createAsync(() => getEnabled())
|
||||||
const planData = plansMap[(params.plan as PlanID) ?? "20"] ?? plansMap["20"]
|
const planData = plansMap[(params.plan as PlanID) ?? "20"] ?? plansMap["20"]
|
||||||
const plan = planData.id
|
const plan = planData.id
|
||||||
|
|
||||||
|
|
@ -359,7 +366,7 @@ export default function BlackSubscribe() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Show when={enabled()}>
|
||||||
<Title>{i18n.t("black.subscribe.title")}</Title>
|
<Title>{i18n.t("black.subscribe.title")}</Title>
|
||||||
<section data-slot="subscribe-form">
|
<section data-slot="subscribe-form">
|
||||||
<div data-slot="form-card">
|
<div data-slot="form-card">
|
||||||
|
|
@ -472,6 +479,6 @@ export default function BlackSubscribe() {
|
||||||
<A href={language.route("/legal/terms-of-service")}>{i18n.t("black.finePrint.terms")}</A>
|
<A href={language.route("/legal/terms-of-service")}>{i18n.t("black.finePrint.terms")}</A>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { Billing } from "@opencode-ai/console-core/billing.js"
|
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||||
import type { APIEvent } from "@solidjs/start/server"
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
import { and, Database, eq, isNull, sql } from "@opencode-ai/console-core/drizzle/index.js"
|
import { and, Database, eq, sql } from "@opencode-ai/console-core/drizzle/index.js"
|
||||||
import { BillingTable, PaymentTable, SubscriptionTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
import { BillingTable, LiteTable, PaymentTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
||||||
import { Identifier } from "@opencode-ai/console-core/identifier.js"
|
import { Identifier } from "@opencode-ai/console-core/identifier.js"
|
||||||
import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
|
import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
|
||||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||||
import { Resource } from "@opencode-ai/console-resource"
|
import { Resource } from "@opencode-ai/console-resource"
|
||||||
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
import { LiteData } from "@opencode-ai/console-core/lite.js"
|
||||||
import { AuthTable } from "@opencode-ai/console-core/schema/auth.sql.js"
|
import { BlackData } from "@opencode-ai/console-core/black.js"
|
||||||
|
|
||||||
export async function POST(input: APIEvent) {
|
export async function POST(input: APIEvent) {
|
||||||
const body = await Billing.stripe().webhooks.constructEventAsync(
|
const body = await Billing.stripe().webhooks.constructEventAsync(
|
||||||
|
|
@ -103,310 +103,93 @@ export async function POST(input: APIEvent) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (body.type === "checkout.session.completed" && body.data.object.mode === "subscription") {
|
if (body.type === "customer.subscription.created") {
|
||||||
const workspaceID = body.data.object.custom_fields.find((f) => f.key === "workspaceid")?.text?.value
|
const type = body.data.object.metadata?.type
|
||||||
const amountInCents = body.data.object.amount_total as number
|
if (type === "lite") {
|
||||||
const customerID = body.data.object.customer as string
|
const workspaceID = body.data.object.metadata?.workspaceID
|
||||||
const customerEmail = body.data.object.customer_details?.email as string
|
const userID = body.data.object.metadata?.userID
|
||||||
const invoiceID = body.data.object.invoice as string
|
const customerID = body.data.object.customer as string
|
||||||
const subscriptionID = body.data.object.subscription as string
|
const invoiceID = body.data.object.latest_invoice as string
|
||||||
const promoCode = body.data.object.discounts?.[0]?.promotion_code as string
|
const subscriptionID = body.data.object.id as string
|
||||||
|
|
||||||
if (!workspaceID) throw new Error("Workspace ID not found")
|
if (!workspaceID) throw new Error("Workspace ID not found")
|
||||||
if (!customerID) throw new Error("Customer ID not found")
|
if (!userID) throw new Error("User ID not found")
|
||||||
if (!amountInCents) throw new Error("Amount not found")
|
if (!customerID) throw new Error("Customer ID not found")
|
||||||
if (!invoiceID) throw new Error("Invoice ID not found")
|
if (!invoiceID) throw new Error("Invoice ID not found")
|
||||||
if (!subscriptionID) throw new Error("Subscription ID not found")
|
if (!subscriptionID) throw new Error("Subscription ID not found")
|
||||||
|
|
||||||
// get payment id from invoice
|
// get payment id from invoice
|
||||||
const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
|
const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
|
||||||
expand: ["payments"],
|
expand: ["payments"],
|
||||||
})
|
})
|
||||||
const paymentID = invoice.payments?.data[0].payment.payment_intent as string
|
const paymentID = invoice.payments?.data[0].payment.payment_intent as string
|
||||||
if (!paymentID) throw new Error("Payment ID not found")
|
if (!paymentID) throw new Error("Payment ID not found")
|
||||||
|
|
||||||
// get payment method for the payment intent
|
// get payment method for the payment intent
|
||||||
const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
|
const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
|
||||||
expand: ["payment_method"],
|
expand: ["payment_method"],
|
||||||
})
|
})
|
||||||
const paymentMethod = paymentIntent.payment_method
|
const paymentMethod = paymentIntent.payment_method
|
||||||
if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
|
if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
|
||||||
|
|
||||||
// get coupon id from promotion code
|
await Actor.provide("system", { workspaceID }, async () => {
|
||||||
const couponID = await (async () => {
|
// look up current billing
|
||||||
if (!promoCode) return
|
const billing = await Billing.get()
|
||||||
const coupon = await Billing.stripe().promotionCodes.retrieve(promoCode)
|
if (!billing) throw new Error(`Workspace with ID ${workspaceID} not found`)
|
||||||
const couponID = coupon.coupon.id
|
if (billing.customerID && billing.customerID !== customerID) throw new Error("Customer ID mismatch")
|
||||||
if (!couponID) throw new Error("Coupon not found for promotion code")
|
|
||||||
return couponID
|
|
||||||
})()
|
|
||||||
|
|
||||||
await Actor.provide("system", { workspaceID }, async () => {
|
// set customer metadata
|
||||||
// look up current billing
|
if (!billing?.customerID) {
|
||||||
const billing = await Billing.get()
|
await Billing.stripe().customers.update(customerID, {
|
||||||
if (!billing) throw new Error(`Workspace with ID ${workspaceID} not found`)
|
metadata: {
|
||||||
|
workspaceID,
|
||||||
// Temporarily skip this check because during Black drop, user can checkout
|
|
||||||
// as a new customer
|
|
||||||
//if (billing.customerID !== customerID) throw new Error("Customer ID mismatch")
|
|
||||||
|
|
||||||
// Temporarily check the user to apply to. After Black drop, we will allow
|
|
||||||
// look up the user to apply to
|
|
||||||
const users = await Database.use((tx) =>
|
|
||||||
tx
|
|
||||||
.select({ id: UserTable.id, email: AuthTable.subject })
|
|
||||||
.from(UserTable)
|
|
||||||
.innerJoin(AuthTable, and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email")))
|
|
||||||
.where(and(eq(UserTable.workspaceID, workspaceID), isNull(UserTable.timeDeleted))),
|
|
||||||
)
|
|
||||||
const user = users.find((u) => u.email === customerEmail) ?? users[0]
|
|
||||||
if (!user) {
|
|
||||||
console.error(`Error: User with email ${customerEmail} not found in workspace ${workspaceID}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set customer metadata
|
|
||||||
if (!billing?.customerID) {
|
|
||||||
await Billing.stripe().customers.update(customerID, {
|
|
||||||
metadata: {
|
|
||||||
workspaceID,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await Database.transaction(async (tx) => {
|
|
||||||
await tx
|
|
||||||
.update(BillingTable)
|
|
||||||
.set({
|
|
||||||
customerID,
|
|
||||||
subscriptionID,
|
|
||||||
subscription: {
|
|
||||||
status: "subscribed",
|
|
||||||
coupon: couponID,
|
|
||||||
seats: 1,
|
|
||||||
plan: "200",
|
|
||||||
},
|
},
|
||||||
paymentMethodID: paymentMethod.id,
|
|
||||||
paymentMethodLast4: paymentMethod.card?.last4 ?? null,
|
|
||||||
paymentMethodType: paymentMethod.type,
|
|
||||||
})
|
})
|
||||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
}
|
||||||
|
|
||||||
await tx.insert(SubscriptionTable).values({
|
await Database.transaction(async (tx) => {
|
||||||
workspaceID,
|
await tx
|
||||||
id: Identifier.create("subscription"),
|
.update(BillingTable)
|
||||||
userID: user.id,
|
.set({
|
||||||
})
|
customerID,
|
||||||
|
liteSubscriptionID: subscriptionID,
|
||||||
|
lite: {},
|
||||||
|
paymentMethodID: paymentMethod.id,
|
||||||
|
paymentMethodLast4: paymentMethod.card?.last4 ?? null,
|
||||||
|
paymentMethodType: paymentMethod.type,
|
||||||
|
})
|
||||||
|
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||||
|
|
||||||
await tx.insert(PaymentTable).values({
|
await tx.insert(LiteTable).values({
|
||||||
workspaceID,
|
workspaceID,
|
||||||
id: Identifier.create("payment"),
|
id: Identifier.create("lite"),
|
||||||
amount: centsToMicroCents(amountInCents),
|
userID: userID,
|
||||||
paymentID,
|
})
|
||||||
invoiceID,
|
|
||||||
customerID,
|
|
||||||
enrichment: {
|
|
||||||
type: "subscription",
|
|
||||||
couponID,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
}
|
|
||||||
if (body.type === "customer.subscription.created") {
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
id: "evt_1Smq802SrMQ2Fneksse5FMNV",
|
|
||||||
object: "event",
|
|
||||||
api_version: "2025-07-30.basil",
|
|
||||||
created: 1767766916,
|
|
||||||
data: {
|
|
||||||
object: {
|
|
||||||
id: "sub_1Smq7x2SrMQ2Fnek8F1yf3ZD",
|
|
||||||
object: "subscription",
|
|
||||||
application: null,
|
|
||||||
application_fee_percent: null,
|
|
||||||
automatic_tax: {
|
|
||||||
disabled_reason: null,
|
|
||||||
enabled: false,
|
|
||||||
liability: null,
|
|
||||||
},
|
|
||||||
billing_cycle_anchor: 1770445200,
|
|
||||||
billing_cycle_anchor_config: null,
|
|
||||||
billing_mode: {
|
|
||||||
flexible: {
|
|
||||||
proration_discounts: "included",
|
|
||||||
},
|
|
||||||
type: "flexible",
|
|
||||||
updated_at: 1770445200,
|
|
||||||
},
|
|
||||||
billing_thresholds: null,
|
|
||||||
cancel_at: null,
|
|
||||||
cancel_at_period_end: false,
|
|
||||||
canceled_at: null,
|
|
||||||
cancellation_details: {
|
|
||||||
comment: null,
|
|
||||||
feedback: null,
|
|
||||||
reason: null,
|
|
||||||
},
|
|
||||||
collection_method: "charge_automatically",
|
|
||||||
created: 1770445200,
|
|
||||||
currency: "usd",
|
|
||||||
customer: "cus_TkKmZZvysJ2wej",
|
|
||||||
customer_account: null,
|
|
||||||
days_until_due: null,
|
|
||||||
default_payment_method: null,
|
|
||||||
default_source: "card_1Smq7u2SrMQ2FneknjyOa7sq",
|
|
||||||
default_tax_rates: [],
|
|
||||||
description: null,
|
|
||||||
discounts: [],
|
|
||||||
ended_at: null,
|
|
||||||
invoice_settings: {
|
|
||||||
account_tax_ids: null,
|
|
||||||
issuer: {
|
|
||||||
type: "self",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
object: "list",
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
id: "si_TkKnBKXFX76t0O",
|
|
||||||
object: "subscription_item",
|
|
||||||
billing_thresholds: null,
|
|
||||||
created: 1770445200,
|
|
||||||
current_period_end: 1772864400,
|
|
||||||
current_period_start: 1770445200,
|
|
||||||
discounts: [],
|
|
||||||
metadata: {},
|
|
||||||
plan: {
|
|
||||||
id: "price_1SmfFG2SrMQ2FnekJuzwHMea",
|
|
||||||
object: "plan",
|
|
||||||
active: true,
|
|
||||||
amount: 20000,
|
|
||||||
amount_decimal: "20000",
|
|
||||||
billing_scheme: "per_unit",
|
|
||||||
created: 1767725082,
|
|
||||||
currency: "usd",
|
|
||||||
interval: "month",
|
|
||||||
interval_count: 1,
|
|
||||||
livemode: false,
|
|
||||||
metadata: {},
|
|
||||||
meter: null,
|
|
||||||
nickname: null,
|
|
||||||
product: "prod_Tk9LjWT1n0DgYm",
|
|
||||||
tiers_mode: null,
|
|
||||||
transform_usage: null,
|
|
||||||
trial_period_days: null,
|
|
||||||
usage_type: "licensed",
|
|
||||||
},
|
|
||||||
price: {
|
|
||||||
id: "price_1SmfFG2SrMQ2FnekJuzwHMea",
|
|
||||||
object: "price",
|
|
||||||
active: true,
|
|
||||||
billing_scheme: "per_unit",
|
|
||||||
created: 1767725082,
|
|
||||||
currency: "usd",
|
|
||||||
custom_unit_amount: null,
|
|
||||||
livemode: false,
|
|
||||||
lookup_key: null,
|
|
||||||
metadata: {},
|
|
||||||
nickname: null,
|
|
||||||
product: "prod_Tk9LjWT1n0DgYm",
|
|
||||||
recurring: {
|
|
||||||
interval: "month",
|
|
||||||
interval_count: 1,
|
|
||||||
meter: null,
|
|
||||||
trial_period_days: null,
|
|
||||||
usage_type: "licensed",
|
|
||||||
},
|
|
||||||
tax_behavior: "unspecified",
|
|
||||||
tiers_mode: null,
|
|
||||||
transform_quantity: null,
|
|
||||||
type: "recurring",
|
|
||||||
unit_amount: 20000,
|
|
||||||
unit_amount_decimal: "20000",
|
|
||||||
},
|
|
||||||
quantity: 1,
|
|
||||||
subscription: "sub_1Smq7x2SrMQ2Fnek8F1yf3ZD",
|
|
||||||
tax_rates: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
has_more: false,
|
|
||||||
total_count: 1,
|
|
||||||
url: "/v1/subscription_items?subscription=sub_1Smq7x2SrMQ2Fnek8F1yf3ZD",
|
|
||||||
},
|
|
||||||
latest_invoice: "in_1Smq7x2SrMQ2FnekSJesfPwE",
|
|
||||||
livemode: false,
|
|
||||||
metadata: {},
|
|
||||||
next_pending_invoice_item_invoice: null,
|
|
||||||
on_behalf_of: null,
|
|
||||||
pause_collection: null,
|
|
||||||
payment_settings: {
|
|
||||||
payment_method_options: null,
|
|
||||||
payment_method_types: null,
|
|
||||||
save_default_payment_method: "off",
|
|
||||||
},
|
|
||||||
pending_invoice_item_interval: null,
|
|
||||||
pending_setup_intent: null,
|
|
||||||
pending_update: null,
|
|
||||||
plan: {
|
|
||||||
id: "price_1SmfFG2SrMQ2FnekJuzwHMea",
|
|
||||||
object: "plan",
|
|
||||||
active: true,
|
|
||||||
amount: 20000,
|
|
||||||
amount_decimal: "20000",
|
|
||||||
billing_scheme: "per_unit",
|
|
||||||
created: 1767725082,
|
|
||||||
currency: "usd",
|
|
||||||
interval: "month",
|
|
||||||
interval_count: 1,
|
|
||||||
livemode: false,
|
|
||||||
metadata: {},
|
|
||||||
meter: null,
|
|
||||||
nickname: null,
|
|
||||||
product: "prod_Tk9LjWT1n0DgYm",
|
|
||||||
tiers_mode: null,
|
|
||||||
transform_usage: null,
|
|
||||||
trial_period_days: null,
|
|
||||||
usage_type: "licensed",
|
|
||||||
},
|
|
||||||
quantity: 1,
|
|
||||||
schedule: null,
|
|
||||||
start_date: 1770445200,
|
|
||||||
status: "active",
|
|
||||||
test_clock: "clock_1Smq6n2SrMQ2FnekQw4yt2PZ",
|
|
||||||
transfer_data: null,
|
|
||||||
trial_end: null,
|
|
||||||
trial_settings: {
|
|
||||||
end_behavior: {
|
|
||||||
missing_payment_method: "create_invoice",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
trial_start: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
livemode: false,
|
|
||||||
pending_webhooks: 0,
|
|
||||||
request: {
|
|
||||||
id: "req_6YO9stvB155WJD",
|
|
||||||
idempotency_key: "581ba059-6f86-49b2-9c49-0d8450255322",
|
|
||||||
},
|
|
||||||
type: "customer.subscription.created",
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
if (body.type === "customer.subscription.updated" && body.data.object.status === "incomplete_expired") {
|
if (body.type === "customer.subscription.updated" && body.data.object.status === "incomplete_expired") {
|
||||||
const subscriptionID = body.data.object.id
|
const subscriptionID = body.data.object.id
|
||||||
if (!subscriptionID) throw new Error("Subscription ID not found")
|
if (!subscriptionID) throw new Error("Subscription ID not found")
|
||||||
|
|
||||||
await Billing.unsubscribe({ subscriptionID })
|
const productID = body.data.object.items.data[0].price.product as string
|
||||||
|
if (productID === LiteData.productID()) {
|
||||||
|
await Billing.unsubscribeLite({ subscriptionID })
|
||||||
|
} else if (productID === BlackData.productID()) {
|
||||||
|
await Billing.unsubscribeBlack({ subscriptionID })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (body.type === "customer.subscription.deleted") {
|
if (body.type === "customer.subscription.deleted") {
|
||||||
const subscriptionID = body.data.object.id
|
const subscriptionID = body.data.object.id
|
||||||
if (!subscriptionID) throw new Error("Subscription ID not found")
|
if (!subscriptionID) throw new Error("Subscription ID not found")
|
||||||
|
|
||||||
await Billing.unsubscribe({ subscriptionID })
|
const productID = body.data.object.items.data[0].price.product as string
|
||||||
|
if (productID === LiteData.productID()) {
|
||||||
|
await Billing.unsubscribeLite({ subscriptionID })
|
||||||
|
} else if (productID === BlackData.productID()) {
|
||||||
|
await Billing.unsubscribeBlack({ subscriptionID })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (body.type === "invoice.payment_succeeded") {
|
if (body.type === "invoice.payment_succeeded") {
|
||||||
if (
|
if (
|
||||||
|
|
@ -430,6 +213,7 @@ export async function POST(input: APIEvent) {
|
||||||
typeof subscriptionData.discounts[0] === "string"
|
typeof subscriptionData.discounts[0] === "string"
|
||||||
? subscriptionData.discounts[0]
|
? subscriptionData.discounts[0]
|
||||||
: subscriptionData.discounts[0]?.coupon?.id
|
: subscriptionData.discounts[0]?.coupon?.id
|
||||||
|
const productID = subscriptionData.items.data[0].price.product as string
|
||||||
|
|
||||||
// get payment id from invoice
|
// get payment id from invoice
|
||||||
const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
|
const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
|
||||||
|
|
@ -459,7 +243,7 @@ export async function POST(input: APIEvent) {
|
||||||
invoiceID,
|
invoiceID,
|
||||||
customerID,
|
customerID,
|
||||||
enrichment: {
|
enrichment: {
|
||||||
type: "subscription",
|
type: productID === LiteData.productID() ? "lite" : "subscription",
|
||||||
couponID,
|
couponID,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||||
import { Database, eq, and, isNull, sql } from "@opencode-ai/console-core/drizzle/index.js"
|
import { Database, eq, and, isNull, sql } from "@opencode-ai/console-core/drizzle/index.js"
|
||||||
import { BillingTable, SubscriptionTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
import { BillingTable, SubscriptionTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
||||||
import { Actor } from "@opencode-ai/console-core/actor.js"
|
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||||
import { Black } from "@opencode-ai/console-core/black.js"
|
import { Subscription } from "@opencode-ai/console-core/subscription.js"
|
||||||
|
import { BlackData } from "@opencode-ai/console-core/black.js"
|
||||||
import { withActor } from "~/context/auth.withActor"
|
import { withActor } from "~/context/auth.withActor"
|
||||||
import { queryBillingInfo } from "../../common"
|
import { queryBillingInfo } from "../../common"
|
||||||
import styles from "./black-section.module.css"
|
import styles from "./black-section.module.css"
|
||||||
|
|
@ -31,17 +32,19 @@ const querySubscription = query(async (workspaceID: string) => {
|
||||||
.then((r) => r[0]),
|
.then((r) => r[0]),
|
||||||
)
|
)
|
||||||
if (!row?.subscription) return null
|
if (!row?.subscription) return null
|
||||||
|
const blackData = BlackData.getLimits({ plan: row.subscription.plan })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plan: row.subscription.plan,
|
plan: row.subscription.plan,
|
||||||
useBalance: row.subscription.useBalance ?? false,
|
useBalance: row.subscription.useBalance ?? false,
|
||||||
rollingUsage: Black.analyzeRollingUsage({
|
rollingUsage: Subscription.analyzeRollingUsage({
|
||||||
plan: row.subscription.plan,
|
limit: blackData.rollingLimit,
|
||||||
|
window: blackData.rollingWindow,
|
||||||
usage: row.rollingUsage ?? 0,
|
usage: row.rollingUsage ?? 0,
|
||||||
timeUpdated: row.timeRollingUpdated ?? new Date(),
|
timeUpdated: row.timeRollingUpdated ?? new Date(),
|
||||||
}),
|
}),
|
||||||
weeklyUsage: Black.analyzeWeeklyUsage({
|
weeklyUsage: Subscription.analyzeWeeklyUsage({
|
||||||
plan: row.subscription.plan,
|
limit: blackData.fixedLimit,
|
||||||
usage: row.fixedUsage ?? 0,
|
usage: row.fixedUsage ?? 0,
|
||||||
timeUpdated: row.timeFixedUpdated ?? new Date(),
|
timeUpdated: row.timeFixedUpdated ?? new Date(),
|
||||||
}),
|
}),
|
||||||
|
|
@ -87,7 +90,7 @@ const enroll = action(async (workspaceID: string) => {
|
||||||
"use server"
|
"use server"
|
||||||
return json(
|
return json(
|
||||||
await withActor(async () => {
|
await withActor(async () => {
|
||||||
await Billing.subscribe({ seats: 1 })
|
await Billing.subscribeBlack({ seats: 1 })
|
||||||
return { error: undefined }
|
return { error: undefined }
|
||||||
}, workspaceID).catch((e) => ({ error: e.message as string })),
|
}, workspaceID).catch((e) => ({ error: e.message as string })),
|
||||||
{ revalidate: [queryBillingInfo.key, querySubscription.key] },
|
{ revalidate: [queryBillingInfo.key, querySubscription.key] },
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ import { BillingSection } from "./billing-section"
|
||||||
import { ReloadSection } from "./reload-section"
|
import { ReloadSection } from "./reload-section"
|
||||||
import { PaymentSection } from "./payment-section"
|
import { PaymentSection } from "./payment-section"
|
||||||
import { BlackSection } from "./black-section"
|
import { BlackSection } from "./black-section"
|
||||||
import { Show } from "solid-js"
|
import { LiteSection } from "./lite-section"
|
||||||
|
import { createMemo, Show } from "solid-js"
|
||||||
import { createAsync, useParams } from "@solidjs/router"
|
import { createAsync, useParams } from "@solidjs/router"
|
||||||
import { queryBillingInfo, querySessionInfo } from "../../common"
|
import { queryBillingInfo, querySessionInfo } from "../../common"
|
||||||
|
|
||||||
|
|
@ -11,14 +12,18 @@ export default function () {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const sessionInfo = createAsync(() => querySessionInfo(params.id!))
|
const sessionInfo = createAsync(() => querySessionInfo(params.id!))
|
||||||
const billingInfo = createAsync(() => queryBillingInfo(params.id!))
|
const billingInfo = createAsync(() => queryBillingInfo(params.id!))
|
||||||
|
const isBlack = createMemo(() => billingInfo()?.subscriptionID || billingInfo()?.timeSubscriptionBooked)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-page="workspace-[id]">
|
<div data-page="workspace-[id]">
|
||||||
<div data-slot="sections">
|
<div data-slot="sections">
|
||||||
<Show when={sessionInfo()?.isAdmin}>
|
<Show when={sessionInfo()?.isAdmin}>
|
||||||
<Show when={billingInfo()?.subscriptionID || billingInfo()?.timeSubscriptionBooked}>
|
<Show when={isBlack()}>
|
||||||
<BlackSection />
|
<BlackSection />
|
||||||
</Show>
|
</Show>
|
||||||
|
<Show when={!isBlack()}>
|
||||||
|
<LiteSection />
|
||||||
|
</Show>
|
||||||
<BillingSection />
|
<BillingSection />
|
||||||
<Show when={billingInfo()?.customerID}>
|
<Show when={billingInfo()?.customerID}>
|
||||||
<ReloadSection />
|
<ReloadSection />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,190 @@
|
||||||
|
.root {
|
||||||
|
[data-slot="title-row"] {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="usage"] {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-6);
|
||||||
|
margin-top: var(--space-4);
|
||||||
|
|
||||||
|
@media (max-width: 40rem) {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="usage-item"] {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="usage-header"] {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="usage-label"] {
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="usage-value"] {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="progress"] {
|
||||||
|
height: 8px;
|
||||||
|
background-color: var(--color-bg-surface);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="progress-bar"] {
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="reset-time"] {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="setting-row"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-3);
|
||||||
|
margin-top: var(--space-4);
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="toggle-label"] {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
border: 1px solid #bbb;
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&::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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + span {
|
||||||
|
background-color: #21ad0e;
|
||||||
|
border-color: #148605;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
transform: translateX(1rem) translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&: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);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(input:disabled) {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:disabled + span {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="beta-notice"] {
|
||||||
|
padding: var(--space-3) var(--space-4);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
background-color: var(--color-bg-surface);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-top: var(--space-3);
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="other-message"] {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="promo-description"] {
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-top: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="promo-models-title"] {
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="promo-models"] {
|
||||||
|
margin: var(--space-2) 0 0 var(--space-4);
|
||||||
|
padding: 0;
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="subscribe-button"] {
|
||||||
|
align-self: flex-start;
|
||||||
|
margin-top: var(--space-4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,285 @@
|
||||||
|
import { action, useParams, useAction, useSubmission, json, query, createAsync } from "@solidjs/router"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
|
import { Show } from "solid-js"
|
||||||
|
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||||
|
import { Database, eq, and, isNull } from "@opencode-ai/console-core/drizzle/index.js"
|
||||||
|
import { BillingTable, LiteTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
||||||
|
import { Actor } from "@opencode-ai/console-core/actor.js"
|
||||||
|
import { Subscription } from "@opencode-ai/console-core/subscription.js"
|
||||||
|
import { LiteData } from "@opencode-ai/console-core/lite.js"
|
||||||
|
import { withActor } from "~/context/auth.withActor"
|
||||||
|
import { queryBillingInfo } from "../../common"
|
||||||
|
import styles from "./lite-section.module.css"
|
||||||
|
import { useI18n } from "~/context/i18n"
|
||||||
|
import { useLanguage } from "~/context/language"
|
||||||
|
|
||||||
|
const queryLiteSubscription = query(async (workspaceID: string) => {
|
||||||
|
"use server"
|
||||||
|
return withActor(async () => {
|
||||||
|
const row = await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.select({
|
||||||
|
userID: LiteTable.userID,
|
||||||
|
rollingUsage: LiteTable.rollingUsage,
|
||||||
|
weeklyUsage: LiteTable.weeklyUsage,
|
||||||
|
monthlyUsage: LiteTable.monthlyUsage,
|
||||||
|
timeRollingUpdated: LiteTable.timeRollingUpdated,
|
||||||
|
timeWeeklyUpdated: LiteTable.timeWeeklyUpdated,
|
||||||
|
timeMonthlyUpdated: LiteTable.timeMonthlyUpdated,
|
||||||
|
timeCreated: LiteTable.timeCreated,
|
||||||
|
lite: BillingTable.lite,
|
||||||
|
})
|
||||||
|
.from(BillingTable)
|
||||||
|
.innerJoin(LiteTable, eq(LiteTable.workspaceID, BillingTable.workspaceID))
|
||||||
|
.where(and(eq(LiteTable.workspaceID, Actor.workspace()), isNull(LiteTable.timeDeleted)))
|
||||||
|
.then((r) => r[0]),
|
||||||
|
)
|
||||||
|
if (!row) return null
|
||||||
|
|
||||||
|
const limits = LiteData.getLimits()
|
||||||
|
const mine = row.userID === Actor.userID()
|
||||||
|
|
||||||
|
return {
|
||||||
|
mine,
|
||||||
|
useBalance: row.lite?.useBalance ?? false,
|
||||||
|
rollingUsage: Subscription.analyzeRollingUsage({
|
||||||
|
limit: limits.rollingLimit,
|
||||||
|
window: limits.rollingWindow,
|
||||||
|
usage: row.rollingUsage ?? 0,
|
||||||
|
timeUpdated: row.timeRollingUpdated ?? new Date(),
|
||||||
|
}),
|
||||||
|
weeklyUsage: Subscription.analyzeWeeklyUsage({
|
||||||
|
limit: limits.weeklyLimit,
|
||||||
|
usage: row.weeklyUsage ?? 0,
|
||||||
|
timeUpdated: row.timeWeeklyUpdated ?? new Date(),
|
||||||
|
}),
|
||||||
|
monthlyUsage: Subscription.analyzeMonthlyUsage({
|
||||||
|
limit: limits.monthlyLimit,
|
||||||
|
usage: row.monthlyUsage ?? 0,
|
||||||
|
timeUpdated: row.timeMonthlyUpdated ?? new Date(),
|
||||||
|
timeSubscribed: row.timeCreated,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}, workspaceID)
|
||||||
|
}, "lite.subscription.get")
|
||||||
|
|
||||||
|
function formatResetTime(seconds: number, i18n: ReturnType<typeof useI18n>) {
|
||||||
|
const days = Math.floor(seconds / 86400)
|
||||||
|
if (days >= 1) {
|
||||||
|
const hours = Math.floor((seconds % 86400) / 3600)
|
||||||
|
return `${days} ${days === 1 ? i18n.t("workspace.lite.time.day") : i18n.t("workspace.lite.time.days")} ${hours} ${hours === 1 ? i18n.t("workspace.lite.time.hour") : i18n.t("workspace.lite.time.hours")}`
|
||||||
|
}
|
||||||
|
const hours = Math.floor(seconds / 3600)
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60)
|
||||||
|
if (hours >= 1)
|
||||||
|
return `${hours} ${hours === 1 ? i18n.t("workspace.lite.time.hour") : i18n.t("workspace.lite.time.hours")} ${minutes} ${minutes === 1 ? i18n.t("workspace.lite.time.minute") : i18n.t("workspace.lite.time.minutes")}`
|
||||||
|
if (minutes === 0) return i18n.t("workspace.lite.time.fewSeconds")
|
||||||
|
return `${minutes} ${minutes === 1 ? i18n.t("workspace.lite.time.minute") : i18n.t("workspace.lite.time.minutes")}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const createLiteCheckoutUrl = action(async (workspaceID: string, successUrl: string, cancelUrl: string) => {
|
||||||
|
"use server"
|
||||||
|
return json(
|
||||||
|
await withActor(
|
||||||
|
() =>
|
||||||
|
Billing.generateLiteCheckoutUrl({ successUrl, cancelUrl })
|
||||||
|
.then((data) => ({ error: undefined, data }))
|
||||||
|
.catch((e) => ({
|
||||||
|
error: e.message as string,
|
||||||
|
data: undefined,
|
||||||
|
})),
|
||||||
|
workspaceID,
|
||||||
|
),
|
||||||
|
{ revalidate: [queryBillingInfo.key, queryLiteSubscription.key] },
|
||||||
|
)
|
||||||
|
}, "liteCheckoutUrl")
|
||||||
|
|
||||||
|
const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => {
|
||||||
|
"use server"
|
||||||
|
return json(
|
||||||
|
await withActor(
|
||||||
|
() =>
|
||||||
|
Billing.generateSessionUrl({ returnUrl })
|
||||||
|
.then((data) => ({ error: undefined, data }))
|
||||||
|
.catch((e) => ({
|
||||||
|
error: e.message as string,
|
||||||
|
data: undefined,
|
||||||
|
})),
|
||||||
|
workspaceID,
|
||||||
|
),
|
||||||
|
{ revalidate: [queryBillingInfo.key, queryLiteSubscription.key] },
|
||||||
|
)
|
||||||
|
}, "liteSessionUrl")
|
||||||
|
|
||||||
|
const setLiteUseBalance = action(async (form: FormData) => {
|
||||||
|
"use server"
|
||||||
|
const workspaceID = form.get("workspaceID")?.toString()
|
||||||
|
if (!workspaceID) return { error: "Workspace ID is required" }
|
||||||
|
const useBalance = form.get("useBalance")?.toString() === "true"
|
||||||
|
|
||||||
|
return json(
|
||||||
|
await withActor(async () => {
|
||||||
|
await Database.use((tx) =>
|
||||||
|
tx
|
||||||
|
.update(BillingTable)
|
||||||
|
.set({
|
||||||
|
lite: useBalance ? { useBalance: true } : {},
|
||||||
|
})
|
||||||
|
.where(eq(BillingTable.workspaceID, workspaceID)),
|
||||||
|
)
|
||||||
|
return { error: undefined }
|
||||||
|
}, workspaceID).catch((e) => ({ error: e.message as string })),
|
||||||
|
{ revalidate: [queryBillingInfo.key, queryLiteSubscription.key] },
|
||||||
|
)
|
||||||
|
}, "setLiteUseBalance")
|
||||||
|
|
||||||
|
export function LiteSection() {
|
||||||
|
const params = useParams()
|
||||||
|
const i18n = useI18n()
|
||||||
|
const language = useLanguage()
|
||||||
|
const lite = createAsync(() => queryLiteSubscription(params.id!))
|
||||||
|
const sessionAction = useAction(createSessionUrl)
|
||||||
|
const sessionSubmission = useSubmission(createSessionUrl)
|
||||||
|
const checkoutAction = useAction(createLiteCheckoutUrl)
|
||||||
|
const checkoutSubmission = useSubmission(createLiteCheckoutUrl)
|
||||||
|
const useBalanceSubmission = useSubmission(setLiteUseBalance)
|
||||||
|
const [store, setStore] = createStore({
|
||||||
|
redirecting: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
async function onClickSession() {
|
||||||
|
const result = await sessionAction(params.id!, window.location.href)
|
||||||
|
if (result.data) {
|
||||||
|
setStore("redirecting", true)
|
||||||
|
window.location.href = result.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onClickSubscribe() {
|
||||||
|
const result = await checkoutAction(params.id!, window.location.href, window.location.href)
|
||||||
|
if (result.data) {
|
||||||
|
setStore("redirecting", true)
|
||||||
|
window.location.href = result.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Show when={lite() && lite()!.mine && lite()!}>
|
||||||
|
{(sub) => (
|
||||||
|
<section class={styles.root}>
|
||||||
|
<div data-slot="section-title">
|
||||||
|
<h2>{i18n.t("workspace.lite.subscription.title")}</h2>
|
||||||
|
<div data-slot="title-row">
|
||||||
|
<p>{i18n.t("workspace.lite.subscription.message")}</p>
|
||||||
|
<button
|
||||||
|
data-color="primary"
|
||||||
|
disabled={sessionSubmission.pending || store.redirecting}
|
||||||
|
onClick={onClickSession}
|
||||||
|
>
|
||||||
|
{sessionSubmission.pending || store.redirecting
|
||||||
|
? i18n.t("workspace.lite.loading")
|
||||||
|
: i18n.t("workspace.lite.subscription.manage")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div data-slot="beta-notice">
|
||||||
|
{i18n.t("workspace.lite.subscription.selectProvider")}{" "}
|
||||||
|
<a href={language.route("/docs/providers/#opencode-go")} target="_blank" rel="noopener noreferrer">
|
||||||
|
{i18n.t("common.learnMore")}
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
<div data-slot="usage">
|
||||||
|
<div data-slot="usage-item">
|
||||||
|
<div data-slot="usage-header">
|
||||||
|
<span data-slot="usage-label">{i18n.t("workspace.lite.subscription.rollingUsage")}</span>
|
||||||
|
<span data-slot="usage-value">{sub().rollingUsage.usagePercent}%</span>
|
||||||
|
</div>
|
||||||
|
<div data-slot="progress">
|
||||||
|
<div data-slot="progress-bar" style={{ width: `${sub().rollingUsage.usagePercent}%` }} />
|
||||||
|
</div>
|
||||||
|
<span data-slot="reset-time">
|
||||||
|
{i18n.t("workspace.lite.subscription.resetsIn")}{" "}
|
||||||
|
{formatResetTime(sub().rollingUsage.resetInSec, i18n)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div data-slot="usage-item">
|
||||||
|
<div data-slot="usage-header">
|
||||||
|
<span data-slot="usage-label">{i18n.t("workspace.lite.subscription.weeklyUsage")}</span>
|
||||||
|
<span data-slot="usage-value">{sub().weeklyUsage.usagePercent}%</span>
|
||||||
|
</div>
|
||||||
|
<div data-slot="progress">
|
||||||
|
<div data-slot="progress-bar" style={{ width: `${sub().weeklyUsage.usagePercent}%` }} />
|
||||||
|
</div>
|
||||||
|
<span data-slot="reset-time">
|
||||||
|
{i18n.t("workspace.lite.subscription.resetsIn")} {formatResetTime(sub().weeklyUsage.resetInSec, i18n)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div data-slot="usage-item">
|
||||||
|
<div data-slot="usage-header">
|
||||||
|
<span data-slot="usage-label">{i18n.t("workspace.lite.subscription.monthlyUsage")}</span>
|
||||||
|
<span data-slot="usage-value">{sub().monthlyUsage.usagePercent}%</span>
|
||||||
|
</div>
|
||||||
|
<div data-slot="progress">
|
||||||
|
<div data-slot="progress-bar" style={{ width: `${sub().monthlyUsage.usagePercent}%` }} />
|
||||||
|
</div>
|
||||||
|
<span data-slot="reset-time">
|
||||||
|
{i18n.t("workspace.lite.subscription.resetsIn")}{" "}
|
||||||
|
{formatResetTime(sub().monthlyUsage.resetInSec, i18n)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form action={setLiteUseBalance} method="post" data-slot="setting-row">
|
||||||
|
<p>{i18n.t("workspace.lite.subscription.useBalance")}</p>
|
||||||
|
<input type="hidden" name="workspaceID" value={params.id} />
|
||||||
|
<input type="hidden" name="useBalance" value={sub().useBalance ? "false" : "true"} />
|
||||||
|
<label data-slot="toggle-label">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={sub().useBalance}
|
||||||
|
disabled={useBalanceSubmission.pending}
|
||||||
|
onChange={(e) => e.currentTarget.form?.requestSubmit()}
|
||||||
|
/>
|
||||||
|
<span></span>
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
<Show when={lite() && !lite()!.mine}>
|
||||||
|
<section class={styles.root}>
|
||||||
|
<div data-slot="section-title">
|
||||||
|
<h2>{i18n.t("workspace.lite.other.title")}</h2>
|
||||||
|
</div>
|
||||||
|
<p data-slot="other-message">{i18n.t("workspace.lite.other.message")}</p>
|
||||||
|
</section>
|
||||||
|
</Show>
|
||||||
|
<Show when={lite() === null}>
|
||||||
|
<section class={styles.root}>
|
||||||
|
<div data-slot="section-title">
|
||||||
|
<h2>{i18n.t("workspace.lite.promo.title")}</h2>
|
||||||
|
</div>
|
||||||
|
<p data-slot="promo-description">{i18n.t("workspace.lite.promo.description")}</p>
|
||||||
|
<h3 data-slot="promo-models-title">{i18n.t("workspace.lite.promo.modelsTitle")}</h3>
|
||||||
|
<ul data-slot="promo-models">
|
||||||
|
<li>Kimi K2.5</li>
|
||||||
|
<li>GLM-5</li>
|
||||||
|
<li>MiniMax M2.5</li>
|
||||||
|
</ul>
|
||||||
|
<p data-slot="promo-description">{i18n.t("workspace.lite.promo.footer")}</p>
|
||||||
|
<button
|
||||||
|
data-slot="subscribe-button"
|
||||||
|
data-color="primary"
|
||||||
|
disabled={checkoutSubmission.pending || store.redirecting}
|
||||||
|
onClick={onClickSubscribe}
|
||||||
|
>
|
||||||
|
{checkoutSubmission.pending || store.redirecting
|
||||||
|
? i18n.t("workspace.lite.promo.subscribing")
|
||||||
|
: i18n.t("workspace.lite.promo.subscribe")}
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
</Show>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue