Commit 9ebdf9b1 authored by Kantz's avatar Kantz
Browse files

Merge branch 'dev_pnpm' into 'dev'

Dev pnpm

See merge request kantz/tutor_react!11
parents 68c41d59 1286052a
...@@ -33,7 +33,8 @@ Frontend (Vite): ...@@ -33,7 +33,8 @@ Frontend (Vite):
``` powershell ``` powershell
cd math-tutor/frontend cd math-tutor/frontend
npm install corepack enable
pnpm install --frozen-lockfile
``` ```
## Run the app ## Run the app
...@@ -50,7 +51,7 @@ Frontend (Vite): ...@@ -50,7 +51,7 @@ Frontend (Vite):
```powershell ```powershell
cd math-tutor/frontend cd math-tutor/frontend
npm run dev pnpm run dev
``` ```
### Task Deep Links ### Task Deep Links
...@@ -75,9 +76,22 @@ Add the frontend- and backend-adress in the `backend/.env`-file in the frontend- ...@@ -75,9 +76,22 @@ Add the frontend- and backend-adress in the `backend/.env`-file in the frontend-
```powershell ```powershell
python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
npm run dev -- --host 0.0.0.0 pnpm run dev -- --host 0.0.0.0
``` ```
### Frontend package manager policy (pnpm)
- Required versions:
- Node.js: 22.x or newer
- pnpm: pinned via `math-tutor/frontend/package.json` (`packageManager`)
- Lockfile policy:
- Commit `pnpm-lock.yaml`.
- Use `pnpm install --frozen-lockfile` in local CI-like checks and Docker builds.
- Install-script policy (strict):
- Dependency build/install scripts are allowlisted.
- After adding/updating dependencies, run `pnpm approve-builds` and review what is allowed.
- Check blocked scripts with `pnpm ignored-builds`.
### Vite Proxy (Development) ### Vite Proxy (Development)
The frontend now supports a dev proxy for API calls: The frontend now supports a dev proxy for API calls:
......
...@@ -2,11 +2,13 @@ FROM node:22-alpine AS build ...@@ -2,11 +2,13 @@ FROM node:22-alpine AS build
WORKDIR /app WORKDIR /app
COPY package*.json ./ RUN corepack enable
RUN npm ci
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . . COPY . .
RUN npm run build RUN pnpm run build
FROM nginx:1.27-alpine FROM nginx:1.27-alpine
......
This diff is collapsed.
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"packageManager": "pnpm@10.30.3",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc -b && vite build", "build": "tsc -b && vite build",
...@@ -32,5 +33,8 @@ ...@@ -32,5 +33,8 @@
}, },
"overrides": { "overrides": {
"vite": "npm:rolldown-vite@7.2.5" "vite": "npm:rolldown-vite@7.2.5"
},
"pnpm": {
"onlyBuiltDependencies": []
} }
} }
This diff is collapsed.
import { useEffect, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import ReactMarkdown, { defaultUrlTransform } from "react-markdown"; import ReactMarkdown, { defaultUrlTransform } from "react-markdown";
import type { RetrievedDoc } from "../Retrieval/DocPanel"; import type { RetrievedDoc } from "../Retrieval/DocPanel";
import { t } from "../../i18n"; import { t } from "../../i18n";
import { escapeAsterisksInsideMath } from "../../utils/mathMarkdown";
type MessageBubbleProps = { type MessageBubbleProps = {
role: "user" | "assistant"; role: "user" | "assistant";
...@@ -28,6 +29,7 @@ export default function MessageBubble({ ...@@ -28,6 +29,7 @@ export default function MessageBubble({
}: MessageBubbleProps) { }: MessageBubbleProps) {
const bubbleRef = useRef<HTMLDivElement | null>(null); const bubbleRef = useRef<HTMLDivElement | null>(null);
const [isRawView, setIsRawView] = useState(false); const [isRawView, setIsRawView] = useState(false);
const renderedText = useMemo(() => escapeAsterisksInsideMath(text), [text]);
useEffect(() => { useEffect(() => {
if (isRawView) { if (isRawView) {
...@@ -146,7 +148,7 @@ export default function MessageBubble({ ...@@ -146,7 +148,7 @@ export default function MessageBubble({
}, },
}} }}
> >
{text} {renderedText}
</ReactMarkdown> </ReactMarkdown>
)} )}
</div> </div>
......
import { useEffect, useRef } from "react"; import { useEffect, useMemo, useRef } from "react";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import { t } from "../../i18n"; import { t } from "../../i18n";
import { escapeAsterisksInsideMath } from "../../utils/mathMarkdown";
declare global { declare global {
interface Window { interface Window {
...@@ -30,6 +31,10 @@ export default function DocCard({ ...@@ -30,6 +31,10 @@ export default function DocCard({
defaultOpen = false, defaultOpen = false,
}: DocCardProps) { }: DocCardProps) {
const contentRef = useRef<HTMLDivElement | null>(null); const contentRef = useRef<HTMLDivElement | null>(null);
const renderedSnippet = useMemo(
() => escapeAsterisksInsideMath(snippetMarkdown),
[snippetMarkdown]
);
useEffect(() => { useEffect(() => {
const typeset = () => { const typeset = () => {
...@@ -60,7 +65,7 @@ export default function DocCard({ ...@@ -60,7 +65,7 @@ export default function DocCard({
</summary> </summary>
{subtitle ? <div className="doc-subtitle">{subtitle}</div> : null} {subtitle ? <div className="doc-subtitle">{subtitle}</div> : null}
<div className="doc-snippet" ref={contentRef}> <div className="doc-snippet" ref={contentRef}>
<ReactMarkdown>{snippetMarkdown}</ReactMarkdown> <ReactMarkdown>{renderedSnippet}</ReactMarkdown>
</div> </div>
<div className="doc-actions"> <div className="doc-actions">
<button className="btn small" type="button" onClick={onInspect}> <button className="btn small" type="button" onClick={onInspect}>
......
const MATH_SEGMENT_PATTERN =
/(\$\$[\s\S]*?\$\$|\\\[[\s\S]*?\\\]|\\\([\s\S]*?\\\)|\$(?:\\.|[^$\\\n])+\$)/g;
const escapeAsterisks = (value: string) => value.replace(/\*/g, "\\*");
export const escapeAsterisksInsideMath = (input: string): string => {
return input.replace(MATH_SEGMENT_PATTERN, (segment) => {
if (segment.startsWith("$$") && segment.endsWith("$$")) {
const inner = segment.slice(2, -2);
return `$$${escapeAsterisks(inner)}$$`;
}
if (segment.startsWith("\\[") && segment.endsWith("\\]")) {
const inner = segment.slice(2, -2);
return `\\[${escapeAsterisks(inner)}\\]`;
}
if (segment.startsWith("\\(") && segment.endsWith("\\)")) {
const inner = segment.slice(2, -2);
return `\\(${escapeAsterisks(inner)}\\)`;
}
if (segment.startsWith("$") && segment.endsWith("$")) {
const inner = segment.slice(1, -1);
return `$${escapeAsterisks(inner)}$`;
}
return segment;
});
};
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment