mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-29 07:05:14 -07:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcab47f25a | ||
|
|
7e7feeac54 | ||
|
|
328bdca61c | ||
|
|
abb752d623 | ||
|
|
f0924d19d3 |
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 0.4.2
|
current_version = 0.4.3
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
tag_name = v{new_version}
|
tag_name = v{new_version}
|
||||||
|
|||||||
@@ -226,6 +226,46 @@ jobs:
|
|||||||
args: ${{ matrix.args }}
|
args: ${{ matrix.args }}
|
||||||
includeUpdaterJson: true
|
includeUpdaterJson: true
|
||||||
|
|
||||||
|
# Tauri's bundler signs the .app and notarizes it, but the .dmg wrapper
|
||||||
|
# ships unnotarized. Gatekeeper rejects that on macOS 15 Sequoia (caught
|
||||||
|
# by Homebrew Cask CI) and causes "app isn't signed" dialogs on older
|
||||||
|
# Intel Macs when Apple's notarization servers are slow (see issue #509).
|
||||||
|
# Submit the .dmg to notarytool, staple the ticket, and overwrite the
|
||||||
|
# release asset uploaded by tauri-action.
|
||||||
|
- name: Notarize and staple DMG (macOS)
|
||||||
|
if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-15-intel'
|
||||||
|
env:
|
||||||
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY }}
|
||||||
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
KEY_PATH="$HOME/.appstoreconnect/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8"
|
||||||
|
TARGET=$(echo "${{ matrix.args }}" | sed -n 's/.*--target \([a-z0-9_-]*\).*/\1/p')
|
||||||
|
DMG_DIR="tauri/src-tauri/target/${TARGET}/release/bundle/dmg"
|
||||||
|
# Match the release tag tauri-action resolved from tauri.conf.json's
|
||||||
|
# version field; GITHUB_REF_NAME is a branch name under workflow_dispatch.
|
||||||
|
RELEASE_TAG="v$(jq -r '.version' tauri/src-tauri/tauri.conf.json)"
|
||||||
|
shopt -s nullglob
|
||||||
|
dmgs=("${DMG_DIR}"/*.dmg)
|
||||||
|
if [ ${#dmgs[@]} -eq 0 ]; then
|
||||||
|
echo "::error::No DMGs found in ${DMG_DIR} — tauri bundler output path may have changed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
for dmg in "${dmgs[@]}"; do
|
||||||
|
echo "::group::Notarize $(basename "$dmg")"
|
||||||
|
xcrun notarytool submit "$dmg" \
|
||||||
|
--key "$KEY_PATH" \
|
||||||
|
--key-id "$APPLE_API_KEY_ID" \
|
||||||
|
--issuer "$APPLE_API_ISSUER" \
|
||||||
|
--wait --timeout 20m
|
||||||
|
xcrun stapler staple "$dmg"
|
||||||
|
spctl -a -t open --context context:primary-signature -vv "$dmg"
|
||||||
|
gh release upload "${RELEASE_TAG}" "$dmg" --clobber \
|
||||||
|
--repo "${GITHUB_REPOSITORY}"
|
||||||
|
echo "::endgroup::"
|
||||||
|
done
|
||||||
|
|
||||||
build-cuda-windows:
|
build-cuda-windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
+14
-1
@@ -7,6 +7,18 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.4.3] - 2026-04-20
|
||||||
|
|
||||||
|
A patch focused on two user-impacting reliability fixes: macOS DMG notarization (unblocks `brew install voicebox` on macOS 15 Sequoia and fixes spurious "app isn't signed" Gatekeeper dialogs on older Intel Macs) and Kokoro Japanese voice initialization on fresh installs.
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
|
||||||
|
- **DMGs are now notarized and stapled** ([#523](https://github.com/jamiepine/voicebox/pull/523)). Tauri's bundler notarizes the `.app` inside the DMG but ships the DMG wrapper itself unnotarized. Gatekeeper rejects that on macOS 15 Sequoia (confirmed by Homebrew Cask CI failing on both arm and intel Sequoia runners) and causes the "the app is not signed" dialog on older Intel Macs when Apple's notarization servers are slow or unreachable ([#509](https://github.com/jamiepine/voicebox/issues/509)). The release workflow now submits each DMG to `notarytool`, staples the ticket, verifies with `spctl`, and overwrites the draft-release asset `tauri-action` uploaded. Adds ~5-10 min per macOS job.
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
- **Kokoro Japanese voices no longer crash on fresh installs** ([#521](https://github.com/jamiepine/voicebox/pull/521), fixes [#514](https://github.com/jamiepine/voicebox/issues/514)). `misaki[ja]` pulls in `fugashi`, which needs a MeCab dictionary on disk. The `unidic` package that was being installed ships no data and expects a ~526MB runtime download that `just setup` doesn't run (and which wouldn't survive PyInstaller anyway). Swapped to `unidic-lite`, which bundles a MeCab-compatible dict inside the wheel (~50MB). Collected in `build_binary.py` so frozen builds pick up `unidic_lite/dicdir/`.
|
||||||
|
|
||||||
## [0.4.2] - 2026-04-20
|
## [0.4.2] - 2026-04-20
|
||||||
|
|
||||||
This release localizes the entire app. English, Simplified Chinese (zh-CN), Traditional Chinese (zh-TW), and Japanese (ja) are wired up end-to-end across every tab, modal, dialog, and toast — 559 translation keys per locale, parity verified. Plus a batch of reliability fixes: offline-mode now actually stays offline, Chatterbox accepts reference samples it used to reject, MLX Qwen 0.6B points at the right repo, and macOS system audio survives backgrounding.
|
This release localizes the entire app. English, Simplified Chinese (zh-CN), Traditional Chinese (zh-TW), and Japanese (ja) are wired up end-to-end across every tab, modal, dialog, and toast — 559 translation keys per locale, parity verified. Plus a batch of reliability fixes: offline-mode now actually stays offline, Chatterbox accepts reference samples it used to reject, MLX Qwen 0.6B points at the right repo, and macOS system audio survives backgrounding.
|
||||||
@@ -626,7 +638,8 @@ The first public release of Voicebox — an open-source voice synthesis studio p
|
|||||||
|
|
||||||
Tauri v2, React, TypeScript, Tailwind CSS, FastAPI, Qwen3-TTS, Whisper, SQLite
|
Tauri v2, React, TypeScript, Tailwind CSS, FastAPI, Qwen3-TTS, Whisper, SQLite
|
||||||
|
|
||||||
[Unreleased]: https://github.com/jamiepine/voicebox/compare/v0.4.2...HEAD
|
[Unreleased]: https://github.com/jamiepine/voicebox/compare/v0.4.3...HEAD
|
||||||
|
[0.4.3]: https://github.com/jamiepine/voicebox/compare/v0.4.2...v0.4.3
|
||||||
[0.4.2]: https://github.com/jamiepine/voicebox/compare/v0.4.1...v0.4.2
|
[0.4.2]: https://github.com/jamiepine/voicebox/compare/v0.4.1...v0.4.2
|
||||||
[0.4.1]: https://github.com/jamiepine/voicebox/compare/v0.4.0...v0.4.1
|
[0.4.1]: https://github.com/jamiepine/voicebox/compare/v0.4.0...v0.4.1
|
||||||
[0.4.0]: https://github.com/jamiepine/voicebox/compare/v0.3.0...v0.4.0
|
[0.4.0]: https://github.com/jamiepine/voicebox/compare/v0.3.0...v0.4.0
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@voicebox/app",
|
"name": "@voicebox/app",
|
||||||
"version": "0.4.2",
|
"version": "0.4.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
# Backend package
|
# Backend package
|
||||||
|
|
||||||
__version__ = "0.4.1"
|
__version__ = "0.4.3"
|
||||||
|
|||||||
@@ -193,8 +193,6 @@ class MLXTTSBackend:
|
|||||||
|
|
||||||
logger.info("Generating audio for text: %s", text)
|
logger.info("Generating audio for text: %s", text)
|
||||||
|
|
||||||
model_name = f"qwen-tts-{self._current_model_size}"
|
|
||||||
|
|
||||||
def _generate_sync():
|
def _generate_sync():
|
||||||
"""Run synchronous generation in thread pool."""
|
"""Run synchronous generation in thread pool."""
|
||||||
# MLX generate() returns a generator yielding GenerationResult objects
|
# MLX generate() returns a generator yielding GenerationResult objects
|
||||||
@@ -220,14 +218,12 @@ class MLXTTSBackend:
|
|||||||
logger.warning("Regenerating without voice prompt.")
|
logger.warning("Regenerating without voice prompt.")
|
||||||
ref_audio = None
|
ref_audio = None
|
||||||
|
|
||||||
# Model is loaded → weights are on disk. Force offline so
|
# Inference runs with the process's default HF_HUB_OFFLINE
|
||||||
# lazy tokenizer/config lookups inside mlx_audio don't hang
|
# state. Forcing offline here (previously used to avoid lazy
|
||||||
# when the user is disconnected (issue #462).
|
# mlx_audio lookups hanging when the network drops mid-inference,
|
||||||
with force_offline_if_cached(True, model_name):
|
# issue #462) regressed online users because libraries make
|
||||||
# Check if model supports voice cloning via generate method
|
# legitimate metadata calls during generation.
|
||||||
# MLX API may support ref_audio parameter directly
|
|
||||||
try:
|
try:
|
||||||
# Try with voice cloning parameters if supported
|
|
||||||
if ref_audio:
|
if ref_audio:
|
||||||
# Check if generate accepts ref_audio parameter
|
# Check if generate accepts ref_audio parameter
|
||||||
import inspect
|
import inspect
|
||||||
@@ -347,8 +343,6 @@ class MLXSTTBackend:
|
|||||||
"""
|
"""
|
||||||
await self.load_model_async(model_size)
|
await self.load_model_async(model_size)
|
||||||
|
|
||||||
progress_model_name = f"whisper-{self.model_size}"
|
|
||||||
|
|
||||||
def _transcribe_sync():
|
def _transcribe_sync():
|
||||||
"""Run synchronous transcription in thread pool."""
|
"""Run synchronous transcription in thread pool."""
|
||||||
# MLX Whisper transcription using generate method
|
# MLX Whisper transcription using generate method
|
||||||
@@ -357,10 +351,9 @@ class MLXSTTBackend:
|
|||||||
if language:
|
if language:
|
||||||
decode_options["language"] = language
|
decode_options["language"] = language
|
||||||
|
|
||||||
# Model is loaded → weights are on disk. Force offline so
|
# Inference runs with the process's default HF_HUB_OFFLINE
|
||||||
# lazy tokenizer/config lookups don't hang when the user is
|
# state — see the comment in MLXTTSBackend.generate for the
|
||||||
# disconnected (issue #462).
|
# regression this revert fixes (issue #462).
|
||||||
with force_offline_if_cached(True, progress_model_name):
|
|
||||||
result = self.model.generate(str(audio_path), **decode_options)
|
result = self.model.generate(str(audio_path), **decode_options)
|
||||||
|
|
||||||
# Extract text from result
|
# Extract text from result
|
||||||
|
|||||||
@@ -172,14 +172,12 @@ class PyTorchTTSBackend:
|
|||||||
# This shouldn't happen in practice, but handle it
|
# This shouldn't happen in practice, but handle it
|
||||||
return {"prompt": cached_prompt}, True
|
return {"prompt": cached_prompt}, True
|
||||||
|
|
||||||
model_name = f"qwen-tts-{self._current_model_size}"
|
|
||||||
|
|
||||||
def _create_prompt_sync():
|
def _create_prompt_sync():
|
||||||
"""Run synchronous voice prompt creation in thread pool."""
|
"""Run synchronous voice prompt creation in thread pool."""
|
||||||
# Model is loaded → weights are on disk. Force offline so
|
# Inference runs with the process's default HF_HUB_OFFLINE
|
||||||
# lazy tokenizer/config lookups inside qwen_tts don't hang
|
# state. Forcing offline here (issue #462) regressed online
|
||||||
# when the user is disconnected (issue #462).
|
# users whose libraries issue legitimate metadata lookups
|
||||||
with force_offline_if_cached(True, model_name):
|
# during voice-prompt creation.
|
||||||
return self.model.create_voice_clone_prompt(
|
return self.model.create_voice_clone_prompt(
|
||||||
ref_audio=str(audio_path),
|
ref_audio=str(audio_path),
|
||||||
ref_text=reference_text,
|
ref_text=reference_text,
|
||||||
@@ -227,18 +225,14 @@ class PyTorchTTSBackend:
|
|||||||
# Load model
|
# Load model
|
||||||
await self.load_model_async(None)
|
await self.load_model_async(None)
|
||||||
|
|
||||||
model_name = f"qwen-tts-{self._current_model_size}"
|
|
||||||
|
|
||||||
def _generate_sync():
|
def _generate_sync():
|
||||||
"""Run synchronous generation in thread pool."""
|
"""Run synchronous generation in thread pool."""
|
||||||
# Set seed if provided
|
# Set seed if provided
|
||||||
if seed is not None:
|
if seed is not None:
|
||||||
manual_seed(seed, self.device)
|
manual_seed(seed, self.device)
|
||||||
|
|
||||||
# Model is loaded → weights are on disk. Force offline so
|
# See _create_prompt_sync comment — inference runs with the
|
||||||
# lazy tokenizer/config lookups inside qwen_tts don't hang
|
# process's default HF_HUB_OFFLINE state (issue #462).
|
||||||
# when the user is disconnected (issue #462).
|
|
||||||
with force_offline_if_cached(True, model_name):
|
|
||||||
wavs, sample_rate = self.model.generate_voice_clone(
|
wavs, sample_rate = self.model.generate_voice_clone(
|
||||||
text=text,
|
text=text,
|
||||||
voice_clone_prompt=voice_prompt,
|
voice_clone_prompt=voice_prompt,
|
||||||
@@ -342,17 +336,15 @@ class PyTorchSTTBackend:
|
|||||||
"""
|
"""
|
||||||
await self.load_model_async(model_size)
|
await self.load_model_async(model_size)
|
||||||
|
|
||||||
progress_model_name = f"whisper-{self.model_size}"
|
|
||||||
|
|
||||||
def _transcribe_sync():
|
def _transcribe_sync():
|
||||||
"""Run synchronous transcription in thread pool."""
|
"""Run synchronous transcription in thread pool."""
|
||||||
# Load audio
|
# Load audio
|
||||||
audio, _sr = load_audio(audio_path, sample_rate=16000)
|
audio, _sr = load_audio(audio_path, sample_rate=16000)
|
||||||
|
|
||||||
# Model is loaded → weights are on disk. Force offline so
|
# Inference runs with the process's default HF_HUB_OFFLINE
|
||||||
# `get_decoder_prompt_ids` and any lazy tokenizer lookups
|
# state — forcing offline here (issue #462) broke online users
|
||||||
# don't hang when the user is disconnected (issue #462).
|
# whose `get_decoder_prompt_ids` / tokenizer calls issue
|
||||||
with force_offline_if_cached(True, progress_model_name):
|
# legitimate metadata lookups.
|
||||||
# Process audio
|
# Process audio
|
||||||
inputs = self.processor(
|
inputs = self.processor(
|
||||||
audio,
|
audio,
|
||||||
|
|||||||
@@ -186,7 +186,6 @@ class QwenCustomVoiceBackend:
|
|||||||
await self.load_model_async(None)
|
await self.load_model_async(None)
|
||||||
|
|
||||||
speaker = voice_prompt.get("preset_voice_id") or QWEN_CV_DEFAULT_SPEAKER
|
speaker = voice_prompt.get("preset_voice_id") or QWEN_CV_DEFAULT_SPEAKER
|
||||||
model_name = f"qwen-custom-voice-{self._current_model_size}"
|
|
||||||
|
|
||||||
def _generate_sync():
|
def _generate_sync():
|
||||||
if seed is not None:
|
if seed is not None:
|
||||||
@@ -206,10 +205,10 @@ class QwenCustomVoiceBackend:
|
|||||||
if instruct:
|
if instruct:
|
||||||
kwargs["instruct"] = instruct
|
kwargs["instruct"] = instruct
|
||||||
|
|
||||||
# Model is loaded → weights are on disk. Force offline so
|
# Inference runs with the process's default HF_HUB_OFFLINE
|
||||||
# lazy tokenizer/config lookups inside qwen_tts don't hang
|
# state. Forcing offline here (issue #462) regressed online
|
||||||
# when the user is disconnected (issue #462).
|
# users whose libraries issue legitimate metadata lookups
|
||||||
with force_offline_if_cached(True, model_name):
|
# during generation.
|
||||||
wavs, sample_rate = self.model.generate_custom_voice(**kwargs)
|
wavs, sample_rate = self.model.generate_custom_voice(**kwargs)
|
||||||
return wavs[0], sample_rate
|
return wavs[0], sample_rate
|
||||||
|
|
||||||
|
|||||||
@@ -287,6 +287,12 @@ def build_server(cuda=False):
|
|||||||
"en_core_web_sm",
|
"en_core_web_sm",
|
||||||
"--hidden-import",
|
"--hidden-import",
|
||||||
"en_core_web_sm",
|
"en_core_web_sm",
|
||||||
|
# unidic-lite ships the MeCab dictionary used by fugashi (pulled in
|
||||||
|
# by misaki[ja]). The dict lives in unidic_lite/dicdir/ and is
|
||||||
|
# discovered via the package's DICDIR constant, so the data files
|
||||||
|
# must be collected or Japanese Kokoro voices crash at runtime.
|
||||||
|
"--collect-all",
|
||||||
|
"unidic_lite",
|
||||||
"--hidden-import",
|
"--hidden-import",
|
||||||
"loguru",
|
"loguru",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ misaki[en,ja,zh]>=0.9.4
|
|||||||
# spacy model for misaki English G2P — must be pre-installed or misaki
|
# spacy model for misaki English G2P — must be pre-installed or misaki
|
||||||
# tries spacy.cli.download() at runtime which crashes frozen builds
|
# tries spacy.cli.download() at runtime which crashes frozen builds
|
||||||
en_core_web_sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl
|
en_core_web_sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl
|
||||||
|
# fugashi (pulled in by misaki[ja]) needs a MeCab dictionary on disk.
|
||||||
|
# unidic-lite ships one inside the wheel (~50MB); the full `unidic` package
|
||||||
|
# requires `python -m unidic download` (~526MB) which breaks frozen builds
|
||||||
|
# for the same reason en_core_web_sm does.
|
||||||
|
unidic-lite>=1.0.8
|
||||||
|
|
||||||
# Audio processing
|
# Audio processing
|
||||||
librosa>=0.10.0
|
librosa>=0.10.0
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@voicebox/landing",
|
"name": "@voicebox/landing",
|
||||||
"version": "0.4.2",
|
"version": "0.4.3",
|
||||||
"description": "Landing page for voicebox.sh",
|
"description": "Landing page for voicebox.sh",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbo",
|
"dev": "next dev --turbo",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 186 KiB |
@@ -29,10 +29,10 @@ const TUTORIALS: (Tutorial | null)[] = [
|
|||||||
thumbnail: "/tutorials/kqxqjRsdD5E.jpg",
|
thumbnail: "/tutorials/kqxqjRsdD5E.jpg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "yu9QHqOEqqA",
|
id: "05YBqrWTLQ0",
|
||||||
title: "This FREE AI Tool Just Destroyed ElevenLabs Voice Cloning (VoiceBox)",
|
title: "2026年最好的声音克隆工具?Voicebox完整测评:从下载到API调用,附速度对比",
|
||||||
author: "Danish Sofi",
|
author: "Tech指南",
|
||||||
thumbnail: "/tutorials/yu9QHqOEqqA.jpg",
|
thumbnail: "/tutorials/05YBqrWTLQ0.jpg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "RRRBxNXgeKQ",
|
id: "RRRBxNXgeKQ",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "voicebox",
|
"name": "voicebox",
|
||||||
"version": "0.4.2",
|
"version": "0.4.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"app",
|
"app",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@voicebox/tauri",
|
"name": "@voicebox/tauri",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.4.2",
|
"version": "0.4.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
Generated
+1
-1
@@ -5041,7 +5041,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "voicebox"
|
name = "voicebox"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "voicebox"
|
name = "voicebox"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
description = "A production-quality desktop app for Qwen3-TTS voice cloning and generation"
|
description = "A production-quality desktop app for Qwen3-TTS voice cloning and generation"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
license = ""
|
license = ""
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Voicebox",
|
"productName": "Voicebox",
|
||||||
"version": "0.4.2",
|
"version": "0.4.3",
|
||||||
"identifier": "sh.voicebox.app",
|
"identifier": "sh.voicebox.app",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "bun run dev",
|
"beforeDevCommand": "bun run dev",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@voicebox/web",
|
"name": "@voicebox/web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.4.2",
|
"version": "0.4.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
Reference in New Issue
Block a user