Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ff40762
added handling of drop event for .vrma and .fbx animation files
Jun 19, 2023
d07f467
corrected README.md link to three-vrm
Jun 19, 2023
f09a4b3
resetting the mixer to avoid remainders of old animations mixing in w…
Jun 19, 2023
b95f755
fixed github link to point to the forked repo
Jun 19, 2023
1eb2ebf
VRMAファイル適応
tegnike May 7, 2024
85fc5c5
VRM適用時に位置をリセットさせない
tegnike May 18, 2024
de71943
Merge branch 'develop' into feature/add-animation-vrma
tegnike Feb 9, 2025
1969ae5
Merge branch 'develop' into feature/add-animation-vrma
tegnike May 15, 2025
d1cf239
一旦感情に合わせてアニメーションが動作するまで
tegnike May 15, 2025
57230ca
モーションをループさせ、任意のタイミングで待機モーションに戻すようにする
tegnike May 16, 2025
6322d8f
fbxのD&Dを復活
tegnike May 16, 2025
297378b
エラーfix
tegnike May 30, 2025
9aefb20
Merge branch 'develop' into feature/add-animation-vrma
tegnike May 30, 2025
5cdd80b
feat: make QUEUE_CHECK_DELAY configurable from settings
claude[bot] May 30, 2025
903d4d0
refactor: キューチェック遅延設定の改善
tegnike May 30, 2025
677877e
Merge branch 'develop' into feature/add-animation-vrma
tegnike Jul 21, 2025
ef1bffd
Merge branch 'develop' into feature/add-animation-vrma
tegnike Jul 29, 2025
33aceca
feat(animation): add loadAnimationFromUrl method and improve animatio…
tegnike Jul 29, 2025
253c57a
refactor(animation): remove FBX animation loading code and related files
tegnike Jul 30, 2025
c367445
feat(animation): update emotion animation paths and add foot constrai…
tegnike Jul 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ copy, modify, and distribute copies of the Software for **non-commercial purpose
implied, including but not limited to the warranties of merchantability,
fitness for a particular purpose, and noninfringement.

*Note: This license applies only to non-commercial use. For commercial use,
please refer to the Commercial License below.*
_Note: This license applies only to non-commercial use. For commercial use,
please refer to the Commercial License below._

===============================================================================
Commercial License
Expand Down
3 changes: 3 additions & 0 deletions locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,9 @@
"CustomAPIDescription": "Note: Messages are automatically included in the request body. In streaming mode, the server must return text/event-stream.",
"EditSlideScripts": "Edit Dialogue",
"PleaseSelectSlide": "Please select a slide",
"QueueCheckDelay": "Queue Check Delay",
"QueueCheckDelayInfo": "Time to wait before resetting to neutral expression after speech queue becomes empty (in seconds).",
"QueueCheckDelayLabel": "Queue Check Delay: {{delay}}s",
"XAIAPIKeyLabel": "xAI API Key",
"DynamicRetrievalDescription": "Sets the threshold for when the model performs a search. If 0, the search is always performed; if 1, the search is never performed.",
"DynamicRetrieval": "Dynamic Search",
Expand Down
3 changes: 3 additions & 0 deletions locales/ja/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,9 @@
"XAIAPIKeyLabel": "xAI APIキー",
"OpenRouterAPIKeyLabel": "OpenRouter APIキー",
"OpenRouterModelNameInstruction": "OpenRouterからモデル識別子を入力してください(例: \"openai/gpt-4o\", \"mistralai/mistral-large-latest\")。モデル識別子はOpenRouterモデルページで確認できます。",
"QueueCheckDelay": "キューチェック遅延",
"QueueCheckDelayInfo": "音声キューが空になった後、ニュートラル表情にリセットするまでの待機時間(秒)。",
"QueueCheckDelayLabel": "キューチェック遅延: {{delay}}秒",
"ImageDisplayPosition": "画像表示位置",
"ImageDisplayPositionDescription": "アップロードした画像を表示する位置を選択してください",
"InputArea": "入力エリア",
Expand Down
Binary file modified public/idle_loop.vrma
Binary file not shown.
Binary file added public/idle_loop_.vrma
Binary file not shown.
Binary file added public/vrma/angry.vrma
Binary file not shown.
Binary file added public/vrma/happy.vrma
Binary file not shown.
Binary file added public/vrma/sad.vrma
Binary file not shown.
25 changes: 25 additions & 0 deletions src/components/settings/character.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,31 @@ const Character = () => {
)
})}
</div>

<div className="my-6">
<div className="my-4 text-xl font-bold">{t('QueueCheckDelay')}</div>
<div className="my-4 text-base whitespace-pre-line">
{t('QueueCheckDelayInfo')}
</div>
<div className="select-none">
{t('QueueCheckDelayLabel', {
delay: settingsStore((s) => s.queueCheckDelay),
})}
</div>
<input
type="range"
min="0.5"
max="60"
step="0.1"
value={settingsStore((s) => s.queueCheckDelay)}
onChange={(e) =>
settingsStore.setState({
queueCheckDelay: parseFloat(e.target.value),
})
}
className="mt-2 mb-4 input-range"
/>
</div>
</div>
</>
)
Expand Down
4 changes: 4 additions & 0 deletions src/components/vrmViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export default function VrmViewer() {
const image = reader.result as string
image !== '' && homeStore.setState({ modalImage: image })
}
} else if (file_type === 'vrma') {
const blob = new Blob([file], { type: 'application/octet-stream' })
const url = window.URL.createObjectURL(blob)
viewer.loadVrma(url)
}
})
}
Expand Down
10 changes: 10 additions & 0 deletions src/features/messages/speakCharacter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ const createSpeakCharacter = () => {
) => {
let called = false
const ss = settingsStore.getState()
const hs = homeStore.getState()
onStart?.()

const initialToken = SpeakQueue.currentStopToken
Expand Down Expand Up @@ -327,6 +328,15 @@ const createSpeakCharacter = () => {
isNeedDecode: result.isNeedDecode,
onComplete: guardedOnComplete, // Pass the guarded function
})

// VRMの場合、ここで感情アニメーションを再生
if (ss.modelType === 'vrm') {
if (hs.viewer && talk.emotion && talk.emotion !== 'neutral') {
// neutral は常時アイドルアニメーションが流れているため、個別に再生しない
hs.viewer.playEmotionAnimation(talk.emotion, 0.3)
console.log(`VRM emotion animation started: ${talk.emotion}`)
}
}
})
.catch((error) => {
console.error('Error in processAndSynthesizePromise chain:', error)
Expand Down
5 changes: 3 additions & 2 deletions src/features/messages/speakQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ type SpeakTask = {
}

export class SpeakQueue {
private static readonly QUEUE_CHECK_DELAY = 1500
private queue: SpeakTask[] = []
private isProcessing = false
private currentSessionId: string | null = null
Expand Down Expand Up @@ -148,8 +147,9 @@ export class SpeakQueue {

private async scheduleNeutralExpression() {
const initialLength = this.queue.length
const ss = settingsStore.getState()
await new Promise((resolve) =>
setTimeout(resolve, SpeakQueue.QUEUE_CHECK_DELAY)
setTimeout(resolve, ss.queueCheckDelay * 1000)
)

if (this.shouldResetToNeutral(initialLength)) {
Expand All @@ -159,6 +159,7 @@ export class SpeakQueue {
await Live2DHandler.resetToIdle()
} else {
await hs.viewer.model?.playEmotion('neutral')
hs.viewer.switchToIdleAnimation()
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/features/stores/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ interface General {
whisperTranscriptionModel: WhisperTranscriptionModel
initialSpeechTimeout: number
chatLogWidth: number
queueCheckDelay: number
imageDisplayPosition: 'input' | 'side' | 'icon'
multiModalMode: 'ai-decide' | 'always' | 'never'
multiModalAiDecisionPrompt: string
Expand Down Expand Up @@ -538,6 +539,8 @@ const getInitialValuesFromEnv = (): SettingsState => ({
angryMotionGroup: process.env.NEXT_PUBLIC_ANGRY_MOTION_GROUP || '',
relaxedMotionGroup: process.env.NEXT_PUBLIC_RELAXED_MOTION_GROUP || '',
surprisedMotionGroup: process.env.NEXT_PUBLIC_SURPRISED_MOTION_GROUP || '',
queueCheckDelay:
parseFloat(process.env.NEXT_PUBLIC_QUEUE_CHECK_DELAY || '1.5') || 1.5,
})

const settingsStore = create<SettingsState>()(
Expand Down Expand Up @@ -710,6 +713,7 @@ const settingsStore = create<SettingsState>()(
enableMultiModal: state.enableMultiModal,
colorTheme: state.colorTheme,
customModel: state.customModel,
queueCheckDelay: state.queueCheckDelay,
}),
})
)
Expand Down
Loading