Skip to content

Commit ea7cce9

Browse files
committed
Merge remote-tracking branch 'origin/master' into rm-tracking-cookie
2 parents dd0ea51 + b09bd29 commit ea7cce9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2251
-1643
lines changed

src/.claude/settings.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,17 @@
1717
"WebFetch(domain:cocalc.com)",
1818
"WebFetch(domain:doc.cocalc.com)",
1919
"WebFetch(domain:docs.anthropic.com)",
20-
"WebFetch(domain:github.com)"
20+
"WebFetch(domain:github.com)",
21+
"Bash(git checkout:*)",
22+
"Bash(git push:*)",
23+
"Bash(NODE_OPTIONS=--max-old-space-size=8192 ../node_modules/.bin/tsc --noEmit)",
24+
"Bash(docker run:*)",
25+
"Bash(../node_modules/.bin/tsc:*)",
26+
"Bash(npm view:*)",
27+
"WebFetch(domain:www.anthropic.com)",
28+
"WebFetch(domain:mistral.ai)",
29+
"Bash(pnpm i18n:*)",
30+
"WebFetch(domain:simplelocalize.io)"
2131
],
2232
"deny": []
2333
}

src/CLAUDE.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,40 @@ CoCalc is organized as a monorepo with key packages:
157157
- REFUSE to modify files when the git repository is on the `master` or `main` branch.
158158
- NEVER proactively create documentation files (`*.md`) or README files. Only create documentation files if explicitly requested by the User.
159159

160+
## React-intl / Internationalization (i18n)
161+
162+
CoCalc uses react-intl for internationalization with SimpleLocalize as the translation platform.
163+
164+
### Translation ID Naming Convention
165+
166+
Translation IDs follow a hierarchical pattern: `[directory].[subdir].[filename].[aspect].[label|title|tooltip|...]`
167+
168+
Examples:
169+
- `labels.masked_files` - for common UI labels
170+
- `account.sign-out.button.title` - for account sign-out dialog
171+
- `command.generic.force_build.label` - for command labels
172+
173+
### Translation Workflow
174+
175+
**For new translation keys:**
176+
1. Add the translation to source code (e.g., `packages/frontend/i18n/common.ts`)
177+
2. Run `pnpm i18n:extract` - updates `extracted.json` from source code
178+
3. Run `pnpm i18n:upload` - sends new strings to SimpleLocalize
179+
4. New keys are automatically translated to all languages
180+
5. Run `pnpm i18n:download` - fetches translations
181+
6. Run `pnpm i18n:compile` - compiles translation files
182+
183+
**For editing existing translation keys:**
184+
Same flow as above, but **before 3. i18n:upload**, delete the key. Only new keys are auto-translated. `pnpm i18n:delete [id]`.
185+
186+
### Translation File Structure
187+
188+
- `packages/frontend/i18n/README.md` - more information
189+
- `packages/frontend/i18n/common.ts` - shared translation definitions
190+
- `packages/frontend/i18n/extracted.json` - auto-generated, do not edit manually
191+
- `packages/frontend/i18n/[locale].json` - downloaded translations per language
192+
- `packages/frontend/i18n/[locale].compiled.json` - compiled for runtime use
193+
160194
# Ignore
161195

162196
- Ignore files covered by `.gitignore`

src/packages/conat/service/typed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { Options, ServiceCall } from "./service";
99
export type { ConatService };
1010

1111
export interface Extra {
12-
ping: typeof pingConatService;
12+
ping: (opts?: { maxWait?: number }) => Promise<void>;
1313
waitFor: (opts?: { maxWait?: number }) => Promise<void>;
1414
}
1515

src/packages/frontend/admin/llm/admin-llm-test.tsx

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import { Alert, Button, Input, Select, Space, Table } from "antd";
1+
import {
2+
Alert,
3+
Button,
4+
Input,
5+
Progress,
6+
Select,
7+
Space,
8+
Table,
9+
Tooltip,
10+
} from "antd";
211

312
import {
413
redux,
@@ -20,12 +29,13 @@ import { trunc_middle } from "@cocalc/util/misc";
2029
import { COLORS } from "@cocalc/util/theme";
2130
import { PROMPTS } from "./tests";
2231
import { Value } from "./value";
23-
2432
interface TestResult {
2533
model: string;
2634
status: "pending" | "running" | "passed" | "failed";
2735
output: string;
2836
error?: string;
37+
firstResponseTime?: number; // Time in milliseconds until first token
38+
totalTime?: number; // Total time in milliseconds until completion
2939
}
3040

3141
export function TestLLMAdmin() {
@@ -101,6 +111,10 @@ export function TestLLMAdmin() {
101111

102112
return new Promise((resolve) => {
103113
try {
114+
const startTime = Date.now();
115+
let firstResponseTime: number | undefined;
116+
let totalTime: number | undefined;
117+
104118
const llmStream = webapp_client.openai_client.queryStream({
105119
input: prompt,
106120
project_id: null,
@@ -116,6 +130,10 @@ export function TestLLMAdmin() {
116130
llmStream.on("token", (token) => {
117131
console.log({ model, system, token });
118132
if (token != null) {
133+
// Record first response time if this is the first token
134+
if (firstResponseTime === undefined) {
135+
firstResponseTime = Date.now() - startTime;
136+
}
119137
reply += token;
120138
// Update the result in real-time
121139
setTestResults((prev) =>
@@ -125,22 +143,28 @@ export function TestLLMAdmin() {
125143
);
126144
} else {
127145
// Stream is complete (token is null)
146+
totalTime = Date.now() - startTime;
128147
const passed = expectedRegex.test(reply);
129148
resolve({
130149
model,
131150
status: passed ? "passed" : "failed",
132151
output: reply,
152+
firstResponseTime,
153+
totalTime,
133154
});
134155
}
135156
});
136157

137158
llmStream.on("error", (err) => {
159+
totalTime = Date.now() - startTime;
138160
console.error(`Error in LLM stream for model ${model}:`, err);
139161
resolve({
140162
model,
141163
status: "failed",
142164
output: reply,
143165
error: err?.toString(),
166+
firstResponseTime,
167+
totalTime,
144168
});
145169
});
146170

@@ -241,6 +265,57 @@ export function TestLLMAdmin() {
241265
}
242266
}
243267

268+
function formatTiming(timeMs: number | undefined): string {
269+
if (timeMs === undefined) return "-";
270+
return `${(timeMs / 1000).toFixed(1)}s`;
271+
}
272+
273+
function renderTimingColumn(record: TestResult) {
274+
const { firstResponseTime, totalTime, status } = record;
275+
276+
if (status === "pending" || status === "running") {
277+
return <span style={{ color: COLORS.GRAY_M }}>-</span>;
278+
}
279+
280+
if (firstResponseTime === undefined || totalTime === undefined) {
281+
return <span style={{ color: COLORS.GRAY_M }}>-</span>;
282+
}
283+
284+
// Calculate progress bar values (normalize to 10 seconds max)
285+
const maxTime = Math.max(
286+
10000,
287+
...testResults.filter((r) => r.totalTime).map((r) => r.totalTime!),
288+
);
289+
const totalPercent = Math.min(100, (totalTime / maxTime) * 100);
290+
291+
// Determine if this is one of the slowest (top 10% quantile)
292+
const completedResults = testResults.filter(
293+
(r) => r.totalTime !== undefined,
294+
);
295+
const sortedTimes = completedResults
296+
.map((r) => r.totalTime!)
297+
.sort((a, b) => b - a);
298+
const slowThreshold =
299+
sortedTimes[Math.floor(sortedTimes.length * 0.1)] || 0;
300+
const isSlow = totalTime >= slowThreshold && completedResults.length > 1;
301+
302+
return (
303+
<div>
304+
<Tooltip title="First response time / Total completion time">
305+
<div style={{ marginBottom: 2 }}>
306+
{formatTiming(firstResponseTime)}/{formatTiming(totalTime)}
307+
</div>
308+
</Tooltip>
309+
<Progress
310+
percent={totalPercent}
311+
size="small"
312+
status={isSlow ? "exception" : "normal"}
313+
showInfo={false}
314+
/>
315+
</div>
316+
);
317+
}
318+
244319
function renderTestResults() {
245320
if (testResults.length === 0) {
246321
return (
@@ -292,6 +367,12 @@ export function TestLLMAdmin() {
292367
<span style={{ color: COLORS.GRAY_M }}>-</span>
293368
),
294369
},
370+
{
371+
title: "Timing",
372+
key: "timing",
373+
width: 120,
374+
render: (_, record: TestResult) => renderTimingColumn(record),
375+
},
295376
{
296377
title: "Test",
297378
key: "test",

src/packages/frontend/frame-editors/latex-editor/build.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export const Build: React.FC<Props> = React.memo((props) => {
143143
// const y: ExecOutput | undefined = job_infos.get(stage)?.toJS();
144144

145145
if (!x) return;
146-
const value = x.stdout ?? "" + x.stderr ?? "";
146+
const value = (x.stdout ?? "") + (x.stderr ?? "");
147147
if (!value) return;
148148
// const time: number | undefined = x.get("time");
149149
// const time_str = time ? `(${(time / 1000).toFixed(1)} seconds)` : "";
@@ -235,7 +235,7 @@ export const Build: React.FC<Props> = React.memo((props) => {
235235
if (!info || info.type !== "async" || info.status !== "running") return;
236236
const stats_str = getResourceUsage(info.stats, "last");
237237
const start = info.start;
238-
logTail = tail(info.stdout ?? "" + info.stderr ?? "", 6);
238+
logTail = tail((info.stdout ?? "") + (info.stderr ?? ""), 6);
239239
isLongRunning ||=
240240
typeof start === "number" &&
241241
webapp_client.server_time() - start > WARN_LONG_RUNNING_S * 1000;

0 commit comments

Comments
 (0)