Compare commits
41 Commits
31915d30b9
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 24fcc32d3e | |||
| a301791a73 | |||
| 8a9f450c67 | |||
| 5669a81411 | |||
| 7b655fbc2a | |||
| c7419fd9d0 | |||
| d90ee6f01c | |||
| 3bd5e2d07b | |||
| 1d33f7ef55 | |||
| 1367a3eef6 | |||
| aa084ffb62 | |||
| 58f6b620fc | |||
| 505d98c30e | |||
| 5069cc5e06 | |||
| 568d398993 | |||
| ed90e81bb6 | |||
| 8cccd83451 | |||
| 76442d2629 | |||
| 991c286380 | |||
| b5ef991861 | |||
| 41c1520999 | |||
| 2bf3aa8bbd | |||
| b5145e0021 | |||
| 238454ef57 | |||
| 602a527322 | |||
| 04dbc5f9e1 | |||
| e81fd1bc83 | |||
| ad6c0e7112 | |||
| 2d03547d58 | |||
| caad6681f7 | |||
| 8a2e175ba8 | |||
| ccf1a15901 | |||
| 1f9c5fc940 | |||
| 3ad1d3bbaf | |||
| 13ada3312d | |||
| 35097e22ff | |||
| af3310297b | |||
| 9a5b62c2ab | |||
| 9bb8f119b5 | |||
| f64b838030 | |||
| b3fffec838 |
@@ -11,7 +11,7 @@ spec:
|
|||||||
sources:
|
sources:
|
||||||
- repoURL: ghcr.io/akuity/kargo-charts
|
- repoURL: ghcr.io/akuity/kargo-charts
|
||||||
chart: kargo
|
chart: kargo
|
||||||
targetRevision: 1.10.7
|
targetRevision: 1.10.8
|
||||||
helm:
|
helm:
|
||||||
valueFiles:
|
valueFiles:
|
||||||
- $values/kargo/eu-central-1/values.yaml
|
- $values/kargo/eu-central-1/values.yaml
|
||||||
|
|||||||
@@ -43,11 +43,11 @@ images:
|
|||||||
- name: ghcr.io/now-chess/now-chess-systems/core
|
- name: ghcr.io/now-chess/now-chess-systems/core
|
||||||
newTag: 0.51.0
|
newTag: 0.51.0
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/frontend
|
- name: ghcr.io/now-chess/now-chess-systems/frontend
|
||||||
newTag: 0.6.3
|
newTag: 0.7.0
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/io
|
- name: ghcr.io/now-chess/now-chess-systems/io
|
||||||
newTag: 0.23.0
|
newTag: 0.23.0
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/official-bots
|
- name: ghcr.io/now-chess/now-chess-systems/official-bots
|
||||||
newTag: 0.36.0
|
newTag: 0.44.0
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/rule
|
- name: ghcr.io/now-chess/now-chess-systems/rule
|
||||||
newTag: 0.18.0
|
newTag: 0.18.0
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/store
|
- name: ghcr.io/now-chess/now-chess-systems/store
|
||||||
|
|||||||
@@ -120,11 +120,11 @@ images:
|
|||||||
- name: ghcr.io/now-chess/now-chess-systems/core
|
- name: ghcr.io/now-chess/now-chess-systems/core
|
||||||
newTag: 0.53.0
|
newTag: 0.53.0
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/frontend
|
- name: ghcr.io/now-chess/now-chess-systems/frontend
|
||||||
newTag: 0.6.3
|
newTag: 0.7.0
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/io
|
- name: ghcr.io/now-chess/now-chess-systems/io
|
||||||
newTag: 0.23.0
|
newTag: 0.23.0
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/official-bots
|
- name: ghcr.io/now-chess/now-chess-systems/official-bots
|
||||||
newTag: 0.36.0
|
newTag: 0.44.0
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/rule
|
- name: ghcr.io/now-chess/now-chess-systems/rule
|
||||||
newTag: 0.18.0
|
newTag: 0.18.0
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/store
|
- name: ghcr.io/now-chess/now-chess-systems/store
|
||||||
|
|||||||
@@ -84,11 +84,11 @@ images:
|
|||||||
- name: ghcr.io/now-chess/now-chess-systems/core
|
- name: ghcr.io/now-chess/now-chess-systems/core
|
||||||
newTag: 0.53.0-x86_v2
|
newTag: 0.53.0-x86_v2
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/frontend
|
- name: ghcr.io/now-chess/now-chess-systems/frontend
|
||||||
newTag: 0.6.3-x86_v2
|
newTag: 0.7.0-x86_v2
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/io
|
- name: ghcr.io/now-chess/now-chess-systems/io
|
||||||
newTag: 0.23.0-x86_v2
|
newTag: 0.23.0-x86_v2
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/official-bots
|
- name: ghcr.io/now-chess/now-chess-systems/official-bots
|
||||||
newTag: 0.35.0-x86_v2
|
newTag: 0.44.0-x86_v2
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/rule
|
- name: ghcr.io/now-chess/now-chess-systems/rule
|
||||||
newTag: 0.18.0-x86_v2
|
newTag: 0.18.0-x86_v2
|
||||||
- name: ghcr.io/now-chess/now-chess-systems/store
|
- name: ghcr.io/now-chess/now-chess-systems/store
|
||||||
|
|||||||
@@ -1197,3 +1197,331 @@ spec:
|
|||||||
- name: spark-output
|
- name: spark-output
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: spark-analytics-output
|
claimName: spark-analytics-output
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: spark-accuracy-blunder-driver
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: spark-analytics
|
||||||
|
app.kubernetes.io/part-of: nowchess
|
||||||
|
spec:
|
||||||
|
clusterIP: None
|
||||||
|
selector:
|
||||||
|
app: spark-accuracy-blunder
|
||||||
|
ports:
|
||||||
|
- name: driver
|
||||||
|
port: 44167
|
||||||
|
targetPort: 44167
|
||||||
|
- name: blockmanager
|
||||||
|
port: 44777
|
||||||
|
targetPort: 44777
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: spark-clock-pressure-driver
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: spark-analytics
|
||||||
|
app.kubernetes.io/part-of: nowchess
|
||||||
|
spec:
|
||||||
|
clusterIP: None
|
||||||
|
selector:
|
||||||
|
app: spark-clock-pressure
|
||||||
|
ports:
|
||||||
|
- name: driver
|
||||||
|
port: 44167
|
||||||
|
targetPort: 44167
|
||||||
|
- name: blockmanager
|
||||||
|
port: 44777
|
||||||
|
targetPort: 44777
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: spark-smurf-anomaly-driver
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: spark-analytics
|
||||||
|
app.kubernetes.io/part-of: nowchess
|
||||||
|
spec:
|
||||||
|
clusterIP: None
|
||||||
|
selector:
|
||||||
|
app: spark-smurf-anomaly
|
||||||
|
ports:
|
||||||
|
- name: driver
|
||||||
|
port: 44167
|
||||||
|
targetPort: 44167
|
||||||
|
- name: blockmanager
|
||||||
|
port: 44777
|
||||||
|
targetPort: 44777
|
||||||
|
---
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: spark-accuracy-blunder
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: spark-analytics
|
||||||
|
app.kubernetes.io/part-of: nowchess
|
||||||
|
spec:
|
||||||
|
schedule: "0 9 * * 6"
|
||||||
|
suspend: true
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
backoffLimit: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: spark-accuracy-blunder
|
||||||
|
app.kubernetes.io/name: spark-analytics
|
||||||
|
app.kubernetes.io/part-of: nowchess
|
||||||
|
spec:
|
||||||
|
serviceAccountName: spark-analytics
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
affinity:
|
||||||
|
podAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: spark-worker
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 185
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: ghcr-pull-secret
|
||||||
|
containers:
|
||||||
|
- name: spark-driver
|
||||||
|
image: ghcr.io/now-chess/now-chess-systems/analytics:latest
|
||||||
|
command:
|
||||||
|
- /opt/spark/bin/spark-submit
|
||||||
|
- --master
|
||||||
|
- spark://spark-master:7077
|
||||||
|
- --conf
|
||||||
|
- spark.executor.instances=1
|
||||||
|
- --conf
|
||||||
|
- spark.executor.cores=1
|
||||||
|
- --conf
|
||||||
|
- spark.executor.memory=512m
|
||||||
|
- --conf
|
||||||
|
- spark.driver.memory=512m
|
||||||
|
- --conf
|
||||||
|
- spark.driver.host=spark-accuracy-blunder-driver
|
||||||
|
- --conf
|
||||||
|
- spark.driver.port=44167
|
||||||
|
- --conf
|
||||||
|
- spark.blockManager.port=44777
|
||||||
|
- --class
|
||||||
|
- de.nowchess.analytics.AccuracyBlunderJob
|
||||||
|
- file:///app/analytics.jar
|
||||||
|
- /spark-output/accuracy
|
||||||
|
env:
|
||||||
|
- name: NOWCHESS_JDBC_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ncs-db-secrets
|
||||||
|
key: STORE_DB_URL
|
||||||
|
- name: NOWCHESS_DB_USER
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ncs-db-secrets
|
||||||
|
key: STORE_DB_USER
|
||||||
|
- name: NOWCHESS_DB_PASS
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ncs-db-secrets
|
||||||
|
key: STORE_DB_PASSWORD
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 64Mi
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 1Gi
|
||||||
|
volumeMounts:
|
||||||
|
- name: spark-output
|
||||||
|
mountPath: /spark-output
|
||||||
|
volumes:
|
||||||
|
- name: spark-output
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: spark-analytics-output
|
||||||
|
---
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: spark-clock-pressure
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: spark-analytics
|
||||||
|
app.kubernetes.io/part-of: nowchess
|
||||||
|
spec:
|
||||||
|
schedule: "0 10 * * 6"
|
||||||
|
suspend: true
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
backoffLimit: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: spark-clock-pressure
|
||||||
|
app.kubernetes.io/name: spark-analytics
|
||||||
|
app.kubernetes.io/part-of: nowchess
|
||||||
|
spec:
|
||||||
|
serviceAccountName: spark-analytics
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
affinity:
|
||||||
|
podAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: spark-worker
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 185
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: ghcr-pull-secret
|
||||||
|
containers:
|
||||||
|
- name: spark-driver
|
||||||
|
image: ghcr.io/now-chess/now-chess-systems/analytics:latest
|
||||||
|
command:
|
||||||
|
- /opt/spark/bin/spark-submit
|
||||||
|
- --master
|
||||||
|
- spark://spark-master:7077
|
||||||
|
- --conf
|
||||||
|
- spark.executor.instances=1
|
||||||
|
- --conf
|
||||||
|
- spark.executor.cores=1
|
||||||
|
- --conf
|
||||||
|
- spark.executor.memory=512m
|
||||||
|
- --conf
|
||||||
|
- spark.driver.memory=512m
|
||||||
|
- --conf
|
||||||
|
- spark.driver.host=spark-clock-pressure-driver
|
||||||
|
- --conf
|
||||||
|
- spark.driver.port=44167
|
||||||
|
- --conf
|
||||||
|
- spark.blockManager.port=44777
|
||||||
|
- --class
|
||||||
|
- de.nowchess.analytics.ClockPressureJob
|
||||||
|
- file:///app/analytics.jar
|
||||||
|
- /spark-output/clock-pressure
|
||||||
|
env:
|
||||||
|
- name: NOWCHESS_JDBC_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ncs-db-secrets
|
||||||
|
key: STORE_DB_URL
|
||||||
|
- name: NOWCHESS_DB_USER
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ncs-db-secrets
|
||||||
|
key: STORE_DB_USER
|
||||||
|
- name: NOWCHESS_DB_PASS
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ncs-db-secrets
|
||||||
|
key: STORE_DB_PASSWORD
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 64Mi
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 1Gi
|
||||||
|
volumeMounts:
|
||||||
|
- name: spark-output
|
||||||
|
mountPath: /spark-output
|
||||||
|
volumes:
|
||||||
|
- name: spark-output
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: spark-analytics-output
|
||||||
|
---
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: spark-smurf-anomaly
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: spark-analytics
|
||||||
|
app.kubernetes.io/part-of: nowchess
|
||||||
|
spec:
|
||||||
|
schedule: "0 11 * * 6"
|
||||||
|
suspend: true
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
backoffLimit: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: spark-smurf-anomaly
|
||||||
|
app.kubernetes.io/name: spark-analytics
|
||||||
|
app.kubernetes.io/part-of: nowchess
|
||||||
|
spec:
|
||||||
|
serviceAccountName: spark-analytics
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
affinity:
|
||||||
|
podAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: spark-worker
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 185
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: ghcr-pull-secret
|
||||||
|
containers:
|
||||||
|
- name: spark-driver
|
||||||
|
image: ghcr.io/now-chess/now-chess-systems/analytics:latest
|
||||||
|
command:
|
||||||
|
- /opt/spark/bin/spark-submit
|
||||||
|
- --master
|
||||||
|
- spark://spark-master:7077
|
||||||
|
- --conf
|
||||||
|
- spark.executor.instances=1
|
||||||
|
- --conf
|
||||||
|
- spark.executor.cores=1
|
||||||
|
- --conf
|
||||||
|
- spark.executor.memory=512m
|
||||||
|
- --conf
|
||||||
|
- spark.driver.memory=512m
|
||||||
|
- --conf
|
||||||
|
- spark.driver.host=spark-smurf-anomaly-driver
|
||||||
|
- --conf
|
||||||
|
- spark.driver.port=44167
|
||||||
|
- --conf
|
||||||
|
- spark.blockManager.port=44777
|
||||||
|
- --class
|
||||||
|
- de.nowchess.analytics.SmurfAnomalyJob
|
||||||
|
- file:///app/analytics.jar
|
||||||
|
- /spark-output/smurf-anomaly
|
||||||
|
- "15"
|
||||||
|
env:
|
||||||
|
- name: NOWCHESS_JDBC_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ncs-db-secrets
|
||||||
|
key: STORE_DB_URL
|
||||||
|
- name: NOWCHESS_DB_USER
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ncs-db-secrets
|
||||||
|
key: STORE_DB_USER
|
||||||
|
- name: NOWCHESS_DB_PASS
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ncs-db-secrets
|
||||||
|
key: STORE_DB_PASSWORD
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 64Mi
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 1Gi
|
||||||
|
volumeMounts:
|
||||||
|
- name: spark-output
|
||||||
|
mountPath: /spark-output
|
||||||
|
volumes:
|
||||||
|
- name: spark-output
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: spark-analytics-output
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ data:
|
|||||||
("weekly-activity", "Weekly Activity", "analytics_weekly_activity"),
|
("weekly-activity", "Weekly Activity", "analytics_weekly_activity"),
|
||||||
("rating-mismatch", "Rating Mismatch (Upsets)", "analytics_rating_mismatch"),
|
("rating-mismatch", "Rating Mismatch (Upsets)", "analytics_rating_mismatch"),
|
||||||
("termination-stats", "Termination Types", "analytics_termination_stats"),
|
("termination-stats", "Termination Types", "analytics_termination_stats"),
|
||||||
|
("accuracy-by-rating", "Accuracy by Rating", "analytics_accuracy_by_rating"),
|
||||||
|
("blunder-outcome", "Blunder Outcome", "analytics_blunder_outcome"),
|
||||||
|
("clock-by-rating", "Clock Pressure by Rating", "analytics_clock_by_rating"),
|
||||||
|
("scramble-outcome", "Scramble Outcome", "analytics_scramble_outcome"),
|
||||||
|
("smurf-anomaly", "Smurf Anomaly Scores", "analytics_smurf_anomaly"),
|
||||||
|
("flagged-smurfs", "Flagged Smurfs", "analytics_flagged_smurfs"),
|
||||||
]
|
]
|
||||||
|
|
||||||
CSS = """
|
CSS = """
|
||||||
@@ -181,37 +187,52 @@ data:
|
|||||||
if (btn) btn.disabled = !hasKey();
|
if (btn) btn.disabled = !hasKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function nimFetch(payload) {
|
async function nimStream(payload, onDelta) {
|
||||||
const ctrl = new AbortController();
|
const ctrl = new AbortController();
|
||||||
const tid = setTimeout(() => ctrl.abort(), 65000);
|
const tid = setTimeout(() => ctrl.abort(), 180000);
|
||||||
let resp;
|
|
||||||
try {
|
try {
|
||||||
resp = await fetch('/explain', {
|
const resp = await fetch('/explain', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
signal: ctrl.signal,
|
signal: ctrl.signal,
|
||||||
});
|
});
|
||||||
|
if (resp.status === 429) {
|
||||||
|
const retryAfter = parseInt(resp.headers.get('Retry-After') || '10', 10);
|
||||||
|
const err = new Error('Rate limited — retry in ' + retryAfter + 's');
|
||||||
|
err.retryAfter = retryAfter;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
if (!resp.ok) {
|
||||||
|
const msg = await resp.text();
|
||||||
|
throw new Error('Proxy ' + resp.status + ': ' + msg);
|
||||||
|
}
|
||||||
|
const reader = resp.body.getReader();
|
||||||
|
const dec = new TextDecoder();
|
||||||
|
const SEP = String.fromCharCode(10, 10); // SSE frame separator (two newlines)
|
||||||
|
let buf = '', full = '';
|
||||||
|
for (;;) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
buf += dec.decode(value, { stream: true });
|
||||||
|
let idx;
|
||||||
|
while ((idx = buf.indexOf(SEP)) >= 0) {
|
||||||
|
const frame = buf.slice(0, idx).trim();
|
||||||
|
buf = buf.slice(idx + 2);
|
||||||
|
if (!frame.startsWith('data:')) continue;
|
||||||
|
const obj = JSON.parse(frame.slice(5).trim());
|
||||||
|
if (obj.error) throw new Error(obj.error);
|
||||||
|
if (obj.delta) { full += obj.delta; onDelta(full); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return full || '(no response)';
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(tid);
|
clearTimeout(tid);
|
||||||
}
|
}
|
||||||
if (resp.status === 429) {
|
|
||||||
const retryAfter = parseInt(resp.headers.get('Retry-After') || '10', 10);
|
|
||||||
const err = new Error('Rate limited — retry in ' + retryAfter + 's');
|
|
||||||
err.retryAfter = retryAfter;
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
if (!resp.ok) {
|
|
||||||
const msg = await resp.text();
|
|
||||||
throw new Error('Proxy ' + resp.status + ': ' + msg);
|
|
||||||
}
|
|
||||||
const data = await resp.json();
|
|
||||||
if (data.error) throw new Error(data.error);
|
|
||||||
return data.text || '(no response)';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function queueNim(payload) {
|
function queueNim(payload, onDelta) {
|
||||||
const next = nimQueue.then(() => nimFetch(payload));
|
const next = nimQueue.then(() => nimStream(payload, onDelta));
|
||||||
nimQueue = next.catch(() => {});
|
nimQueue = next.catch(() => {});
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
@@ -231,7 +252,10 @@ data:
|
|||||||
textEl.textContent = '';
|
textEl.textContent = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const text = await queueNim({ key: key, mode: 'dataset', dataset: window.DATASET });
|
const text = await queueNim(
|
||||||
|
{ key: key, mode: 'dataset', dataset: window.DATASET },
|
||||||
|
partial => { textEl.textContent = partial; },
|
||||||
|
);
|
||||||
textEl.textContent = text;
|
textEl.textContent = text;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
textEl.className = 'explain-error';
|
textEl.className = 'explain-error';
|
||||||
@@ -269,10 +293,13 @@ data:
|
|||||||
textEl.textContent = 'Analyzing…';
|
textEl.textContent = 'Analyzing…';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const text = await queueNim({
|
const text = await queueNim(
|
||||||
key: key, mode: 'row',
|
{
|
||||||
dataset: { label: dataset.label, headers: dataset.headers, row: row },
|
key: key, mode: 'row',
|
||||||
});
|
dataset: { label: dataset.label, headers: dataset.headers, row: row },
|
||||||
|
},
|
||||||
|
partial => { textEl.textContent = partial; },
|
||||||
|
);
|
||||||
rowCache[idx] = text;
|
rowCache[idx] = text;
|
||||||
textEl.textContent = text;
|
textEl.textContent = text;
|
||||||
btn.innerHTML = '✓';
|
btn.innerHTML = '✓';
|
||||||
@@ -474,6 +501,7 @@ data:
|
|||||||
self.end_headers()
|
self.end_headers()
|
||||||
return
|
return
|
||||||
length = int(self.headers.get("Content-Length", 0))
|
length = int(self.headers.get("Content-Length", 0))
|
||||||
|
headers_sent = False
|
||||||
try:
|
try:
|
||||||
body = json.loads(self.rfile.read(length))
|
body = json.loads(self.rfile.read(length))
|
||||||
api_key = body.get("key", "")
|
api_key = body.get("key", "")
|
||||||
@@ -532,7 +560,7 @@ data:
|
|||||||
],
|
],
|
||||||
"max_tokens": max_tokens,
|
"max_tokens": max_tokens,
|
||||||
"temperature": 0.4,
|
"temperature": 0.4,
|
||||||
"stream": False,
|
"stream": True,
|
||||||
}).encode("utf-8")
|
}).encode("utf-8")
|
||||||
req = urllib.request.Request(
|
req = urllib.request.Request(
|
||||||
"https://integrate.api.nvidia.com/v1/chat/completions",
|
"https://integrate.api.nvidia.com/v1/chat/completions",
|
||||||
@@ -544,10 +572,7 @@ data:
|
|||||||
method="POST",
|
method="POST",
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=60) as r:
|
upstream = urllib.request.urlopen(req, timeout=110)
|
||||||
nim_data = json.loads(r.read())
|
|
||||||
text = nim_data.get("choices", [{}])[0].get("message", {}).get("content", "(no response)")
|
|
||||||
self._send_json({"text": text})
|
|
||||||
except urllib.error.HTTPError as he:
|
except urllib.error.HTTPError as he:
|
||||||
if he.code == 429:
|
if he.code == 429:
|
||||||
retry_after = he.headers.get("Retry-After", "10")
|
retry_after = he.headers.get("Retry-After", "10")
|
||||||
@@ -561,10 +586,46 @@ data:
|
|||||||
self.wfile.write(resp_body)
|
self.wfile.write(resp_body)
|
||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
pass
|
pass
|
||||||
else:
|
return
|
||||||
self._send_json({"error": f"NIM API error {he.code}: {he.reason}"})
|
raise RuntimeError(f"NIM API error {he.code}: {he.reason}")
|
||||||
|
|
||||||
|
# Relay NIM's SSE stream to the browser as our own SSE.
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "text/event-stream")
|
||||||
|
self.send_header("Cache-Control", "no-cache")
|
||||||
|
self.send_header("X-Accel-Buffering", "no")
|
||||||
|
self.end_headers()
|
||||||
|
headers_sent = True
|
||||||
|
for raw in upstream:
|
||||||
|
line = raw.decode("utf-8", "replace").strip()
|
||||||
|
if not line.startswith("data:"):
|
||||||
|
continue
|
||||||
|
data = line[5:].strip()
|
||||||
|
if data == "[DONE]":
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
obj = json.loads(data)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
piece = obj.get("choices", [{}])[0].get("delta", {}).get("content", "")
|
||||||
|
if piece and not self._emit({"delta": piece}):
|
||||||
|
return
|
||||||
|
self._emit({"done": True})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._send_json({"error": str(e)})
|
if not headers_sent:
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "text/event-stream")
|
||||||
|
self.send_header("Cache-Control", "no-cache")
|
||||||
|
self.end_headers()
|
||||||
|
self._emit({"error": str(e)})
|
||||||
|
|
||||||
|
def _emit(self, obj: dict) -> bool:
|
||||||
|
try:
|
||||||
|
self.wfile.write(("data: " + json.dumps(obj) + "\n\n").encode("utf-8"))
|
||||||
|
self.wfile.flush()
|
||||||
|
return True
|
||||||
|
except (BrokenPipeError, ConnectionResetError):
|
||||||
|
return False
|
||||||
|
|
||||||
def _send(self, body: str):
|
def _send(self, body: str):
|
||||||
data = body.encode("utf-8")
|
data = body.encode("utf-8")
|
||||||
|
|||||||
@@ -353,6 +353,70 @@ patches:
|
|||||||
target:
|
target:
|
||||||
kind: CronJob
|
kind: CronJob
|
||||||
name: spark-termination-stats
|
name: spark-termination-stats
|
||||||
|
# Eval-annotated dump required for per-move accuracy; the default 2013 dump
|
||||||
|
# carries no [%eval] comments — point this at an evals dump for real output.
|
||||||
|
- patch: |-
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: spark-accuracy-blunder
|
||||||
|
spec:
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: spark-driver
|
||||||
|
env:
|
||||||
|
- name: NOWCHESS_PGN_PATH
|
||||||
|
value: "https://database.lichess.org/standard/lichess_db_standard_rated_2013-01.pgn.zst"
|
||||||
|
- name: NOWCHESS_PGN_CACHE_DIR
|
||||||
|
value: "/spark-output/.pgn-cache"
|
||||||
|
target:
|
||||||
|
kind: CronJob
|
||||||
|
name: spark-accuracy-blunder
|
||||||
|
# Clock-annotated dump required; the default 2013 dump carries no [%clk]
|
||||||
|
# comments — point this at a dump with clocks for real output.
|
||||||
|
- patch: |-
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: spark-clock-pressure
|
||||||
|
spec:
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: spark-driver
|
||||||
|
env:
|
||||||
|
- name: NOWCHESS_PGN_PATH
|
||||||
|
value: "https://database.lichess.org/standard/lichess_db_standard_rated_2013-01.pgn.zst"
|
||||||
|
- name: NOWCHESS_PGN_CACHE_DIR
|
||||||
|
value: "/spark-output/.pgn-cache"
|
||||||
|
target:
|
||||||
|
kind: CronJob
|
||||||
|
name: spark-clock-pressure
|
||||||
|
- patch: |-
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: spark-smurf-anomaly
|
||||||
|
spec:
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: spark-driver
|
||||||
|
env:
|
||||||
|
- name: NOWCHESS_PGN_PATH
|
||||||
|
value: "https://database.lichess.org/standard/lichess_db_standard_rated_2013-01.pgn.zst"
|
||||||
|
- name: NOWCHESS_PGN_CACHE_DIR
|
||||||
|
value: "/spark-output/.pgn-cache"
|
||||||
|
target:
|
||||||
|
kind: CronJob
|
||||||
|
name: spark-smurf-anomaly
|
||||||
- patch: |-
|
- patch: |-
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ metadata:
|
|||||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||||
|
# AI /explain streams Server-Sent Events — disable response buffering so
|
||||||
|
# chunks flush to the client immediately, and allow gaps between chunks.
|
||||||
|
nginx.ingress.kubernetes.io/proxy-buffering: "off"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
|
||||||
spec:
|
spec:
|
||||||
ingressClassName: nginx
|
ingressClassName: nginx
|
||||||
rules:
|
rules:
|
||||||
|
|||||||
Reference in New Issue
Block a user