add gen_voice tool, message timestamps, image multimodal, group chat, whisper STT

- gen_voice: IndexTTS2 voice cloning via tools/gen_voice script, ref audio
  cached on server to avoid re-upload
- Message timestamps: created_at column in messages table, prepended to
  content in API calls so LLM sees message times
- Image understanding: photos converted to base64 multimodal content
  for vision-capable models
- Group chat: independent session contexts per chat_id, sendMessageDraft
  disabled in groups (private chat only)
- Voice transcription: whisper service integration, transcribed text
  injected as [语音消息] prefix
- Integration tests marked #[ignore] (require external services)
- Reference voice asset: assets/ref_voice.mp3
- .gitignore: target/, noc.service, config/state/db files
This commit is contained in:
Fam Zheng
2026-04-09 20:12:15 +01:00
parent 9d5dd4eb16
commit ec1bd7cb25
6 changed files with 370 additions and 54 deletions

View File

@@ -25,6 +25,7 @@ fn tools() -> serde_json::Value {
/// Test non-streaming tool call round-trip
#[tokio::test]
#[ignore] // requires Ollama on ailab
async fn test_tool_call_roundtrip_non_streaming() {
let client = reqwest::Client::new();
let url = format!("{OLLAMA_URL}/chat/completions");
@@ -101,6 +102,7 @@ async fn test_tool_call_roundtrip_non_streaming() {
/// Test tool call with conversation history (simulates real scenario)
#[tokio::test]
#[ignore] // requires Ollama on ailab
async fn test_tool_call_with_history() {
let client = reqwest::Client::new();
let url = format!("{OLLAMA_URL}/chat/completions");
@@ -199,6 +201,7 @@ async fn test_tool_call_with_history() {
/// Test multimodal image input
#[tokio::test]
#[ignore] // requires Ollama on ailab
async fn test_image_multimodal() {
let client = reqwest::Client::new();
let url = format!("{OLLAMA_URL}/chat/completions");
@@ -232,6 +235,7 @@ async fn test_image_multimodal() {
/// Test streaming tool call round-trip (matches our actual code path)
#[tokio::test]
#[ignore] // requires Ollama on ailab
async fn test_tool_call_roundtrip_streaming() {
let client = reqwest::Client::new();
let url = format!("{OLLAMA_URL}/chat/completions");