Files
meeting-assistant/MeetingAssistant/MeetingAssistantOptions.cs
T
codex 1a89382f02
PR and Push Build/Test / build-and-test (push) Failing after 9m48s
Improve toast duration and speaker override samples
2026-06-05 10:52:08 +02:00

467 lines
15 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.
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 } }
```
Use `{ "crop": null }` when no confident presentation/shared-screen crop exists.
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 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 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 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? 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!,
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
}