diff --git a/src-tauri/migrations/001.sql b/src-tauri/migrations/001.sql index 0f5406d..e5bc1f1 100644 --- a/src-tauri/migrations/001.sql +++ b/src-tauri/migrations/001.sql @@ -25,6 +25,10 @@ INSERT INTO settings(setting_name, setting_status) VALUES("transcriptionAccuracy INSERT INTO settings(setting_name, setting_status) VALUES("settingKeyOpenai", ""); INSERT INTO settings(setting_name, setting_status) VALUES("settingLanguage", "日本語"); INSERT INTO settings(setting_name, setting_status) VALUES("settingTemplate", ""); +INSERT INTO settings(setting_name, setting_status) VALUES("settingHook", ""); +INSERT INTO settings(setting_name, setting_status) VALUES("settingResource", ""); +INSERT INTO settings(setting_name, setting_status) VALUES("settingModel", "gpt-3.5-turbo"); +INSERT INTO settings(setting_name, setting_status) VALUES("settingAILanguage", "None"); CREATE TABLE models ( id INTEGER PRIMARY KEY AUTOINCREMENT, model_name TEXT, diff --git a/src-tauri/src/module/chat_online.rs b/src-tauri/src/module/chat_online.rs index 2209393..e26aa25 100644 --- a/src-tauri/src/module/chat_online.rs +++ b/src-tauri/src/module/chat_online.rs @@ -158,13 +158,12 @@ impl ChatOnline { #[tokio::main] async fn request_gpt( + model: &str, question: &str, token: String, template: String, ) -> Result> { let url = "https://api.openai.com/v1/chat/completions"; - - let model = "gpt-3.5-turbo"; let temperature = 0; let client = Client::new(); @@ -197,14 +196,20 @@ impl ChatOnline { .send() .await?; - println!("Status: {}", response.status()); + // println!("Status: {}", response.status()); + let status = response.status(); let json_response: Value = response.json().await?; - println!("Response: {:?}", json_response); - let response_text = json_response["choices"][0]["message"]["content"] - .as_str() - .unwrap_or("choices[0].message.content field not found"); + // println!("Response: {:?}", json_response); + let response_text = if status == 200 { + json_response["choices"][0]["message"]["content"] + .as_str() + .unwrap_or("choices[0].message.content field not found") + .to_string() + } else { + json_response.to_string() + }; - Ok(response_text.to_string()) + Ok(response_text) } fn convert(&mut self) -> Result<(), rusqlite::Error> { @@ -217,24 +222,100 @@ impl ChatOnline { ); if result.is_ok() { let question = result.unwrap(); + + let result = self.sqlite.select_ai_resource(); + let resource = if result.is_ok() { + let command = result.unwrap().replace("{{question}}", &question); + println!("command: {}", command); + if command == "" { + "".to_string() + } else { + let result = std::process::Command::new("sh") + .arg("-c") + .arg(command) + .output() + .expect("failed"); + if !result.stderr.is_empty() { + String::from_utf8(result.stderr).expect("Found invalid UTF-8") + } else { + String::from_utf8(result.stdout).expect("Found invalid UTF-8") + } + } + } else { + "".to_string() + }; let result = self.sqlite.select_ai_template(); let template = if result.is_ok() { - result.unwrap() + if resource == "" { + result.unwrap() + } else { + result + .unwrap() + .replace("{{resource}}", &resource) + .replace("{{question}}", &question) + } } else { "".to_string() }; - let result = Self::request_gpt(&question, self.token.clone(), template); + + let result = self.sqlite.select_ai_model(); + let model = if result.is_ok() { + result.unwrap() + } else { + "gpt-3.5-turbo".to_string() + }; + let result = Self::request_gpt(&model, &question, self.token.clone(), template); if result.is_ok() { let answer = result.unwrap(); - let updated = self.sqlite.update_model_vosk_to_whisper( - speech.id, - format!("Q. {}\nA. {}", question, answer), - ); + + let result = self.sqlite.select_ai_hook(); + let hook = if result.is_ok() { + result.unwrap() + } else { + "".to_string() + }; + let output = if hook != "" { + let command = hook + .replace("{{resource}}", &resource) + .replace("{{answer}}", &answer) + .replace("{{question}}", &question); + let result = std::process::Command::new("sh") + .arg("-c") + .arg(command) + .output() + .expect("failed"); + if !result.stderr.is_empty() { + String::from_utf8(result.stderr).expect("Found invalid UTF-8") + } else { + String::from_utf8(result.stdout).expect("Found invalid UTF-8") + } + } else { + "".to_string() + }; + let message = if output != "" { + format!("Q. {}\nA. {}\nCLI. {}", question, answer, output) + } else { + format!("Q. {}\nA. {}", question, answer) + }; + + let updated = self.sqlite.update_model_vosk_to_whisper(speech.id, message); self.app_handle .clone() .emit_all("finalTextConverted", updated.unwrap()) .unwrap(); + + let result = self.sqlite.select_ai_language(); + if result.is_ok() { + let lang = result.unwrap(); + if lang != "None" { + std::process::Command::new("sh") + .arg("-c") + .arg(format!("say -v {} \"{}\"", lang, answer)) + .output() + .expect("failed"); + } + } } else { println!("gpt api is temporally failed, so skipping...") } diff --git a/src-tauri/src/module/sqlite.rs b/src-tauri/src/module/sqlite.rs index 27ad2d9..b89c3ff 100644 --- a/src-tauri/src/module/sqlite.rs +++ b/src-tauri/src/module/sqlite.rs @@ -81,6 +81,30 @@ impl Sqlite { ); } + pub fn select_ai_language(&self) -> Result { + return self.conn.query_row( + "SELECT setting_status FROM settings WHERE setting_name = \"settingAILanguage\"", + params![], + |row| Ok(row.get_unwrap(0)), + ); + } + + pub fn select_ai_model(&self) -> Result { + return self.conn.query_row( + "SELECT setting_status FROM settings WHERE setting_name = \"settingModel\"", + params![], + |row| Ok(row.get_unwrap(0)), + ); + } + + pub fn select_ai_resource(&self) -> Result { + return self.conn.query_row( + "SELECT setting_status FROM settings WHERE setting_name = \"settingResource\"", + params![], + |row| Ok(row.get_unwrap(0)), + ); + } + pub fn select_ai_template(&self) -> Result { return self.conn.query_row( "SELECT setting_status FROM settings WHERE setting_name = \"settingTemplate\"", @@ -89,6 +113,14 @@ impl Sqlite { ); } + pub fn select_ai_hook(&self) -> Result { + return self.conn.query_row( + "SELECT setting_status FROM settings WHERE setting_name = \"settingHook\"", + params![], + |row| Ok(row.get_unwrap(0)), + ); + } + pub fn update_model_vosk_to_whisper( &self, id: u16, diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index d7036ea..3cdaf5c 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -8,7 +8,7 @@ }, "package": { "productName": "Lycoris", - "version": "0.9.0" + "version": "0.9.1" }, "tauri": { "allowlist": { diff --git a/src/components/molecules/SettingAILanguage.tsx b/src/components/molecules/SettingAILanguage.tsx new file mode 100644 index 0000000..c7d59fd --- /dev/null +++ b/src/components/molecules/SettingAILanguage.tsx @@ -0,0 +1,73 @@ +import { ChangeEvent } from "react"; +import { useRecoilState } from 'recoil'; +import { settingKeyState } from "../../store/atoms/settingKeyState"; + +const SettingAILanguage = (): JSX.Element => { + const settingAILanguages = ["None", "Kyoko", "Samantha", "Tingting", "Yuna", "Thomas", "Anna", "Milena", "Mónica", "Luciana", "Yelda", "Linh", "Alice", "Xander", "Montse", "Lesya", "Alva", "Lekha", "Zuzana", "Zosia"] + const [settingKey, setSettingKey] = useRecoilState(settingKeyState("settingAILanguage")) + + const change = (e: ChangeEvent) => { + const settingKey = e.target.value + setSettingKey(settingKey) + } + + const mapper = (aiLanguage: string) => { + switch (aiLanguage) { + case "None": + return "しない"; + case "Kyoko": + return "日本語"; + case "Samantha": + return "英語"; + case "Tingting": + return "中国語"; + case "Yuna": + return "韓国語"; + case "Thomas": + return "フランス語"; + case "Anna": + return "ドイツ語"; + case "Milena": + return "ロシア語"; + case "Mónica": + return "スペイン語"; + case "Luciana": + return "ポルトガル語"; + case "Yelda": + return "トルコ語"; + case "Linh": + return "ベトナム語"; + case "Alice": + return "イタリア語"; + case "Xander": + return "オランダ語"; + case "Montse": + return "カタルーニャ語"; + case "Lesya": + return "ウクライナ語"; + case "Alva": + return "スウェーデン語"; + case "Lekha": + return "ヒンディー語"; + case "Zuzana": + return "チェコ語"; + case "Zosia": + return "ポーランド語"; + default: + throw new Error("unknown languageType"); + } + } + + return ( +
+

発話

+ +
+ ) +} + +export { SettingAILanguage } diff --git a/src/components/molecules/SettingHook.tsx b/src/components/molecules/SettingHook.tsx new file mode 100644 index 0000000..ea20c03 --- /dev/null +++ b/src/components/molecules/SettingHook.tsx @@ -0,0 +1,25 @@ +import { ChangeEvent } from "react"; +import { useRecoilState } from 'recoil'; +import { settingKeyState } from "../../store/atoms/settingKeyState"; + +type SettingHookProps = { + hookName: string; +} + +const SettingHook = (props: SettingHookProps): JSX.Element => { + const { hookName } = props; + const [settingKey, setSettingKey] = useRecoilState(settingKeyState(hookName)) + const change = (e: ChangeEvent) => { + const settingKey = e.target.value + setSettingKey(settingKey) + } + + return ( +
+

CLI Hook

+