Skip to content

Commit d705dcd

Browse files
HarshHarsh
authored andcommitted
Merge branch 'develop' of https://github.com/bluewave-labs/verifywise into develop
2 parents cce74b4 + cbd93b1 commit d705dcd

File tree

19 files changed

+288
-131
lines changed

19 files changed

+288
-131
lines changed

.claude/settings.local.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44
"Bash(npx playwright test:*)",
55
"Bash(find /Users/harsh/Data/vw/verifywise/Clients/src -type f -name *login* -o -name *Login*)",
66
"Bash(grep -E \"\\\\.\\(ts|js\\)$\")",
7-
"Bash(npm run:*)"
7+
"Bash(npm run:*)",
8+
"Bash(ls:*)",
9+
"Bash(npx tsc:*)",
10+
"Bash(python3:*)",
11+
"Bash(find /Users/harsh/Data/vw/verifywise/Clients -type f \\\\\\(-name *.yaml -o -name *.yml -o -name docker-compose* \\\\\\))",
12+
"Bash(find /Users/harsh/Data/vw/verifywise -type f \\\\\\(-name *.yaml -o -name *.yml -o -name docker-compose* \\\\\\) -not -path */node_modules/*)",
13+
"Bash(grep -r \"superadmin\\\\|super.admin\\\\|SuperAdmin\" /Users/harsh/Data/vw/verifywise/Servers/src --include=*.ts -i)",
14+
"Bash(grep:*)"
815
]
916
}
1017
}

AIGateway/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ WORKDIR /app
33
COPY requirements.txt .
44
RUN pip install --no-cache-dir -r requirements.txt
55
COPY src/ .
6-
CMD ["sh", "-c", "alembic upgrade head && uvicorn app:app --host 0.0.0.0 --port 8100"]
6+
COPY start.sh .
7+
RUN chmod +x start.sh
8+
CMD ["./start.sh"]

AIGateway/src/routers/prompts.py

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
import json as _json
12
import re
23
from typing import Any, Optional
34

45
from fastapi import APIRouter, HTTPException, Request
6+
from fastapi.responses import StreamingResponse
57
from pydantic import BaseModel, field_validator
68

79
from crud import prompts as crud
810
from middlewares.auth import verify_internal_key
11+
from services.llm_service import stream_chat_completion
12+
from services.proxy_service import resolve_endpoint_for_key
913
from utils.auth import get_org_id, get_user_id, require_admin
1014
from utils.notifications import notify_config_change
1115

@@ -377,27 +381,42 @@ async def delete_test_dataset(
377381
return {"deleted": True}
378382

379383

380-
@router.post("/test", status_code=501)
384+
@router.post("/test")
381385
async def test_prompt(request: Request, body: TestPromptRequest):
382386
"""
383-
Test a prompt by resolving variables and proxying to the LLM completion
384-
endpoint. The LLM proxy integration lives in the Express backend for now;
385-
this stub returns 501 until the proxy is co-located in the AIGateway
386-
service.
387+
Test a prompt by resolving variables and streaming the LLM response.
388+
Returns an SSE stream compatible with the frontend's streamPromptTest().
387389
"""
388390
verify_internal_key(request)
391+
org_id = get_org_id(request)
389392

390-
# Resolve variables so callers can at least validate substitution locally
393+
# Resolve variables in the prompt content
391394
resolved_content = crud.resolve_variables(
392395
body.content,
393396
body.variables or {},
394397
)
395398

396-
raise HTTPException(
397-
status_code=501,
398-
detail=(
399-
"test-prompt requires proxy integration — "
400-
"LLM proxy currently runs in the Express backend. "
401-
"Resolved content is available but cannot be forwarded yet."
402-
),
403-
)
399+
# Resolve the endpoint to get provider, model, and API key
400+
try:
401+
endpoint = await resolve_endpoint_for_key(
402+
organization_id=org_id,
403+
endpoint_slug=body.endpoint_slug,
404+
allowed_endpoint_ids=[],
405+
)
406+
except ValueError as e:
407+
raise HTTPException(status_code=404, detail=str(e))
408+
409+
# Stream the LLM response
410+
async def _stream():
411+
try:
412+
async for chunk_str in stream_chat_completion(
413+
model=endpoint["model"],
414+
messages=resolved_content,
415+
api_key=endpoint["decrypted_key"],
416+
):
417+
yield chunk_str
418+
except Exception as e:
419+
yield f"data: {_json.dumps({'error': str(e)})}\n\n"
420+
yield "data: [DONE]\n\n"
421+
422+
return StreamingResponse(_stream(), media_type="text/event-stream")

AIGateway/start.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/bin/sh
2+
set -e
3+
4+
echo "Waiting for main server to create required tables..."
5+
6+
DELAY=2
7+
MAX_DELAY=30
8+
ATTEMPT=1
9+
10+
until python -c "
11+
import asyncio, asyncpg, os
12+
13+
async def check():
14+
conn = await asyncpg.connect(
15+
host=os.environ['DB_HOST'],
16+
port=int(os.environ.get('DB_PORT', 5432)),
17+
user=os.environ['DB_USER'],
18+
password=os.environ['DB_PASSWORD'],
19+
database=os.environ['DB_NAME']
20+
)
21+
exists = await conn.fetchval(
22+
\"SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema='verifywise' AND table_name='organizations')\"
23+
)
24+
await conn.close()
25+
if not exists:
26+
raise SystemExit(1)
27+
28+
asyncio.run(check())
29+
" 2>/dev/null; do
30+
echo "Attempt $ATTEMPT: organizations table not ready, retrying in ${DELAY}s..."
31+
sleep $DELAY
32+
ATTEMPT=$((ATTEMPT + 1))
33+
DELAY=$((DELAY * 2))
34+
if [ $DELAY -gt $MAX_DELAY ]; then
35+
DELAY=$MAX_DELAY
36+
fi
37+
done
38+
39+
echo "Tables ready! Running AI Gateway migrations..."
40+
alembic upgrade head
41+
42+
echo "Starting AI Gateway..."
43+
uvicorn app:app --host 0.0.0.0 --port 8100

Clients/src/application/hooks/__tests__/useAuth.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ function createWrapper(authToken: string) {
3232
expirationDate: null,
3333
onboardingStatus: "completed",
3434
isOrgCreator: false,
35+
isSuperAdmin: false,
36+
activeOrganizationId: null,
3537
},
3638
},
3739
});
3840

3941
return ({ children }: { children: React.ReactNode }) =>
40-
React.createElement(Provider, { store }, children);
42+
React.createElement(Provider, { store, children });
4143
}
4244

4345
describe("useAuth", () => {

Clients/src/application/hooks/__tests__/useIsAdmin.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@ function createWrapper(authToken: string) {
2727
expirationDate: null,
2828
onboardingStatus: "completed",
2929
isOrgCreator: false,
30+
isSuperAdmin: false,
31+
activeOrganizationId: null,
3032
},
3133
},
3234
});
3335

3436
return ({ children }: { children: React.ReactNode }) =>
35-
React.createElement(Provider, { store }, children);
37+
React.createElement(Provider, { store, children });
3638
}
3739

3840
describe("useIsAdmin", () => {

Clients/src/application/hooks/__tests__/useLogout.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,16 @@ function createWrapper() {
2929
expirationDate: Date.now() + 3600000,
3030
onboardingStatus: "completed",
3131
isOrgCreator: false,
32+
isSuperAdmin: false,
33+
activeOrganizationId: null,
3234
},
3335
},
3436
});
3537

3638
const Wrapper = ({ children }: { children: React.ReactNode }) =>
3739
React.createElement(
3840
Provider,
39-
{ store },
40-
React.createElement(MemoryRouter, null, children)
41+
{ store, children: React.createElement(MemoryRouter, null, children) },
4142
);
4243

4344
return { Wrapper, store };

Clients/src/application/redux/ui/__tests__/uiSlice.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe("uiSlice", () => {
6565
stateWithTable,
6666
setRowsPerPage({ table: "vendors", value: 25 })
6767
);
68-
expect(state.vendors.rowsPerPage).toBe(25);
68+
expect((state as any).vendors.rowsPerPage).toBe(25);
6969
});
7070

7171
it("should be a no-op for an unknown table key", () => {

Clients/src/presentation/components/CommandPalette/__tests__/CommandPalette.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { screen, fireEvent } from "@testing-library/react";
1+
import { screen } from "@testing-library/react";
22
import { renderWithProviders } from "../../../../test/renderWithProviders";
33
import { CommandPalette } from "../index";
44

Clients/src/presentation/components/Inputs/Field/__tests__/Field.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ describe("Field Component", () => {
103103
it("does not display error when error prop is empty", () => {
104104
renderWithProviders(<Field label="Email" />);
105105

106-
expect(screen.queryByClassName?.("input-error")).not.toBeTruthy();
106+
expect(screen.queryByText(/error/i)).not.toBeTruthy();
107107
});
108108

109109
it("renders disabled input", () => {

0 commit comments

Comments
 (0)