feat: add scoped read-only audio preview and isolated Dify transport

This commit is contained in:
2026-10-09 16:10:59 +08:00
parent 27772bec61
commit 4d0af7f3f4
36 changed files with 1517 additions and 68 deletions
@@ -7,6 +7,7 @@ namespace app\adminapi\logic\tcm;
use app\common\service\followupaudio\FollowupAudioAccess as Access;
use app\common\service\followupaudio\FollowupAudioApply as Apply;
use app\common\service\followupaudio\FollowupAudioFields as Fields;
use app\common\service\followupaudio\FollowupAudioGate as Gate;
use app\common\service\followupaudio\FollowupAudioProviderConfig as ProviderConfig;
use app\common\service\followupaudio\FollowupAudioStore as Store;
use app\common\service\followupaudio\FollowupAudioUpload as Upload;
@@ -16,15 +17,19 @@ final class FollowupAudioLogic
{
public static function capabilities(int $diagnosisId, int $actor, array $info): array
{
$diagnosis = Access::diagnosis($diagnosisId, $actor, $info);
$enabled = Store::enabled();
$diagnosis = Access::diagnosis($diagnosisId, $actor, $info, false, false);
$scope = Gate::scopeAllowed($diagnosisId, $actor);
$preview = Gate::previewOnly();
$ready = Store::ready();
$enabled = Store::enabled() && $scope;
$verified = Store::verified();
$daily = Access::canDaily($actor, $info);
return [
'enabled' => $enabled, 'audio_verified' => $verified,
'can_upload' => $enabled && $verified, 'can_apply' => $enabled && $verified,
'preview_only' => $preview, 'preview_ready' => $preview && $ready, 'test_scope_allowed' => $scope,
'can_upload' => $enabled && $ready, 'can_review' => $enabled && $ready, 'can_apply' => $enabled && $verified && !$preview,
'can_daily' => $daily, 'limits' => Upload::limits(),
'models' => ProviderConfig::publicModels(),
'models' => $enabled ? ProviderConfig::readyModels() : [],
// No migration/dictionary dependency is introduced when the feature is OFF.
'fields' => $enabled ? self::catalogForActor($diagnosis, $actor, $info) : [],
];
@@ -35,7 +40,7 @@ final class FollowupAudioLogic
if (!Store::enabled()) {
throw new DomainException('回访录音功能尚未启用');
}
if ($verified && !Store::verified()) {
if ($verified && !Store::ready()) {
throw new DomainException('当前服务与模型的音频能力尚未验证,暂不接受真实录音或写入');
}
}
@@ -53,7 +58,7 @@ final class FollowupAudioLogic
if (!$date || $date->format('Y-m-d H:i:s') !== $p['recorded_at'] || $date->getTimestamp() > time() + 300) {
throw new DomainException('请填写正确的实际通话时间(北京时间),不能晚于当前时间');
}
if (!in_array($p['model_key'], ['qwen', 'openai'], true) || !Store::verified($p['model_key'])) {
if (!in_array($p['model_key'], ['qwen', 'openai'], true) || !Store::ready($p['model_key'])) {
throw new DomainException('所选模型音频能力尚未验证');
}
return Store::create($upload, $p['recorded_at'], $p['model_key'], $actor, $info);
@@ -81,6 +86,7 @@ final class FollowupAudioLogic
public static function apply(int $id, int $version, array $items, int $actor, array $info): array
{
Gate::assertMayApply();
self::requireEnabled(true);
$task = Access::task($id, $actor, $info);
self::checkDailyItems($task, $items, $actor, $info);