create-interview-cockpit 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/client/src/api.ts +67 -0
- package/template/client/src/components/ChatView.tsx +2 -0
- package/template/client/src/components/CodeContextPanel.tsx +4 -0
- package/template/client/src/components/FileViewerModal.tsx +1 -0
- package/template/client/src/components/GitDiffPanel.tsx +403 -0
- package/template/client/src/components/GitDiffViewerModal.tsx +124 -0
- package/template/client/src/components/GithubActionsLabModal.tsx +207 -2
- package/template/client/src/components/LabsPanel.tsx +11 -1
- package/template/client/src/githubActionsLab.ts +232 -2
- package/template/client/src/store.ts +14 -0
- package/template/client/src/types.ts +23 -0
- package/template/client/tsconfig.tsbuildinfo +1 -1
- package/template/cockpit.json +1 -1
- package/template/server/src/gha-runner.ts +2 -1
- package/template/server/src/index.ts +624 -0
- package/template/server/src/storage.ts +12 -0
|
@@ -19,7 +19,12 @@ import {
|
|
|
19
19
|
X,
|
|
20
20
|
} from "lucide-react";
|
|
21
21
|
import MonacoEditorLib from "@monaco-editor/react";
|
|
22
|
-
import type {
|
|
22
|
+
import type {
|
|
23
|
+
BeforeMount,
|
|
24
|
+
Monaco,
|
|
25
|
+
OnChange,
|
|
26
|
+
OnMount,
|
|
27
|
+
} from "@monaco-editor/react";
|
|
23
28
|
import { useStore } from "../store";
|
|
24
29
|
import {
|
|
25
30
|
cloneGhaLabWorkspace,
|
|
@@ -42,8 +47,11 @@ const DEFAULT_W = Math.min(1280, window.innerWidth - 48);
|
|
|
42
47
|
const DEFAULT_H = Math.min(820, window.innerHeight - 48);
|
|
43
48
|
const EDITOR_FONT =
|
|
44
49
|
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
|
|
50
|
+
const GHA_LAB_MODEL_ROOT = "file:///gha-lab/";
|
|
45
51
|
type ResizeDir = "e" | "s" | "se" | "sw" | "w" | "ne" | "nw" | "n";
|
|
46
52
|
|
|
53
|
+
let ghaLabMonacoTypeLibsInjected = false;
|
|
54
|
+
|
|
47
55
|
const EVENTS = [
|
|
48
56
|
"push",
|
|
49
57
|
"pull_request",
|
|
@@ -61,6 +69,8 @@ function baseName(filePath: string): string {
|
|
|
61
69
|
function getEditorLanguage(filePath: string): string {
|
|
62
70
|
const lower = filePath.toLowerCase();
|
|
63
71
|
if (lower.endsWith(".yml") || lower.endsWith(".yaml")) return "yaml";
|
|
72
|
+
if (lower.endsWith(".html")) return "html";
|
|
73
|
+
if (lower.endsWith(".css")) return "css";
|
|
64
74
|
if (lower.endsWith(".json")) return "json";
|
|
65
75
|
if (lower.endsWith(".md") || lower.endsWith(".markdown")) return "markdown";
|
|
66
76
|
if (lower.endsWith(".js") || lower.endsWith(".mjs")) return "javascript";
|
|
@@ -70,6 +80,13 @@ function getEditorLanguage(filePath: string): string {
|
|
|
70
80
|
return "plaintext";
|
|
71
81
|
}
|
|
72
82
|
|
|
83
|
+
function getGhaLabModelPath(filePath: string): string {
|
|
84
|
+
return `${GHA_LAB_MODEL_ROOT}${filePath
|
|
85
|
+
.split("/")
|
|
86
|
+
.map(encodeURIComponent)
|
|
87
|
+
.join("/")}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
73
90
|
// Tiny grouped-by-folder list to keep the modal lean.
|
|
74
91
|
function groupByFolder(paths: string[]): { folder: string; files: string[] }[] {
|
|
75
92
|
const map = new Map<string, string[]>();
|
|
@@ -155,6 +172,8 @@ export default function GithubActionsLabModal() {
|
|
|
155
172
|
const [rightCollapsed, setRightCollapsed] = useState(false);
|
|
156
173
|
const abortRef = useRef<AbortController | null>(null);
|
|
157
174
|
const consoleEndRef = useRef<HTMLDivElement | null>(null);
|
|
175
|
+
const monacoRef = useRef<Monaco | null>(null);
|
|
176
|
+
const monacoModelUrisRef = useRef<Set<string>>(new Set());
|
|
158
177
|
|
|
159
178
|
useEffect(() => {
|
|
160
179
|
// Keep workflow selection valid when files change
|
|
@@ -521,6 +540,50 @@ export default function GithubActionsLabModal() {
|
|
|
521
540
|
|
|
522
541
|
const clearConsole = () => setConsoleLines([]);
|
|
523
542
|
|
|
543
|
+
const syncMonacoWorkspaceModels = useCallback(
|
|
544
|
+
(monaco: Monaco, files: Record<string, string>) => {
|
|
545
|
+
const nextUris = new Set<string>();
|
|
546
|
+
|
|
547
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
548
|
+
const uri = monaco.Uri.parse(getGhaLabModelPath(filePath));
|
|
549
|
+
const uriText = uri.toString();
|
|
550
|
+
nextUris.add(uriText);
|
|
551
|
+
|
|
552
|
+
const existing = monaco.editor.getModel(uri);
|
|
553
|
+
if (existing) {
|
|
554
|
+
if (existing.getValue() !== content) existing.setValue(content);
|
|
555
|
+
} else {
|
|
556
|
+
monaco.editor.createModel(content, getEditorLanguage(filePath), uri);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
for (const uriText of monacoModelUrisRef.current) {
|
|
561
|
+
if (nextUris.has(uriText)) continue;
|
|
562
|
+
monaco.editor.getModel(monaco.Uri.parse(uriText))?.dispose();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
monacoModelUrisRef.current = nextUris;
|
|
566
|
+
},
|
|
567
|
+
[],
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
useEffect(() => {
|
|
571
|
+
const monaco = monacoRef.current;
|
|
572
|
+
if (!monaco) return;
|
|
573
|
+
syncMonacoWorkspaceModels(monaco, workspace.files);
|
|
574
|
+
}, [syncMonacoWorkspaceModels, workspace.files]);
|
|
575
|
+
|
|
576
|
+
useEffect(() => {
|
|
577
|
+
return () => {
|
|
578
|
+
const monaco = monacoRef.current;
|
|
579
|
+
if (!monaco) return;
|
|
580
|
+
for (const uriText of monacoModelUrisRef.current) {
|
|
581
|
+
monaco.editor.getModel(monaco.Uri.parse(uriText))?.dispose();
|
|
582
|
+
}
|
|
583
|
+
monacoModelUrisRef.current.clear();
|
|
584
|
+
};
|
|
585
|
+
}, []);
|
|
586
|
+
|
|
524
587
|
// ── Console input ─────────────────────────────────────────────────
|
|
525
588
|
const [consoleInput, setConsoleInput] = useState("");
|
|
526
589
|
const handleConsoleSubmit = (e: React.FormEvent) => {
|
|
@@ -533,6 +596,139 @@ export default function GithubActionsLabModal() {
|
|
|
533
596
|
|
|
534
597
|
// ── Monaco config ─────────────────────────────────────────────────
|
|
535
598
|
const handleBeforeMount = useCallback<BeforeMount>((monaco: Monaco) => {
|
|
599
|
+
monacoRef.current = monaco;
|
|
600
|
+
|
|
601
|
+
// Monaco does not read the template's tsconfig.json or package.json, so
|
|
602
|
+
// configure its in-browser TypeScript worker like a Vite React TS project.
|
|
603
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
604
|
+
const tsLang = monaco.languages.typescript as any;
|
|
605
|
+
const compilerOptions = {
|
|
606
|
+
target: tsLang.ScriptTarget.ESNext,
|
|
607
|
+
module: tsLang.ModuleKind.ESNext,
|
|
608
|
+
moduleResolution:
|
|
609
|
+
tsLang.ModuleResolutionKind.Bundler ??
|
|
610
|
+
tsLang.ModuleResolutionKind.NodeJs,
|
|
611
|
+
jsx: tsLang.JsxEmit.ReactJSX ?? tsLang.JsxEmit.Preserve,
|
|
612
|
+
jsxImportSource: "react",
|
|
613
|
+
allowJs: true,
|
|
614
|
+
strict: true,
|
|
615
|
+
esModuleInterop: true,
|
|
616
|
+
allowSyntheticDefaultImports: true,
|
|
617
|
+
allowNonTsExtensions: true,
|
|
618
|
+
resolveJsonModule: true,
|
|
619
|
+
};
|
|
620
|
+
tsLang.typescriptDefaults.setCompilerOptions(compilerOptions);
|
|
621
|
+
tsLang.javascriptDefaults.setCompilerOptions(compilerOptions);
|
|
622
|
+
tsLang.typescriptDefaults.setDiagnosticsOptions({
|
|
623
|
+
noSemanticValidation: false,
|
|
624
|
+
noSyntaxValidation: false,
|
|
625
|
+
diagnosticCodesToIgnore: [2688],
|
|
626
|
+
});
|
|
627
|
+
tsLang.javascriptDefaults.setDiagnosticsOptions({
|
|
628
|
+
noSemanticValidation: false,
|
|
629
|
+
noSyntaxValidation: false,
|
|
630
|
+
diagnosticCodesToIgnore: [2688],
|
|
631
|
+
});
|
|
632
|
+
tsLang.typescriptDefaults.setEagerModelSync(true);
|
|
633
|
+
tsLang.javascriptDefaults.setEagerModelSync(true);
|
|
634
|
+
|
|
635
|
+
if (!ghaLabMonacoTypeLibsInjected) {
|
|
636
|
+
const reactViteTypes = `
|
|
637
|
+
declare module "react" {
|
|
638
|
+
export type ReactNode = any;
|
|
639
|
+
export type ReactElement = any;
|
|
640
|
+
export type CSSProperties = Record<string, string | number>;
|
|
641
|
+
export interface FC<P = {}> {
|
|
642
|
+
(props: P): ReactElement | null;
|
|
643
|
+
}
|
|
644
|
+
export const StrictMode: FC<{ children?: ReactNode }>;
|
|
645
|
+
const React: {
|
|
646
|
+
createElement: (...args: any[]) => ReactElement;
|
|
647
|
+
Fragment: any;
|
|
648
|
+
StrictMode: FC<{ children?: ReactNode }>;
|
|
649
|
+
};
|
|
650
|
+
export default React;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
declare module "react/jsx-runtime" {
|
|
654
|
+
export namespace JSX {
|
|
655
|
+
interface Element {}
|
|
656
|
+
interface IntrinsicElements {
|
|
657
|
+
[elemName: string]: any;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
export const Fragment: any;
|
|
661
|
+
export function jsx(type: any, props: any, key?: any): any;
|
|
662
|
+
export function jsxs(type: any, props: any, key?: any): any;
|
|
663
|
+
export function jsxDEV(
|
|
664
|
+
type: any,
|
|
665
|
+
props: any,
|
|
666
|
+
key: any,
|
|
667
|
+
isStaticChildren: boolean,
|
|
668
|
+
source: any,
|
|
669
|
+
self: any,
|
|
670
|
+
): any;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
declare namespace JSX {
|
|
674
|
+
interface Element {}
|
|
675
|
+
interface IntrinsicElements {
|
|
676
|
+
[elemName: string]: any;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
declare module "react-dom/client" {
|
|
681
|
+
export function createRoot(container: Element | DocumentFragment): {
|
|
682
|
+
render(children: any): void;
|
|
683
|
+
unmount(): void;
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
declare module "vite" {
|
|
688
|
+
export function defineConfig(config: any): any;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
declare module "@vitejs/plugin-react" {
|
|
692
|
+
export default function react(options?: any): any;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
declare module "*.css" {
|
|
696
|
+
const classes: Record<string, string>;
|
|
697
|
+
export default classes;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
interface ImportMetaEnv {
|
|
701
|
+
readonly BASE_URL: string;
|
|
702
|
+
readonly DEV: boolean;
|
|
703
|
+
readonly MODE: string;
|
|
704
|
+
readonly PROD: boolean;
|
|
705
|
+
readonly SSR: boolean;
|
|
706
|
+
readonly [key: string]: any;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
interface ImportMeta {
|
|
710
|
+
readonly env: ImportMetaEnv;
|
|
711
|
+
}
|
|
712
|
+
`;
|
|
713
|
+
tsLang.typescriptDefaults.addExtraLib(
|
|
714
|
+
reactViteTypes,
|
|
715
|
+
"file:///__gha-lab-types/react-vite.d.ts",
|
|
716
|
+
);
|
|
717
|
+
tsLang.javascriptDefaults.addExtraLib(
|
|
718
|
+
reactViteTypes,
|
|
719
|
+
"file:///__gha-lab-types/react-vite.d.ts",
|
|
720
|
+
);
|
|
721
|
+
tsLang.typescriptDefaults.addExtraLib(
|
|
722
|
+
reactViteTypes,
|
|
723
|
+
"file:///node_modules/vite/client.d.ts",
|
|
724
|
+
);
|
|
725
|
+
tsLang.javascriptDefaults.addExtraLib(
|
|
726
|
+
reactViteTypes,
|
|
727
|
+
"file:///node_modules/vite/client.d.ts",
|
|
728
|
+
);
|
|
729
|
+
ghaLabMonacoTypeLibsInjected = true;
|
|
730
|
+
}
|
|
731
|
+
|
|
536
732
|
monaco.editor.defineTheme("gha-lab-dark", {
|
|
537
733
|
base: "vs-dark",
|
|
538
734
|
inherit: true,
|
|
@@ -554,6 +750,14 @@ export default function GithubActionsLabModal() {
|
|
|
554
750
|
});
|
|
555
751
|
}, []);
|
|
556
752
|
|
|
753
|
+
const handleMount = useCallback<OnMount>(
|
|
754
|
+
(_editor, monaco) => {
|
|
755
|
+
monacoRef.current = monaco;
|
|
756
|
+
syncMonacoWorkspaceModels(monaco, workspace.files);
|
|
757
|
+
},
|
|
758
|
+
[syncMonacoWorkspaceModels, workspace.files],
|
|
759
|
+
);
|
|
760
|
+
|
|
557
761
|
const handleEditorChange = useCallback<OnChange>(
|
|
558
762
|
(next) => {
|
|
559
763
|
updateFile(activeFile, next ?? "");
|
|
@@ -873,9 +1077,10 @@ export default function GithubActionsLabModal() {
|
|
|
873
1077
|
width="100%"
|
|
874
1078
|
language={getEditorLanguage(activeFile)}
|
|
875
1079
|
theme="gha-lab-dark"
|
|
876
|
-
path={
|
|
1080
|
+
path={getGhaLabModelPath(activeFile)}
|
|
877
1081
|
value={workspace.files[activeFile]}
|
|
878
1082
|
beforeMount={handleBeforeMount}
|
|
1083
|
+
onMount={handleMount}
|
|
879
1084
|
onChange={handleEditorChange}
|
|
880
1085
|
options={{
|
|
881
1086
|
fontFamily: EDITOR_FONT,
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import { useStore } from "../store";
|
|
3
3
|
import { DOCKER_DEEP_DIVE_LAB, parseInfraLabWorkspace } from "../infraLab";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_GHA_LAB,
|
|
6
|
+
parseGhaLabWorkspace,
|
|
7
|
+
REACT_VITE_TYPESCRIPT_GHA_LAB,
|
|
8
|
+
} from "../githubActionsLab";
|
|
5
9
|
import { ENTERPRISE_LOCAL_AUTH_LAB } from "../enterpriseLocalLab";
|
|
6
10
|
import {
|
|
7
11
|
parseFrontendLabWorkspace,
|
|
@@ -656,6 +660,12 @@ export default function LabsPanel() {
|
|
|
656
660
|
origin="github-actions"
|
|
657
661
|
emptyText="Save a GitHub Actions lab to reopen it here"
|
|
658
662
|
newLabMenu={[
|
|
663
|
+
{
|
|
664
|
+
label: "React Vite TypeScript Starter",
|
|
665
|
+
description:
|
|
666
|
+
"Scaffolded React app with an empty .github/workflows/ci.yml",
|
|
667
|
+
onClick: () => openGhaLab(REACT_VITE_TYPESCRIPT_GHA_LAB),
|
|
668
|
+
},
|
|
659
669
|
{
|
|
660
670
|
label: "Workflows + Composite Action",
|
|
661
671
|
description:
|
|
@@ -164,6 +164,223 @@ require("fs").readdirSync(".").forEach((f) => console.log(" -", f));
|
|
|
164
164
|
`,
|
|
165
165
|
};
|
|
166
166
|
|
|
167
|
+
const REACT_VITE_TYPESCRIPT_FILES: Record<string, string> = {
|
|
168
|
+
"README.md": `# React Vite TypeScript GitHub Actions Lab
|
|
169
|
+
|
|
170
|
+
This template starts with a small React + Vite + TypeScript application and an intentionally empty workflow file.
|
|
171
|
+
|
|
172
|
+
## App files
|
|
173
|
+
|
|
174
|
+
- \`package.json\` defines the Vite scripts and React dependencies.
|
|
175
|
+
- \`index.html\` mounts the app.
|
|
176
|
+
- \`src/main.tsx\` boots React.
|
|
177
|
+
- \`src/App.tsx\` contains the starter UI.
|
|
178
|
+
- \`.github/workflows/ci.yml\` is blank on purpose so you can write the CI workflow from scratch.
|
|
179
|
+
|
|
180
|
+
## Local app commands
|
|
181
|
+
|
|
182
|
+
npm install
|
|
183
|
+
npm run dev
|
|
184
|
+
npm run build
|
|
185
|
+
|
|
186
|
+
## GitHub Actions task
|
|
187
|
+
|
|
188
|
+
Open \`.github/workflows/ci.yml\`, add your workflow, then run it with the lab controls.
|
|
189
|
+
`,
|
|
190
|
+
|
|
191
|
+
".github/workflows/ci.yml": "",
|
|
192
|
+
|
|
193
|
+
".gitignore": `node_modules
|
|
194
|
+
dist
|
|
195
|
+
.DS_Store
|
|
196
|
+
*.local
|
|
197
|
+
`,
|
|
198
|
+
|
|
199
|
+
"package.json": `{
|
|
200
|
+
"name": "gha-react-vite-typescript-app",
|
|
201
|
+
"private": true,
|
|
202
|
+
"version": "0.0.0",
|
|
203
|
+
"type": "module",
|
|
204
|
+
"scripts": {
|
|
205
|
+
"dev": "vite --host 0.0.0.0",
|
|
206
|
+
"build": "tsc --noEmit && vite build",
|
|
207
|
+
"preview": "vite preview --host 0.0.0.0"
|
|
208
|
+
},
|
|
209
|
+
"dependencies": {
|
|
210
|
+
"react": "^19.0.0",
|
|
211
|
+
"react-dom": "^19.0.0"
|
|
212
|
+
},
|
|
213
|
+
"devDependencies": {
|
|
214
|
+
"@types/react": "^19.0.0",
|
|
215
|
+
"@types/react-dom": "^19.0.0",
|
|
216
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
217
|
+
"typescript": "^5.9.3",
|
|
218
|
+
"vite": "^6.0.0"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
`,
|
|
222
|
+
|
|
223
|
+
"index.html": `<!doctype html>
|
|
224
|
+
<html lang="en">
|
|
225
|
+
<head>
|
|
226
|
+
<meta charset="UTF-8" />
|
|
227
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
228
|
+
<title>React Vite TypeScript App</title>
|
|
229
|
+
</head>
|
|
230
|
+
<body>
|
|
231
|
+
<div id="root"></div>
|
|
232
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
233
|
+
</body>
|
|
234
|
+
</html>
|
|
235
|
+
`,
|
|
236
|
+
|
|
237
|
+
"tsconfig.json": `{
|
|
238
|
+
"compilerOptions": {
|
|
239
|
+
"target": "ES2020",
|
|
240
|
+
"useDefineForClassFields": true,
|
|
241
|
+
"lib": ["DOM", "DOM.Iterable", "ES2020"],
|
|
242
|
+
"allowJs": false,
|
|
243
|
+
"skipLibCheck": true,
|
|
244
|
+
"esModuleInterop": true,
|
|
245
|
+
"allowSyntheticDefaultImports": true,
|
|
246
|
+
"strict": true,
|
|
247
|
+
"forceConsistentCasingInFileNames": true,
|
|
248
|
+
"module": "ESNext",
|
|
249
|
+
"moduleResolution": "Node",
|
|
250
|
+
"resolveJsonModule": true,
|
|
251
|
+
"isolatedModules": true,
|
|
252
|
+
"noEmit": true,
|
|
253
|
+
"jsx": "react-jsx"
|
|
254
|
+
},
|
|
255
|
+
"include": ["src"]
|
|
256
|
+
}
|
|
257
|
+
`,
|
|
258
|
+
|
|
259
|
+
"vite.config.ts": `import { defineConfig } from "vite";
|
|
260
|
+
import react from "@vitejs/plugin-react";
|
|
261
|
+
|
|
262
|
+
export default defineConfig({
|
|
263
|
+
plugins: [react()],
|
|
264
|
+
});
|
|
265
|
+
`,
|
|
266
|
+
|
|
267
|
+
"src/main.tsx": `import { StrictMode } from "react";
|
|
268
|
+
import { createRoot } from "react-dom/client";
|
|
269
|
+
import App from "./App";
|
|
270
|
+
import "./index.css";
|
|
271
|
+
|
|
272
|
+
createRoot(document.getElementById("root")!).render(
|
|
273
|
+
<StrictMode>
|
|
274
|
+
<App />
|
|
275
|
+
</StrictMode>,
|
|
276
|
+
);
|
|
277
|
+
`,
|
|
278
|
+
|
|
279
|
+
"src/App.tsx": `const checks = [
|
|
280
|
+
"Edit .github/workflows/ci.yml from a clean slate",
|
|
281
|
+
"Install dependencies with npm install",
|
|
282
|
+
"Run npm run build in your workflow",
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
export default function App() {
|
|
286
|
+
return (
|
|
287
|
+
<main className="app-shell">
|
|
288
|
+
<section className="hero-card">
|
|
289
|
+
<p className="eyebrow">GitHub Actions Practice</p>
|
|
290
|
+
<h1>React + Vite + TypeScript</h1>
|
|
291
|
+
<p className="lead">
|
|
292
|
+
The application is ready. Your CI workflow starts empty so you can
|
|
293
|
+
build it one line at a time.
|
|
294
|
+
</p>
|
|
295
|
+
<ul>
|
|
296
|
+
{checks.map((check) => (
|
|
297
|
+
<li key={check}>{check}</li>
|
|
298
|
+
))}
|
|
299
|
+
</ul>
|
|
300
|
+
</section>
|
|
301
|
+
</main>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
`,
|
|
305
|
+
|
|
306
|
+
"src/index.css": `:root {
|
|
307
|
+
color: #e5eefb;
|
|
308
|
+
background: #0f172a;
|
|
309
|
+
font-family:
|
|
310
|
+
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
311
|
+
sans-serif;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
* {
|
|
315
|
+
box-sizing: border-box;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
body {
|
|
319
|
+
margin: 0;
|
|
320
|
+
min-width: 320px;
|
|
321
|
+
min-height: 100vh;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.app-shell {
|
|
325
|
+
display: grid;
|
|
326
|
+
min-height: 100vh;
|
|
327
|
+
place-items: center;
|
|
328
|
+
padding: 2rem;
|
|
329
|
+
background:
|
|
330
|
+
radial-gradient(circle at top left, rgba(59, 130, 246, 0.35), transparent 32rem),
|
|
331
|
+
linear-gradient(135deg, #0f172a 0%, #111827 100%);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.hero-card {
|
|
335
|
+
width: min(100%, 720px);
|
|
336
|
+
padding: 2rem;
|
|
337
|
+
border: 1px solid rgba(148, 163, 184, 0.2);
|
|
338
|
+
border-radius: 24px;
|
|
339
|
+
background: rgba(15, 23, 42, 0.78);
|
|
340
|
+
box-shadow: 0 24px 80px rgba(2, 6, 23, 0.45);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.eyebrow {
|
|
344
|
+
margin: 0 0 0.75rem;
|
|
345
|
+
color: #fbbf24;
|
|
346
|
+
font-size: 0.78rem;
|
|
347
|
+
font-weight: 700;
|
|
348
|
+
letter-spacing: 0.16em;
|
|
349
|
+
text-transform: uppercase;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
h1 {
|
|
353
|
+
margin: 0;
|
|
354
|
+
font-size: clamp(2.5rem, 8vw, 5rem);
|
|
355
|
+
line-height: 0.95;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.lead {
|
|
359
|
+
color: #cbd5e1;
|
|
360
|
+
font-size: 1.1rem;
|
|
361
|
+
line-height: 1.7;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
ul {
|
|
365
|
+
display: grid;
|
|
366
|
+
gap: 0.75rem;
|
|
367
|
+
margin: 1.5rem 0 0;
|
|
368
|
+
padding: 0;
|
|
369
|
+
list-style: none;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
li {
|
|
373
|
+
padding: 0.85rem 1rem;
|
|
374
|
+
border: 1px solid rgba(148, 163, 184, 0.16);
|
|
375
|
+
border-radius: 14px;
|
|
376
|
+
background: rgba(30, 41, 59, 0.72);
|
|
377
|
+
}
|
|
378
|
+
`,
|
|
379
|
+
|
|
380
|
+
"src/vite-env.d.ts": `/// <reference types="vite/client" />
|
|
381
|
+
`,
|
|
382
|
+
};
|
|
383
|
+
|
|
167
384
|
export const DEFAULT_GHA_LAB: GithubActionsLabWorkspace = {
|
|
168
385
|
version: 1,
|
|
169
386
|
label: "GitHub Actions Playground",
|
|
@@ -173,6 +390,15 @@ export const DEFAULT_GHA_LAB: GithubActionsLabWorkspace = {
|
|
|
173
390
|
files: DEFAULT_FILES,
|
|
174
391
|
};
|
|
175
392
|
|
|
393
|
+
export const REACT_VITE_TYPESCRIPT_GHA_LAB: GithubActionsLabWorkspace = {
|
|
394
|
+
version: 1,
|
|
395
|
+
label: "React Vite TypeScript Starter",
|
|
396
|
+
activeFile: ".github/workflows/ci.yml",
|
|
397
|
+
defaultEvent: "push",
|
|
398
|
+
defaultWorkflow: ".github/workflows/ci.yml",
|
|
399
|
+
files: REACT_VITE_TYPESCRIPT_FILES,
|
|
400
|
+
};
|
|
401
|
+
|
|
176
402
|
// ─── Helpers (mirror infraLab.ts API surface) ────────────────────────────
|
|
177
403
|
|
|
178
404
|
export function cloneGhaLabWorkspace(
|
|
@@ -183,7 +409,10 @@ export function cloneGhaLabWorkspace(
|
|
|
183
409
|
source.files && Object.keys(source.files).length > 0
|
|
184
410
|
? { ...source.files }
|
|
185
411
|
: { ...DEFAULT_FILES };
|
|
186
|
-
const activeFile =
|
|
412
|
+
const activeFile = Object.prototype.hasOwnProperty.call(
|
|
413
|
+
sourceFiles,
|
|
414
|
+
source.activeFile,
|
|
415
|
+
)
|
|
187
416
|
? source.activeFile
|
|
188
417
|
: (Object.keys(sourceFiles)[0] ?? ".github/workflows/ci.yml");
|
|
189
418
|
|
|
@@ -193,7 +422,8 @@ export function cloneGhaLabWorkspace(
|
|
|
193
422
|
activeFile,
|
|
194
423
|
defaultEvent: source.defaultEvent || "push",
|
|
195
424
|
defaultWorkflow:
|
|
196
|
-
source.defaultWorkflow &&
|
|
425
|
+
source.defaultWorkflow &&
|
|
426
|
+
Object.prototype.hasOwnProperty.call(sourceFiles, source.defaultWorkflow)
|
|
197
427
|
? source.defaultWorkflow
|
|
198
428
|
: Object.keys(sourceFiles).find((f) =>
|
|
199
429
|
f.startsWith(".github/workflows/"),
|
|
@@ -200,6 +200,10 @@ interface Store {
|
|
|
200
200
|
toggleSidebar: () => void;
|
|
201
201
|
fetchAvailableFiles: () => Promise<void>;
|
|
202
202
|
updateCodeContext: (questionId: string, files: string[]) => Promise<void>;
|
|
203
|
+
updateGitDiffContext: (
|
|
204
|
+
questionId: string,
|
|
205
|
+
ctx: import("./types").GitDiffContext | null,
|
|
206
|
+
) => Promise<void>;
|
|
203
207
|
refreshCurrentQuestion: () => Promise<void>;
|
|
204
208
|
uploadTopicFiles: (
|
|
205
209
|
topicId: string,
|
|
@@ -830,6 +834,16 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
830
834
|
}));
|
|
831
835
|
},
|
|
832
836
|
|
|
837
|
+
updateGitDiffContext: async (questionId, ctx) => {
|
|
838
|
+
// Send null explicitly so the server can clear the field; PATCH ignores undefined.
|
|
839
|
+
await api.updateQuestion(questionId, { gitDiffContext: ctx } as any);
|
|
840
|
+
set((s) => ({
|
|
841
|
+
currentQuestion: s.currentQuestion
|
|
842
|
+
? { ...s.currentQuestion, gitDiffContext: ctx ?? undefined }
|
|
843
|
+
: null,
|
|
844
|
+
}));
|
|
845
|
+
},
|
|
846
|
+
|
|
833
847
|
refreshCurrentQuestion: async () => {
|
|
834
848
|
const { selectedQuestionId } = get();
|
|
835
849
|
if (selectedQuestionId) {
|
|
@@ -151,6 +151,8 @@ export interface Question {
|
|
|
151
151
|
title: string;
|
|
152
152
|
systemContext: string;
|
|
153
153
|
codeContextFiles: string[];
|
|
154
|
+
/** Optional git diff selection that the chat LLM can lazy-fetch via readFile. */
|
|
155
|
+
gitDiffContext?: GitDiffContext;
|
|
154
156
|
contextFiles: ContextFile[];
|
|
155
157
|
messages: Message[];
|
|
156
158
|
annotations?: Annotation[];
|
|
@@ -161,3 +163,24 @@ export interface Question {
|
|
|
161
163
|
linkedConversationIds?: string[];
|
|
162
164
|
createdAt: string;
|
|
163
165
|
}
|
|
166
|
+
|
|
167
|
+
export type GitDiffMode = "two-dot" | "three-dot" | "working-tree";
|
|
168
|
+
|
|
169
|
+
export interface GitDiffContext {
|
|
170
|
+
baseRef: string;
|
|
171
|
+
/** Branch / ref / tag. Empty when mode is working-tree. */
|
|
172
|
+
headRef: string;
|
|
173
|
+
mode: GitDiffMode;
|
|
174
|
+
selectedFiles: string[];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export type GitDiffStatus = "A" | "M" | "D" | "R" | "C" | "T" | "U" | "?";
|
|
178
|
+
|
|
179
|
+
export interface GitDiffFileEntry {
|
|
180
|
+
path: string;
|
|
181
|
+
oldPath?: string;
|
|
182
|
+
status: GitDiffStatus;
|
|
183
|
+
additions: number;
|
|
184
|
+
deletions: number;
|
|
185
|
+
binary: boolean;
|
|
186
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/app.tsx","./src/api.ts","./src/browsersecuritytemplates.ts","./src/enterpriselocallab.ts","./src/githubactionslab.ts","./src/infralab.ts","./src/main.tsx","./src/reactlab.ts","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/aisettingsmodal.tsx","./src/components/annotationdialog.tsx","./src/components/browsersecuritylabmodal.tsx","./src/components/canvaslabmodal.tsx","./src/components/chatmessage.tsx","./src/components/chatview.tsx","./src/components/codecontextpanel.tsx","./src/components/codelineannotationpopup.tsx","./src/components/coderunnermodal.tsx","./src/components/deploymentlabmodal.tsx","./src/components/docrefmodal.tsx","./src/components/fileattachments.tsx","./src/components/filepickermodal.tsx","./src/components/fileviewermodal.tsx","./src/components/ghahistorypanel.tsx","./src/components/ghajobspanel.tsx","./src/components/githubactionslabmodal.tsx","./src/components/infralabmodal.tsx","./src/components/labspanel.tsx","./src/components/linkedconvospicker.tsx","./src/components/markdownrenderer.tsx","./src/components/mermaiddiagram.tsx","./src/components/notesmodal.tsx","./src/components/plotembed.tsx","./src/components/sidebar.tsx","./src/components/textannotator.tsx","./src/components/vizcraftembed.tsx","./src/components/workspaceswitcher.tsx"],"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/app.tsx","./src/api.ts","./src/browsersecuritytemplates.ts","./src/enterpriselocallab.ts","./src/githubactionslab.ts","./src/infralab.ts","./src/main.tsx","./src/reactlab.ts","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/aisettingsmodal.tsx","./src/components/annotationdialog.tsx","./src/components/browsersecuritylabmodal.tsx","./src/components/canvaslabmodal.tsx","./src/components/chatmessage.tsx","./src/components/chatview.tsx","./src/components/codecontextpanel.tsx","./src/components/codelineannotationpopup.tsx","./src/components/coderunnermodal.tsx","./src/components/deploymentlabmodal.tsx","./src/components/docrefmodal.tsx","./src/components/fileattachments.tsx","./src/components/filepickermodal.tsx","./src/components/fileviewermodal.tsx","./src/components/ghahistorypanel.tsx","./src/components/ghajobspanel.tsx","./src/components/gitdiffpanel.tsx","./src/components/gitdiffviewermodal.tsx","./src/components/githubactionslabmodal.tsx","./src/components/infralabmodal.tsx","./src/components/labspanel.tsx","./src/components/linkedconvospicker.tsx","./src/components/markdownrenderer.tsx","./src/components/mermaiddiagram.tsx","./src/components/notesmodal.tsx","./src/components/plotembed.tsx","./src/components/sidebar.tsx","./src/components/textannotator.tsx","./src/components/vizcraftembed.tsx","./src/components/workspaceswitcher.tsx"],"version":"5.9.3"}
|
package/template/cockpit.json
CHANGED
|
@@ -180,7 +180,8 @@ function parseWorkspace(input: unknown): GithubActionsLabWorkspace {
|
|
|
180
180
|
? candidate.label.trim()
|
|
181
181
|
: "GitHub Actions Lab",
|
|
182
182
|
activeFile:
|
|
183
|
-
typeof candidate.activeFile === "string" &&
|
|
183
|
+
typeof candidate.activeFile === "string" &&
|
|
184
|
+
Object.prototype.hasOwnProperty.call(files, candidate.activeFile)
|
|
184
185
|
? candidate.activeFile
|
|
185
186
|
: fileNames[0],
|
|
186
187
|
defaultEvent:
|