88 lines
6.6 KiB
PHP
88 lines
6.6 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace think\facade {
|
|
class Log { public static function warning(string $message): void {} public static function write(string $message, string $level): void {} }
|
|
}
|
|
namespace app\common\service {
|
|
require __DIR__ . '/fixtures/CancellableCurlMultiStub.php';
|
|
function config(string $name): array { return $GLOBALS['uploadTransportConfig']; }
|
|
final class DifyRequiredAttachmentUpload {
|
|
public static function prepare(string $endpoint, string $key, string $user, array $files, int $timeout,
|
|
?callable $shouldAbort = null, array $options = []): array {
|
|
$GLOBALS['uploadTransportStages'][] = compact('endpoint', 'user', 'files', 'timeout');
|
|
if ($GLOBALS['uploadTransportScenario'] === 'cancel') return ['ok' => false, 'error_code' => 'AI_ASSISTANT_CANCELLED', 'error' => '合成取消'];
|
|
if ($GLOBALS['uploadTransportScenario'] === 'upload-failed') return ['ok' => false, 'error_code' => 'REQUIRED_FILES_UPLOAD_FAILED', 'error' => '合成上传失败'];
|
|
$result = [];
|
|
foreach ($files as $index => $file) {
|
|
foreach (['uploading', 'uploaded'] as $status) {
|
|
if (is_callable($options['on_upload'] ?? null)) ($options['on_upload'])(['index' => $index + 1, 'total' => count($files), 'status' => $status]);
|
|
}
|
|
$result[] = ['type' => $file['type'], 'transfer_method' => 'local_file', 'upload_file_id' => 'fixture-upload-' . ($index + 1)];
|
|
}
|
|
return ['ok' => true, 'files' => $result, 'attachment_transport' => 'uploaded_files'];
|
|
}
|
|
}
|
|
function curl_init(): \stdClass { return new \stdClass(); }
|
|
function curl_setopt_array(\stdClass $handle, array $options): bool { $handle->options = $options; return true; }
|
|
function curl_exec(\stdClass $handle): string|bool {
|
|
$payload = json_decode($handle->options[CURLOPT_POSTFIELDS], true);
|
|
$GLOBALS['uploadTransportRequests'][] = $payload;
|
|
$stream = ($payload['response_mode'] ?? '') === 'streaming';
|
|
$handle->code = $GLOBALS['uploadTransportScenario'] === 'inputs' && !empty($payload['inputs']) ? 400 : 200;
|
|
($handle->options[CURLOPT_HEADERFUNCTION])($handle, 'HTTP/1.1 ' . $handle->code . " Synthetic\r\n");
|
|
if ($handle->code !== 200) {
|
|
$body = json_encode(['code' => 'invalid_param', 'message' => 'synthetic_undeclared_input input variable invalid']);
|
|
if ($stream) { ($handle->options[CURLOPT_WRITEFUNCTION])($handle, $body); return true; }
|
|
return $body;
|
|
}
|
|
if (!$stream) return json_encode(['answer' => '合成回答']);
|
|
$write = $handle->options[CURLOPT_WRITEFUNCTION];
|
|
$write($handle, 'data: ' . json_encode(['event' => 'message', 'answer' => '合成回答']) . "\n\n");
|
|
$write($handle, "data: {\"event\":\"message_end\"}\n\n");
|
|
return true;
|
|
}
|
|
function curl_errno(\stdClass $handle): int { return 0; }
|
|
function curl_getinfo(\stdClass $handle, int $option): int { return $handle->code; }
|
|
function curl_close(\stdClass $handle): void {}
|
|
}
|
|
namespace {
|
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
|
use app\common\service\DifyChatService;
|
|
use app\adminapi\service\AiRequiredSource;
|
|
function uploadTransportExpect(bool $ok, string $message): void { if (!$ok) throw new RuntimeException($message); }
|
|
$uploadTransportConfig = ['enable' => true, 'base_url' => 'https://synthetic.invalid/v1/chat-messages',
|
|
'timeout' => 5, 'required_attachment_transport' => 'upload',
|
|
'models' => ['qwen' => ['name' => 'synthetic', 'api_key' => 'fixture-only']]];
|
|
$files = [['type' => 'image', 'url' => 'https://synthetic.invalid/one.png'],
|
|
['type' => 'image', 'url' => 'https://synthetic.invalid/two.png'],
|
|
['type' => 'document', 'url' => 'https://synthetic.invalid/three.pdf']];
|
|
$options = ['required_files' => true, 'knowledge_mode' => 'evidence', 'required_sections' => AiRequiredSource::manifest([])];
|
|
foreach ([false, true] as $stream) foreach (['success', 'inputs', 'upload-failed', 'cancel'] as $scenario) {
|
|
$uploadTransportScenario = $scenario; $uploadTransportStages = []; $uploadTransportRequests = []; $events = [];
|
|
$opts = $options + ['on_progress' => static function (array $event) use (&$events): void { $events[] = $event; }];
|
|
$result = $stream ? DifyChatService::streamChat('qwen', ['synthetic_undeclared_input' => 'value'], '合成资料', 'fixture-same-user',
|
|
static fn (string $text): bool => true, null, $files, $opts)
|
|
: DifyChatService::chat('qwen', ['synthetic_undeclared_input' => 'value'], '合成资料', 'fixture-same-user', $files, $opts);
|
|
uploadTransportExpect(count($uploadTransportStages) === 1, 'all required files are staged once per request');
|
|
uploadTransportExpect($uploadTransportStages[0]['user'] === 'fixture-same-user' && count($uploadTransportStages[0]['files']) === 3,
|
|
'upload and chat share end user and complete source set');
|
|
if (in_array($scenario, ['upload-failed', 'cancel'], true)) {
|
|
uploadTransportExpect(empty($result['ok']) && $uploadTransportRequests === [], 'failed or cancelled upload never starts model generation');
|
|
uploadTransportExpect($result['error_code'] === ($scenario === 'cancel' ? 'AI_ASSISTANT_CANCELLED' : 'REQUIRED_FILES_UPLOAD_FAILED'),
|
|
'upload failure retains actual cause');
|
|
continue;
|
|
}
|
|
uploadTransportExpect(!empty($result['ok']) && $result['attachment_transfer'] === 'uploaded_files', 'final receipt identifies actual uploads');
|
|
uploadTransportExpect(count($uploadTransportRequests) === ($scenario === 'inputs' ? 2 : 1), 'bounded input compatibility remains intact');
|
|
foreach ($uploadTransportRequests as $payload) {
|
|
uploadTransportExpect($payload['user'] === 'fixture-same-user' && count($payload['files']) === 3, 'every generation attempt retains all uploaded files');
|
|
uploadTransportExpect(array_column($payload['files'], 'transfer_method') === ['local_file', 'local_file', 'local_file'], 'Dify gets upload IDs, not resource URLs');
|
|
uploadTransportExpect(!array_key_exists('url', $payload['files'][0]) && $payload['files'][0]['upload_file_id'] === 'fixture-upload-1', 'original URL never leaks into uploaded wire files');
|
|
}
|
|
uploadTransportExpect(count(array_filter($events, static fn (array $event): bool => ($event['status'] ?? '') === 'uploading')) >= 3,
|
|
'safe per-file upload progress reaches the desktop');
|
|
}
|
|
echo "Dify required upload transport / complete IDs / same user / input retry reuse / failure / cancel: OK\n";
|
|
}
|