react-os-shell 0.1.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/LICENSE +21 -0
- package/README.md +242 -0
- package/dist/Calculator-BNBRNV4P.js +184 -0
- package/dist/Calculator-BNBRNV4P.js.map +1 -0
- package/dist/Calendar-5EYUVGUU.js +423 -0
- package/dist/Calendar-5EYUVGUU.js.map +1 -0
- package/dist/Checkers-MIAHIKJH.js +214 -0
- package/dist/Checkers-MIAHIKJH.js.map +1 -0
- package/dist/Chess-C5BY45NA.js +190 -0
- package/dist/Chess-C5BY45NA.js.map +1 -0
- package/dist/ConfirmDialog-ZP4AHVUD.js +3 -0
- package/dist/ConfirmDialog-ZP4AHVUD.js.map +1 -0
- package/dist/CurrencyConverter-TYPU2IRF.js +223 -0
- package/dist/CurrencyConverter-TYPU2IRF.js.map +1 -0
- package/dist/Email-JEYYJ3YV.js +1835 -0
- package/dist/Email-JEYYJ3YV.js.map +1 -0
- package/dist/Game2048-3RH3ELRD.js +191 -0
- package/dist/Game2048-3RH3ELRD.js.map +1 -0
- package/dist/GeminiChat-BXLBJFT4.js +184 -0
- package/dist/GeminiChat-BXLBJFT4.js.map +1 -0
- package/dist/Minesweeper-VQGLAZON.js +270 -0
- package/dist/Minesweeper-VQGLAZON.js.map +1 -0
- package/dist/Notepad-YTZRCAXX.js +389 -0
- package/dist/Notepad-YTZRCAXX.js.map +1 -0
- package/dist/PomodoroTimer-HARIJN4S.js +196 -0
- package/dist/PomodoroTimer-HARIJN4S.js.map +1 -0
- package/dist/Spreadsheet-IOKEDNS6.js +446 -0
- package/dist/Spreadsheet-IOKEDNS6.js.map +1 -0
- package/dist/Sudoku-XHLYCEVT.js +197 -0
- package/dist/Sudoku-XHLYCEVT.js.map +1 -0
- package/dist/Tetris-ZHCZYL24.js +243 -0
- package/dist/Tetris-ZHCZYL24.js.map +1 -0
- package/dist/Weather-ROZ7TRNW.js +310 -0
- package/dist/Weather-ROZ7TRNW.js.map +1 -0
- package/dist/apps/index.d.ts +55 -0
- package/dist/apps/index.js +48 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/chunk-5O2KEISQ.js +155 -0
- package/dist/chunk-5O2KEISQ.js.map +1 -0
- package/dist/chunk-D7PYW2QS.js +265 -0
- package/dist/chunk-D7PYW2QS.js.map +1 -0
- package/dist/chunk-GP4Y3VCB.js +806 -0
- package/dist/chunk-GP4Y3VCB.js.map +1 -0
- package/dist/chunk-NSU7OHPC.js +39 -0
- package/dist/chunk-NSU7OHPC.js.map +1 -0
- package/dist/chunk-PDFQNHW7.js +24 -0
- package/dist/chunk-PDFQNHW7.js.map +1 -0
- package/dist/chunk-RFTLYCSF.js +144 -0
- package/dist/chunk-RFTLYCSF.js.map +1 -0
- package/dist/chunk-SVBID2P6.js +142 -0
- package/dist/chunk-SVBID2P6.js.map +1 -0
- package/dist/chunk-TFGOLXGD.js +41 -0
- package/dist/chunk-TFGOLXGD.js.map +1 -0
- package/dist/chunk-WIJ45SYD.js +120 -0
- package/dist/chunk-WIJ45SYD.js.map +1 -0
- package/dist/chunk-WQIS72NL.js +1470 -0
- package/dist/chunk-WQIS72NL.js.map +1 -0
- package/dist/index.d.ts +642 -0
- package/dist/index.js +3443 -0
- package/dist/index.js.map +1 -0
- package/dist/sounds-NT4DEZGD.js +3 -0
- package/dist/sounds-NT4DEZGD.js.map +1 -0
- package/dist/styles.css +174 -0
- package/dist/types-CFIZ1_xt.d.ts +67 -0
- package/package.json +76 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/apps/Game2048.tsx
|
|
5
|
+
var TILE_COLORS = {
|
|
6
|
+
0: "bg-gray-200",
|
|
7
|
+
2: "bg-gray-100 text-gray-700",
|
|
8
|
+
4: "bg-gray-200 text-gray-700",
|
|
9
|
+
8: "bg-orange-300 text-white",
|
|
10
|
+
16: "bg-orange-400 text-white",
|
|
11
|
+
32: "bg-orange-500 text-white",
|
|
12
|
+
64: "bg-red-500 text-white",
|
|
13
|
+
128: "bg-yellow-300 text-gray-800",
|
|
14
|
+
256: "bg-yellow-400 text-gray-800",
|
|
15
|
+
512: "bg-yellow-500 text-white",
|
|
16
|
+
1024: "bg-yellow-600 text-white",
|
|
17
|
+
2048: "bg-yellow-300 text-gray-800 ring-4 ring-yellow-400"
|
|
18
|
+
};
|
|
19
|
+
var empty = () => Array.from({ length: 4 }, () => Array(4).fill(0));
|
|
20
|
+
function addRandom(b) {
|
|
21
|
+
const cells = [];
|
|
22
|
+
b.forEach((r2, i) => r2.forEach((v, j) => {
|
|
23
|
+
if (!v) cells.push([i, j]);
|
|
24
|
+
}));
|
|
25
|
+
if (!cells.length) return b;
|
|
26
|
+
const [r, c] = cells[Math.floor(Math.random() * cells.length)];
|
|
27
|
+
const nb = b.map((r2) => [...r2]);
|
|
28
|
+
nb[r][c] = Math.random() < 0.9 ? 2 : 4;
|
|
29
|
+
return nb;
|
|
30
|
+
}
|
|
31
|
+
function slideRow(row) {
|
|
32
|
+
const nums = row.filter((v) => v);
|
|
33
|
+
let score = 0;
|
|
34
|
+
const merged = [];
|
|
35
|
+
for (let i = 0; i < nums.length; i++) {
|
|
36
|
+
if (i + 1 < nums.length && nums[i] === nums[i + 1]) {
|
|
37
|
+
merged.push(nums[i] * 2);
|
|
38
|
+
score += nums[i] * 2;
|
|
39
|
+
i++;
|
|
40
|
+
} else merged.push(nums[i]);
|
|
41
|
+
}
|
|
42
|
+
while (merged.length < 4) merged.push(0);
|
|
43
|
+
return { row: merged, score };
|
|
44
|
+
}
|
|
45
|
+
function rotate(b) {
|
|
46
|
+
return b[0].map((_, i) => b.map((r) => r[i]).reverse());
|
|
47
|
+
}
|
|
48
|
+
function move(board, dir) {
|
|
49
|
+
let b = board.map((r) => [...r]);
|
|
50
|
+
const rotations = { left: 0, down: 1, right: 2, up: 3 };
|
|
51
|
+
for (let i = 0; i < rotations[dir]; i++) b = rotate(b);
|
|
52
|
+
let score = 0;
|
|
53
|
+
b = b.map((r) => {
|
|
54
|
+
const res = slideRow(r);
|
|
55
|
+
score += res.score;
|
|
56
|
+
return res.row;
|
|
57
|
+
});
|
|
58
|
+
for (let i = 0; i < (4 - rotations[dir]) % 4; i++) b = rotate(b);
|
|
59
|
+
return { board: b, score };
|
|
60
|
+
}
|
|
61
|
+
function hasValidMoves(b) {
|
|
62
|
+
for (let i = 0; i < 4; i++)
|
|
63
|
+
for (let j = 0; j < 4; j++) {
|
|
64
|
+
if (!b[i][j]) return true;
|
|
65
|
+
if (j < 3 && b[i][j] === b[i][j + 1]) return true;
|
|
66
|
+
if (i < 3 && b[i][j] === b[i + 1][j]) return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
function boardsEqual(a, b) {
|
|
71
|
+
return a.every((r, i) => r.every((v, j) => v === b[i][j]));
|
|
72
|
+
}
|
|
73
|
+
function initBoard() {
|
|
74
|
+
return addRandom(addRandom(empty()));
|
|
75
|
+
}
|
|
76
|
+
function Game2048() {
|
|
77
|
+
const [board, setBoard] = useState(initBoard);
|
|
78
|
+
const [score, setScore] = useState(0);
|
|
79
|
+
const [best, setBest] = useState(() => Number(localStorage.getItem("game2048_best") || 0));
|
|
80
|
+
const [won, setWon] = useState(false);
|
|
81
|
+
const [showWin, setShowWin] = useState(false);
|
|
82
|
+
const [gameOver, setGameOver] = useState(false);
|
|
83
|
+
const handleMove = useCallback((dir) => {
|
|
84
|
+
if (gameOver) return;
|
|
85
|
+
setBoard((prev) => {
|
|
86
|
+
const { board: nb, score: gained } = move(prev, dir);
|
|
87
|
+
if (boardsEqual(prev, nb)) return prev;
|
|
88
|
+
const withNew = addRandom(nb);
|
|
89
|
+
setScore((s) => {
|
|
90
|
+
const ns = s + gained;
|
|
91
|
+
setBest((b) => {
|
|
92
|
+
const nb2 = Math.max(b, ns);
|
|
93
|
+
localStorage.setItem("game2048_best", String(nb2));
|
|
94
|
+
return nb2;
|
|
95
|
+
});
|
|
96
|
+
return ns;
|
|
97
|
+
});
|
|
98
|
+
if (!won && withNew.some((r) => r.some((v) => v >= 2048))) {
|
|
99
|
+
setWon(true);
|
|
100
|
+
setShowWin(true);
|
|
101
|
+
}
|
|
102
|
+
if (!hasValidMoves(withNew)) setGameOver(true);
|
|
103
|
+
return withNew;
|
|
104
|
+
});
|
|
105
|
+
}, [gameOver, won]);
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
const keyMap = {
|
|
108
|
+
ArrowLeft: "left",
|
|
109
|
+
ArrowRight: "right",
|
|
110
|
+
ArrowUp: "up",
|
|
111
|
+
ArrowDown: "down"
|
|
112
|
+
};
|
|
113
|
+
const handler = (e) => {
|
|
114
|
+
const dir = keyMap[e.key];
|
|
115
|
+
if (dir) {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
handleMove(dir);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
window.addEventListener("keydown", handler);
|
|
121
|
+
return () => window.removeEventListener("keydown", handler);
|
|
122
|
+
}, [handleMove]);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
let sx = 0, sy = 0;
|
|
125
|
+
const el = document.getElementById("game2048-board");
|
|
126
|
+
if (!el) return;
|
|
127
|
+
const ts = (e) => {
|
|
128
|
+
sx = e.touches[0].clientX;
|
|
129
|
+
sy = e.touches[0].clientY;
|
|
130
|
+
};
|
|
131
|
+
const te = (e) => {
|
|
132
|
+
const dx = e.changedTouches[0].clientX - sx, dy = e.changedTouches[0].clientY - sy;
|
|
133
|
+
if (Math.max(Math.abs(dx), Math.abs(dy)) < 30) return;
|
|
134
|
+
if (Math.abs(dx) > Math.abs(dy)) handleMove(dx > 0 ? "right" : "left");
|
|
135
|
+
else handleMove(dy > 0 ? "down" : "up");
|
|
136
|
+
};
|
|
137
|
+
el.addEventListener("touchstart", ts, { passive: true });
|
|
138
|
+
el.addEventListener("touchend", te, { passive: true });
|
|
139
|
+
return () => {
|
|
140
|
+
el.removeEventListener("touchstart", ts);
|
|
141
|
+
el.removeEventListener("touchend", te);
|
|
142
|
+
};
|
|
143
|
+
}, [handleMove]);
|
|
144
|
+
const newGame = () => {
|
|
145
|
+
setBoard(initBoard());
|
|
146
|
+
setScore(0);
|
|
147
|
+
setWon(false);
|
|
148
|
+
setShowWin(false);
|
|
149
|
+
setGameOver(false);
|
|
150
|
+
};
|
|
151
|
+
const fontSize = (v) => v >= 1024 ? "text-lg" : v >= 128 ? "text-xl" : "text-2xl";
|
|
152
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center select-none", children: [
|
|
153
|
+
/* @__PURE__ */ jsxs("div", { className: "w-full max-w-[340px]", children: [
|
|
154
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
|
|
155
|
+
/* @__PURE__ */ jsx("h1", { className: "text-3xl font-bold text-gray-800", children: "2048" }),
|
|
156
|
+
/* @__PURE__ */ jsx("button", { onClick: newGame, className: "px-4 py-1.5 bg-gray-700 text-white rounded font-semibold text-sm hover:bg-gray-800", children: "New Game" })
|
|
157
|
+
] }),
|
|
158
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-3 mb-3", children: [
|
|
159
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 bg-gray-700 text-white rounded px-3 py-1 text-center", children: [
|
|
160
|
+
/* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wider opacity-70", children: "Score" }),
|
|
161
|
+
/* @__PURE__ */ jsx("div", { className: "text-lg font-bold", children: score })
|
|
162
|
+
] }),
|
|
163
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 bg-gray-700 text-white rounded px-3 py-1 text-center", children: [
|
|
164
|
+
/* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wider opacity-70", children: "Best" }),
|
|
165
|
+
/* @__PURE__ */ jsx("div", { className: "text-lg font-bold", children: best })
|
|
166
|
+
] })
|
|
167
|
+
] }),
|
|
168
|
+
/* @__PURE__ */ jsxs("div", { id: "game2048-board", className: "relative bg-gray-300 rounded-lg p-2 grid grid-cols-4 gap-2", children: [
|
|
169
|
+
board.flat().map((v, i) => /* @__PURE__ */ jsx("div", { className: `aspect-square rounded-md flex items-center justify-center font-bold transition-all duration-100
|
|
170
|
+
${TILE_COLORS[v] || "bg-yellow-200 text-gray-800"} ${fontSize(v)} ${v ? "animate-pop" : ""}`, children: v || "" }, i)),
|
|
171
|
+
showWin && /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 bg-yellow-300/80 rounded-lg flex flex-col items-center justify-center gap-3", children: [
|
|
172
|
+
/* @__PURE__ */ jsx("div", { className: "text-4xl font-bold text-gray-800", children: "You Win!" }),
|
|
173
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
174
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setShowWin(false), className: "px-4 py-2 bg-gray-700 text-white rounded font-semibold text-sm", children: "Continue" }),
|
|
175
|
+
/* @__PURE__ */ jsx("button", { onClick: newGame, className: "px-4 py-2 bg-gray-500 text-white rounded font-semibold text-sm", children: "New Game" })
|
|
176
|
+
] })
|
|
177
|
+
] }),
|
|
178
|
+
gameOver && /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 bg-gray-800/70 rounded-lg flex flex-col items-center justify-center gap-3", children: [
|
|
179
|
+
/* @__PURE__ */ jsx("div", { className: "text-3xl font-bold text-white", children: "Game Over" }),
|
|
180
|
+
/* @__PURE__ */ jsx("button", { onClick: newGame, className: "px-4 py-2 bg-white text-gray-800 rounded font-semibold text-sm", children: "Try Again" })
|
|
181
|
+
] })
|
|
182
|
+
] }),
|
|
183
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 text-center mt-3", children: "Use arrow keys or swipe to play" })
|
|
184
|
+
] }),
|
|
185
|
+
/* @__PURE__ */ jsx("style", { children: `@keyframes pop{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}.animate-pop{animation:pop .15s ease-out}` })
|
|
186
|
+
] });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export { Game2048 as default };
|
|
190
|
+
//# sourceMappingURL=Game2048-3RH3ELRD.js.map
|
|
191
|
+
//# sourceMappingURL=Game2048-3RH3ELRD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/apps/Game2048.tsx"],"names":["r","nb"],"mappings":"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","file":"Game2048-3RH3ELRD.js","sourcesContent":["import { useState, useCallback, useEffect } from 'react';\n\ntype Board = number[][];\n\nconst TILE_COLORS: Record<number, string> = {\n 0: 'bg-gray-200', 2: 'bg-gray-100 text-gray-700', 4: 'bg-gray-200 text-gray-700',\n 8: 'bg-orange-300 text-white', 16: 'bg-orange-400 text-white', 32: 'bg-orange-500 text-white',\n 64: 'bg-red-500 text-white', 128: 'bg-yellow-300 text-gray-800', 256: 'bg-yellow-400 text-gray-800',\n 512: 'bg-yellow-500 text-white', 1024: 'bg-yellow-600 text-white', 2048: 'bg-yellow-300 text-gray-800 ring-4 ring-yellow-400',\n};\n\nconst empty = (): Board => Array.from({ length: 4 }, () => Array(4).fill(0));\n\nfunction addRandom(b: Board): Board {\n const cells: [number, number][] = [];\n b.forEach((r, i) => r.forEach((v, j) => { if (!v) cells.push([i, j]); }));\n if (!cells.length) return b;\n const [r, c] = cells[Math.floor(Math.random() * cells.length)];\n const nb = b.map(r => [...r]);\n nb[r][c] = Math.random() < 0.9 ? 2 : 4;\n return nb;\n}\n\nfunction slideRow(row: number[]): { row: number[]; score: number } {\n const nums = row.filter(v => v);\n let score = 0;\n const merged: number[] = [];\n for (let i = 0; i < nums.length; i++) {\n if (i + 1 < nums.length && nums[i] === nums[i + 1]) {\n merged.push(nums[i] * 2);\n score += nums[i] * 2;\n i++;\n } else merged.push(nums[i]);\n }\n while (merged.length < 4) merged.push(0);\n return { row: merged, score };\n}\n\nfunction rotate(b: Board): Board {\n return b[0].map((_, i) => b.map(r => r[i]).reverse());\n}\n\nfunction move(board: Board, dir: 'left' | 'right' | 'up' | 'down'): { board: Board; score: number } {\n let b = board.map(r => [...r]);\n const rotations = { left: 0, down: 1, right: 2, up: 3 };\n for (let i = 0; i < rotations[dir]; i++) b = rotate(b);\n let score = 0;\n b = b.map(r => { const res = slideRow(r); score += res.score; return res.row; });\n for (let i = 0; i < (4 - rotations[dir]) % 4; i++) b = rotate(b);\n return { board: b, score };\n}\n\nfunction hasValidMoves(b: Board): boolean {\n for (let i = 0; i < 4; i++)\n for (let j = 0; j < 4; j++) {\n if (!b[i][j]) return true;\n if (j < 3 && b[i][j] === b[i][j + 1]) return true;\n if (i < 3 && b[i][j] === b[i + 1][j]) return true;\n }\n return false;\n}\n\nfunction boardsEqual(a: Board, b: Board): boolean {\n return a.every((r, i) => r.every((v, j) => v === b[i][j]));\n}\n\nfunction initBoard(): Board {\n return addRandom(addRandom(empty()));\n}\n\nexport default function Game2048() {\n const [board, setBoard] = useState<Board>(initBoard);\n const [score, setScore] = useState(0);\n const [best, setBest] = useState(() => Number(localStorage.getItem('game2048_best') || 0));\n const [won, setWon] = useState(false);\n const [showWin, setShowWin] = useState(false);\n const [gameOver, setGameOver] = useState(false);\n\n const handleMove = useCallback((dir: 'left' | 'right' | 'up' | 'down') => {\n if (gameOver) return;\n setBoard(prev => {\n const { board: nb, score: gained } = move(prev, dir);\n if (boardsEqual(prev, nb)) return prev;\n const withNew = addRandom(nb);\n setScore(s => {\n const ns = s + gained;\n setBest(b => { const nb = Math.max(b, ns); localStorage.setItem('game2048_best', String(nb)); return nb; });\n return ns;\n });\n if (!won && withNew.some(r => r.some(v => v >= 2048))) { setWon(true); setShowWin(true); }\n if (!hasValidMoves(withNew)) setGameOver(true);\n return withNew;\n });\n }, [gameOver, won]);\n\n useEffect(() => {\n const keyMap: Record<string, 'left' | 'right' | 'up' | 'down'> = {\n ArrowLeft: 'left', ArrowRight: 'right', ArrowUp: 'up', ArrowDown: 'down',\n };\n const handler = (e: KeyboardEvent) => {\n const dir = keyMap[e.key];\n if (dir) { e.preventDefault(); handleMove(dir); }\n };\n window.addEventListener('keydown', handler);\n return () => window.removeEventListener('keydown', handler);\n }, [handleMove]);\n\n useEffect(() => {\n let sx = 0, sy = 0;\n const el = document.getElementById('game2048-board');\n if (!el) return;\n const ts = (e: TouchEvent) => { sx = e.touches[0].clientX; sy = e.touches[0].clientY; };\n const te = (e: TouchEvent) => {\n const dx = e.changedTouches[0].clientX - sx, dy = e.changedTouches[0].clientY - sy;\n if (Math.max(Math.abs(dx), Math.abs(dy)) < 30) return;\n if (Math.abs(dx) > Math.abs(dy)) handleMove(dx > 0 ? 'right' : 'left');\n else handleMove(dy > 0 ? 'down' : 'up');\n };\n el.addEventListener('touchstart', ts, { passive: true });\n el.addEventListener('touchend', te, { passive: true });\n return () => { el.removeEventListener('touchstart', ts); el.removeEventListener('touchend', te); };\n }, [handleMove]);\n\n const newGame = () => { setBoard(initBoard()); setScore(0); setWon(false); setShowWin(false); setGameOver(false); };\n\n const fontSize = (v: number) => v >= 1024 ? 'text-lg' : v >= 128 ? 'text-xl' : 'text-2xl';\n\n return (\n <div className=\"flex flex-col items-center select-none\">\n <div className=\"w-full max-w-[340px]\">\n <div className=\"flex items-center justify-between mb-3\">\n <h1 className=\"text-3xl font-bold text-gray-800\">2048</h1>\n <button onClick={newGame} className=\"px-4 py-1.5 bg-gray-700 text-white rounded font-semibold text-sm hover:bg-gray-800\">\n New Game\n </button>\n </div>\n <div className=\"flex gap-3 mb-3\">\n <div className=\"flex-1 bg-gray-700 text-white rounded px-3 py-1 text-center\">\n <div className=\"text-[10px] uppercase tracking-wider opacity-70\">Score</div>\n <div className=\"text-lg font-bold\">{score}</div>\n </div>\n <div className=\"flex-1 bg-gray-700 text-white rounded px-3 py-1 text-center\">\n <div className=\"text-[10px] uppercase tracking-wider opacity-70\">Best</div>\n <div className=\"text-lg font-bold\">{best}</div>\n </div>\n </div>\n <div id=\"game2048-board\" className=\"relative bg-gray-300 rounded-lg p-2 grid grid-cols-4 gap-2\">\n {board.flat().map((v, i) => (\n <div key={i} className={`aspect-square rounded-md flex items-center justify-center font-bold transition-all duration-100\n ${TILE_COLORS[v] || 'bg-yellow-200 text-gray-800'} ${fontSize(v)} ${v ? 'animate-pop' : ''}`}>\n {v || ''}\n </div>\n ))}\n {showWin && (\n <div className=\"absolute inset-0 bg-yellow-300/80 rounded-lg flex flex-col items-center justify-center gap-3\">\n <div className=\"text-4xl font-bold text-gray-800\">You Win!</div>\n <div className=\"flex gap-2\">\n <button onClick={() => setShowWin(false)} className=\"px-4 py-2 bg-gray-700 text-white rounded font-semibold text-sm\">\n Continue\n </button>\n <button onClick={newGame} className=\"px-4 py-2 bg-gray-500 text-white rounded font-semibold text-sm\">\n New Game\n </button>\n </div>\n </div>\n )}\n {gameOver && (\n <div className=\"absolute inset-0 bg-gray-800/70 rounded-lg flex flex-col items-center justify-center gap-3\">\n <div className=\"text-3xl font-bold text-white\">Game Over</div>\n <button onClick={newGame} className=\"px-4 py-2 bg-white text-gray-800 rounded font-semibold text-sm\">\n Try Again\n </button>\n </div>\n )}\n </div>\n <p className=\"text-xs text-gray-400 text-center mt-3\">Use arrow keys or swipe to play</p>\n </div>\n <style>{`@keyframes pop{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}.animate-pop{animation:pop .15s ease-out}`}</style>\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { useGoogleAuth, getGoogleAccessToken } from './chunk-5O2KEISQ.js';
|
|
2
|
+
import { toast_default } from './chunk-WIJ45SYD.js';
|
|
3
|
+
import { useState, useRef, useEffect } from 'react';
|
|
4
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var GEMINI_API = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent";
|
|
7
|
+
function GeminiChat() {
|
|
8
|
+
const { isConnected, user, connect, disconnect, hasClientId, setClientId, loading: authLoading, error: authError } = useGoogleAuth();
|
|
9
|
+
const [messages, setMessages] = useState([]);
|
|
10
|
+
const [input, setInput] = useState("");
|
|
11
|
+
const [loading, setLoading] = useState(false);
|
|
12
|
+
const [clientIdInput, setClientIdInput] = useState("");
|
|
13
|
+
const scrollRef = useRef(null);
|
|
14
|
+
const inputRef = useRef(null);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: "smooth" });
|
|
17
|
+
}, [messages]);
|
|
18
|
+
const sendMessage = async () => {
|
|
19
|
+
const text = input.trim();
|
|
20
|
+
if (!text || loading) return;
|
|
21
|
+
const token = getGoogleAccessToken();
|
|
22
|
+
if (!token) {
|
|
23
|
+
toast_default.error("Google session expired. Please reconnect.");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const userMsg = { role: "user", content: text, timestamp: /* @__PURE__ */ new Date() };
|
|
27
|
+
setMessages((prev) => [...prev, userMsg]);
|
|
28
|
+
setInput("");
|
|
29
|
+
setLoading(true);
|
|
30
|
+
try {
|
|
31
|
+
const contents = [...messages, userMsg].map((m) => ({
|
|
32
|
+
role: m.role === "model" ? "model" : "user",
|
|
33
|
+
parts: [{ text: m.content }]
|
|
34
|
+
}));
|
|
35
|
+
const res = await fetch(`${GEMINI_API}`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: {
|
|
38
|
+
Authorization: `Bearer ${token}`,
|
|
39
|
+
"Content-Type": "application/json"
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify({ contents })
|
|
42
|
+
});
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
const err = await res.json().catch(() => ({}));
|
|
45
|
+
throw new Error(err.error?.message || `API error ${res.status}`);
|
|
46
|
+
}
|
|
47
|
+
const data = await res.json();
|
|
48
|
+
const reply = data.candidates?.[0]?.content?.parts?.[0]?.text || "No response.";
|
|
49
|
+
setMessages((prev) => [...prev, { role: "model", content: reply, timestamp: /* @__PURE__ */ new Date() }]);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
toast_default.error(err.message || "Failed to get response from Gemini.");
|
|
52
|
+
setMessages((prev) => [...prev, { role: "model", content: `Error: ${err.message}`, timestamp: /* @__PURE__ */ new Date() }]);
|
|
53
|
+
}
|
|
54
|
+
setLoading(false);
|
|
55
|
+
setTimeout(() => inputRef.current?.focus(), 50);
|
|
56
|
+
};
|
|
57
|
+
const clearChat = () => {
|
|
58
|
+
setMessages([]);
|
|
59
|
+
};
|
|
60
|
+
if (!isConnected) {
|
|
61
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsxs("div", { className: "text-center max-w-md space-y-4 px-6", children: [
|
|
62
|
+
/* @__PURE__ */ jsx("div", { className: "h-16 w-16 mx-auto rounded-2xl bg-gradient-to-br from-blue-500 via-purple-500 to-pink-500 flex items-center justify-center", children: /* @__PURE__ */ jsx("svg", { className: "h-8 w-8 text-white", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.455 2.456L21.75 6l-1.036.259a3.375 3.375 0 00-2.455 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z" }) }) }),
|
|
63
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Gemini AI" }),
|
|
64
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: "Connect your Google account to chat with Gemini AI." }),
|
|
65
|
+
!hasClientId ? /* @__PURE__ */ jsxs("div", { className: "text-left space-y-2 bg-gray-50 rounded-lg p-4", children: [
|
|
66
|
+
/* @__PURE__ */ jsx("label", { className: "block text-xs font-medium text-gray-700", children: "Google OAuth Client ID" }),
|
|
67
|
+
/* @__PURE__ */ jsx(
|
|
68
|
+
"input",
|
|
69
|
+
{
|
|
70
|
+
value: clientIdInput,
|
|
71
|
+
onChange: (e) => setClientIdInput(e.target.value),
|
|
72
|
+
placeholder: "123456789.apps.googleusercontent.com",
|
|
73
|
+
className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-blue-500"
|
|
74
|
+
}
|
|
75
|
+
),
|
|
76
|
+
/* @__PURE__ */ jsx(
|
|
77
|
+
"button",
|
|
78
|
+
{
|
|
79
|
+
onClick: () => {
|
|
80
|
+
if (clientIdInput.trim()) setClientId(clientIdInput.trim());
|
|
81
|
+
},
|
|
82
|
+
disabled: !clientIdInput.trim(),
|
|
83
|
+
className: "w-full bg-gray-900 text-white px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-800 disabled:opacity-40",
|
|
84
|
+
children: "Save Client ID"
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-gray-400", children: 'Enable "Generative Language API" in your Google Cloud project' })
|
|
88
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
89
|
+
"button",
|
|
90
|
+
{
|
|
91
|
+
onClick: connect,
|
|
92
|
+
disabled: authLoading,
|
|
93
|
+
className: "inline-flex items-center gap-2 bg-white border border-gray-300 shadow-sm px-6 py-2.5 text-sm font-medium rounded-lg hover:bg-gray-50 disabled:opacity-50",
|
|
94
|
+
children: [
|
|
95
|
+
/* @__PURE__ */ jsxs("svg", { className: "h-5 w-5", viewBox: "0 0 24 24", children: [
|
|
96
|
+
/* @__PURE__ */ jsx("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z", fill: "#4285F4" }),
|
|
97
|
+
/* @__PURE__ */ jsx("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: "#34A853" }),
|
|
98
|
+
/* @__PURE__ */ jsx("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z", fill: "#FBBC05" }),
|
|
99
|
+
/* @__PURE__ */ jsx("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: "#EA4335" })
|
|
100
|
+
] }),
|
|
101
|
+
authLoading ? "Connecting..." : "Sign in with Google"
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
),
|
|
105
|
+
authError && /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600", children: authError })
|
|
106
|
+
] }) });
|
|
107
|
+
}
|
|
108
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
|
|
109
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-b border-gray-200 shrink-0", children: [
|
|
110
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
111
|
+
/* @__PURE__ */ jsx("div", { className: "h-6 w-6 rounded-lg bg-gradient-to-br from-blue-500 via-purple-500 to-pink-500 flex items-center justify-center", children: /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5 text-white", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z" }) }) }),
|
|
112
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-gray-900", children: "Gemini" }),
|
|
113
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: "2.5 Flash" })
|
|
114
|
+
] }),
|
|
115
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
116
|
+
/* @__PURE__ */ jsx("button", { onClick: clearChat, className: "text-xs text-gray-500 hover:text-gray-700", children: "Clear" }),
|
|
117
|
+
/* @__PURE__ */ jsxs(
|
|
118
|
+
"button",
|
|
119
|
+
{
|
|
120
|
+
onClick: () => window.dispatchEvent(new Event("open-google-connect")),
|
|
121
|
+
title: "Google Services",
|
|
122
|
+
className: "flex items-center gap-2 hover:bg-gray-100 rounded-md px-1.5 py-1 transition-colors",
|
|
123
|
+
children: [
|
|
124
|
+
user?.picture ? /* @__PURE__ */ jsx("img", { src: user.picture, alt: "", className: "h-6 w-6 rounded-full" }) : /* @__PURE__ */ jsx("div", { className: "h-6 w-6 rounded-full bg-gray-200" }),
|
|
125
|
+
/* @__PURE__ */ jsxs("div", { className: "text-left", children: [
|
|
126
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-gray-900", children: user?.name }),
|
|
127
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-gray-500", children: user?.email })
|
|
128
|
+
] })
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
] })
|
|
133
|
+
] }),
|
|
134
|
+
/* @__PURE__ */ jsxs("div", { ref: scrollRef, className: "flex-1 overflow-y-auto px-4 py-4 space-y-4", children: [
|
|
135
|
+
messages.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-center", children: /* @__PURE__ */ jsxs("div", { children: [
|
|
136
|
+
/* @__PURE__ */ jsx("div", { className: "h-12 w-12 mx-auto mb-3 rounded-xl bg-gradient-to-br from-blue-500/10 via-purple-500/10 to-pink-500/10 flex items-center justify-center", children: /* @__PURE__ */ jsx("svg", { className: "h-6 w-6 text-purple-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z" }) }) }),
|
|
137
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: "Ask Gemini anything" }),
|
|
138
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 mt-1", children: "Powered by Google AI" })
|
|
139
|
+
] }) }),
|
|
140
|
+
messages.map((msg, i) => /* @__PURE__ */ jsx("div", { className: `flex ${msg.role === "user" ? "justify-end" : "justify-start"}`, children: /* @__PURE__ */ jsxs("div", { className: `max-w-[80%] rounded-2xl px-4 py-2.5 ${msg.role === "user" ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-900"}`, children: [
|
|
141
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm whitespace-pre-wrap leading-relaxed", children: msg.content }),
|
|
142
|
+
/* @__PURE__ */ jsx("p", { className: `text-[10px] mt-1 ${msg.role === "user" ? "text-blue-200" : "text-gray-400"}`, children: msg.timestamp.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }) })
|
|
143
|
+
] }) }, i)),
|
|
144
|
+
loading && /* @__PURE__ */ jsx("div", { className: "flex justify-start", children: /* @__PURE__ */ jsx("div", { className: "bg-gray-100 rounded-2xl px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
145
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-gray-400 animate-bounce", style: { animationDelay: "0ms" } }),
|
|
146
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-gray-400 animate-bounce", style: { animationDelay: "150ms" } }),
|
|
147
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-gray-400 animate-bounce", style: { animationDelay: "300ms" } })
|
|
148
|
+
] }) }) })
|
|
149
|
+
] }),
|
|
150
|
+
/* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-t border-gray-200 shrink-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
|
|
151
|
+
/* @__PURE__ */ jsx(
|
|
152
|
+
"textarea",
|
|
153
|
+
{
|
|
154
|
+
ref: inputRef,
|
|
155
|
+
value: input,
|
|
156
|
+
onChange: (e) => setInput(e.target.value),
|
|
157
|
+
onKeyDown: (e) => {
|
|
158
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
159
|
+
e.preventDefault();
|
|
160
|
+
sendMessage();
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
placeholder: "Message Gemini...",
|
|
164
|
+
rows: 1,
|
|
165
|
+
className: "flex-1 rounded-xl border border-gray-300 px-4 py-2.5 text-sm resize-none focus:border-blue-500 focus:ring-blue-500 max-h-32",
|
|
166
|
+
style: { minHeight: "42px" }
|
|
167
|
+
}
|
|
168
|
+
),
|
|
169
|
+
/* @__PURE__ */ jsx(
|
|
170
|
+
"button",
|
|
171
|
+
{
|
|
172
|
+
onClick: sendMessage,
|
|
173
|
+
disabled: loading || !input.trim(),
|
|
174
|
+
className: "shrink-0 bg-blue-600 text-white rounded-xl p-2.5 hover:bg-blue-700 disabled:opacity-40 transition-colors",
|
|
175
|
+
children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5" }) })
|
|
176
|
+
}
|
|
177
|
+
)
|
|
178
|
+
] }) })
|
|
179
|
+
] });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export { GeminiChat as default };
|
|
183
|
+
//# sourceMappingURL=GeminiChat-BXLBJFT4.js.map
|
|
184
|
+
//# sourceMappingURL=GeminiChat-BXLBJFT4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/apps/GeminiChat.tsx"],"names":[],"mappings":"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","file":"GeminiChat-BXLBJFT4.js","sourcesContent":["import { useState, useRef, useEffect } from 'react';\nimport useGoogleAuth, { getGoogleAccessToken } from '../hooks/useGoogleAuth';\nimport toast from '../shell/toast';\n\ninterface ChatMessage {\n role: 'user' | 'model';\n content: string;\n timestamp: Date;\n}\n\nconst GEMINI_API = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent';\n\nexport default function GeminiChat() {\n const { isConnected, user, connect, disconnect, hasClientId, setClientId, loading: authLoading, error: authError } = useGoogleAuth();\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [input, setInput] = useState('');\n const [loading, setLoading] = useState(false);\n const [clientIdInput, setClientIdInput] = useState('');\n const scrollRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' });\n }, [messages]);\n\n const sendMessage = async () => {\n const text = input.trim();\n if (!text || loading) return;\n\n const token = getGoogleAccessToken();\n if (!token) { toast.error('Google session expired. Please reconnect.'); return; }\n\n const userMsg: ChatMessage = { role: 'user', content: text, timestamp: new Date() };\n setMessages(prev => [...prev, userMsg]);\n setInput('');\n setLoading(true);\n\n try {\n // Build conversation history for context\n const contents = [...messages, userMsg].map(m => ({\n role: m.role === 'model' ? 'model' : 'user',\n parts: [{ text: m.content }],\n }));\n\n const res = await fetch(`${GEMINI_API}`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ contents }),\n });\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}));\n throw new Error(err.error?.message || `API error ${res.status}`);\n }\n\n const data = await res.json();\n const reply = data.candidates?.[0]?.content?.parts?.[0]?.text || 'No response.';\n setMessages(prev => [...prev, { role: 'model', content: reply, timestamp: new Date() }]);\n } catch (err: any) {\n toast.error(err.message || 'Failed to get response from Gemini.');\n setMessages(prev => [...prev, { role: 'model', content: `Error: ${err.message}`, timestamp: new Date() }]);\n }\n setLoading(false);\n setTimeout(() => inputRef.current?.focus(), 50);\n };\n\n const clearChat = () => {\n setMessages([]);\n };\n\n // ── Not connected ──\n if (!isConnected) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-md space-y-4 px-6\">\n <div className=\"h-16 w-16 mx-auto rounded-2xl bg-gradient-to-br from-blue-500 via-purple-500 to-pink-500 flex items-center justify-center\">\n <svg className=\"h-8 w-8 text-white\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.455 2.456L21.75 6l-1.036.259a3.375 3.375 0 00-2.455 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z\" /></svg>\n </div>\n <h2 className=\"text-lg font-semibold text-gray-900\">Gemini AI</h2>\n <p className=\"text-sm text-gray-500\">Connect your Google account to chat with Gemini AI.</p>\n\n {!hasClientId ? (\n <div className=\"text-left space-y-2 bg-gray-50 rounded-lg p-4\">\n <label className=\"block text-xs font-medium text-gray-700\">Google OAuth Client ID</label>\n <input value={clientIdInput} onChange={e => setClientIdInput(e.target.value)} placeholder=\"123456789.apps.googleusercontent.com\"\n className=\"w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-blue-500\" />\n <button onClick={() => { if (clientIdInput.trim()) setClientId(clientIdInput.trim()); }} disabled={!clientIdInput.trim()}\n className=\"w-full bg-gray-900 text-white px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-800 disabled:opacity-40\">Save Client ID</button>\n <p className=\"text-[10px] text-gray-400\">Enable \"Generative Language API\" in your Google Cloud project</p>\n </div>\n ) : (\n <button onClick={connect} disabled={authLoading}\n className=\"inline-flex items-center gap-2 bg-white border border-gray-300 shadow-sm px-6 py-2.5 text-sm font-medium rounded-lg hover:bg-gray-50 disabled:opacity-50\">\n <svg className=\"h-5 w-5\" viewBox=\"0 0 24 24\"><path d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z\" fill=\"#4285F4\"/><path d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\" fill=\"#34A853\"/><path d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\" fill=\"#FBBC05\"/><path d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\" fill=\"#EA4335\"/></svg>\n {authLoading ? 'Connecting...' : 'Sign in with Google'}\n </button>\n )}\n {authError && <p className=\"text-sm text-red-600\">{authError}</p>}\n </div>\n </div>\n );\n }\n\n // ── Connected: Chat UI ──\n return (\n <div className=\"flex flex-col h-full\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-2 border-b border-gray-200 shrink-0\">\n <div className=\"flex items-center gap-2\">\n <div className=\"h-6 w-6 rounded-lg bg-gradient-to-br from-blue-500 via-purple-500 to-pink-500 flex items-center justify-center\">\n <svg className=\"h-3.5 w-3.5 text-white\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z\" /></svg>\n </div>\n <span className=\"text-sm font-semibold text-gray-900\">Gemini</span>\n <span className=\"text-xs text-gray-400\">2.5 Flash</span>\n </div>\n <div className=\"flex items-center gap-2\">\n <button onClick={clearChat} className=\"text-xs text-gray-500 hover:text-gray-700\">Clear</button>\n <button onClick={() => window.dispatchEvent(new Event('open-google-connect'))} title=\"Google Services\"\n className=\"flex items-center gap-2 hover:bg-gray-100 rounded-md px-1.5 py-1 transition-colors\">\n {user?.picture ? (\n <img src={user.picture} alt=\"\" className=\"h-6 w-6 rounded-full\" />\n ) : (\n <div className=\"h-6 w-6 rounded-full bg-gray-200\" />\n )}\n <div className=\"text-left\">\n <p className=\"text-[11px] font-medium text-gray-900\">{user?.name}</p>\n <p className=\"text-[10px] text-gray-500\">{user?.email}</p>\n </div>\n </button>\n </div>\n </div>\n\n {/* Messages */}\n <div ref={scrollRef} className=\"flex-1 overflow-y-auto px-4 py-4 space-y-4\">\n {messages.length === 0 && (\n <div className=\"flex items-center justify-center h-full text-center\">\n <div>\n <div className=\"h-12 w-12 mx-auto mb-3 rounded-xl bg-gradient-to-br from-blue-500/10 via-purple-500/10 to-pink-500/10 flex items-center justify-center\">\n <svg className=\"h-6 w-6 text-purple-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z\" /></svg>\n </div>\n <p className=\"text-sm text-gray-500\">Ask Gemini anything</p>\n <p className=\"text-xs text-gray-400 mt-1\">Powered by Google AI</p>\n </div>\n </div>\n )}\n {messages.map((msg, i) => (\n <div key={i} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>\n <div className={`max-w-[80%] rounded-2xl px-4 py-2.5 ${msg.role === 'user' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-900'}`}>\n <div className=\"text-sm whitespace-pre-wrap leading-relaxed\">{msg.content}</div>\n <p className={`text-[10px] mt-1 ${msg.role === 'user' ? 'text-blue-200' : 'text-gray-400'}`}>\n {msg.timestamp.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })}\n </p>\n </div>\n </div>\n ))}\n {loading && (\n <div className=\"flex justify-start\">\n <div className=\"bg-gray-100 rounded-2xl px-4 py-3\">\n <div className=\"flex items-center gap-1.5\">\n <div className=\"w-2 h-2 rounded-full bg-gray-400 animate-bounce\" style={{ animationDelay: '0ms' }} />\n <div className=\"w-2 h-2 rounded-full bg-gray-400 animate-bounce\" style={{ animationDelay: '150ms' }} />\n <div className=\"w-2 h-2 rounded-full bg-gray-400 animate-bounce\" style={{ animationDelay: '300ms' }} />\n </div>\n </div>\n </div>\n )}\n </div>\n\n {/* Input */}\n <div className=\"px-4 py-3 border-t border-gray-200 shrink-0\">\n <div className=\"flex items-end gap-2\">\n <textarea\n ref={inputRef}\n value={input}\n onChange={e => setInput(e.target.value)}\n onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }}\n placeholder=\"Message Gemini...\"\n rows={1}\n className=\"flex-1 rounded-xl border border-gray-300 px-4 py-2.5 text-sm resize-none focus:border-blue-500 focus:ring-blue-500 max-h-32\"\n style={{ minHeight: '42px' }}\n />\n <button onClick={sendMessage} disabled={loading || !input.trim()}\n className=\"shrink-0 bg-blue-600 text-white rounded-xl p-2.5 hover:bg-blue-700 disabled:opacity-40 transition-colors\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5\" /></svg>\n </button>\n </div>\n </div>\n </div>\n );\n}\n"]}
|