Add premium My Case MVP
This commit is contained in:
+28
-1
@@ -3,6 +3,8 @@ declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||||
require_once __DIR__ . '/../includes/BvjAnalyzerAgent.php';
|
||||
require_once __DIR__ . '/../includes/CaseResults.php';
|
||||
require_once __DIR__ . '/../includes/ToolModels.php';
|
||||
|
||||
dbnToolsRequireMethod('POST');
|
||||
dbnToolsRequireAuth();
|
||||
@@ -54,7 +56,7 @@ try {
|
||||
|
||||
$language = dbnToolsNormalizeLanguage($input['language'] ?? 'en');
|
||||
$advocateRole = trim((string)($input['advocate_role'] ?? ''));
|
||||
$engine = (string)($input['engine'] ?? 'azure_mini');
|
||||
$engine = ToolModels::engineForUser($ftUid, (string)($input['engine'] ?? 'azure_mini'));
|
||||
$sliceInput = $input['slices'] ?? [];
|
||||
$controls = is_array($input['controls'] ?? null) ? $input['controls'] : [];
|
||||
$additionalNotes = mb_substr(trim((string)($input['additional_notes'] ?? '')), 0, 2000, 'UTF-8');
|
||||
@@ -112,6 +114,17 @@ try {
|
||||
'file_count' => count($uploadedFiles),
|
||||
]);
|
||||
|
||||
// Optional: append user's case-context chunks to the last uploaded document text,
|
||||
// so the agent reads them as supplementary background.
|
||||
$useMyCase = !empty($input['use_my_case']);
|
||||
if ($useMyCase && !empty($uploadedFiles)) {
|
||||
$retrievalQuery = mb_substr((string)$uploadedFiles[0]['text'], 0, 2000, 'UTF-8');
|
||||
$caseBlock = dbnToolsCaseContext(true, $retrievalQuery, 5);
|
||||
if ($caseBlock !== '') {
|
||||
$uploadedFiles[0]['text'] .= "\n\n" . $caseBlock;
|
||||
}
|
||||
}
|
||||
|
||||
$result = (new DbnBvjAnalyzerAgent())->run(
|
||||
$uploadedFiles,
|
||||
$advocateRole,
|
||||
@@ -138,6 +151,20 @@ try {
|
||||
'bvj_doc_type' => $result['doc_meta']['doc_type'] ?? null,
|
||||
]);
|
||||
|
||||
if ($ftUid > 0) {
|
||||
$ownerId = CaseStore::caseResolveClientId($ftUid);
|
||||
$resultId = CaseResults::save($ftUid, $ownerId, 'barnevernet', $input, $result, [
|
||||
'used_case_context' => $useMyCase ? 1 : 0,
|
||||
'case_doc_ids' => dbnToolsLastCaseDocIds(),
|
||||
'model' => $result['trace_metadata']['deployment'] ?? $engine,
|
||||
'latency_ms' => $result['latency_ms'],
|
||||
'credits_charged' => FreeTier::cost('barnevernet'),
|
||||
]);
|
||||
if ($resultId > 0) {
|
||||
$result['result_id'] = $resultId;
|
||||
}
|
||||
}
|
||||
|
||||
$emit('final', ['result' => $result]);
|
||||
|
||||
} catch (DbnToolsHttpException $e) {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../includes/bootstrap.php';
|
||||
require_once __DIR__ . '/../../includes/CaseResults.php';
|
||||
|
||||
dbnToolsRequireMethod('POST');
|
||||
dbnToolsRequireAuth();
|
||||
|
||||
if (!dbnToolsIsFreeTier()) {
|
||||
dbnToolsError('Saved analyses are SSO-only.', 403, 'sso_only');
|
||||
}
|
||||
|
||||
$userId = (int)($_SESSION['dbn_tools_sso_uid'] ?? 0);
|
||||
if ($userId <= 0) {
|
||||
dbnToolsError('Missing user id.', 401, 'no_user');
|
||||
}
|
||||
|
||||
$input = dbnToolsJsonInput(4000);
|
||||
$action = (string)($input['action'] ?? '');
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
dbnToolsError('Missing result id.', 422, 'missing_id');
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'pin':
|
||||
$pinned = CaseResults::togglePin($userId, $id);
|
||||
if ($pinned === null) {
|
||||
dbnToolsError('Result not found.', 404, 'not_found');
|
||||
}
|
||||
dbnToolsRespond(['ok' => true, 'pinned' => $pinned]);
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
if (!CaseResults::softDelete($userId, $id)) {
|
||||
dbnToolsError('Result not found or already deleted.', 404, 'not_found');
|
||||
}
|
||||
dbnToolsRespond(['ok' => true]);
|
||||
break;
|
||||
|
||||
case 'rename':
|
||||
$title = (string)($input['title'] ?? '');
|
||||
if (!CaseResults::updateTitle($userId, $id, $title)) {
|
||||
dbnToolsError('Could not rename — empty title or result not found.', 422, 'rename_failed');
|
||||
}
|
||||
dbnToolsRespond(['ok' => true]);
|
||||
break;
|
||||
|
||||
default:
|
||||
dbnToolsError('Unknown action.', 422, 'unknown_action');
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../includes/bootstrap.php';
|
||||
require_once __DIR__ . '/../../includes/CaseResults.php';
|
||||
|
||||
dbnToolsRequireMethod('GET');
|
||||
dbnToolsRequireAuth();
|
||||
|
||||
if (!dbnToolsIsFreeTier()) {
|
||||
http_response_code(403);
|
||||
exit('Saved analyses are SSO-only.');
|
||||
}
|
||||
|
||||
$userId = (int)($_SESSION['dbn_tools_sso_uid'] ?? 0);
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
|
||||
if ($userId <= 0 || $id <= 0) {
|
||||
http_response_code(404);
|
||||
exit('Not found.');
|
||||
}
|
||||
|
||||
$result = CaseResults::get($userId, $id);
|
||||
if (!$result) {
|
||||
http_response_code(404);
|
||||
exit('Not found.');
|
||||
}
|
||||
|
||||
$filename = sprintf(
|
||||
'dbn-%s-%d-%s.json',
|
||||
$result['tool'],
|
||||
$id,
|
||||
date('Ymd-His', strtotime((string)$result['created_at']))
|
||||
);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
header('Cache-Control: no-store');
|
||||
|
||||
echo json_encode([
|
||||
'id' => (int)$result['id'],
|
||||
'tool' => $result['tool'],
|
||||
'title' => $result['title'],
|
||||
'created_at' => $result['created_at'],
|
||||
'model' => $result['model'],
|
||||
'latency_ms' => $result['latency_ms'],
|
||||
'used_case_context' => (bool)$result['used_case_context'],
|
||||
'case_doc_ids' => $result['case_doc_ids'],
|
||||
'input' => $result['input_payload'],
|
||||
'output' => $result['output_payload'],
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
+28
-1
@@ -3,6 +3,8 @@ declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||||
require_once __DIR__ . '/../includes/DeepResearchAgent.php';
|
||||
require_once __DIR__ . '/../includes/CaseResults.php';
|
||||
require_once __DIR__ . '/../includes/ToolModels.php';
|
||||
|
||||
dbnToolsRequireMethod('POST');
|
||||
dbnToolsRequireAuth();
|
||||
@@ -59,7 +61,7 @@ try {
|
||||
$seedQuery = trim((string)($input['query'] ?? ''));
|
||||
$pastedText = trim((string)($input['paste_text'] ?? ''));
|
||||
$sliceInput = $input['slices'] ?? [];
|
||||
$engine = (string)($input['engine'] ?? 'azure_mini');
|
||||
$engine = ToolModels::engineForUser($ftUid, (string)($input['engine'] ?? 'azure_mini'));
|
||||
$controls = is_array($input['controls'] ?? null) ? $input['controls'] : [];
|
||||
$advocateRole = trim((string)($input['advocate_role'] ?? ''));
|
||||
if (mb_strlen($advocateRole, 'UTF-8') > 200) {
|
||||
@@ -115,6 +117,16 @@ try {
|
||||
'upload_count' => count($uploadedFiles),
|
||||
]);
|
||||
|
||||
// Optional: append user's case-context chunks to pasted_text so the agent sees them.
|
||||
$useMyCase = !empty($input['use_my_case']);
|
||||
if ($useMyCase) {
|
||||
$retrievalQuery = $seedQuery !== '' ? $seedQuery : mb_substr($pastedText, 0, 2000, 'UTF-8');
|
||||
$caseBlock = dbnToolsCaseContext(true, $retrievalQuery, 5);
|
||||
if ($caseBlock !== '') {
|
||||
$pastedText = ($pastedText === '') ? $caseBlock : ($pastedText . "\n\n" . $caseBlock);
|
||||
}
|
||||
}
|
||||
|
||||
$result = (new DbnDeepResearchAgent())->run(
|
||||
$seedQuery,
|
||||
$pastedText,
|
||||
@@ -144,6 +156,21 @@ try {
|
||||
'advocate_role' => $advocateRole !== '' ? $advocateRole : null,
|
||||
]);
|
||||
|
||||
if ($ftUid > 0) {
|
||||
$toolSlug = $advocateRole !== '' ? 'advocate' : 'deep-research';
|
||||
$ownerId = CaseStore::caseResolveClientId($ftUid);
|
||||
$resultId = CaseResults::save($ftUid, $ownerId, $toolSlug, $input, $result, [
|
||||
'used_case_context' => $useMyCase ? 1 : 0,
|
||||
'case_doc_ids' => dbnToolsLastCaseDocIds(),
|
||||
'model' => $result['trace_metadata']['deployment'] ?? $engine,
|
||||
'latency_ms' => $result['latency_ms'],
|
||||
'credits_charged' => FreeTier::cost($toolSlug),
|
||||
]);
|
||||
if ($resultId > 0) {
|
||||
$result['result_id'] = $resultId;
|
||||
}
|
||||
}
|
||||
|
||||
$emit('final', ['result' => $result]);
|
||||
|
||||
} catch (DbnToolsHttpException $e) {
|
||||
|
||||
+27
-1
@@ -3,6 +3,8 @@ declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||||
require_once __DIR__ . '/../includes/DiscrepancyAgent.php';
|
||||
require_once __DIR__ . '/../includes/CaseResults.php';
|
||||
require_once __DIR__ . '/../includes/ToolModels.php';
|
||||
|
||||
dbnToolsRequireMethod('POST');
|
||||
dbnToolsRequireAuth();
|
||||
@@ -42,7 +44,7 @@ try {
|
||||
}
|
||||
|
||||
$language = dbnToolsNormalizeLanguage($input['language'] ?? 'en');
|
||||
$engine = (string)($input['engine'] ?? 'azure_mini');
|
||||
$engine = ToolModels::engineForUser($ftUid, (string)($input['engine'] ?? 'azure_mini'));
|
||||
$sliceInput = $input['slices'] ?? [];
|
||||
|
||||
// Extract file A
|
||||
@@ -111,6 +113,16 @@ try {
|
||||
'file_b' => $fileB['filename'],
|
||||
]);
|
||||
|
||||
// Optional: append the user's case-context as supplementary background to Doc A.
|
||||
$useMyCase = !empty($input['use_my_case']);
|
||||
if ($useMyCase) {
|
||||
$retrievalQuery = mb_substr((string)$fileA['text'], 0, 2000, 'UTF-8');
|
||||
$caseBlock = dbnToolsCaseContext(true, $retrievalQuery, 5);
|
||||
if ($caseBlock !== '') {
|
||||
$fileA['text'] .= "\n\n" . $caseBlock;
|
||||
}
|
||||
}
|
||||
|
||||
$result = (new DbnDiscrepancyAgent())->run(
|
||||
$fileA,
|
||||
$fileB,
|
||||
@@ -135,6 +147,20 @@ try {
|
||||
'deployment' => $result['trace_metadata']['deployment'] ?? null,
|
||||
]);
|
||||
|
||||
if ($ftUid > 0) {
|
||||
$ownerId = CaseStore::caseResolveClientId($ftUid);
|
||||
$resultId = CaseResults::save($ftUid, $ownerId, 'discrepancy', $input, $result, [
|
||||
'used_case_context' => $useMyCase ? 1 : 0,
|
||||
'case_doc_ids' => dbnToolsLastCaseDocIds(),
|
||||
'model' => $result['trace_metadata']['deployment'] ?? $engine,
|
||||
'latency_ms' => $result['latency_ms'],
|
||||
'credits_charged' => FreeTier::cost('discrepancy'),
|
||||
]);
|
||||
if ($resultId > 0) {
|
||||
$result['result_id'] = $resultId;
|
||||
}
|
||||
}
|
||||
|
||||
$emit('final', ['result' => $result]);
|
||||
|
||||
} catch (DbnToolsHttpException $e) {
|
||||
|
||||
@@ -3,6 +3,7 @@ declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||||
require_once __DIR__ . '/../includes/KorrespondAgent.php';
|
||||
require_once __DIR__ . '/../includes/CaseResults.php';
|
||||
|
||||
dbnToolsRequireMethod('POST');
|
||||
dbnToolsRequireAuth();
|
||||
@@ -184,6 +185,21 @@ try {
|
||||
'deployment' => 'gpt-4o',
|
||||
]);
|
||||
|
||||
// Premium: persist the run for paid (Plus/Pro) users so it shows up in Min Sak → Saved analyses.
|
||||
if ($ftUid > 0) {
|
||||
$ownerId = CaseStore::caseResolveClientId($ftUid);
|
||||
$resultId = CaseResults::save($ftUid, $ownerId, 'korrespond', $input, $result, [
|
||||
'used_case_context' => !empty($intake['use_my_case']) ? 1 : 0,
|
||||
'case_doc_ids' => dbnToolsLastCaseDocIds(),
|
||||
'model' => 'gpt-4o',
|
||||
'latency_ms' => $result['latency_ms'],
|
||||
'credits_charged' => FreeTier::cost('korrespond'),
|
||||
]);
|
||||
if ($resultId > 0) {
|
||||
$result['result_id'] = $resultId;
|
||||
}
|
||||
}
|
||||
|
||||
$emit('final', ['result' => $result]);
|
||||
|
||||
} catch (DbnToolsHttpException $e) {
|
||||
|
||||
+10
-1
@@ -18,7 +18,7 @@ if ($userId <= 0 || $email === '') {
|
||||
$input = dbnToolsJsonInput(2000);
|
||||
$sku = (string)($input['sku'] ?? '');
|
||||
|
||||
$validSubscriptions = ['light', 'pro', 'pro_plus'];
|
||||
$validSubscriptions = ['plus', 'pro'];
|
||||
$validTopups = ['topup_s', 'topup_m', 'topup_l'];
|
||||
|
||||
if (!in_array($sku, array_merge($validSubscriptions, $validTopups), true)) {
|
||||
@@ -55,9 +55,18 @@ try {
|
||||
];
|
||||
|
||||
if ($isSub) {
|
||||
FreeTier::ensureRow($userId);
|
||||
$detail = FreeTier::balanceDetail($userId);
|
||||
$params['subscription_data'] = [
|
||||
'metadata' => ['user_id' => (string)$userId, 'tier' => $sku],
|
||||
];
|
||||
if ($sku === 'plus' && empty($detail['trial_started_at'])) {
|
||||
$params['subscription_data']['trial_period_days'] = 14;
|
||||
$params['subscription_data']['trial_settings'] = [
|
||||
'end_behavior' => ['missing_payment_method' => 'cancel'],
|
||||
];
|
||||
}
|
||||
$params['payment_method_collection'] = 'always';
|
||||
} else {
|
||||
$params['payment_intent_data'] = [
|
||||
'metadata' => ['user_id' => (string)$userId, 'sku' => $sku, 'credits' => (string)StripeClient::topupCredits($sku)],
|
||||
|
||||
@@ -76,6 +76,10 @@ try {
|
||||
handleSubscriptionDeleted($db, $object);
|
||||
break;
|
||||
|
||||
case 'customer.subscription.trial_will_end':
|
||||
// Stripe sends the customer reminder email. We mirror state on subscription.updated/deleted.
|
||||
break;
|
||||
|
||||
case 'invoice.paid':
|
||||
handleInvoicePaid($db, $object);
|
||||
break;
|
||||
@@ -154,8 +158,10 @@ function handleSubscriptionChange(PDO $db, array $sub): void
|
||||
|
||||
$periodEndTs = (int)($sub['current_period_end'] ?? 0);
|
||||
$periodStartTs = (int)($sub['current_period_start'] ?? 0);
|
||||
$trialEndTs = (int)($sub['trial_end'] ?? 0);
|
||||
$periodEndIso = $periodEndTs > 0 ? gmdate('Y-m-d H:i:s', $periodEndTs) : null;
|
||||
$periodStartIso = $periodStartTs > 0 ? gmdate('Y-m-d H:i:s', $periodStartTs) : null;
|
||||
$trialEndIso = $trialEndTs > 0 ? gmdate('Y-m-d H:i:s', $trialEndTs) : null;
|
||||
|
||||
// Upsert subscription ledger
|
||||
$db->prepare(
|
||||
@@ -172,7 +178,7 @@ function handleSubscriptionChange(PDO $db, array $sub): void
|
||||
|
||||
// Only flip the live tier flag if subscription is active/trialing.
|
||||
if (in_array($status, ['active', 'trialing'], true)) {
|
||||
FreeTier::setTier($userId, $tier, $customerId, $subId, $periodEndIso);
|
||||
FreeTier::setTier($userId, $tier, $customerId, $subId, $periodEndIso, $status === 'trialing' ? $trialEndIso : null);
|
||||
} elseif (in_array($status, ['canceled', 'unpaid', 'incomplete_expired'], true)) {
|
||||
FreeTier::clearTier($userId);
|
||||
}
|
||||
|
||||
+31
-2
@@ -2,6 +2,8 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../includes/LegalTools.php';
|
||||
require_once __DIR__ . '/../includes/CaseResults.php';
|
||||
require_once __DIR__ . '/../includes/ToolModels.php';
|
||||
|
||||
dbnToolsRequireMethod('POST');
|
||||
dbnToolsRequireAuth();
|
||||
@@ -11,12 +13,13 @@ if ($ftRemaining >= 0) { header('X-Credits-Remaining: ' . $ftRemaining); }
|
||||
$input = dbnToolsJsonInput(400000);
|
||||
$language = dbnToolsNormalizeLanguage($input['language'] ?? 'en');
|
||||
|
||||
dbnToolsWithTelemetry('timeline', $language, function () use ($input, $language): array {
|
||||
dbnToolsWithTelemetry('timeline', $language, function () use ($input, $language, $ftUid): array {
|
||||
$text = dbnToolsString($input, 'text', 128000);
|
||||
|
||||
$validEngines = ['azure_mini', 'azure_full', 'gpu'];
|
||||
$engine = in_array((string)($input['engine'] ?? ''), $validEngines, true)
|
||||
? (string)$input['engine'] : 'azure_mini';
|
||||
$engine = ToolModels::engineForUser($ftUid, $engine);
|
||||
|
||||
$validFocus = ['all', 'deadlines', 'hearings', 'cps'];
|
||||
$focus = in_array((string)($input['focus'] ?? ''), $validFocus, true)
|
||||
@@ -29,5 +32,31 @@ dbnToolsWithTelemetry('timeline', $language, function () use ($input, $language)
|
||||
$includeBackground = ($input['include_background'] ?? true) !== false;
|
||||
$userNotes = dbnToolsString($input, 'user_notes', 2000, false);
|
||||
|
||||
return (new DbnLegalToolsService())->timeline($text, $language, $engine, $focus, $confidenceFilter, $includeRelative, $includeBackground, $userNotes);
|
||||
// Optional: prepend the user's case-context chunks so the timeline includes events
|
||||
// referenced in their uploaded case documents.
|
||||
$useMyCase = !empty($input['use_my_case']);
|
||||
if ($useMyCase) {
|
||||
$caseBlock = dbnToolsCaseContext(true, $text, 5);
|
||||
if ($caseBlock !== '') {
|
||||
$text = $text . "\n\n" . $caseBlock;
|
||||
}
|
||||
}
|
||||
|
||||
$result = (new DbnLegalToolsService())->timeline($text, $language, $engine, $focus, $confidenceFilter, $includeRelative, $includeBackground, $userNotes);
|
||||
|
||||
// Persist for paid users (silently no-op for free)
|
||||
if ($ftUid > 0) {
|
||||
$ownerId = CaseStore::caseResolveClientId($ftUid);
|
||||
$resultId = CaseResults::save($ftUid, $ownerId, 'timeline', $input, $result, [
|
||||
'used_case_context' => $useMyCase ? 1 : 0,
|
||||
'case_doc_ids' => dbnToolsLastCaseDocIds(),
|
||||
'model' => $engine,
|
||||
'latency_ms' => (int)($result['latency_ms'] ?? 0),
|
||||
'credits_charged' => FreeTier::cost('timeline'),
|
||||
]);
|
||||
if ($resultId > 0) {
|
||||
$result['result_id'] = $resultId;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user