This commit is contained in:
Your Name
2026-08-18 14:08:38 +08:00
parent 8b9df1154c
commit bc1228a310
77 changed files with 10763 additions and 1181 deletions
@@ -21,7 +21,8 @@ use app\adminapi\logic\tcm\DiagnosisAiLogic;
use app\adminapi\logic\tcm\DiagnosisLogic;
use app\adminapi\logic\tcm\PatientAiReportLogic;
use app\adminapi\logic\tcm\TrackingNoteLogic;
use app\adminapi\validate\tcm\DiagnosisValidate;
use app\adminapi\service\AssistantSseProtocol;
use app\adminapi\validate\tcm\DiagnosisValidate;
use app\common\model\Order;
use app\common\model\WechatChatRecord;
@@ -168,10 +169,13 @@ class DiagnosisController extends BaseAdminController
*
* @return \think\response\Json
*/
public function trackingWindow()
{
$params = (new DiagnosisValidate())->goCheck('trackingWindow');
$result = DiagnosisLogic::fetchTrackingWindow(
public function trackingWindow()
{
$params = (new DiagnosisValidate())->goCheck('trackingWindow');
if (!DiagnosisLogic::canViewReadonlyDiagnosis((int) $params['id'], $this->adminId, $this->adminInfo)) {
return $this->fail(DiagnosisLogic::getError() ?: '诊单不存在或无权访问');
}
$result = DiagnosisLogic::fetchTrackingWindow(
(int) $params['id'],
(string) ($params['start_date'] ?? ''),
(string) ($params['end_date'] ?? '')
@@ -865,9 +869,9 @@ class DiagnosisController extends BaseAdminController
/**
* @notes 基于当前授权诊单向 AI 助手提问,不接收客户端上游配置
*/
public function aiAssistant()
{
$params = (new DiagnosisValidate())->post()->goCheck('aiAssistant');
public function aiAssistant()
{
$params = (new DiagnosisValidate())->post()->goCheck('aiAssistant');
$result = DiagnosisAiLogic::assistant(
(int) $params['id'],
(string) $params['task'],
@@ -877,11 +881,113 @@ class DiagnosisController extends BaseAdminController
);
if ($result === null) {
return $this->fail(DiagnosisAiLogic::getError());
}
return $this->data($result);
}
/**
}
return $this->data($result);
}
/**
* @notes 基于当前授权诊单向 AI 助手提问(SSE 真流式)
*
* 路由:POST tcm.diagnosis/aiAssistantStream body: id, task, prompt
*/
public function aiAssistantStream()
{
// 登录由全局中间件完成;请求校验、旧助手权限与 DataScope 必须全部
// 在任何 SSE header / start 事件之前完成,失败时仍返回标准 JSON。
$params = (new DiagnosisValidate())->post()->goCheck('aiAssistant');
$prepared = DiagnosisAiLogic::prepareAssistant(
(int) $params['id'],
(string) $params['task'],
(string) ($params['prompt'] ?? ''),
$this->adminId,
$this->adminInfo
);
if ($prepared === null) {
return $this->fail(DiagnosisAiLogic::getError());
}
$this->runAssistantSse($prepared);
}
/** @param array<string,mixed> $prepared */
private function runAssistantSse(array $prepared): void
{
while (ob_get_level() > 0) {
ob_end_clean();
}
@ini_set('output_buffering', 'off');
@ini_set('zlib.output_compression', '0');
ignore_user_abort(true);
if (function_exists('apache_setenv')) {
@apache_setenv('no-gzip', '1');
}
header('Content-Type: text/event-stream; charset=utf-8');
header('Cache-Control: no-cache, no-transform');
header('Connection: keep-alive');
header('X-Accel-Buffering: no');
header('Content-Encoding: none');
echo ':' . str_repeat(' ', 2048) . "\n\n";
$this->flushSseOutput();
$protocol = new AssistantSseProtocol();
$emit = function (string $event, array $payload) use ($protocol): bool {
if ($protocol->isTerminal() || connection_aborted()) {
return false;
}
$encoded = $protocol->encode($event, $payload);
if ($encoded === null) {
return false;
}
echo $encoded;
$this->flushSseOutput();
return !connection_aborted();
};
$emit('start', [
'task' => (string) ($prepared['task'] ?? ''),
'model_key' => (string) ($prepared['profile'] ?? ''),
'message' => '已连接,正在生成…',
]);
try {
$result = DiagnosisAiLogic::streamPreparedAssistant(
$prepared,
static fn (string $delta): bool => $emit('delta', ['text' => $delta]),
static fn (): bool => connection_aborted() === 1
);
if (connection_aborted()) {
exit;
}
if ($result === null) {
$emit('error', [
'code' => 'AI_ASSISTANT_FAILED',
'message' => 'AI 助手暂时不可用,请稍后重试',
]);
} else {
$emit('done', $result);
}
} catch (\Throwable $e) {
$emit('error', [
'code' => 'AI_ASSISTANT_FAILED',
'message' => 'AI 助手暂时不可用,请稍后重试',
]);
}
exit;
}
private function flushSseOutput(): void
{
if (function_exists('ob_flush')) {
@ob_flush();
}
flush();
}
/**
* @notes 对当前授权诊单生成一次结构化 AI 智能分析,仅接受 qwen/openai 模型键
*/
public function aiAnalysis()