update voice & update flow

main
reng 5 months ago
parent 644c9b175b
commit e94ac148f2
  1. BIN
      vite/public/assets/0721/onyx/q4-1.mp3
  2. BIN
      vite/public/assets/0721/onyx/q4-2.mp3
  3. BIN
      vite/public/assets/0721/onyx/q4.mp3
  4. BIN
      vite/public/assets/0721/onyx/q5.mp3
  5. BIN
      vite/public/assets/0721/shimmer/q4-1.mp3
  6. BIN
      vite/public/assets/0721/shimmer/q4-2.mp3
  7. BIN
      vite/public/assets/0721/shimmer/q4.mp3
  8. BIN
      vite/public/assets/0721/shimmer/q5.mp3
  9. 50
      vite/public/cuelist_free.json
  10. 37
      vite/src-tauri/Cargo.lock
  11. 1
      vite/src-tauri/Cargo.toml
  12. 29
      vite/src-tauri/src/lib.rs
  13. 2
      vite/src/App.jsx
  14. 25
      vite/src/comps/light.jsx
  15. 35
      vite/src/pages/flow_free.jsx
  16. 22
      vite/src/util/osc.js
  17. 4
      vite/src/util/useChat.jsx

@ -31,9 +31,9 @@
"id": 4,
"name": "Q4",
"type": "phone",
"description": "Guide to call",
"description": "引導撥號",
"auto": false,
"audioFile": "assets/q4.mp3",
"audioFile": "assets/0721/onyx/q4.mp3",
"nextcue": 4.1,
"callback":"numpad"
},
@ -41,42 +41,60 @@
"id": 4.1,
"name": "Q4.1",
"type": "phone",
"description": "Guide to construct scene",
"description": "電話開頭",
"auto": true,
"audioFile": "assets/q4-1.mp3",
"nextcue": 4.2,
"status":"intro"
"audioFile": "assets/0721/onyx/q4-1.mp3",
"nextcue": 4.2
},
{
"id": 4.2,
"name": "Q4.2",
"type": "phone",
"description": "示範影片,引導回憶",
"auto": true,
"audioFile": "assets/0721/onyx/q4-2.mp3",
"nextcue": 4.3,
"status":"intro"
},
{
"id": 4.3,
"name": "Q4.3",
"type": "chat",
"description": "chat",
"auto": true,
"nextcue": 4.3,
"nextcue": 4.4,
"duration": 90,
"status":"go"
},
{
"id": 4.3,
"name": "Q4.3",
"id": 4.4,
"name": "Q4.4",
"type": "chat_end",
"description": "chat end",
"description": "對話收尾",
"auto": true,
"nextcue": 5.1
},
{
"id": 5.1,
"name": "Q5.1",
"type": "phone",
"description": "引導打給遺憾對象",
"auto": true,
"nextcue": 5
"audioFile": "assets/0721/onyx/q5.mp3",
"nextcue": 5.2
},
{
"id": 5,
"name": "Q5",
"id": 5.2,
"name": "Q5.2",
"type": "user_input",
"description": "call",
"duration": 60,
"auto": true,
"nextcue": 5.1
"nextcue": 5.3
},
{
"id": 5.1,
"name": "Q5.1",
"id": 5.3,
"name": "Q5.3",
"type": "summary",
"description": "summary",
"auto": true,

@ -95,6 +95,7 @@ name = "app"
version = "0.1.0"
dependencies = [
"dotenv",
"enttecopendmx",
"log",
"rosc",
"serde",
@ -1025,6 +1026,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
[[package]]
name = "enttecopendmx"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439b5a6167b4d55c80838155742c65c5159f76ee4acd25324bdeb142828aa0c5"
dependencies = [
"libftd2xx",
]
[[package]]
name = "enumflags2"
version = "0.7.12"
@ -2137,6 +2147,27 @@ version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libftd2xx"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6b3be3aa1917532fb3918dde5fda1c82d7169d2bbd2e79353bac2cd3985e68a"
dependencies = [
"libftd2xx-ffi",
"log",
"paste",
"static_assertions",
]
[[package]]
name = "libftd2xx-ffi"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebd82d23ae0cbbf0fb65ad0310b474e45ddfdece8aa03a34130c59c04333c701"
dependencies = [
"cfg-if",
]
[[package]]
name = "libloading"
version = "0.7.4"
@ -2716,6 +2747,12 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pathdiff"
version = "0.2.3"

@ -31,3 +31,4 @@ webview2-com = "0.37.0"
windows = "0.61.1"
tauri-plugin-fs = "2"
tauri-plugin-opener = "2"
enttecopendmx ="0.1.0"

@ -85,7 +85,8 @@ pub fn run() {
.invoke_handler(tauri::generate_handler![
get_env,
send_osc_message,
reset_permission
reset_permission,
send_dmx_message
])
.plugin(tauri_plugin_http::init())
.setup(|app| {
@ -101,3 +102,29 @@ pub fn run() {
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
static mut LIGHT: Option<enttecopendmx::EnttecOpenDMX> = None;
#[tauri::command]
fn send_dmx_message(message: &str) -> Result<(), String> {
println!("Sending DMX message: {}", message);
let val = message.parse::<u8>().map_err(|e| e.to_string())?;
unsafe {
if LIGHT.is_none() {
let mut dmx = enttecopendmx::EnttecOpenDMX::new().map_err(|e| e.to_string())?;
dmx.open().map_err(|e| e.to_string())?;
LIGHT = Some(dmx);
}
if let Some(ref mut dmx) = LIGHT {
dmx.set_channel(1, val);
dmx.render().unwrap();
} else {
return Err("DMX interface not available".into());
}
}
Ok(())
}

@ -9,7 +9,7 @@ function App() {
return (
<div className='w-full flex flex-row gap-2 justify-center py-2 px-8 *:bg-pink-200 *:px-2'>
<a href="/">Conversation</a>
<a href="/flow">Flow</a>
{/* <a href="/flow">Flow</a> */}
<a href="/free-flow">Free Flow</a>
<a href="/settings">Settings</a>
</div>

@ -1,8 +1,8 @@
import gsap from "gsap"
import { forwardRef, useEffect, useImperativeHandle, useRef } from "react"
import { OSC_ADDRESS, sendOsc } from "../util/osc";
import { invoke } from '@tauri-apps/api/core';
const FADE_TIME=3;
const FADE_TIME=5;
export const Light=forwardRef((props, ref)=>{
@ -18,7 +18,16 @@ export const Light=forwardRef((props, ref)=>{
duration: time,
onUpdate: () => {
// console.log(refVal.current.val);
sendOsc(OSC_ADDRESS.LIGHT, refVal.current.val.toString());
// sendOsc(OSC_ADDRESS.LIGHT, refVal.current.val.toString());
invoke('send_dmx_message', {
message: Math.floor(refVal.current.val * 255).toString(),
}).then(() => {
console.log(`dmx message sent: ${Math.floor(refVal.current.val * 255)}`);
}).catch((error) => {
console.error('Error sending DMX message:', error);
});
if(refContainer.current)
refContainer.current.style.background= `rgba(0, 255, 0, ${refVal.current.val})`; // Update background color based on value
},
@ -41,15 +50,15 @@ export const Light=forwardRef((props, ref)=>{
},[]);
return (
<div ref={refContainer} className="flex flex-row gap-4 !w-auto p-1 border">
<span className="flex flex-col justify-between">
<div ref={refContainer} className="grid grid-cols-2 justify-between p-1 border *:overflow-hidden">
{/* <span className="flex flex-col justify-between"> */}
<label className="text-center">Light</label>
<input ref={refInput} type="text" className="border w-[5vw]" defaultValue={5}/>
</span>
<span className="flex flex-col justify-between">
{/* </span>
<span className="flex flex-col justify-between"> */}
<button onClick={()=>fade(0,1)}>fadeIn</button>
<button onClick={()=>fade(1,0)}>fadeOut</button>
</span>
{/* </span> */}
</div>
)
});

@ -28,6 +28,11 @@ const ChatStatus={
Processing: 'processing',
}
const Voice={
ONYX: 'onyx',
SHIMMER: 'shimmer',
}
export function FreeFlow(){
const { data }=useData();
@ -39,6 +44,8 @@ export function FreeFlow(){
const [autoSend, setAutoSend] = useState(true);
const [userId, setUserId] = useState();
const [summary, setSummary] = useState(null);
const [voice, setVoice] = useState(Voice.ONYX);
const [chatStatus, setChatStatus] = useState(ChatStatus.System); // System, User, Processing
@ -78,7 +85,11 @@ export function FreeFlow(){
refAudio.current.pause(); // Stop any currently playing audio
}
const audio = new Audio(url);
let audioUrl = url;
if(voice==Voice.SHIMMER) audioUrl = url.replace(Voice.ONYX, Voice.SHIMMER);
console.log('Using voice:', voice, 'for audio:', audioUrl);
const audio = new Audio(audioUrl);
audio.loop=refCurrentCue.current?.loop || false; // Set loop if defined in cue
audio.play().catch(error => {
console.error("Audio playback error:", error);
@ -122,14 +133,14 @@ export function FreeFlow(){
// Special case for starting a conversation
resetTranscript();
console.log('Starting conversation...');
sendMessage();
sendMessage(null, false, false, voice); // Send initial message with voice
setChatWelcome(true);
resetData(); // Reset data for new conversation
break;
case 'chat_end':
const message= refInput.current?.value?.trim();
console.log('Ending conversation with message:', message);
sendMessage(message, false, true);
sendMessage(message, false, true, voice);
setChatWelcome(false);
break;
@ -246,7 +257,7 @@ export function FreeFlow(){
const message= refInput.current?.value?.trim();
if(message && message.length>0) {
console.log('Ending conversation with message:', message);
sendMessage(message, false, false);
sendMessage(message, false, false, voice);
setChatWelcome(false);
setChatStatus(ChatStatus.Processing); // Set chat status to Processing
@ -341,19 +352,25 @@ export function FreeFlow(){
<main className="items-start">
<section className="flex-1 flex flex-col gap-2 self-stretch overflow-hidden">
<DebugControl/>
<div className="w-full p-2 flex flex-row justify-center gap-2 *:w-[12vw] *:h-[5rem]">
<div className="w-full p-2 grid grid-cols-4 gap-2 items-stretch justify-center *:max-h-[5rem]">
<div className="bg-gray-100 text-4xl font-bold mb-4 flex justify-center items-center">
{refCurrentCue.current?.name}
</div>
<Countdown ref={refTimer} />
<button className="!bg-red-300" onClick={onStop}>Stop</button>
<button className="!bg-yellow-300" onClick={()=>{
saveHistory(history);
}}>Save Log</button>
<button onClick={saveImage}>Save image</button>
{/* <button onClick={saveImage}>Save image</button> */}
<NumPad onSend={onNumpad} />
<Light ref={refLight} />
<VoiceAnalysis/>
<div className="flex flex-col">
<label className="text-center">Voice</label>
<button className={`${voice==Voice.ONYX && 'bg-gray-300'}`} onClick={() => setVoice(Voice.ONYX)}>Onyx</button>
<button className={`${voice==Voice.SHIMMER && 'bg-gray-300'}`} onClick={() => setVoice(Voice.SHIMMER)}>Shimmer</button>
</div>
<button className="!bg-yellow-300" onClick={()=>{
saveHistory(history);
}}>Save Log</button>
</div>
<div className="flex-1 overflow-y-auto">
<table className="border-collapse **:border-y w-full **:p-2">

@ -20,13 +20,18 @@ export async function sendOsc(key, message){
console.warn('sendOsc: message is empty, skipping');
return;
}
console.log(`Sending OSC message: ${key} -> ${message}`);
await invoke('send_osc_message', {
key: key,
message: message.toString(),
host:`0.0.0.0:0`,
target: '127.0.0.1:9000',
});
try{
console.log(`Sending OSC message: ${key} -> ${message}`);
await invoke('send_osc_message', {
key: key,
message: message.toString(),
host:`0.0.0.0:0`,
target: '127.0.0.1:9000',
});
}catch (error){
console.error('Error sending OSC message:', error);
}
}
@ -43,7 +48,6 @@ export async function updatePrompt(prompt) {
body: JSON.stringify({ prompt })
});
}catch(error){
console.error('Error updating prompt:', error);
throw error;
console.error('Error updating prompt:', error);
}
}

@ -35,7 +35,7 @@ export function ChatProvider({children}){
}
function sendMessage(message, force_no_audio=false, isLastMessage=false) {
function sendMessage(message, force_no_audio=false, isLastMessage=false, voice=null) {
console.log('Sending chat message:', message);
setStatus(Status.PROCESSING_TEXT);
@ -60,7 +60,7 @@ export function ChatProvider({children}){
if(response.output_text && (!force_no_audio && audioOutput)){
setStatus(Status.PROCESSING_AUDIO);
textToSpeech(response.output_text, data?.voice, data?.voice_prompt).then(url => {
textToSpeech(response.output_text, voice||data?.voice, data?.voice_prompt).then(url => {
setStatus(Status.SUCCESS);
setAudioUrl(url); // Store the audio URL

Loading…
Cancel
Save