Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4a76bf0fc | ||
|
|
601bbd0f2b | ||
|
|
df1f2d55a2 | ||
|
|
6eb479b702 | ||
|
|
206d81e02c | ||
|
|
6d58d899f7 | ||
|
|
b75a27d43e | ||
|
|
e77b2cfd61 | ||
|
|
d0ce2950e4 | ||
|
|
5a1aca9189 | ||
|
|
f07e877204 | ||
|
|
58ad4359da | ||
|
|
ce2763720e | ||
|
|
950df3de19 | ||
|
|
1d9f05e4f5 | ||
|
|
46361cf35c | ||
|
|
9d78b69cd3 | ||
|
|
c09d3dd5a7 | ||
|
|
fe89bedfcc | ||
|
|
1e48d7fe82 | ||
|
|
2a904ec56f | ||
|
|
0ce61c817b | ||
|
|
1ffed2fa6c | ||
|
|
c79f1a72d8 | ||
|
|
9c5bbba6ea | ||
|
|
ce17f9dd94 | ||
|
|
92ab4217c2 | ||
|
|
7867ba441f | ||
|
|
7419ebc872 | ||
|
|
7e681b0bc0 | ||
|
|
4e9ef3ecc1 | ||
|
|
7e0e35af3f | ||
|
|
2410593023 | ||
|
|
1de12604cf | ||
|
|
ac0b37a7b7 | ||
|
|
7e1051af07 | ||
|
|
93615bef28 | ||
|
|
a04e4e81fb | ||
|
|
296250f1b7 | ||
|
|
443214871e | ||
|
|
1c2416b6de | ||
|
|
d86c10816d | ||
|
|
04a634a80d | ||
|
|
e31f00ad22 | ||
|
|
a90e8de050 | ||
|
|
eabf770053 | ||
|
|
86d7bdc542 | ||
|
|
d3ab78bba0 | ||
|
|
a531f3f36d | ||
|
|
bb3382311d | ||
|
|
ad545d0cc9 | ||
|
|
ac244b1458 | ||
|
|
f202536b65 | ||
|
|
405cc3f610 | ||
|
|
878c1b8c2d | ||
|
|
bb4d978684 | ||
|
|
afec40e8da |
35
.github/workflows/publish.yml
vendored
@@ -41,6 +41,13 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Install OpenCode
|
||||
if: inputs.bump || inputs.version
|
||||
run: bun i -g opencode-ai
|
||||
@@ -49,14 +56,16 @@ jobs:
|
||||
run: |
|
||||
./script/version.ts
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
OPENCODE_BUMP: ${{ inputs.bump }}
|
||||
OPENCODE_VERSION: ${{ inputs.version }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GH_REPO: ${{ (github.ref_name == 'beta' && 'anomalyco/opencode-beta') || github.repository }}
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
release: ${{ steps.version.outputs.release }}
|
||||
tag: ${{ steps.version.outputs.tag }}
|
||||
repo: ${{ steps.version.outputs.repo }}
|
||||
|
||||
build-cli:
|
||||
needs: version
|
||||
@@ -69,6 +78,13 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Build
|
||||
id: build
|
||||
run: |
|
||||
@@ -76,7 +92,8 @@ jobs:
|
||||
env:
|
||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ needs.version.outputs.repo }}
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -189,6 +206,13 @@ jobs:
|
||||
if: contains(matrix.settings.host, 'ubuntu')
|
||||
run: cargo tauri --version
|
||||
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Build and upload artifacts
|
||||
uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
|
||||
timeout-minutes: 60
|
||||
@@ -196,14 +220,16 @@ jobs:
|
||||
projectPath: packages/desktop
|
||||
uploadWorkflowArtifacts: true
|
||||
tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
|
||||
args: --target ${{ matrix.settings.target }} --config ./src-tauri/tauri.prod.conf.json --verbose
|
||||
args: --target ${{ matrix.settings.target }} --config ${{ (github.ref_name == 'beta' && './src-tauri/tauri.beta.conf.json') || './src-tauri/tauri.prod.conf.json' }} --verbose
|
||||
updaterJsonPreferNsis: true
|
||||
releaseId: ${{ needs.version.outputs.release }}
|
||||
tagName: ${{ needs.version.outputs.tag }}
|
||||
releaseDraft: true
|
||||
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
|
||||
repo: ${{ (github.ref_name == 'beta' && 'opencode-beta') || '' }}
|
||||
releaseCommitish: ${{ github.sha }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
@@ -280,4 +306,5 @@ jobs:
|
||||
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
GH_REPO: ${{ needs.version.outputs.repo }}
|
||||
NPM_CONFIG_PROVENANCE: false
|
||||
|
||||
1
.gitignore
vendored
@@ -27,3 +27,4 @@ target
|
||||
opencode-dev
|
||||
logs/
|
||||
*.bun-build
|
||||
tsconfig.tsbuildinfo
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
description: Translate content for a specified locale while preserving technical terms
|
||||
mode: subagent
|
||||
model: opencode/gemini-3-pro
|
||||
model: opencode/gemini-3.1-pro
|
||||
---
|
||||
|
||||
You are a professional translator and localization specialist.
|
||||
|
||||
@@ -5,8 +5,16 @@ import DESCRIPTION from "./github-triage.txt"
|
||||
const TEAM = {
|
||||
desktop: ["adamdotdevin", "iamdavidhill", "Brendonovich", "nexxeln"],
|
||||
zen: ["fwang", "MrMushrooooom"],
|
||||
tui: ["thdxr", "kommander", "rekram1-node"],
|
||||
core: ["thdxr", "rekram1-node", "jlongster"],
|
||||
tui: [
|
||||
"thdxr",
|
||||
"kommander",
|
||||
// "rekram1-node" (on vacation)
|
||||
],
|
||||
core: [
|
||||
"thdxr",
|
||||
// "rekram1-node", (on vacation)
|
||||
"jlongster",
|
||||
],
|
||||
docs: ["R44VC0RP"],
|
||||
windows: ["Hona"],
|
||||
} as const
|
||||
@@ -42,10 +50,7 @@ async function githubFetch(endpoint: string, options: RequestInit = {}) {
|
||||
export default tool({
|
||||
description: DESCRIPTION,
|
||||
args: {
|
||||
assignee: tool.schema
|
||||
.enum(ASSIGNEES as [string, ...string[]])
|
||||
.describe("The username of the assignee")
|
||||
.default("rekram1-node"),
|
||||
assignee: tool.schema.enum(ASSIGNEES as [string, ...string[]]).describe("The username of the assignee"),
|
||||
labels: tool.schema
|
||||
.array(tool.schema.enum(["nix", "opentui", "perf", "web", "desktop", "zen", "docs", "windows", "core"]))
|
||||
.describe("The labels(s) to add to the issue")
|
||||
@@ -68,7 +73,8 @@ export default tool({
|
||||
results.push("Dropped label: nix (issue does not mention nix)")
|
||||
}
|
||||
|
||||
const assignee = nix ? "rekram1-node" : web ? pick(TEAM.desktop) : args.assignee
|
||||
// const assignee = nix ? "rekram1-node" : web ? pick(TEAM.desktop) : args.assignee
|
||||
const assignee = web ? pick(TEAM.desktop) : args.assignee
|
||||
|
||||
if (labels.includes("zen") && !zen) {
|
||||
throw new Error("Only add the zen label when issue title/body contains 'zen'")
|
||||
|
||||
@@ -4,3 +4,5 @@ Choose labels and assignee using the current triage policy and ownership rules.
|
||||
Pick the most fitting labels for the issue and assign one owner.
|
||||
|
||||
If unsure, choose the team/section with the most overlap with the issue and assign a member from that team at random.
|
||||
|
||||
(Note: rekram1-node is on vacation, do not assign issues to him.)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
139
README.bn.md
Normal file
@@ -0,0 +1,139 @@
|
||||
<p align="center">
|
||||
<a href="https://opencode.ai">
|
||||
<picture>
|
||||
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
|
||||
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">ওপেন সোর্স এআই কোডিং এজেন্ট।</p>
|
||||
<p align="center">
|
||||
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
|
||||
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
|
||||
<a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="README.md">English</a> |
|
||||
<a href="README.zh.md">简体中文</a> |
|
||||
<a href="README.zht.md">繁體中文</a> |
|
||||
<a href="README.ko.md">한국어</a> |
|
||||
<a href="README.de.md">Deutsch</a> |
|
||||
<a href="README.es.md">Español</a> |
|
||||
<a href="README.fr.md">Français</a> |
|
||||
<a href="README.it.md">Italiano</a> |
|
||||
<a href="README.da.md">Dansk</a> |
|
||||
<a href="README.ja.md">日本語</a> |
|
||||
<a href="README.pl.md">Polski</a> |
|
||||
<a href="README.ru.md">Русский</a> |
|
||||
<a href="README.bs.md">Bosanski</a> |
|
||||
<a href="README.ar.md">العربية</a> |
|
||||
<a href="README.no.md">Norsk</a> |
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
---
|
||||
|
||||
### ইনস্টলেশন (Installation)
|
||||
|
||||
```bash
|
||||
# YOLO
|
||||
curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
# Package managers
|
||||
npm i -g opencode-ai@latest # or bun/pnpm/yarn
|
||||
scoop install opencode # Windows
|
||||
choco install opencode # Windows
|
||||
brew install anomalyco/tap/opencode # macOS and Linux (recommended, always up to date)
|
||||
brew install opencode # macOS and Linux (official brew formula, updated less)
|
||||
sudo pacman -S opencode # Arch Linux (Stable)
|
||||
paru -S opencode-bin # Arch Linux (Latest from AUR)
|
||||
mise use -g opencode # Any OS
|
||||
nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> ইনস্টল করার আগে ০.১.x এর চেয়ে পুরোনো ভার্সনগুলো মুছে ফেলুন।
|
||||
|
||||
### ডেস্কটপ অ্যাপ (BETA)
|
||||
|
||||
OpenCode ডেস্কটপ অ্যাপ্লিকেশন হিসেবেও উপলব্ধ। সরাসরি [রিলিজ পেজ](https://github.com/anomalyco/opencode/releases) অথবা [opencode.ai/download](https://opencode.ai/download) থেকে ডাউনলোড করুন।
|
||||
|
||||
| প্ল্যাটফর্ম | ডাউনলোড |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, or AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
brew install --cask opencode-desktop
|
||||
# Windows (Scoop)
|
||||
scoop bucket add extras; scoop install extras/opencode-desktop
|
||||
```
|
||||
|
||||
#### ইনস্টলেশন ডিরেক্টরি (Installation Directory)
|
||||
|
||||
ইনস্টল স্ক্রিপ্টটি ইনস্টলেশন পাতের জন্য নিম্নলিখিত অগ্রাধিকার ক্রম মেনে চলে:
|
||||
|
||||
1. `$OPENCODE_INSTALL_DIR` - কাস্টম ইনস্টলেশন ডিরেক্টরি
|
||||
2. `$XDG_BIN_DIR` - XDG বেস ডিরেক্টরি স্পেসিফিকেশন সমর্থিত পাথ
|
||||
3. `$HOME/bin` - সাধারণ ব্যবহারকারী বাইনারি ডিরেক্টরি (যদি বিদ্যমান থাকে বা তৈরি করা যায়)
|
||||
4. `$HOME/.opencode/bin` - ডিফল্ট ফলব্যাক
|
||||
|
||||
```bash
|
||||
# উদাহরণ
|
||||
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
|
||||
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
|
||||
```
|
||||
|
||||
### এজেন্টস (Agents)
|
||||
|
||||
OpenCode এ দুটি বিল্ট-ইন এজেন্ট রয়েছে যা আপনি `Tab` কি(key) দিয়ে পরিবর্তন করতে পারবেন।
|
||||
|
||||
- **build** - ডিফল্ট, ডেভেলপমেন্টের কাজের জন্য সম্পূর্ণ অ্যাক্সেসযুক্ত এজেন্ট
|
||||
- **plan** - বিশ্লেষণ এবং কোড এক্সপ্লোরেশনের জন্য রিড-ওনলি এজেন্ট
|
||||
- ডিফল্টভাবে ফাইল এডিট করতে দেয় না
|
||||
- ব্যাশ কমান্ড চালানোর আগে অনুমতি চায়
|
||||
- অপরিচিত কোডবেস এক্সপ্লোর করা বা পরিবর্তনের পরিকল্পনা করার জন্য আদর্শ
|
||||
|
||||
এছাড়াও জটিল অনুসন্ধান এবং মাল্টিস্টেপ টাস্কের জন্য একটি **general** সাবএজেন্ট অন্তর্ভুক্ত রয়েছে।
|
||||
এটি অভ্যন্তরীণভাবে ব্যবহৃত হয় এবং মেসেজে `@general` লিখে ব্যবহার করা যেতে পারে।
|
||||
|
||||
এজেন্টদের সম্পর্কে আরও জানুন: [docs](https://opencode.ai/docs/agents)।
|
||||
|
||||
### ডকুমেন্টেশন (Documentation)
|
||||
|
||||
কিভাবে OpenCode কনফিগার করবেন সে সম্পর্কে আরও তথ্যের জন্য, [**আমাদের ডকস দেখুন**](https://opencode.ai/docs)।
|
||||
|
||||
### অবদান (Contributing)
|
||||
|
||||
আপনি যদি OpenCode এ অবদান রাখতে চান, অনুগ্রহ করে একটি পুল রিকোয়েস্ট সাবমিট করার আগে আমাদের [কন্ট্রিবিউটিং ডকস](./CONTRIBUTING.md) পড়ে নিন।
|
||||
|
||||
### OpenCode এর উপর বিল্ডিং (Building on OpenCode)
|
||||
|
||||
আপনি যদি এমন প্রজেক্টে কাজ করেন যা OpenCode এর সাথে সম্পর্কিত এবং প্রজেক্টের নামের অংশ হিসেবে "opencode" ব্যবহার করেন, উদাহরণস্বরূপ "opencode-dashboard" বা "opencode-mobile", তবে দয়া করে আপনার README তে একটি নোট যোগ করে স্পষ্ট করুন যে এই প্রজেক্টটি OpenCode দল দ্বারা তৈরি হয়নি এবং আমাদের সাথে এর কোনো সরাসরি সম্পর্ক নেই।
|
||||
|
||||
### সচরাচর জিজ্ঞাসিত প্রশ্নাবলী (FAQ)
|
||||
|
||||
#### এটি ক্লড কোড (Claude Code) থেকে কীভাবে আলাদা?
|
||||
|
||||
ক্যাপাবিলিটির দিক থেকে এটি ক্লড কোডের (Claude Code) মতই। এখানে মূল পার্থক্যগুলো দেওয়া হলো:
|
||||
|
||||
- ১০০% ওপেন সোর্স
|
||||
- কোনো প্রোভাইডারের সাথে আবদ্ধ নয়। যদিও আমরা [OpenCode Zen](https://opencode.ai/zen) এর মাধ্যমে মডেলসমূহ ব্যবহারের পরামর্শ দিই, OpenCode ক্লড (Claude), ওপেনএআই (OpenAI), গুগল (Google), অথবা লোকাল মডেলগুলোর সাথেও ব্যবহার করা যেতে পারে। যেমন যেমন মডেলগুলো উন্নত হবে, তাদের মধ্যকার পার্থক্য কমে আসবে এবং দামও কমবে, তাই প্রোভাইডার-অজ্ঞাস্টিক হওয়া খুবই গুরুত্বপূর্ণ।
|
||||
- আউট-অফ-দ্য-বক্স LSP সাপোর্ট
|
||||
- TUI এর উপর ফোকাস। OpenCode নিওভিম (neovim) ব্যবহারকারী এবং [terminal.shop](https://terminal.shop) এর নির্মাতাদের দ্বারা তৈরি; আমরা টার্মিনালে কী কী সম্ভব তার সীমাবদ্ধতা ছাড়িয়ে যাওয়ার চেষ্টা করছি।
|
||||
- ক্লায়েন্ট/সার্ভার আর্কিটেকচার। এটি যেমন OpenCode কে আপনার কম্পিউটারে চালানোর সুযোগ দেয়, তেমনি আপনি মোবাইল অ্যাপ থেকে রিমোটলি এটি নিয়ন্ত্রণ করতে পারবেন, অর্থাৎ TUI ফ্রন্টএন্ড কেবল সম্ভাব্য ক্লায়েন্টগুলোর মধ্যে একটি।
|
||||
|
||||
---
|
||||
|
||||
**আমাদের কমিউনিটিতে যুক্ত হোন** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
30
bun.lock
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -75,7 +75,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -109,7 +109,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -136,7 +136,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -160,7 +160,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -184,7 +184,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -217,7 +217,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
@@ -246,7 +246,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -262,7 +262,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -376,7 +376,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -396,7 +396,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.90.10",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -407,7 +407,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -420,7 +420,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -462,7 +462,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
@@ -473,7 +473,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
|
||||
@@ -30,6 +30,10 @@ inputs:
|
||||
description: "Comma-separated list of trigger phrases (case-insensitive). Defaults to '/opencode,/oc'"
|
||||
required: false
|
||||
|
||||
variant:
|
||||
description: "Model variant for provider-specific reasoning effort (e.g., high, max, minimal)"
|
||||
required: false
|
||||
|
||||
oidc_base_url:
|
||||
description: "Base URL for OIDC token exchange API. Only required when running a custom GitHub App install. Defaults to https://api.opencode.ai"
|
||||
required: false
|
||||
@@ -71,4 +75,5 @@ runs:
|
||||
PROMPT: ${{ inputs.prompt }}
|
||||
USE_GITHUB_TOKEN: ${{ inputs.use_github_token }}
|
||||
MENTIONS: ${{ inputs.mentions }}
|
||||
VARIANT: ${{ inputs.variant }}
|
||||
OIDC_BASE_URL: ${{ inputs.oidc_base_url }}
|
||||
|
||||
@@ -225,7 +225,7 @@ export async function hoverSessionItem(page: Page, sessionID: string) {
|
||||
export async function openSessionMoreMenu(page: Page, sessionID: string) {
|
||||
await expect(page).toHaveURL(new RegExp(`/session/${sessionID}(?:[/?#]|$)`))
|
||||
|
||||
const scroller = page.locator(".session-scroller").first()
|
||||
const scroller = page.locator(".scroll-view__viewport").first()
|
||||
await expect(scroller).toBeVisible()
|
||||
await expect(scroller.getByRole("heading", { level: 1 }).first()).toBeVisible({ timeout: 30_000 })
|
||||
|
||||
|
||||
@@ -20,11 +20,8 @@ export const settingsNotificationsAgentSelector = '[data-action="settings-notifi
|
||||
export const settingsNotificationsPermissionsSelector = '[data-action="settings-notifications-permissions"]'
|
||||
export const settingsNotificationsErrorsSelector = '[data-action="settings-notifications-errors"]'
|
||||
export const settingsSoundsAgentSelector = '[data-action="settings-sounds-agent"]'
|
||||
export const settingsSoundsAgentEnabledSelector = '[data-action="settings-sounds-agent-enabled"]'
|
||||
export const settingsSoundsPermissionsSelector = '[data-action="settings-sounds-permissions"]'
|
||||
export const settingsSoundsPermissionsEnabledSelector = '[data-action="settings-sounds-permissions-enabled"]'
|
||||
export const settingsSoundsErrorsSelector = '[data-action="settings-sounds-errors"]'
|
||||
export const settingsSoundsErrorsEnabledSelector = '[data-action="settings-sounds-errors-enabled"]'
|
||||
export const settingsUpdatesStartupSelector = '[data-action="settings-updates-startup"]'
|
||||
export const settingsReleaseNotesSelector = '[data-action="settings-release-notes"]'
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ test("session can be renamed via header menu", async ({ page, sdk, gotoSession }
|
||||
const menu = await openSessionMoreMenu(page, session.id)
|
||||
await clickMenuItem(menu, /rename/i)
|
||||
|
||||
const input = page.locator(".session-scroller").locator(inlineInputSelector).first()
|
||||
const input = page.locator(".scroll-view__viewport").locator(inlineInputSelector).first()
|
||||
await expect(input).toBeVisible()
|
||||
await expect(input).toBeFocused()
|
||||
await input.fill(renamedTitle)
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
settingsNotificationsPermissionsSelector,
|
||||
settingsReleaseNotesSelector,
|
||||
settingsSoundsAgentSelector,
|
||||
settingsSoundsAgentEnabledSelector,
|
||||
settingsSoundsErrorsSelector,
|
||||
settingsSoundsPermissionsSelector,
|
||||
settingsThemeSelector,
|
||||
@@ -336,21 +335,19 @@ test("changing sound agent selection persists in localStorage", async ({ page, g
|
||||
expect(stored?.sounds?.agent).not.toBe("staplebops-01")
|
||||
})
|
||||
|
||||
test("disabling agent sound disables sound selection", async ({ page, gotoSession }) => {
|
||||
test("selecting none disables agent sound", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
const dialog = await openSettings(page)
|
||||
const select = dialog.locator(settingsSoundsAgentSelector)
|
||||
const switchContainer = dialog.locator(settingsSoundsAgentEnabledSelector)
|
||||
const trigger = select.locator('[data-slot="select-select-trigger"]')
|
||||
await expect(select).toBeVisible()
|
||||
await expect(switchContainer).toBeVisible()
|
||||
await expect(trigger).toBeEnabled()
|
||||
|
||||
await switchContainer.locator('[data-slot="switch-control"]').click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await expect(trigger).toBeDisabled()
|
||||
await trigger.click()
|
||||
const items = page.locator('[data-slot="select-select-item"]')
|
||||
await expect(items.first()).toBeVisible()
|
||||
await items.first().click()
|
||||
|
||||
const stored = await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
|
||||
@@ -6,6 +6,7 @@ test("smoke terminal mounts and can create a second tab", async ({ page, gotoSes
|
||||
await gotoSession()
|
||||
|
||||
const terminals = page.locator(terminalSelector)
|
||||
const tabs = page.locator('#terminal-panel [data-slot="tabs-trigger"]')
|
||||
const opened = await terminals.first().isVisible()
|
||||
|
||||
if (!opened) {
|
||||
@@ -21,6 +22,7 @@ test("smoke terminal mounts and can create a second tab", async ({ page, gotoSes
|
||||
await page.locator(promptSelector).click()
|
||||
await page.keyboard.press("Control+Alt+T")
|
||||
|
||||
await expect(terminals).toHaveCount(2)
|
||||
await expect(terminals.nth(1).locator("textarea")).toHaveCount(1)
|
||||
await expect(tabs).toHaveCount(2)
|
||||
await expect(terminals).toHaveCount(1)
|
||||
await expect(terminals.first().locator("textarea")).toHaveCount(1)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -89,6 +89,8 @@ const EXAMPLES = [
|
||||
"prompt.example.25",
|
||||
] as const
|
||||
|
||||
const NON_EMPTY_TEXT = /[^\s\u200B]/
|
||||
|
||||
export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const sdk = useSDK()
|
||||
const sync = useSync()
|
||||
@@ -636,7 +638,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
let buffer = ""
|
||||
|
||||
const flushText = () => {
|
||||
const content = buffer.replace(/\r\n?/g, "\n").replace(/\u200B/g, "")
|
||||
let content = buffer
|
||||
if (content.includes("\r")) content = content.replace(/\r\n?/g, "\n")
|
||||
if (content.includes("\u200B")) content = content.replace(/\u200B/g, "")
|
||||
buffer = ""
|
||||
if (!content) return
|
||||
parts.push({ type: "text", content, start: position, end: position + content.length })
|
||||
@@ -714,10 +718,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const rawParts = parseFromDOM()
|
||||
const images = imageAttachments()
|
||||
const cursorPosition = getCursorPosition(editorRef)
|
||||
const rawText = rawParts.map((p) => ("content" in p ? p.content : "")).join("")
|
||||
const trimmed = rawText.replace(/\u200B/g, "").trim()
|
||||
const rawText =
|
||||
rawParts.length === 1 && rawParts[0]?.type === "text"
|
||||
? rawParts[0].content
|
||||
: rawParts.map((p) => ("content" in p ? p.content : "")).join("")
|
||||
const hasNonText = rawParts.some((part) => part.type !== "text")
|
||||
const shouldReset = trimmed.length === 0 && !hasNonText && images.length === 0
|
||||
const shouldReset = !NON_EMPTY_TEXT.test(rawText) && !hasNonText && images.length === 0
|
||||
|
||||
if (shouldReset) {
|
||||
closePopover()
|
||||
@@ -757,19 +763,31 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
|
||||
const addPart = (part: ContentPart) => {
|
||||
const selection = window.getSelection()
|
||||
if (!selection || selection.rangeCount === 0) return
|
||||
if (part.type === "image") return false
|
||||
|
||||
const cursorPosition = getCursorPosition(editorRef)
|
||||
const currentPrompt = prompt.current()
|
||||
const rawText = currentPrompt.map((p) => ("content" in p ? p.content : "")).join("")
|
||||
const textBeforeCursor = rawText.substring(0, cursorPosition)
|
||||
const atMatch = textBeforeCursor.match(/@(\S*)$/)
|
||||
const selection = window.getSelection()
|
||||
if (!selection) return false
|
||||
|
||||
if (selection.rangeCount === 0 || !editorRef.contains(selection.anchorNode)) {
|
||||
editorRef.focus()
|
||||
const cursor = prompt.cursor() ?? promptLength(prompt.current())
|
||||
setCursorPosition(editorRef, cursor)
|
||||
}
|
||||
|
||||
if (selection.rangeCount === 0) return false
|
||||
const range = selection.getRangeAt(0)
|
||||
if (!editorRef.contains(range.startContainer)) return false
|
||||
|
||||
if (part.type === "file" || part.type === "agent") {
|
||||
const cursorPosition = getCursorPosition(editorRef)
|
||||
const rawText = prompt
|
||||
.current()
|
||||
.map((p) => ("content" in p ? p.content : ""))
|
||||
.join("")
|
||||
const textBeforeCursor = rawText.substring(0, cursorPosition)
|
||||
const atMatch = textBeforeCursor.match(/@(\S*)$/)
|
||||
const pill = createPill(part)
|
||||
const gap = document.createTextNode(" ")
|
||||
const range = selection.getRangeAt(0)
|
||||
|
||||
if (atMatch) {
|
||||
const start = atMatch.index ?? cursorPosition - atMatch[0].length
|
||||
@@ -784,8 +802,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
range.collapse(true)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
} else if (part.type === "text") {
|
||||
const range = selection.getRangeAt(0)
|
||||
}
|
||||
|
||||
if (part.type === "text") {
|
||||
const fragment = createTextFragment(part.content)
|
||||
const last = fragment.lastChild
|
||||
range.deleteContents()
|
||||
@@ -821,6 +840,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
handleInput()
|
||||
closePopover()
|
||||
return true
|
||||
}
|
||||
|
||||
const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => {
|
||||
|
||||
@@ -7,6 +7,19 @@ import { getCursorPosition } from "./editor-dom"
|
||||
|
||||
export const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"]
|
||||
export const ACCEPTED_FILE_TYPES = [...ACCEPTED_IMAGE_TYPES, "application/pdf"]
|
||||
const LARGE_PASTE_CHARS = 8000
|
||||
const LARGE_PASTE_BREAKS = 120
|
||||
|
||||
function largePaste(text: string) {
|
||||
if (text.length >= LARGE_PASTE_CHARS) return true
|
||||
let breaks = 0
|
||||
for (const char of text) {
|
||||
if (char !== "\n") continue
|
||||
breaks += 1
|
||||
if (breaks >= LARGE_PASTE_BREAKS) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type PromptAttachmentsInput = {
|
||||
editor: () => HTMLDivElement | undefined
|
||||
@@ -14,7 +27,7 @@ type PromptAttachmentsInput = {
|
||||
isDialogActive: () => boolean
|
||||
setDraggingType: (type: "image" | "@mention" | null) => void
|
||||
focusEditor: () => void
|
||||
addPart: (part: ContentPart) => void
|
||||
addPart: (part: ContentPart) => boolean
|
||||
readClipboardImage?: () => Promise<File | null>
|
||||
}
|
||||
|
||||
@@ -89,6 +102,13 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
|
||||
}
|
||||
|
||||
if (!plainText) return
|
||||
|
||||
if (largePaste(plainText)) {
|
||||
if (input.addPart({ type: "text", content: plainText, start: 0, end: 0 })) return
|
||||
input.focusEditor()
|
||||
if (input.addPart({ type: "text", content: plainText, start: 0, end: 0 })) return
|
||||
}
|
||||
|
||||
const inserted = typeof document.execCommand === "function" && document.execCommand("insertText", false, plainText)
|
||||
if (inserted) return
|
||||
|
||||
|
||||
@@ -24,6 +24,28 @@ describe("prompt-input editor dom", () => {
|
||||
expect((container.childNodes[1] as HTMLElement).tagName).toBe("BR")
|
||||
})
|
||||
|
||||
test("createTextFragment avoids break-node explosion for large multiline content", () => {
|
||||
const content = Array.from({ length: 220 }, () => "line").join("\n")
|
||||
const fragment = createTextFragment(content)
|
||||
const container = document.createElement("div")
|
||||
container.appendChild(fragment)
|
||||
|
||||
expect(container.childNodes.length).toBe(1)
|
||||
expect(container.childNodes[0]?.nodeType).toBe(Node.TEXT_NODE)
|
||||
expect(container.textContent).toBe(content)
|
||||
})
|
||||
|
||||
test("createTextFragment keeps terminal break in large multiline fallback", () => {
|
||||
const content = `${Array.from({ length: 220 }, () => "line").join("\n")}\n`
|
||||
const fragment = createTextFragment(content)
|
||||
const container = document.createElement("div")
|
||||
container.appendChild(fragment)
|
||||
|
||||
expect(container.childNodes.length).toBe(2)
|
||||
expect(container.childNodes[0]?.textContent).toBe(content.slice(0, -1))
|
||||
expect((container.childNodes[1] as HTMLElement).tagName).toBe("BR")
|
||||
})
|
||||
|
||||
test("length helpers treat breaks as one char and ignore zero-width chars", () => {
|
||||
const container = document.createElement("div")
|
||||
container.appendChild(document.createTextNode("ab\u200B"))
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
const MAX_BREAKS = 200
|
||||
|
||||
export function createTextFragment(content: string): DocumentFragment {
|
||||
const fragment = document.createDocumentFragment()
|
||||
let breaks = 0
|
||||
for (const char of content) {
|
||||
if (char !== "\n") continue
|
||||
breaks += 1
|
||||
if (breaks > MAX_BREAKS) {
|
||||
const tail = content.endsWith("\n")
|
||||
const text = tail ? content.slice(0, -1) : content
|
||||
if (text) fragment.appendChild(document.createTextNode(text))
|
||||
if (tail) fragment.appendChild(document.createElement("br"))
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
||||
const segments = content.split("\n")
|
||||
segments.forEach((segment, index) => {
|
||||
if (segment) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Accordion } from "@opencode-ai/ui/accordion"
|
||||
import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
|
||||
import { Code } from "@opencode-ai/ui/code"
|
||||
import { Markdown } from "@opencode-ai/ui/markdown"
|
||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||
import type { Message, Part, UserMessage } from "@opencode-ai/sdk/v2/client"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { getSessionContextMetrics } from "./session-context-metrics"
|
||||
@@ -268,9 +269,9 @@ export function SessionContextTab() {
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
class="@container h-full overflow-y-auto no-scrollbar pb-10"
|
||||
ref={(el) => {
|
||||
<ScrollView
|
||||
class="@container h-full pb-10"
|
||||
viewportRef={(el) => {
|
||||
scroll = el
|
||||
restoreScroll()
|
||||
}}
|
||||
@@ -336,6 +337,6 @@ export function SessionContextTab() {
|
||||
</Accordion>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -452,7 +452,10 @@ export function SessionHeader() {
|
||||
variant: "ghost",
|
||||
class:
|
||||
"rounded-md h-[24px] px-3 border border-border-weak-base bg-surface-panel shadow-none data-[expanded]:bg-surface-base-active",
|
||||
classList: { "rounded-r-none": share.shareUrl() !== undefined },
|
||||
classList: {
|
||||
"rounded-r-none": share.shareUrl() !== undefined,
|
||||
"border-r-0": share.shareUrl() !== undefined,
|
||||
},
|
||||
style: { scale: 1 },
|
||||
}}
|
||||
trigger={<span class="text-12-regular">{language.t("session.share.action.share")}</span>}
|
||||
|
||||
@@ -20,12 +20,17 @@ let demoSoundState = {
|
||||
|
||||
// To prevent audio from overlapping/playing very quickly when navigating the settings menus,
|
||||
// delay the playback by 100ms during quick selection changes and pause existing sounds.
|
||||
const playDemoSound = (src: string) => {
|
||||
const stopDemoSound = () => {
|
||||
if (demoSoundState.cleanup) {
|
||||
demoSoundState.cleanup()
|
||||
}
|
||||
|
||||
clearTimeout(demoSoundState.timeout)
|
||||
demoSoundState.cleanup = undefined
|
||||
}
|
||||
|
||||
const playDemoSound = (src: string | undefined) => {
|
||||
stopDemoSound()
|
||||
if (!src) return
|
||||
|
||||
demoSoundState.timeout = setTimeout(() => {
|
||||
demoSoundState.cleanup = playSound(src)
|
||||
@@ -132,11 +137,17 @@ export const SettingsGeneral: Component = () => {
|
||||
] as const
|
||||
const fontOptionsList = [...fontOptions]
|
||||
|
||||
const soundOptions = [...SOUND_OPTIONS]
|
||||
const noneSound = { id: "none", label: "sound.option.none", src: undefined } as const
|
||||
const soundOptions = [noneSound, ...SOUND_OPTIONS]
|
||||
|
||||
const soundSelectProps = (current: () => string, set: (id: string) => void) => ({
|
||||
const soundSelectProps = (
|
||||
enabled: () => boolean,
|
||||
current: () => string,
|
||||
setEnabled: (value: boolean) => void,
|
||||
set: (id: string) => void,
|
||||
) => ({
|
||||
options: soundOptions,
|
||||
current: soundOptions.find((o) => o.id === current()),
|
||||
current: enabled() ? (soundOptions.find((o) => o.id === current()) ?? noneSound) : noneSound,
|
||||
value: (o: (typeof soundOptions)[number]) => o.id,
|
||||
label: (o: (typeof soundOptions)[number]) => language.t(o.label),
|
||||
onHighlight: (option: (typeof soundOptions)[number] | undefined) => {
|
||||
@@ -145,6 +156,12 @@ export const SettingsGeneral: Component = () => {
|
||||
},
|
||||
onSelect: (option: (typeof soundOptions)[number] | undefined) => {
|
||||
if (!option) return
|
||||
if (option.id === "none") {
|
||||
setEnabled(false)
|
||||
stopDemoSound()
|
||||
return
|
||||
}
|
||||
setEnabled(true)
|
||||
set(option.id)
|
||||
playDemoSound(option.src)
|
||||
},
|
||||
@@ -250,6 +267,18 @@ export const SettingsGeneral: Component = () => {
|
||||
)}
|
||||
</Select>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.general.row.reasoningSummaries.title")}
|
||||
description={language.t("settings.general.row.reasoningSummaries.description")}
|
||||
>
|
||||
<div data-action="settings-reasoning-summaries">
|
||||
<Switch
|
||||
checked={settings.general.showReasoningSummaries()}
|
||||
onChange={(checked) => settings.general.setShowReasoningSummaries(checked)}
|
||||
/>
|
||||
</div>
|
||||
</SettingsRow>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -307,66 +336,45 @@ export const SettingsGeneral: Component = () => {
|
||||
title={language.t("settings.general.sounds.agent.title")}
|
||||
description={language.t("settings.general.sounds.agent.description")}
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div data-action="settings-sounds-agent-enabled">
|
||||
<Switch
|
||||
checked={settings.sounds.agentEnabled()}
|
||||
onChange={(checked) => settings.sounds.setAgentEnabled(checked)}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
disabled={!settings.sounds.agentEnabled()}
|
||||
data-action="settings-sounds-agent"
|
||||
{...soundSelectProps(
|
||||
() => settings.sounds.agent(),
|
||||
(id) => settings.sounds.setAgent(id),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
data-action="settings-sounds-agent"
|
||||
{...soundSelectProps(
|
||||
() => settings.sounds.agentEnabled(),
|
||||
() => settings.sounds.agent(),
|
||||
(value) => settings.sounds.setAgentEnabled(value),
|
||||
(id) => settings.sounds.setAgent(id),
|
||||
)}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.general.sounds.permissions.title")}
|
||||
description={language.t("settings.general.sounds.permissions.description")}
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div data-action="settings-sounds-permissions-enabled">
|
||||
<Switch
|
||||
checked={settings.sounds.permissionsEnabled()}
|
||||
onChange={(checked) => settings.sounds.setPermissionsEnabled(checked)}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
disabled={!settings.sounds.permissionsEnabled()}
|
||||
data-action="settings-sounds-permissions"
|
||||
{...soundSelectProps(
|
||||
() => settings.sounds.permissions(),
|
||||
(id) => settings.sounds.setPermissions(id),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
data-action="settings-sounds-permissions"
|
||||
{...soundSelectProps(
|
||||
() => settings.sounds.permissionsEnabled(),
|
||||
() => settings.sounds.permissions(),
|
||||
(value) => settings.sounds.setPermissionsEnabled(value),
|
||||
(id) => settings.sounds.setPermissions(id),
|
||||
)}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.general.sounds.errors.title")}
|
||||
description={language.t("settings.general.sounds.errors.description")}
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div data-action="settings-sounds-errors-enabled">
|
||||
<Switch
|
||||
checked={settings.sounds.errorsEnabled()}
|
||||
onChange={(checked) => settings.sounds.setErrorsEnabled(checked)}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
disabled={!settings.sounds.errorsEnabled()}
|
||||
data-action="settings-sounds-errors"
|
||||
{...soundSelectProps(
|
||||
() => settings.sounds.errors(),
|
||||
(id) => settings.sounds.setErrors(id),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
data-action="settings-sounds-errors"
|
||||
{...soundSelectProps(
|
||||
() => settings.sounds.errorsEnabled(),
|
||||
() => settings.sounds.errors(),
|
||||
(value) => settings.sounds.setErrorsEnabled(value),
|
||||
(id) => settings.sounds.setErrors(id),
|
||||
)}
|
||||
/>
|
||||
</SettingsRow>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -540,7 +540,7 @@ export const Terminal = (props: TerminalProps) => {
|
||||
disposed = true
|
||||
if (fitFrame !== undefined) cancelAnimationFrame(fitFrame)
|
||||
if (sizeTimer !== undefined) clearTimeout(sizeTimer)
|
||||
if (ws && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) ws.close()
|
||||
if (ws && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) ws.close(1000)
|
||||
|
||||
const finalize = () => {
|
||||
persistTerminal({ term, addon: serializeAddon, cursor, pty: local.pty, onCleanup: props.onCleanup })
|
||||
|
||||
@@ -265,6 +265,9 @@ export function Titlebar() {
|
||||
</div>
|
||||
</div>
|
||||
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
|
||||
<div class="bg-icon-interactive-base text-background-base font-medium px-2 rounded-sm uppercase font-mono">
|
||||
BETA
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex items-center justify-center pointer-events-none">
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface Settings {
|
||||
general: {
|
||||
autoSave: boolean
|
||||
releaseNotes: boolean
|
||||
showReasoningSummaries: boolean
|
||||
}
|
||||
updates: {
|
||||
startup: boolean
|
||||
@@ -42,6 +43,7 @@ const defaultSettings: Settings = {
|
||||
general: {
|
||||
autoSave: true,
|
||||
releaseNotes: true,
|
||||
showReasoningSummaries: false,
|
||||
},
|
||||
updates: {
|
||||
startup: true,
|
||||
@@ -120,6 +122,13 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
||||
setReleaseNotes(value: boolean) {
|
||||
setStore("general", "releaseNotes", value)
|
||||
},
|
||||
showReasoningSummaries: withFallback(
|
||||
() => store.general?.showReasoningSummaries,
|
||||
defaultSettings.general.showReasoningSummaries,
|
||||
),
|
||||
setShowReasoningSummaries(value: boolean) {
|
||||
setStore("general", "showReasoningSummaries", value)
|
||||
},
|
||||
},
|
||||
updates: {
|
||||
startup: withFallback(() => store.updates?.startup, defaultSettings.updates.startup),
|
||||
|
||||
@@ -565,6 +565,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "بلا",
|
||||
"sound.option.alert01": "تنبيه 01",
|
||||
"sound.option.alert02": "تنبيه 02",
|
||||
"sound.option.alert03": "تنبيه 03",
|
||||
|
||||
@@ -571,6 +571,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Nenhum",
|
||||
"sound.option.alert01": "Alerta 01",
|
||||
"sound.option.alert02": "Alerta 02",
|
||||
"sound.option.alert03": "Alerta 03",
|
||||
|
||||
@@ -639,6 +639,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Nijedan",
|
||||
"sound.option.alert01": "Upozorenje 01",
|
||||
"sound.option.alert02": "Upozorenje 02",
|
||||
"sound.option.alert03": "Upozorenje 03",
|
||||
|
||||
@@ -635,6 +635,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Ingen",
|
||||
"sound.option.alert01": "Alarm 01",
|
||||
"sound.option.alert02": "Alarm 02",
|
||||
"sound.option.alert03": "Alarm 03",
|
||||
|
||||
@@ -580,6 +580,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Keine",
|
||||
"sound.option.alert01": "Alarm 01",
|
||||
"sound.option.alert02": "Alarm 02",
|
||||
"sound.option.alert03": "Alarm 03",
|
||||
|
||||
@@ -610,6 +610,8 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "Customise how OpenCode is themed.",
|
||||
"settings.general.row.font.title": "Font",
|
||||
"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.description": "Display model reasoning summaries in the timeline",
|
||||
|
||||
"settings.general.row.wayland.title": "Use native Wayland",
|
||||
"settings.general.row.wayland.description": "Disable X11 fallback on Wayland. Requires restart.",
|
||||
@@ -640,6 +642,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "None",
|
||||
"sound.option.alert01": "Alert 01",
|
||||
"sound.option.alert02": "Alert 02",
|
||||
"sound.option.alert03": "Alert 03",
|
||||
|
||||
@@ -643,6 +643,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Ninguno",
|
||||
"sound.option.alert01": "Alerta 01",
|
||||
"sound.option.alert02": "Alerta 02",
|
||||
"sound.option.alert03": "Alerta 03",
|
||||
|
||||
@@ -579,6 +579,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Aucun",
|
||||
"sound.option.alert01": "Alerte 01",
|
||||
"sound.option.alert02": "Alerte 02",
|
||||
"sound.option.alert03": "Alerte 03",
|
||||
|
||||
@@ -569,6 +569,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "なし",
|
||||
"sound.option.alert01": "アラート 01",
|
||||
"sound.option.alert02": "アラート 02",
|
||||
"sound.option.alert03": "アラート 03",
|
||||
|
||||
@@ -570,6 +570,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "없음",
|
||||
"sound.option.alert01": "알림 01",
|
||||
"sound.option.alert02": "알림 02",
|
||||
"sound.option.alert03": "알림 03",
|
||||
|
||||
@@ -642,6 +642,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Ingen",
|
||||
"sound.option.alert01": "Varsel 01",
|
||||
"sound.option.alert02": "Varsel 02",
|
||||
"sound.option.alert03": "Varsel 03",
|
||||
|
||||
@@ -570,6 +570,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Brak",
|
||||
"sound.option.alert01": "Alert 01",
|
||||
"sound.option.alert02": "Alert 02",
|
||||
"sound.option.alert03": "Alert 03",
|
||||
|
||||
@@ -640,6 +640,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Нет",
|
||||
"sound.option.alert01": "Alert 01",
|
||||
"sound.option.alert02": "Alert 02",
|
||||
"sound.option.alert03": "Alert 03",
|
||||
|
||||
@@ -634,6 +634,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "ไม่มี",
|
||||
"sound.option.alert01": "เสียงเตือน 01",
|
||||
"sound.option.alert02": "เสียงเตือน 02",
|
||||
"sound.option.alert03": "เสียงเตือน 03",
|
||||
|
||||
@@ -633,6 +633,7 @@ export const dict = {
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
|
||||
"sound.option.none": "无",
|
||||
"sound.option.alert01": "警报 01",
|
||||
"sound.option.alert02": "警报 02",
|
||||
"sound.option.alert03": "警报 03",
|
||||
|
||||
@@ -629,6 +629,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "無",
|
||||
"sound.option.alert01": "警報 01",
|
||||
"sound.option.alert02": "警報 02",
|
||||
"sound.option.alert03": "警報 03",
|
||||
|
||||
@@ -943,15 +943,12 @@ export default function Page() {
|
||||
if (next === dockHeight) return
|
||||
|
||||
const el = scroller
|
||||
const stick = el ? el.scrollHeight - el.clientHeight - el.scrollTop < 10 : false
|
||||
const delta = next - dockHeight
|
||||
const stick = el ? el.scrollHeight - el.clientHeight - el.scrollTop < 10 + Math.max(0, delta) : false
|
||||
|
||||
dockHeight = next
|
||||
|
||||
if (stick && el) {
|
||||
requestAnimationFrame(() => {
|
||||
el.scrollTo({ top: el.scrollHeight, behavior: "auto" })
|
||||
})
|
||||
}
|
||||
if (stick) autoScroll.forceScrollToBottom()
|
||||
|
||||
if (el) scheduleScrollState(el)
|
||||
scrollSpy.markDirty()
|
||||
|
||||
@@ -62,7 +62,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
||||
const measure = () => {
|
||||
if (!root) return
|
||||
|
||||
const scroller = document.querySelector(".session-scroller")
|
||||
const scroller = document.querySelector(".scroll-view__viewport")
|
||||
const head = scroller instanceof HTMLElement ? scroller.firstElementChild : undefined
|
||||
const top =
|
||||
head instanceof HTMLElement && head.classList.contains("sticky") ? head.getBoundingClientRect().bottom : 0
|
||||
@@ -95,7 +95,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
||||
window.addEventListener("resize", update)
|
||||
|
||||
const dock = root?.closest('[data-component="session-prompt-dock"]')
|
||||
const scroller = document.querySelector(".session-scroller")
|
||||
const scroller = document.querySelector(".scroll-view__viewport")
|
||||
const observer = new ResizeObserver(update)
|
||||
if (dock instanceof HTMLElement) observer.observe(dock)
|
||||
if (scroller instanceof HTMLElement) observer.observe(scroller)
|
||||
|
||||
@@ -9,6 +9,7 @@ import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { LineComment as LineCommentView, LineCommentEditor } from "@opencode-ai/ui/line-comment"
|
||||
import { Mark } from "@opencode-ai/ui/logo"
|
||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file"
|
||||
import { useComments } from "@/context/comments"
|
||||
@@ -509,51 +510,52 @@ export function FileTabContent(props: { tab: string }) {
|
||||
)
|
||||
|
||||
return (
|
||||
<Tabs.Content
|
||||
value={props.tab}
|
||||
class="mt-3 relative"
|
||||
ref={(el: HTMLDivElement) => {
|
||||
scroll = el
|
||||
restoreScroll()
|
||||
}}
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={state()?.loaded && isImage()}>
|
||||
<div class="px-6 py-4 pb-40">
|
||||
<img
|
||||
src={imageDataUrl()}
|
||||
alt={path()}
|
||||
class="max-w-full"
|
||||
onLoad={() => requestAnimationFrame(restoreScroll)}
|
||||
/>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={state()?.loaded && isSvg()}>
|
||||
<div class="flex flex-col gap-4 px-6 py-4">
|
||||
{renderCode(svgContent() ?? "", "")}
|
||||
<Show when={svgPreviewUrl()}>
|
||||
<div class="flex justify-center pb-40">
|
||||
<img src={svgPreviewUrl()} alt={path()} class="max-w-full max-h-96" />
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={state()?.loaded && isBinary()}>
|
||||
<div class="h-full px-6 pb-42 flex flex-col items-center justify-center text-center gap-6">
|
||||
<Mark class="w-14 opacity-10" />
|
||||
<div class="flex flex-col gap-2 max-w-md">
|
||||
<div class="text-14-semibold text-text-strong truncate">{path()?.split("/").pop()}</div>
|
||||
<div class="text-14-regular text-text-weak">{language.t("session.files.binaryContent")}</div>
|
||||
<Tabs.Content value={props.tab} class="mt-3 relative h-full">
|
||||
<ScrollView
|
||||
class="h-full"
|
||||
viewportRef={(el: HTMLDivElement) => {
|
||||
scroll = el
|
||||
restoreScroll()
|
||||
}}
|
||||
onScroll={handleScroll as any}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={state()?.loaded && isImage()}>
|
||||
<div class="px-6 py-4 pb-40">
|
||||
<img
|
||||
src={imageDataUrl()}
|
||||
alt={path()}
|
||||
class="max-w-full"
|
||||
onLoad={() => requestAnimationFrame(restoreScroll)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={state()?.loaded}>{renderCode(contents(), "pb-40")}</Match>
|
||||
<Match when={state()?.loading}>
|
||||
<div class="px-6 py-4 text-text-weak">{language.t("common.loading")}...</div>
|
||||
</Match>
|
||||
<Match when={state()?.error}>{(err) => <div class="px-6 py-4 text-text-weak">{err()}</div>}</Match>
|
||||
</Switch>
|
||||
</Match>
|
||||
<Match when={state()?.loaded && isSvg()}>
|
||||
<div class="flex flex-col gap-4 px-6 py-4">
|
||||
{renderCode(svgContent() ?? "", "")}
|
||||
<Show when={svgPreviewUrl()}>
|
||||
<div class="flex justify-center pb-40">
|
||||
<img src={svgPreviewUrl()} alt={path()} class="max-w-full max-h-96" />
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={state()?.loaded && isBinary()}>
|
||||
<div class="h-full px-6 pb-42 flex flex-col items-center justify-center text-center gap-6">
|
||||
<Mark class="w-14 opacity-10" />
|
||||
<div class="flex flex-col gap-2 max-w-md">
|
||||
<div class="text-14-semibold text-text-strong truncate">{path()?.split("/").pop()}</div>
|
||||
<div class="text-14-regular text-text-weak">{language.t("session.files.binaryContent")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={state()?.loaded}>{renderCode(contents(), "pb-40")}</Match>
|
||||
<Match when={state()?.loading}>
|
||||
<div class="px-6 py-4 text-text-weak">{language.t("common.loading")}...</div>
|
||||
</Match>
|
||||
<Match when={state()?.error}>{(err) => <div class="px-6 py-4 text-text-weak">{err()}</div>}</Match>
|
||||
</Switch>
|
||||
</ScrollView>
|
||||
</Tabs.Content>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,12 +8,14 @@ import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
||||
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||
import type { UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture"
|
||||
import { SessionContextUsage } from "@/components/session-context-usage"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useSettings } from "@/context/settings"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { useSync } from "@/context/sync"
|
||||
|
||||
@@ -80,6 +82,7 @@ export function MessageTimeline(props: {
|
||||
const navigate = useNavigate()
|
||||
const sdk = useSDK()
|
||||
const sync = useSync()
|
||||
const settings = useSettings()
|
||||
const dialog = useDialog()
|
||||
const language = useLanguage()
|
||||
|
||||
@@ -320,8 +323,8 @@ export function MessageTimeline(props: {
|
||||
<Icon name="arrow-down-to-line" />
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
ref={props.setScrollRef}
|
||||
<ScrollView
|
||||
viewportRef={props.setScrollRef}
|
||||
onWheel={(e) => {
|
||||
const root = e.currentTarget
|
||||
const delta = normalizeWheelDelta({
|
||||
@@ -365,7 +368,7 @@ export function MessageTimeline(props: {
|
||||
if (props.isDesktop) props.onScrollSpyScroll()
|
||||
}}
|
||||
onClick={props.onAutoScrollInteraction}
|
||||
class="relative min-w-0 w-full h-full overflow-y-auto session-scroller"
|
||||
class="relative min-w-0 w-full h-full"
|
||||
style={{
|
||||
"--session-title-height": showHeader() ? "40px" : "0px",
|
||||
"--sticky-accordion-top": showHeader() ? "48px" : "0px",
|
||||
@@ -535,6 +538,7 @@ export function MessageTimeline(props: {
|
||||
sessionID={sessionID() ?? ""}
|
||||
messageID={message.id}
|
||||
lastUserMessageID={props.lastUserMessageID}
|
||||
showReasoningSummaries={settings.general.showReasoningSummaries()}
|
||||
classes={{
|
||||
root: "min-w-0 w-full relative",
|
||||
content: "flex flex-col justify-between !overflow-visible",
|
||||
@@ -545,7 +549,7 @@ export function MessageTimeline(props: {
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollView>
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
|
||||
@@ -143,9 +143,9 @@ export function SessionReviewTab(props: SessionReviewTabProps) {
|
||||
open={props.view().review.open()}
|
||||
onOpenChange={props.view().review.setOpen}
|
||||
classes={{
|
||||
root: props.classes?.root ?? "pb-6",
|
||||
root: props.classes?.root ?? "pb-6 pr-3",
|
||||
header: props.classes?.header ?? "px-3",
|
||||
container: props.classes?.container ?? "px-3",
|
||||
container: props.classes?.container ?? "pl-3",
|
||||
}}
|
||||
diffs={props.diffs()}
|
||||
diffStyle={props.diffStyle}
|
||||
|
||||
@@ -67,11 +67,11 @@ export function TerminalPanel() {
|
||||
on(
|
||||
() => terminal.active(),
|
||||
(activeId) => {
|
||||
if (!activeId || !opened()) return
|
||||
if (!activeId || !open()) return
|
||||
if (document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur()
|
||||
}
|
||||
focusTerminalById(activeId)
|
||||
setTimeout(() => focusTerminalById(activeId), 0)
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -209,21 +209,17 @@ export function TerminalPanel() {
|
||||
</Tabs.List>
|
||||
</Tabs>
|
||||
<div class="flex-1 min-h-0 relative">
|
||||
<For each={all()}>
|
||||
{(pty) => (
|
||||
<div
|
||||
id={`terminal-wrapper-${pty.id}`}
|
||||
class="absolute inset-0"
|
||||
style={{
|
||||
display: terminal.active() === pty.id ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<Show when={pty.id} keyed>
|
||||
<Terminal pty={pty} onCleanup={terminal.update} onConnectError={() => terminal.clone(pty.id)} />
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={terminal.active()} keyed>
|
||||
{(id) => (
|
||||
<Show when={byId().get(id)}>
|
||||
{(pty) => (
|
||||
<div id={`terminal-wrapper-${id}`} class="absolute inset-0">
|
||||
<Terminal pty={pty()} onCleanup={terminal.update} onConnectError={() => terminal.clone(id)} />
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<DragOverlay>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { APIEvent } from "@solidjs/start"
|
||||
import { DownloadPlatform } from "./types"
|
||||
import type { APIEvent } from "@solidjs/start"
|
||||
import type { DownloadPlatform } from "../types"
|
||||
|
||||
const assetNames: Record<string, string> = {
|
||||
"darwin-aarch64-dmg": "opencode-desktop-darwin-aarch64.dmg",
|
||||
@@ -17,17 +17,20 @@ const downloadNames: Record<string, string> = {
|
||||
"windows-x64-nsis": "OpenCode Desktop Installer.exe",
|
||||
} satisfies { [K in DownloadPlatform]?: string }
|
||||
|
||||
export async function GET({ params: { platform } }: APIEvent) {
|
||||
export async function GET({ params: { platform, channel } }: APIEvent) {
|
||||
const assetName = assetNames[platform]
|
||||
if (!assetName) return new Response("Not Found", { status: 404 })
|
||||
|
||||
const resp = await fetch(`https://github.com/anomalyco/opencode/releases/latest/download/${assetName}`, {
|
||||
cf: {
|
||||
// in case gh releases has rate limits
|
||||
cacheTtl: 60 * 5,
|
||||
cacheEverything: true,
|
||||
},
|
||||
} as any)
|
||||
const resp = await fetch(
|
||||
`https://github.com/anomalyco/${channel === "stable" ? "opencode" : "opencode-beta"}/releases/latest/download/${assetName}`,
|
||||
{
|
||||
cf: {
|
||||
// in case gh releases has rate limits
|
||||
cacheTtl: 60 * 5,
|
||||
cacheEverything: true,
|
||||
},
|
||||
} as any,
|
||||
)
|
||||
|
||||
const downloadName = downloadNames[platform]
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import "./index.css"
|
||||
import { Title, Meta } from "@solidjs/meta"
|
||||
import { A, createAsync, query } from "@solidjs/router"
|
||||
import { Header } from "~/component/header"
|
||||
import { Footer } from "~/component/footer"
|
||||
import { IconCopy, IconCheck } from "~/component/icon"
|
||||
import { Meta, Title } from "@solidjs/meta"
|
||||
import { A } from "@solidjs/router"
|
||||
import { createSignal, type JSX, onMount, Show } from "solid-js"
|
||||
import { Faq } from "~/component/faq"
|
||||
import desktopAppIcon from "../../asset/lander/opencode-desktop-icon.png"
|
||||
import { Footer } from "~/component/footer"
|
||||
import { Header } from "~/component/header"
|
||||
import { IconCheck, IconCopy } from "~/component/icon"
|
||||
import { Legal } from "~/component/legal"
|
||||
import { LocaleLinks } from "~/component/locale-links"
|
||||
import { config } from "~/config"
|
||||
import { createSignal, onMount, Show, JSX } from "solid-js"
|
||||
import { DownloadPlatform } from "./types"
|
||||
import { useI18n } from "~/context/i18n"
|
||||
import { useLanguage } from "~/context/language"
|
||||
import { LocaleLinks } from "~/component/locale-links"
|
||||
import desktopAppIcon from "../../asset/lander/opencode-desktop-icon.png"
|
||||
import type { DownloadPlatform } from "./types"
|
||||
|
||||
type OS = "macOS" | "Windows" | "Linux" | null
|
||||
|
||||
@@ -40,8 +40,8 @@ function getDownloadPlatform(os: OS): DownloadPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
function getDownloadHref(platform: DownloadPlatform) {
|
||||
return `/download/${platform}`
|
||||
function getDownloadHref(platform: DownloadPlatform, channel: "stable" | "beta" = "stable") {
|
||||
return `/download/${channel}/${platform}`
|
||||
}
|
||||
|
||||
function IconDownload(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
|
||||
@@ -36,7 +36,7 @@ const getModelsInfo = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
return {
|
||||
all: Object.entries(ZenData.list().models)
|
||||
all: Object.entries(ZenData.list("full").models)
|
||||
.filter(([id, _model]) => !["claude-3-5-haiku"].includes(id))
|
||||
.filter(([id, _model]) => !id.startsWith("alpha-"))
|
||||
.sort(([idA, modelA], [idB, modelB]) => {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { handler } from "~/routes/zen/util/handler"
|
||||
|
||||
export function POST(input: APIEvent) {
|
||||
return handler(input, {
|
||||
format: "oa-compat",
|
||||
modelList: "lite",
|
||||
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
|
||||
parseModel: (url: string, body: any) => body.model,
|
||||
parseIsStream: (url: string, body: any) => !!body.stream,
|
||||
})
|
||||
}
|
||||
@@ -44,6 +44,7 @@ export async function handler(
|
||||
input: APIEvent,
|
||||
opts: {
|
||||
format: ZenData.Format
|
||||
modelList: "lite" | "full"
|
||||
parseApiKey: (headers: Headers) => string | undefined
|
||||
parseModel: (url: string, body: any) => string
|
||||
parseIsStream: (url: string, body: any) => boolean
|
||||
@@ -77,7 +78,7 @@ export async function handler(
|
||||
request: requestId,
|
||||
client: ocClient,
|
||||
})
|
||||
const zenData = ZenData.list()
|
||||
const zenData = ZenData.list(opts.modelList)
|
||||
const modelInfo = validateModel(zenData, model)
|
||||
const dataDumper = createDataDumper(sessionId, requestId, projectId)
|
||||
const trialLimiter = createTrialLimiter(modelInfo.trial, ip, ocClient)
|
||||
@@ -107,11 +108,14 @@ export async function handler(
|
||||
const startTimestamp = Date.now()
|
||||
const reqUrl = providerInfo.modifyUrl(providerInfo.api, isStream)
|
||||
const reqBody = JSON.stringify(
|
||||
providerInfo.modifyBody({
|
||||
...createBodyConverter(opts.format, providerInfo.format)(body),
|
||||
model: providerInfo.model,
|
||||
...(providerInfo.payloadModifier ?? {}),
|
||||
}),
|
||||
providerInfo.modifyBody(
|
||||
{
|
||||
...createBodyConverter(opts.format, providerInfo.format)(body),
|
||||
model: providerInfo.model,
|
||||
...(providerInfo.payloadModifier ?? {}),
|
||||
},
|
||||
authInfo?.workspaceID,
|
||||
),
|
||||
)
|
||||
logger.debug("REQUEST URL: " + reqUrl)
|
||||
logger.debug("REQUEST: " + reqBody.substring(0, 300) + "...")
|
||||
|
||||
@@ -18,9 +18,10 @@ export const openaiHelper: ProviderHelper = () => ({
|
||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
|
||||
headers.set("authorization", `Bearer ${apiKey}`)
|
||||
},
|
||||
modifyBody: (body: Record<string, any>) => {
|
||||
return body
|
||||
},
|
||||
modifyBody: (body: Record<string, any>, workspaceID?: string) => ({
|
||||
...body,
|
||||
...(workspaceID ? { safety_identifier: workspaceID } : {}),
|
||||
}),
|
||||
createBinaryStreamDecoder: () => undefined,
|
||||
streamSeparator: "\n\n",
|
||||
createUsageParser: () => {
|
||||
|
||||
@@ -37,7 +37,7 @@ export type ProviderHelper = (input: { reqModel: string; providerModel: string }
|
||||
format: ZenData.Format
|
||||
modifyUrl: (providerApi: string, isStream?: boolean) => string
|
||||
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => void
|
||||
modifyBody: (body: Record<string, any>) => Record<string, any>
|
||||
modifyBody: (body: Record<string, any>, workspaceID?: string) => Record<string, any>
|
||||
createBinaryStreamDecoder: () => ((chunk: Uint8Array) => Uint8Array | undefined) | undefined
|
||||
streamSeparator: string
|
||||
createUsageParser: () => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { handler } from "~/routes/zen/util/handler"
|
||||
export function POST(input: APIEvent) {
|
||||
return handler(input, {
|
||||
format: "oa-compat",
|
||||
modelList: "full",
|
||||
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
|
||||
parseModel: (url: string, body: any) => body.model,
|
||||
parseIsStream: (url: string, body: any) => !!body.stream,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { handler } from "~/routes/zen/util/handler"
|
||||
export function POST(input: APIEvent) {
|
||||
return handler(input, {
|
||||
format: "anthropic",
|
||||
modelList: "full",
|
||||
parseApiKey: (headers: Headers) => headers.get("x-api-key") ?? undefined,
|
||||
parseModel: (url: string, body: any) => body.model,
|
||||
parseIsStream: (url: string, body: any) => !!body.stream,
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function OPTIONS(input: APIEvent) {
|
||||
}
|
||||
|
||||
export async function GET(input: APIEvent) {
|
||||
const zenData = ZenData.list()
|
||||
const zenData = ZenData.list("full")
|
||||
const disabledModels = await authenticate()
|
||||
|
||||
return new Response(
|
||||
|
||||
@@ -4,6 +4,7 @@ import { handler } from "~/routes/zen/util/handler"
|
||||
export function POST(input: APIEvent) {
|
||||
return handler(input, {
|
||||
format: "google",
|
||||
modelList: "full",
|
||||
parseApiKey: (headers: Headers) => headers.get("x-goog-api-key") ?? undefined,
|
||||
parseModel: (url: string, body: any) => url.split("/").pop()?.split(":")?.[0] ?? "",
|
||||
parseIsStream: (url: string, body: any) =>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { handler } from "~/routes/zen/util/handler"
|
||||
export function POST(input: APIEvent) {
|
||||
return handler(input, {
|
||||
format: "openai",
|
||||
modelList: "full",
|
||||
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
|
||||
parseModel: (url: string, body: any) => body.model,
|
||||
parseIsStream: (url: string, body: any) => !!body.stream,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -73,6 +73,7 @@ export namespace ZenData {
|
||||
|
||||
const ModelsSchema = z.object({
|
||||
models: z.record(z.string(), z.union([ModelSchema, z.array(ModelSchema.extend({ formatFilter: FormatSchema }))])),
|
||||
liteModels: z.record(z.string(), ModelSchema),
|
||||
providers: z.record(z.string(), ProviderSchema),
|
||||
providerFamilies: z.record(z.string(), ProviderFamilySchema),
|
||||
})
|
||||
@@ -81,7 +82,7 @@ export namespace ZenData {
|
||||
return input
|
||||
})
|
||||
|
||||
export const list = fn(z.void(), () => {
|
||||
export const list = fn(z.enum(["lite", "full"]), (modelList) => {
|
||||
const json = JSON.parse(
|
||||
Resource.ZEN_MODELS1.value +
|
||||
Resource.ZEN_MODELS2.value +
|
||||
@@ -114,9 +115,9 @@ export namespace ZenData {
|
||||
Resource.ZEN_MODELS29.value +
|
||||
Resource.ZEN_MODELS30.value,
|
||||
)
|
||||
const { models, providers, providerFamilies } = ModelsSchema.parse(json)
|
||||
const { models, liteModels, providers, providerFamilies } = ModelsSchema.parse(json)
|
||||
return {
|
||||
models,
|
||||
models: modelList === "lite" ? liteModels : models,
|
||||
providers: Object.fromEntries(
|
||||
Object.entries(providers).map(([id, provider]) => [
|
||||
id,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
Here's the process I've been using to create icons:
|
||||
|
||||
- Save source image as `app-icon.png` in `packages/desktop`
|
||||
- `cd` to `src-tauri`
|
||||
- Run `bun tauri icons -o icons/{environment}`
|
||||
- `cd` to `packages/desktop`
|
||||
- Run `bun tauri icon -o src-tauri/icons/{environment}`
|
||||
- Use [Image2Icon](https://img2icnsapp.com/)'s 'Big Sur Icon' preset to generate an `icon.icns` file and place it in the appropriate icons folder
|
||||
|
||||
The Image2Icon step is necessary as the `icon.icns` generated by `app-icon.png` does not apply the shadow/padding expected by macOS,
|
||||
|
||||
BIN
packages/desktop/src-tauri/icons/beta/128x128.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
packages/desktop/src-tauri/icons/beta/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
packages/desktop/src-tauri/icons/beta/32x32.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
packages/desktop/src-tauri/icons/beta/64x64.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
packages/desktop/src-tauri/icons/beta/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
packages/desktop/src-tauri/icons/beta/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
packages/desktop/src-tauri/icons/beta/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
packages/desktop/src-tauri/icons/beta/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
packages/desktop/src-tauri/icons/beta/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
packages/desktop/src-tauri/icons/beta/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
packages/desktop/src-tauri/icons/beta/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
packages/desktop/src-tauri/icons/beta/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
packages/desktop/src-tauri/icons/beta/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
packages/desktop/src-tauri/icons/beta/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |