Files
meeting-assistant/MeetingAssistant/MeetingAssistantOptions.cs
T
codex e192ae7cd8
PR and Push Build/Test / build-and-test (push) Successful in 14m17s
fix: support LiteLLM Responses streaming
2026-07-27 14:44:32 +02:00

479 lines
16 KiB
C#

namespace MeetingAssistant;
public sealed class MeetingAssistantOptions
{
public HotkeyOptions Hotkey { get; set; } = new();
public VaultOptions Vault { get; set; } = new();
public RecordingOptions Recording { get; set; } = new();
public WhisperLocalOptions WhisperLocal { get; set; } = new();
public AzureSpeechOptions AzureSpeech { get; set; } = new();
public FunAsrOptions FunAsr { get; set; } = new();
public SpeakerIdentificationOptions SpeakerIdentification { get; set; } = new();
public AutomationOptions Automation { get; set; } = new();
public CalendarRecordingPromptOptions CalendarRecordingPrompts { get; set; } = new();
public ScreenshotOptions Screenshots { get; set; } = new();
public AgentOptions Agent { get; set; } = new();
public WorkflowRulesEditorOptions WorkflowRulesEditor { get; set; } = new();
public ApiOptions Api { get; set; } = new();
}
public sealed class AutomationOptions
{
public string? RulesPath { get; set; } = "meeting-rules.local.yaml";
}
public sealed class CalendarRecordingPromptOptions
{
public bool Enabled { get; set; } = true;
public TimeSpan SyncInterval { get; set; } = TimeSpan.FromMinutes(30);
public TimeSpan PromptWindow { get; set; } = TimeSpan.FromMinutes(5);
}
public sealed class ScreenshotOptions
{
public string Hotkey { get; set; } = "Ctrl+Alt+S";
public string AttachmentsFolder { get; set; } = "Attachments";
public ScreenshotOcrOptions Ocr { get; set; } = new();
}
public sealed class ScreenshotOcrOptions
{
public const string DefaultPrompt = """
This screenshot was captured during a meeting. Extract information that is useful for meeting notes.
Identify who appears to be talking, who appears to be presenting, and what is being presented when visible.
If people or participant tiles are visible, state whether it is clear from the screenshot exactly who is in the meeting or whether the visible people are only a partial participant result.
Include any attendee names that are clearly visible or strongly deduced from the screenshot in the meeting-assistant metadata. The attendee list may be partial.
If the screenshot contains slides, capture the visible text in clean markdown.
If the screenshot contains a diagram, convert it to Mermaid when possible and preserve labels accurately.
If it contains another kind of shared screen or scene, describe the scene and the relevant visible information.
If you can isolate the actual presentation, shared screen, or similarly relevant meeting content, return crop coordinates for only that content.
Crop coordinates must be pixel coordinates relative to the original screenshot: x, y, width, height.
Do not return crop coordinates for a person, webcam tile, whole app chrome, or content you cannot isolate confidently.
End your response with exactly one fenced json block containing meeting-assistant metadata, for example:
```json
{ "crop": { "x": 0, "y": 0, "width": 1920, "height": 1080 }, "attendees": ["Ada Lovelace"] }
```
Use `{ "crop": null, "attendees": [] }` when no confident presentation/shared-screen crop exists and no attendees are visible or deducible.
Do not invent information that is not visible in the screenshot.
""";
public bool Enabled { get; set; }
public string? Endpoint { get; set; }
public string? Key { get; set; }
public string KeyEnv { get; set; } = "LITELLM_API_KEY";
public string? Model { get; set; }
public string Prompt { get; set; } = DefaultPrompt;
public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(2);
public TimeSpan GetEffectiveTimeout()
{
return Timeout > TimeSpan.Zero ? Timeout : TimeSpan.FromMinutes(2);
}
}
public sealed class HotkeyOptions
{
public string Toggle { get; set; } = "Ctrl+Alt+M";
public string Abort { get; set; } = "Ctrl+Alt+Z";
}
public sealed class VaultOptions
{
public string BaseFolder { get; set; } = @"%USERPROFILE%\OpenCloud\Persönlich\Vault\Exxeta";
public string TranscriptsFolder { get; set; } = @"Meetings\Transcripts";
public string MeetingNotesFolder { get; set; } = @"Meetings\Notes";
public string AssistantContextFolder { get; set; } = @"Meetings\Assistant Context";
public string SummariesFolder { get; set; } = @"Meetings\Summaries";
public string ProjectsFolder { get; set; } = @"Projects";
public string DictationWordsPath { get; set; } = @"Meetings\dictation-words.md";
}
public sealed class RecordingOptions
{
public string TranscriptionProvider { get; set; } = "azure-speech";
public int SampleRate { get; set; } = 16000;
public int Channels { get; set; } = 1;
public string? MicrophoneDeviceId { get; set; }
public double MicrophoneMixGain { get; set; } = 1;
public double SystemAudioMixGain { get; set; } = 1;
public TimeSpan StopProcessingTimeout { get; set; } = TimeSpan.FromMinutes(10);
public TimeSpan MinimumCompletedMeetingDuration { get; set; } = TimeSpan.Zero;
public TimeSpan MetadataLookupTimeout { get; set; } = TimeSpan.FromSeconds(10);
public TimeSpan BackgroundMetadataLookupTimeout { get; set; } = TimeSpan.FromMinutes(1);
public int MaxMetadataAttendeeImportCount { get; set; } = 30;
public TimeSpan OpenMeetingNoteDelay { get; set; } = TimeSpan.FromSeconds(1);
public TimeSpan DictationWordsReadTimeout { get; set; } = TimeSpan.FromSeconds(2);
public string TemporaryRecordingsFolder { get; set; } = @"%LOCALAPPDATA%\MeetingAssistant\Recordings";
public RecordingInactivitySafeguardOptions InactivitySafeguard { get; set; } = new();
}
public sealed class RecordingInactivitySafeguardOptions
{
public bool Enabled { get; set; } = true;
public TimeSpan FirstPromptAfter { get; set; } = TimeSpan.FromMinutes(2);
public TimeSpan[] ReminderPromptAfter { get; set; } =
[
TimeSpan.FromMinutes(5),
TimeSpan.FromMinutes(10)
];
public TimeSpan AutoStopAfter { get; set; } = TimeSpan.FromMinutes(30);
public TimeSpan InferredEndPadding { get; set; } = TimeSpan.FromMinutes(1);
public TimeSpan CheckInterval { get; set; } = TimeSpan.FromSeconds(15);
}
public sealed class WhisperLocalOptions
{
public string ModelPath { get; set; } = @"models\ggml-base.bin";
public string Language { get; set; } = "auto";
public double WindowSeconds { get; set; } = 15;
public PyannoteDiarizationOptions Diarization { get; set; } = new();
}
public sealed class PyannoteDiarizationOptions
{
public bool Enabled { get; set; } = true;
public string DockerCommand { get; set; } = "docker";
public string BaseImage { get; set; } = "python:3.11-slim";
public string Image { get; set; } = "meeting-assistant-pyannote:local";
public bool BuildImage { get; set; } = true;
public string ModelsFolder { get; set; } = @"%LOCALAPPDATA%\MeetingAssistant\Pyannote\models";
public string Model { get; set; } = "pyannote/speaker-diarization-3.1";
public PyannoteAnnotationSource AnnotationSource { get; set; } = PyannoteAnnotationSource.SpeakerDiarization;
public PyannoteAlignmentMode AlignmentMode { get; set; } = PyannoteAlignmentMode.PyannoteTurns;
public string TokenEnv { get; set; } = "HF_TOKEN";
public string? Token { get; set; }
public TimeSpan CommandTimeout { get; set; } = TimeSpan.FromMinutes(30);
}
public sealed class AzureSpeechOptions
{
public string Endpoint { get; set; } = "";
public string Region { get; set; } = "westeurope";
public string Language { get; set; } = "auto";
public string[] AutoDetectLanguages { get; set; } = ["de-DE", "en-US"];
public string LanguageIdMode { get; set; } = "Continuous";
public string? Key { get; set; }
public string KeyEnv { get; set; } = "AZURE_SPEECH_KEY";
public TimeSpan RecognitionStopTimeout { get; set; } = TimeSpan.FromMinutes(3);
public int ReconnectAttemptsBeforeDisconnectedMarker { get; set; } = 5;
public TimeSpan ReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
public bool DiarizeIntermediateResults { get; set; } = true;
public string? PostProcessingOption { get; set; }
public double PhraseListWeight { get; set; } = 1.5;
}
public enum PyannoteAnnotationSource
{
SpeakerDiarization,
ExclusiveSpeakerDiarization
}
public enum PyannoteAlignmentMode
{
BestOverlap,
PyannoteTurns
}
public sealed class FunAsrOptions
{
public string Endpoint { get; set; } = "ws://127.0.0.1:10095";
public string Mode { get; set; } = "2pass";
public int[] ChunkSize { get; set; } = [5, 10, 5];
public int ChunkInterval { get; set; } = 10;
public int EncoderChunkLookBack { get; set; } = 4;
public int DecoderChunkLookBack { get; set; } = 1;
public bool Itn { get; set; } = true;
public bool EmitOnlinePartials { get; set; }
public string WavName { get; set; } = "meeting";
public TimeSpan FinalResultTimeout { get; set; } = TimeSpan.FromSeconds(10);
public FunAsrBackendOptions Backend { get; set; } = new();
public FunAsrDiarizationOptions Diarization { get; set; } = new();
}
public sealed class FunAsrBackendOptions
{
public bool Enabled { get; set; } = true;
public string DockerCommand { get; set; } = "docker";
public string Image { get; set; } = "registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-online-cpu-0.1.13";
public string ContainerName { get; set; } = "meeting-assistant-funasr";
public string ModelsFolder { get; set; } = @"%LOCALAPPDATA%\MeetingAssistant\FunASR\models";
public int ContainerPort { get; set; } = 10095;
public bool Privileged { get; set; } = true;
public TimeSpan CommandTimeout { get; set; } = TimeSpan.FromMinutes(15);
public TimeSpan StartupTimeout { get; set; } = TimeSpan.FromMinutes(10);
public bool StopOnDispose { get; set; } = true;
public string ServerCommand { get; set; } = "cd /workspace/FunASR/runtime && bash run_server_2pass.sh --download-model-dir /workspace/models --vad-dir damo/speech_fsmn_vad_zh-cn-16k-common-onnx --model-dir damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx --online-model-dir damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online-onnx --punc-dir damo/punc_ct-transformer_zh-cn-common-vad_realtime-vocab272727-onnx --itn-dir thuduj12/fst_itn_zh --certfile 0 --hotword /workspace/models/hotwords.txt && tail -f /dev/null";
}
public sealed class FunAsrDiarizationOptions
{
public bool Enabled { get; set; } = true;
public string AsrModel { get; set; } = "paraformer-zh";
public string VadModel { get; set; } = "fsmn-vad";
public string PunctuationModel { get; set; } = "ct-punc";
public string SpeakerModel { get; set; } = "cam++";
public double BatchSizeSeconds { get; set; } = 300;
public double BatchSizeThresholdSeconds { get; set; } = 60;
public int? PresetSpeakerCount { get; set; }
public TimeSpan CommandTimeout { get; set; } = TimeSpan.FromMinutes(30);
}
public sealed class SpeakerIdentificationOptions
{
public bool Enabled { get; set; } = true;
public string DatabasePath { get; set; } = @"%LOCALAPPDATA%\MeetingAssistant\SpeakerIdentity\speaker-identities.db";
public TimeSpan InitialDelay { get; set; } = TimeSpan.FromMinutes(2);
public TimeSpan Interval { get; set; } = TimeSpan.FromMinutes(2);
public int MatchBatchSize { get; set; } = 6;
public int MaxMatchCandidates { get; set; } = 100;
public TimeSpan MatchIdentityActiveAge { get; set; } = TimeSpan.FromDays(365);
public int MaxSnippetsPerSpeaker { get; set; } = 3;
public TimeSpan MinimumSampleSpeechDuration { get; set; } = TimeSpan.FromSeconds(30);
public TimeSpan MaximumSampleSegmentGap { get; set; } = TimeSpan.FromSeconds(1);
public double SilenceBetweenSnippetsSeconds { get; set; } = 1;
public TimeSpan LiveSampleBufferDuration { get; set; } = TimeSpan.FromMinutes(10);
public TimeSpan MergeRecentIdentityAge { get; set; } = TimeSpan.FromDays(14);
public TimeSpan MatchTimeout { get; set; } = TimeSpan.FromMinutes(3);
public AzureSpeechOptions AzureSpeech { get; set; } = new();
public SpeakerIdentityPyannoteValidationOptions PyannoteValidation { get; set; } = new();
}
public sealed class SpeakerIdentityPyannoteValidationOptions
{
public bool Enabled { get; set; }
public double MinimumSingleSpeakerCoverage { get; set; } = 0.90;
public double MinimumMatchingKnownSnippetRatio { get; set; } = 0.50;
public PyannoteDiarizationOptions Diarization { get; set; } = new()
{
Enabled = true,
CommandTimeout = TimeSpan.FromHours(1),
AlignmentMode = PyannoteAlignmentMode.PyannoteTurns
};
}
public sealed class AgentOptions
{
public string Endpoint { get; set; } = "https://litellm.schweigert.cloud";
public string? Key { get; set; }
public string KeyEnv { get; set; } = "LITELLM_API_KEY";
public string Model { get; set; } = "chatgpt/gpt-5.5";
public bool UseStreaming { get; set; } = true;
public bool EnableThinking { get; set; } = true;
public ReasoningEffortOption ReasoningEffort { get; set; } = ReasoningEffortOption.Medium;
public int ReconnectionAttempts { get; set; } = 2;
public TimeSpan ReconnectionDelay { get; set; } = TimeSpan.FromSeconds(2);
public int ContextWindowTokens { get; set; } = 128000;
public int MaxOutputTokens { get; set; } = 8192;
public bool EnableCompaction { get; set; } = true;
public double CompactionRemainingRatio { get; set; } = 0.10;
public string ResponsesCompactPath { get; set; } = "responses/compact";
public string? InitialPrompt { get; set; }
}
public sealed class WorkflowRulesEditorOptions
{
public string? Endpoint { get; set; }
public string? Key { get; set; }
public string? KeyEnv { get; set; }
public string? Model { get; set; }
public bool? UseStreaming { get; set; }
public bool? EnableThinking { get; set; }
public ReasoningEffortOption? ReasoningEffort { get; set; }
public int? ReconnectionAttempts { get; set; }
public TimeSpan? ReconnectionDelay { get; set; }
public int? ContextWindowTokens { get; set; }
public int? MaxOutputTokens { get; set; }
public bool? EnableCompaction { get; set; }
public double? CompactionRemainingRatio { get; set; }
public string? ResponsesCompactPath { get; set; }
public string? InitialPrompt { get; set; }
public AgentOptions ToEffectiveAgentOptions(AgentOptions defaults)
{
return new AgentOptions
{
Endpoint = string.IsNullOrWhiteSpace(Endpoint) ? defaults.Endpoint : Endpoint,
Key = string.IsNullOrWhiteSpace(Key) ? defaults.Key : Key,
KeyEnv = string.IsNullOrWhiteSpace(KeyEnv) ? defaults.KeyEnv : KeyEnv!,
Model = string.IsNullOrWhiteSpace(Model) ? defaults.Model : Model!,
UseStreaming = UseStreaming ?? defaults.UseStreaming,
EnableThinking = EnableThinking ?? defaults.EnableThinking,
ReasoningEffort = ReasoningEffort ?? defaults.ReasoningEffort,
ReconnectionAttempts = ReconnectionAttempts ?? defaults.ReconnectionAttempts,
ReconnectionDelay = ReconnectionDelay ?? defaults.ReconnectionDelay,
ContextWindowTokens = ContextWindowTokens ?? defaults.ContextWindowTokens,
MaxOutputTokens = MaxOutputTokens ?? defaults.MaxOutputTokens,
EnableCompaction = EnableCompaction ?? defaults.EnableCompaction,
CompactionRemainingRatio = CompactionRemainingRatio ?? defaults.CompactionRemainingRatio,
ResponsesCompactPath = string.IsNullOrWhiteSpace(ResponsesCompactPath)
? defaults.ResponsesCompactPath
: ResponsesCompactPath!,
InitialPrompt = string.IsNullOrWhiteSpace(InitialPrompt) ? null : InitialPrompt
};
}
}
public sealed class ApiOptions
{
public string PublicBaseUrl { get; set; } = "http://localhost:5090";
}
public enum ReasoningEffortOption
{
None,
Low,
Medium,
High,
ExtraHigh
}