muryshev commited on
Commit
19eca0c
·
1 Parent(s): e586c03
package-lock.json CHANGED
@@ -9,6 +9,7 @@
9
  "version": "0.0.0",
10
  "dependencies": {
11
  "@fontsource/fira-mono": "^5.0.14",
 
12
  "@reduxjs/toolkit": "^2.3.0",
13
  "@tanstack/react-query": "^5.54.1",
14
  "@tanstack/react-query-devtools": "^5.54.1",
@@ -1175,6 +1176,23 @@
1175
  "@jridgewell/sourcemap-codec": "^1.4.14"
1176
  }
1177
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1178
  "node_modules/@nodelib/fs.scandir": {
1179
  "version": "2.1.5",
1180
  "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1610,6 +1628,11 @@
1610
  "@babel/types": "^7.20.7"
1611
  }
1612
  },
 
 
 
 
 
1613
  "node_modules/@types/estree": {
1614
  "version": "1.0.6",
1615
  "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@@ -1624,6 +1647,11 @@
1624
  "dev": true,
1625
  "license": "MIT"
1626
  },
 
 
 
 
 
1627
  "node_modules/@types/node": {
1628
  "version": "22.13.0",
1629
  "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.0.tgz",
@@ -2051,6 +2079,11 @@
2051
  "dev": true,
2052
  "license": "MIT"
2053
  },
 
 
 
 
 
2054
  "node_modules/base64-js": {
2055
  "version": "1.5.1",
2056
  "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -2273,6 +2306,15 @@
2273
  "node": ">=0.8"
2274
  }
2275
  },
 
 
 
 
 
 
 
 
 
2276
  "node_modules/color-convert": {
2277
  "version": "2.0.1",
2278
  "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2290,9 +2332,30 @@
2290
  "version": "1.1.4",
2291
  "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
2292
  "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
2293
- "dev": true,
2294
  "license": "MIT"
2295
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2296
  "node_modules/colorjs.io": {
2297
  "version": "0.5.2",
2298
  "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
@@ -3408,6 +3471,11 @@
3408
  "url": "https://github.com/sponsors/sindresorhus"
3409
  }
3410
  },
 
 
 
 
 
3411
  "node_modules/lodash.merge": {
3412
  "version": "4.6.2",
3413
  "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -3988,6 +4056,20 @@
3988
  "node": ">=0.10.0"
3989
  }
3990
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3991
  "node_modules/react-contenteditable": {
3992
  "version": "3.3.7",
3993
  "resolved": "https://registry.npmjs.org/react-contenteditable/-/react-contenteditable-3.3.7.tgz",
@@ -4191,6 +4273,22 @@
4191
  "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
4192
  }
4193
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4194
  "node_modules/react-transition-group": {
4195
  "version": "4.4.5",
4196
  "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -4887,6 +4985,19 @@
4887
  "simple-concat": "^1.0.0"
4888
  }
4889
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
4890
  "node_modules/source-map": {
4891
  "version": "0.5.7",
4892
  "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -5180,6 +5291,19 @@
5180
  "punycode": "^2.1.0"
5181
  }
5182
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
5183
  "node_modules/use-isomorphic-layout-effect": {
5184
  "version": "1.2.0",
5185
  "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz",
@@ -5194,6 +5318,22 @@
5194
  }
5195
  }
5196
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5197
  "node_modules/use-sync-external-store": {
5198
  "version": "1.4.0",
5199
  "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
 
9
  "version": "0.0.0",
10
  "dependencies": {
11
  "@fontsource/fira-mono": "^5.0.14",
12
+ "@microlink/react-json-view": "^1.26.1",
13
  "@reduxjs/toolkit": "^2.3.0",
14
  "@tanstack/react-query": "^5.54.1",
15
  "@tanstack/react-query-devtools": "^5.54.1",
 
1176
  "@jridgewell/sourcemap-codec": "^1.4.14"
1177
  }
1178
  },
1179
+ "node_modules/@microlink/react-json-view": {
1180
+ "version": "1.26.1",
1181
+ "resolved": "https://registry.npmjs.org/@microlink/react-json-view/-/react-json-view-1.26.1.tgz",
1182
+ "integrity": "sha512-2H5QCYdZlJi+oN4YBiUYPPFTNh/KLCN9i9yz8NwmSkRqXSRXYtEVIRffc9L34jdopKGK/tK21SeuzXVJHQLkfQ==",
1183
+ "dependencies": {
1184
+ "react-base16-styling": "~0.9.0",
1185
+ "react-lifecycles-compat": "~3.0.4",
1186
+ "react-textarea-autosize": "~8.5.7"
1187
+ },
1188
+ "engines": {
1189
+ "node": ">=17"
1190
+ },
1191
+ "peerDependencies": {
1192
+ "react": ">= 15",
1193
+ "react-dom": ">= 15"
1194
+ }
1195
+ },
1196
  "node_modules/@nodelib/fs.scandir": {
1197
  "version": "2.1.5",
1198
  "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
 
1628
  "@babel/types": "^7.20.7"
1629
  }
1630
  },
1631
+ "node_modules/@types/base16": {
1632
+ "version": "1.0.5",
1633
+ "resolved": "https://registry.npmjs.org/@types/base16/-/base16-1.0.5.tgz",
1634
+ "integrity": "sha512-OzOWrTluG9cwqidEzC/Q6FAmIPcnZfm8BFRlIx0+UIUqnuAmi5OS88O0RpT3Yz6qdmqObvUhasrbNsCofE4W9A=="
1635
+ },
1636
  "node_modules/@types/estree": {
1637
  "version": "1.0.6",
1638
  "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
 
1647
  "dev": true,
1648
  "license": "MIT"
1649
  },
1650
+ "node_modules/@types/lodash": {
1651
+ "version": "4.17.16",
1652
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz",
1653
+ "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g=="
1654
+ },
1655
  "node_modules/@types/node": {
1656
  "version": "22.13.0",
1657
  "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.0.tgz",
 
2079
  "dev": true,
2080
  "license": "MIT"
2081
  },
2082
+ "node_modules/base16": {
2083
+ "version": "1.0.0",
2084
+ "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz",
2085
+ "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ=="
2086
+ },
2087
  "node_modules/base64-js": {
2088
  "version": "1.5.1",
2089
  "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
 
2306
  "node": ">=0.8"
2307
  }
2308
  },
2309
+ "node_modules/color": {
2310
+ "version": "3.2.1",
2311
+ "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
2312
+ "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
2313
+ "dependencies": {
2314
+ "color-convert": "^1.9.3",
2315
+ "color-string": "^1.6.0"
2316
+ }
2317
+ },
2318
  "node_modules/color-convert": {
2319
  "version": "2.0.1",
2320
  "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
 
2332
  "version": "1.1.4",
2333
  "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
2334
  "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
 
2335
  "license": "MIT"
2336
  },
2337
+ "node_modules/color-string": {
2338
+ "version": "1.9.1",
2339
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
2340
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
2341
+ "dependencies": {
2342
+ "color-name": "^1.0.0",
2343
+ "simple-swizzle": "^0.2.2"
2344
+ }
2345
+ },
2346
+ "node_modules/color/node_modules/color-convert": {
2347
+ "version": "1.9.3",
2348
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
2349
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
2350
+ "dependencies": {
2351
+ "color-name": "1.1.3"
2352
+ }
2353
+ },
2354
+ "node_modules/color/node_modules/color-name": {
2355
+ "version": "1.1.3",
2356
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
2357
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
2358
+ },
2359
  "node_modules/colorjs.io": {
2360
  "version": "0.5.2",
2361
  "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
 
3471
  "url": "https://github.com/sponsors/sindresorhus"
3472
  }
3473
  },
3474
+ "node_modules/lodash.curry": {
3475
+ "version": "4.1.1",
3476
+ "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
3477
+ "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA=="
3478
+ },
3479
  "node_modules/lodash.merge": {
3480
  "version": "4.6.2",
3481
  "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
 
4056
  "node": ">=0.10.0"
4057
  }
4058
  },
4059
+ "node_modules/react-base16-styling": {
4060
+ "version": "0.9.1",
4061
+ "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.9.1.tgz",
4062
+ "integrity": "sha512-1s0CY1zRBOQ5M3T61wetEpvQmsYSNtWEcdYzyZNxKa8t7oDvaOn9d21xrGezGAHFWLM7SHcktPuPTrvoqxSfKw==",
4063
+ "dependencies": {
4064
+ "@babel/runtime": "^7.16.7",
4065
+ "@types/base16": "^1.0.2",
4066
+ "@types/lodash": "^4.14.178",
4067
+ "base16": "^1.0.0",
4068
+ "color": "^3.2.1",
4069
+ "csstype": "^3.0.10",
4070
+ "lodash.curry": "^4.1.1"
4071
+ }
4072
+ },
4073
  "node_modules/react-contenteditable": {
4074
  "version": "3.3.7",
4075
  "resolved": "https://registry.npmjs.org/react-contenteditable/-/react-contenteditable-3.3.7.tgz",
 
4273
  "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
4274
  }
4275
  },
4276
+ "node_modules/react-textarea-autosize": {
4277
+ "version": "8.5.9",
4278
+ "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz",
4279
+ "integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==",
4280
+ "dependencies": {
4281
+ "@babel/runtime": "^7.20.13",
4282
+ "use-composed-ref": "^1.3.0",
4283
+ "use-latest": "^1.2.1"
4284
+ },
4285
+ "engines": {
4286
+ "node": ">=10"
4287
+ },
4288
+ "peerDependencies": {
4289
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
4290
+ }
4291
+ },
4292
  "node_modules/react-transition-group": {
4293
  "version": "4.4.5",
4294
  "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
 
4985
  "simple-concat": "^1.0.0"
4986
  }
4987
  },
4988
+ "node_modules/simple-swizzle": {
4989
+ "version": "0.2.2",
4990
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
4991
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
4992
+ "dependencies": {
4993
+ "is-arrayish": "^0.3.1"
4994
+ }
4995
+ },
4996
+ "node_modules/simple-swizzle/node_modules/is-arrayish": {
4997
+ "version": "0.3.2",
4998
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
4999
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
5000
+ },
5001
  "node_modules/source-map": {
5002
  "version": "0.5.7",
5003
  "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
 
5291
  "punycode": "^2.1.0"
5292
  }
5293
  },
5294
+ "node_modules/use-composed-ref": {
5295
+ "version": "1.4.0",
5296
+ "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz",
5297
+ "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==",
5298
+ "peerDependencies": {
5299
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
5300
+ },
5301
+ "peerDependenciesMeta": {
5302
+ "@types/react": {
5303
+ "optional": true
5304
+ }
5305
+ }
5306
+ },
5307
  "node_modules/use-isomorphic-layout-effect": {
5308
  "version": "1.2.0",
5309
  "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz",
 
5318
  }
5319
  }
5320
  },
5321
+ "node_modules/use-latest": {
5322
+ "version": "1.3.0",
5323
+ "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz",
5324
+ "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==",
5325
+ "dependencies": {
5326
+ "use-isomorphic-layout-effect": "^1.1.1"
5327
+ },
5328
+ "peerDependencies": {
5329
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
5330
+ },
5331
+ "peerDependenciesMeta": {
5332
+ "@types/react": {
5333
+ "optional": true
5334
+ }
5335
+ }
5336
+ },
5337
  "node_modules/use-sync-external-store": {
5338
  "version": "1.4.0",
5339
  "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
package.json CHANGED
@@ -11,6 +11,7 @@
11
  },
12
  "dependencies": {
13
  "@fontsource/fira-mono": "^5.0.14",
 
14
  "@reduxjs/toolkit": "^2.3.0",
15
  "@tanstack/react-query": "^5.54.1",
16
  "@tanstack/react-query-devtools": "^5.54.1",
 
11
  },
12
  "dependencies": {
13
  "@fontsource/fira-mono": "^5.0.14",
14
+ "@microlink/react-json-view": "^1.26.1",
15
  "@reduxjs/toolkit": "^2.3.0",
16
  "@tanstack/react-query": "^5.54.1",
17
  "@tanstack/react-query-devtools": "^5.54.1",
src/App.css CHANGED
@@ -11,3 +11,7 @@
11
  padding-left: 10px;
12
  font-weight: 600;
13
  }
 
 
 
 
 
11
  padding-left: 10px;
12
  font-weight: 600;
13
  }
14
+
15
+ .app-content {
16
+ margin-top: 40px;
17
+ }
src/App.tsx CHANGED
@@ -1,14 +1,15 @@
1
- import "./App.css";
2
- import { Routes, Route, BrowserRouter as Router, Navigate } from "react-router-dom";
3
  import Navbar from '@/components/common/navbar/Navbar';
4
- import Page from "@/components/pages/main/Page";
5
- import Logs from "@/components/pages/logsPage/Logs";
6
- import Vectorization from "@/components/pages/vectorizationPage/Vectorization";
7
- import { pdfjs } from "react-pdf";
8
  import LLMConfigList from "@/components/pages/llmConfigs/LlmConfigList";
9
  import LlmPromptList from "@/components/pages/llmPrompts/LlmPromptList";
10
- import LoginPage from "@/components/pages/auth/Login";
 
11
  import { AuthProvider, useAuth } from "@/context/AuthContext";
 
 
 
 
 
12
 
13
  pdfjs.GlobalWorkerOptions.workerSrc = new URL(
14
  "pdfjs-dist/build/pdf.worker.min.mjs",
@@ -45,6 +46,10 @@ const App: React.FC = () => {
45
  <PrivateRoute>
46
  <LlmPromptList />
47
  </PrivateRoute>} />
 
 
 
 
48
  </Routes>
49
  </div>
50
  </Router>
 
 
 
1
  import Navbar from '@/components/common/navbar/Navbar';
2
+ import LoginPage from "@/components/pages/auth/Login";
 
 
 
3
  import LLMConfigList from "@/components/pages/llmConfigs/LlmConfigList";
4
  import LlmPromptList from "@/components/pages/llmPrompts/LlmPromptList";
5
+ import Logs from "@/components/pages/logs/Logs";
6
+ import Vectorization from "@/components/pages/vectorizationPage/Vectorization";
7
  import { AuthProvider, useAuth } from "@/context/AuthContext";
8
+ import { pdfjs } from "react-pdf";
9
+ import { Navigate, Route, BrowserRouter as Router, Routes } from "react-router-dom";
10
+ import "./App.css";
11
+ import QePromptList from "./components/pages/qePrompts/QePromptList";
12
+
13
 
14
  pdfjs.GlobalWorkerOptions.workerSrc = new URL(
15
  "pdfjs-dist/build/pdf.worker.min.mjs",
 
46
  <PrivateRoute>
47
  <LlmPromptList />
48
  </PrivateRoute>} />
49
+ <Route path="/qeprompt" element={
50
+ <PrivateRoute>
51
+ <QePromptList />
52
+ </PrivateRoute>} />
53
  </Routes>
54
  </div>
55
  </Router>
src/api/qePrompts/hooks.ts ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
2
+ import { createQePrompt, deleteQePrompt, fetchQePrompts, setDefaultQePrompt, updateQePrompt } from './qePromptApi';
3
+
4
+ export const useQePrompts = () => {
5
+ const queryClient = useQueryClient();
6
+
7
+ const { data: prompts = [], isLoading, error } = useQuery({
8
+ queryKey: ['qePrompts'],
9
+ queryFn: fetchQePrompts,
10
+ });
11
+
12
+ const createMutation = useMutation({
13
+ mutationFn: createQePrompt,
14
+ onSuccess: () => {
15
+ queryClient.invalidateQueries({ queryKey: ['qePrompts'] });
16
+ },
17
+ });
18
+
19
+ const updateMutation = useMutation({
20
+ mutationFn: updateQePrompt,
21
+ onSuccess: () => {
22
+ queryClient.invalidateQueries({ queryKey: ['qePrompts'] });
23
+ },
24
+ });
25
+
26
+ const setDefaultMutation = useMutation({
27
+ mutationFn: setDefaultQePrompt,
28
+ onSuccess: () => {
29
+ queryClient.invalidateQueries({ queryKey: ['qePrompts'] });
30
+ },
31
+ });
32
+
33
+ const deleteMutation = useMutation({
34
+ mutationFn: deleteQePrompt,
35
+ onSuccess: () => {
36
+ queryClient.invalidateQueries({ queryKey: ['qePrompts'] });
37
+ },
38
+ });
39
+
40
+ return {
41
+ prompts,
42
+ isLoading,
43
+ error: error ? (error instanceof Error ? error.message : 'Failed to fetch prompturations') : null,
44
+ createPrompt: createMutation.mutateAsync,
45
+ updatePrompt: updateMutation.mutateAsync,
46
+ setAsDefaultPrompt: setDefaultMutation.mutateAsync,
47
+ deletePrompt: deleteMutation.mutateAsync,
48
+ };
49
+ };
src/api/qePrompts/qePromptApi.ts ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { query } from '@/shared/api/query';
2
+ import { QePrompt } from './types';
3
+
4
+ export const fetchQePrompts = async (): Promise<QePrompt[]> => {
5
+ const response = await query<QePrompt[]>({
6
+ url: '/qe_prompt/',
7
+ method: 'get',
8
+ });
9
+ if ('error' in response) {
10
+ throw new Error(`Ошибка получения промптов: ${response.error.status}`);
11
+ }
12
+ return response.data;
13
+ };
14
+
15
+ export const fetchQePromptById = async (id: number): Promise<QePrompt> => {
16
+ const response = await query<QePrompt>({
17
+ url: `/qe_prompt/${id}`,
18
+ method: 'get',
19
+ });
20
+ if ('error' in response) {
21
+ throw new Error(`Ошибка получения промпта: ${response.error.status}`);
22
+ }
23
+ return response.data;
24
+ };
25
+
26
+ export const createQePrompt = async (config: Omit<QePrompt, 'id' | 'created_at'>): Promise<QePrompt> => {
27
+ const response = await query<QePrompt>({
28
+ url: '/qe_prompt/',
29
+ method: 'post',
30
+ data: config,
31
+ });
32
+ if ('error' in response) {
33
+ throw new Error(`Ошибка создания промпта: ${response.error.status}`);
34
+ }
35
+ return response.data;
36
+ };
37
+
38
+ export const updateQePrompt = async (config: QePrompt): Promise<void> => {
39
+ const response = await query<void>({
40
+ url: `/qe_prompt/${config.id}`,
41
+ method: 'put',
42
+ data: config,
43
+ });
44
+ if ('error' in response) {
45
+ throw new Error(`Ошибка обновления промпта: ${response.error.status}`);
46
+ }
47
+ };
48
+
49
+ export const setDefaultQePrompt = async (id: number): Promise<void> => {
50
+ const response = await query<void>({
51
+ url: `/qe_prompt/default/${id}`,
52
+ method: 'put',
53
+ });
54
+ if ('error' in response) {
55
+ throw new Error(`Ошибка установки промпта по умолчанию: ${response.error.status}`);
56
+ }
57
+ };
58
+
59
+ export const deleteQePrompt = async (id: number): Promise<void> => {
60
+ const response = await query<void>({
61
+ url: `/qe_prompt/${id}`,
62
+ method: 'delete',
63
+ });
64
+ if ('error' in response) {
65
+ throw new Error(`Ошибка удаления промпта: ${response.error.status}`);
66
+ }
67
+ };
68
+
69
+ export const fetchDefaultQePrompt = async (): Promise<QePrompt | null> => {
70
+ const response = await query<QePrompt>({
71
+ url: '/qe_prompt/default',
72
+ method: 'get',
73
+ });
74
+ if ('error' in response) {
75
+ if (response.error.status === 404) return null; // Если дефолтной записи нет
76
+ throw new Error(`Ошибка получения дефолтной промпта: ${response.error.status}`);
77
+ }
78
+ return response.data;
79
+ };
src/api/qePrompts/types.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ // src/api/qe_configs/types.ts
2
+ export interface QePrompt {
3
+ id: number;
4
+ is_default: boolean;
5
+ text: string;
6
+ name: string;
7
+ type: string;
8
+ created_at?: string;
9
+ }
src/components/common/navbar/Navbar.tsx CHANGED
@@ -1,7 +1,7 @@
 
1
  import React from 'react';
2
  import { NavLink, useNavigate } from 'react-router-dom';
3
  import './Navbar.scss';
4
- import { useAuth } from '@/context/AuthContext';
5
 
6
  const Navbar: React.FC = () => {
7
  const { isAuthenticated, logout } = useAuth();
@@ -49,6 +49,14 @@ const Navbar: React.FC = () => {
49
  Системные промпты
50
  </NavLink>
51
  </li>
 
 
 
 
 
 
 
 
52
  <li>
53
  <button onClick={handleLogout}>Выход</button>
54
  </li>
 
1
+ import { useAuth } from '@/context/AuthContext';
2
  import React from 'react';
3
  import { NavLink, useNavigate } from 'react-router-dom';
4
  import './Navbar.scss';
 
5
 
6
  const Navbar: React.FC = () => {
7
  const { isAuthenticated, logout } = useAuth();
 
49
  Системные промпты
50
  </NavLink>
51
  </li>
52
+ <li>
53
+ <NavLink
54
+ to="/qeprompt"
55
+ className={({ isActive }) => (isActive ? 'active' : '')}
56
+ >
57
+ QE промпты
58
+ </NavLink>
59
+ </li>
60
  <li>
61
  <button onClick={handleLogout}>Выход</button>
62
  </li>
src/components/generics/button/Button.scss CHANGED
@@ -9,6 +9,7 @@
9
  font-family: inherit;
10
  display: flex;
11
  gap: 10px;
 
12
  }
13
 
14
  .btn:hover {
 
9
  font-family: inherit;
10
  display: flex;
11
  gap: 10px;
12
+ align-items: center;
13
  }
14
 
15
  .btn:hover {
src/components/generics/button/Button.tsx CHANGED
@@ -12,7 +12,7 @@ const Button = ({
12
  className,
13
  }: ButtonProps) => {
14
  return (
15
- <button onClick={onClick} className={`btn ${buttonType} ${className}`} disabled={disabled}>
16
  {name}
17
  {icon}
18
  {loading && <Spinner />}
 
12
  className,
13
  }: ButtonProps) => {
14
  return (
15
+ <button onClick={onClick} className={`btn ${buttonType} ${className ?? ''}`} disabled={disabled}>
16
  {name}
17
  {icon}
18
  {loading && <Spinner />}
src/components/pages/documentsPage/Documents.tsx CHANGED
@@ -33,9 +33,9 @@ const Documents: FC = () => {
33
  if (
34
  (!searchParams.get("datasetId") ||
35
  !datasetsData?.find((e) => e.id.toString() == searchParams.get("datasetId"))) &&
36
- datasetsData?.[0].id
37
  ) {
38
- setSearchParams({ datasetId: String(datasetsData?.[0].id) });
39
  }
40
  }, [datasetsData, searchParams, setSearchParams]);
41
 
 
33
  if (
34
  (!searchParams.get("datasetId") ||
35
  !datasetsData?.find((e) => e.id.toString() == searchParams.get("datasetId"))) &&
36
+ datasetsData?.[0]?.id
37
  ) {
38
+ setSearchParams({ datasetId: String(datasetsData?.[0]?.id) });
39
  }
40
  }, [datasetsData, searchParams, setSearchParams]);
41
 
src/components/pages/llmConfigs/LlmConfigModal.scss CHANGED
@@ -49,7 +49,7 @@
49
  label {
50
  display: contents;
51
 
52
- span {
53
  font-weight: 500;
54
  color: #444;
55
  text-align: right;
 
49
  label {
50
  display: contents;
51
 
52
+ span.title {
53
  font-weight: 500;
54
  color: #444;
55
  text-align: right;
src/components/pages/llmConfigs/LlmConfigModal.tsx CHANGED
@@ -128,12 +128,12 @@ interface LLMConfigModalProps {
128
  <form onSubmit={handleSubmit}>
129
  {isEditMode && 'id' in formData && (
130
  <label>
131
- <span>ID:</span>
132
  <input type="text" value={formData.id} disabled />
133
  </label>
134
  )}
135
  <label>
136
- <span>Задать по-умолчанию:</span>
137
  <input
138
  type="checkbox"
139
  name="is_default"
@@ -142,7 +142,7 @@ interface LLMConfigModalProps {
142
  />
143
  </label>
144
  <label>
145
- <span>Модель:</span>
146
  <input
147
  type="text"
148
  name="model"
@@ -151,7 +151,7 @@ interface LLMConfigModalProps {
151
  />
152
  </label>
153
  <label>
154
- <span>Temperature:</span>
155
  <input
156
  type="number"
157
  name="temperature"
@@ -161,7 +161,7 @@ interface LLMConfigModalProps {
161
  />
162
  </label>
163
  <label>
164
- <span>Top P:</span>
165
  <input
166
  type="number"
167
  name="top_p"
@@ -171,7 +171,7 @@ interface LLMConfigModalProps {
171
  />
172
  </label>
173
  <label>
174
- <span>Min P:</span>
175
  <input
176
  type="number"
177
  name="min_p"
@@ -181,7 +181,7 @@ interface LLMConfigModalProps {
181
  />
182
  </label>
183
  <label>
184
- <span>Frequency Penalty:</span>
185
  <input
186
  type="number"
187
  name="frequency_penalty"
@@ -191,7 +191,7 @@ interface LLMConfigModalProps {
191
  />
192
  </label>
193
  <label>
194
- <span>Presence Penalty:</span>
195
  <input
196
  type="number"
197
  name="presence_penalty"
@@ -201,7 +201,7 @@ interface LLMConfigModalProps {
201
  />
202
  </label>
203
  <label>
204
- <span>N predict:</span>
205
  <input
206
  type="number"
207
  name="n_predict"
@@ -211,7 +211,7 @@ interface LLMConfigModalProps {
211
  />
212
  </label>
213
  <label>
214
- <span>Seed:</span>
215
  <input
216
  type="number"
217
  name="seed"
 
128
  <form onSubmit={handleSubmit}>
129
  {isEditMode && 'id' in formData && (
130
  <label>
131
+ <span className='title'>ID:</span>
132
  <input type="text" value={formData.id} disabled />
133
  </label>
134
  )}
135
  <label>
136
+ <span className='title'>Задать по-умолчанию:</span>
137
  <input
138
  type="checkbox"
139
  name="is_default"
 
142
  />
143
  </label>
144
  <label>
145
+ <span className='title'>Модель:</span>
146
  <input
147
  type="text"
148
  name="model"
 
151
  />
152
  </label>
153
  <label>
154
+ <span className='title'>Temperature:</span>
155
  <input
156
  type="number"
157
  name="temperature"
 
161
  />
162
  </label>
163
  <label>
164
+ <span className='title'>Top P:</span>
165
  <input
166
  type="number"
167
  name="top_p"
 
171
  />
172
  </label>
173
  <label>
174
+ <span className='title'>Min P:</span>
175
  <input
176
  type="number"
177
  name="min_p"
 
181
  />
182
  </label>
183
  <label>
184
+ <span className='title'>Frequency Penalty:</span>
185
  <input
186
  type="number"
187
  name="frequency_penalty"
 
191
  />
192
  </label>
193
  <label>
194
+ <span className='title'>Presence Penalty:</span>
195
  <input
196
  type="number"
197
  name="presence_penalty"
 
201
  />
202
  </label>
203
  <label>
204
+ <span className='title'>N predict:</span>
205
  <input
206
  type="number"
207
  name="n_predict"
 
211
  />
212
  </label>
213
  <label>
214
+ <span className='title'>Seed:</span>
215
  <input
216
  type="number"
217
  name="seed"
src/components/pages/llmPrompts/LlmPromptModal.scss CHANGED
@@ -13,6 +13,7 @@
13
  overflow-y: auto;
14
  overflow-x: hidden;
15
 
 
16
  .label {
17
  display: flex;
18
  justify-content: space-between;
@@ -49,7 +50,7 @@
49
  label {
50
  display: contents;
51
 
52
- span {
53
  font-weight: 500;
54
  color: #444;
55
  text-align: right;
 
13
  overflow-y: auto;
14
  overflow-x: hidden;
15
 
16
+
17
  .label {
18
  display: flex;
19
  justify-content: space-between;
 
50
  label {
51
  display: contents;
52
 
53
+ span.title {
54
  font-weight: 500;
55
  color: #444;
56
  text-align: right;
src/components/pages/llmPrompts/LlmPromptModal.tsx CHANGED
@@ -117,12 +117,12 @@ const LlmPromptModal: React.FC<LlmPromptModalProps> = ({
117
  <form onSubmit={handleSubmit}>
118
  {isEditMode && 'id' in formData && (
119
  <label>
120
- <span>ID:</span>
121
  <input type="text" value={formData.id} disabled />
122
  </label>
123
  )}
124
  <label>
125
- <span>Задать по-умолчанию:</span>
126
  <input
127
  type="checkbox"
128
  name="is_default"
@@ -131,7 +131,7 @@ const LlmPromptModal: React.FC<LlmPromptModalProps> = ({
131
  />
132
  </label>
133
  <label>
134
- <span>Название:</span>
135
  <input
136
  type="text"
137
  name="name"
@@ -140,7 +140,7 @@ const LlmPromptModal: React.FC<LlmPromptModalProps> = ({
140
  />
141
  </label>
142
  <label>
143
- <span>Текст:</span>
144
  <textarea
145
  name="text"
146
  value={formData.text || ''}
 
117
  <form onSubmit={handleSubmit}>
118
  {isEditMode && 'id' in formData && (
119
  <label>
120
+ <span className='title'>ID:</span>
121
  <input type="text" value={formData.id} disabled />
122
  </label>
123
  )}
124
  <label>
125
+ <span className='title'>Задать по-умолчанию:</span>
126
  <input
127
  type="checkbox"
128
  name="is_default"
 
131
  />
132
  </label>
133
  <label>
134
+ <span className='title'>Название:</span>
135
  <input
136
  type="text"
137
  name="name"
 
140
  />
141
  </label>
142
  <label>
143
+ <span className='title'>Текст:</span>
144
  <textarea
145
  name="text"
146
  value={formData.text || ''}
src/components/pages/qePrompts/QePromptList.scss ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .qe-prompt-list {
2
+ padding: 20px;
3
+
4
+ .create-button {
5
+ margin-bottom: 20px;
6
+ padding: 8px 16px;
7
+ background-color: #007bff;
8
+ color: white;
9
+ border: none;
10
+ border-radius: 5px;
11
+ cursor: pointer;
12
+
13
+ &:hover {
14
+ background-color: #0056b3;
15
+ }
16
+ }
17
+
18
+ .table-container {
19
+ width: 100%;
20
+ margin-top: 20px;
21
+ border: 1px solid #ddd;
22
+
23
+ .table-header {
24
+ display: flex;
25
+ background-color: #f2f2f2;
26
+ font-weight: bold;
27
+
28
+ .table-cell {
29
+ flex: 1;
30
+ padding: 8px;
31
+ border-right: 1px solid #ddd;
32
+ cursor: pointer;
33
+
34
+ &:nth-child(1) {
35
+ min-width: 30px;
36
+ }
37
+
38
+ &:nth-child(2) {
39
+ flex: 30;
40
+ }
41
+
42
+ &:nth-child(3) {
43
+ min-width: 100px;
44
+ }
45
+
46
+ &:last-child {
47
+ border-right: none;
48
+ }
49
+ }
50
+ }
51
+
52
+ .table-row {
53
+ display: flex;
54
+ border-top: 1px solid #ddd;
55
+
56
+ &:hover {
57
+ background-color: #f5f5f5;
58
+ cursor: pointer;
59
+ }
60
+
61
+ .table-cell {
62
+ flex: 1;
63
+ padding: 8px;
64
+ border-right: 1px solid #ddd;
65
+
66
+ &:nth-child(1) {
67
+ min-width: 30px;
68
+ }
69
+
70
+ &:nth-child(2) {
71
+ flex: 30;
72
+ }
73
+
74
+ &:nth-child(3) {
75
+ min-width: 100px;
76
+ }
77
+
78
+ &:last-child {
79
+ border-right: none;
80
+ display: flex;
81
+ justify-content: center;
82
+ }
83
+ }
84
+ }
85
+
86
+ .set-default-button,
87
+ .delete-button {
88
+ padding: 5px;
89
+ display: flex;
90
+ align-items: center;
91
+ justify-content: center;
92
+ margin:3px;
93
+ }
94
+
95
+ .delete-button {
96
+ background-color: #dc3545;
97
+ color: white;
98
+ border: none;
99
+ border-radius: 5px;
100
+ cursor: pointer;
101
+
102
+ &:hover {
103
+ background-color: #c82333;
104
+ }
105
+ }
106
+
107
+ .set-default-button {
108
+ background-color: #28a745;
109
+ color: white;
110
+ border: none;
111
+ border-radius: 5px;
112
+ cursor: pointer;
113
+
114
+ &:hover {
115
+ background-color: #218838;
116
+ }
117
+ }
118
+ }
119
+
120
+ .modal-overlay {
121
+ position: fixed;
122
+ top: 0;
123
+ left: 0;
124
+ right: 0;
125
+ bottom: 0;
126
+ background-color: rgba(0, 0, 0, 0.5);
127
+ z-index: 1000;
128
+ }
129
+
130
+ .modal-content {
131
+ padding: 20px;
132
+
133
+ .label {
134
+ display: flex;
135
+ justify-content: space-between;
136
+ align-items: center;
137
+ margin-bottom: 20px;
138
+
139
+ .name {
140
+ margin: 0;
141
+ }
142
+
143
+ .close-button {
144
+ background: none;
145
+ border: none;
146
+ cursor: pointer;
147
+ padding: 0;
148
+ }
149
+ }
150
+
151
+ form {
152
+ display: flex;
153
+ flex-direction: column;
154
+ gap: 15px;
155
+
156
+ label {
157
+ display: flex;
158
+ flex-direction: column;
159
+ gap: 5px;
160
+ }
161
+
162
+ input {
163
+ padding: 8px;
164
+ border: 1px solid #ddd;
165
+ border-radius: 5px;
166
+ }
167
+
168
+ .button-group {
169
+ display: flex;
170
+ gap: 10px;
171
+ margin-top: 20px;
172
+
173
+ button {
174
+ padding: 8px 16px;
175
+ border: none;
176
+ border-radius: 5px;
177
+ cursor: pointer;
178
+
179
+ &:first-child {
180
+ background-color: #007bff;
181
+ color: white;
182
+
183
+ &:hover {
184
+ background-color: #0056b3;
185
+ }
186
+ }
187
+
188
+ &:nth-child(2) {
189
+ background-color: #28a745;
190
+ color: white;
191
+
192
+ &:hover {
193
+ background-color: #218838;
194
+ }
195
+ }
196
+
197
+ &:last-child {
198
+ background-color: #6c757d;
199
+ color: white;
200
+
201
+ &:hover {
202
+ background-color: #5a6268;
203
+ }
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
src/components/pages/qePrompts/QePromptList.tsx ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useQePrompts, } from "@/api/qePrompts/hooks";
2
+ import { QePrompt } from "@/api/qePrompts/types";
3
+ import React, { useState } from 'react';
4
+ import { GoStar, GoStarFill, GoTrash } from 'react-icons/go';
5
+ import Modal from 'react-modal';
6
+ import './QePromptList.scss';
7
+ import QePromptModal from './QePromptModal';
8
+
9
+
10
+
11
+ Modal.setAppElement('#root');
12
+
13
+
14
+
15
+ const QePromptList: React.FC = () => {
16
+ const { prompts, isLoading, error, createPrompt, updatePrompt, setAsDefaultPrompt, deletePrompt } = useQePrompts();
17
+ const [sortField, setSortField] = useState<keyof QePrompt | 'created_at'>('id');
18
+ const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
19
+ const [selectedPrompt, setSelectedPrompt] = useState<QePrompt | null>(null);
20
+ const [isModalOpen, setIsModalOpen] = useState(false);
21
+ const [isEditMode, setIsEditMode] = useState(false);
22
+
23
+
24
+ const handleSort = (field: keyof QePrompt | 'created_at') => {
25
+ const newDirection = sortField === field && sortDirection === 'asc' ? 'desc' : 'asc';
26
+ setSortField(field);
27
+ setSortDirection(newDirection);
28
+ const sortedPrompts = [...prompts].sort((a, b) => {
29
+ const aValue = field === 'created_at' ? a.created_at || '' : a[field];
30
+ const bValue = field === 'created_at' ? b.created_at || '' : b[field];
31
+ return newDirection === 'asc' ? (aValue > bValue ? 1 : -1) : (aValue < bValue ? 1 : -1);
32
+ });
33
+ prompts.splice(0, prompts.length, ...sortedPrompts);
34
+ };
35
+
36
+ const openEditModal = (prompt: QePrompt) => {
37
+ setSelectedPrompt(prompt);
38
+ setIsEditMode(true);
39
+ setIsModalOpen(true);
40
+ };
41
+
42
+ const openCreateModal = () => {
43
+ setSelectedPrompt(null); // Ничего не передаем, запрос будет в модалке
44
+ setIsEditMode(false);
45
+ setIsModalOpen(true);
46
+ };
47
+
48
+ const closeModal = () => {
49
+ setSelectedPrompt(null);
50
+ setIsModalOpen(false);
51
+ };
52
+
53
+ const handleSave = async (prompt: QePrompt | Omit<QePrompt, 'id' | 'created_at'>) => {
54
+ if (isEditMode && 'id' in prompt) {
55
+ await updatePrompt(prompt as QePrompt);
56
+ } else {
57
+ await createPrompt(prompt as Omit<QePrompt, 'id' | 'created_at'>);
58
+ }
59
+ };
60
+
61
+ const handleDelete = async (id: number) => {
62
+ if (window.confirm('Удаляем версию промпта?')) {
63
+ await deletePrompt(id);
64
+ }
65
+ };
66
+
67
+ const handleSetDefault = async (id: number) => {
68
+ await setAsDefaultPrompt(id);
69
+ };
70
+
71
+ if (isLoading) return <div>Loading...</div>;
72
+ if (error) return <div>Error: {error}</div>;
73
+
74
+ return (
75
+ <div className="qe-prompt-list">
76
+ <h1>Промпты Query Expansion</h1>
77
+ <button className="create-button" onClick={openCreateModal}>
78
+ Добавить промпт
79
+ </button>
80
+ <div className="table-container">
81
+ <div className="table-header">
82
+ <div className="table-cell"></div>
83
+ <div className="table-cell" onClick={() => handleSort('created_at')}>
84
+ Промпты
85
+ </div>
86
+ <div className="table-cell"></div>
87
+ </div>
88
+ {prompts.map(prompt => (
89
+ <div key={prompt.id} className="table-row">
90
+ <div className="table-cell">
91
+ <button
92
+ className="set-default-button"
93
+ onClick={() => handleSetDefault(prompt.id)}
94
+ >
95
+ {prompt.is_default ? <GoStarFill size={20} /> : <GoStar size={20} />}
96
+ </button>
97
+ </div>
98
+ <div className="table-cell" onClick={() => openEditModal(prompt)}>
99
+ {prompt.name}
100
+ </div>
101
+ {/* <div className="table-cell" onClick={() => openEditModal(prompt)}>
102
+ {prompt.created_at || 'N/A'}
103
+ </div> */}
104
+ <div className="table-cell">
105
+ <button className="delete-button" onClick={() => handleDelete(prompt.id)}>
106
+ <GoTrash size={20} />
107
+ </button>
108
+ </div>
109
+ </div>
110
+ ))}
111
+ </div>
112
+
113
+ <QePromptModal
114
+ isOpen={isModalOpen}
115
+ onRequestClose={closeModal}
116
+ prompt={selectedPrompt}
117
+ onSave={handleSave}
118
+ isEditMode={isEditMode}
119
+ />
120
+ </div>
121
+ );
122
+ };
123
+
124
+ export default QePromptList;
src/components/pages/qePrompts/QePromptModal.scss ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .modal-overlay {
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ right: 0;
6
+ bottom: 0;
7
+ background-color: rgba(0, 0, 0, 0.5);
8
+ z-index: 1000;
9
+ }
10
+
11
+ .modal-content {
12
+ max-height: 80vh;
13
+ overflow-y: auto;
14
+ overflow-x: hidden;
15
+
16
+ .label {
17
+ display: flex;
18
+ justify-content: space-between;
19
+ align-items: center;
20
+ margin-bottom: 20px;
21
+
22
+ .name {
23
+ margin: 0;
24
+ font-size: 1.5rem;
25
+ color: #333;
26
+ }
27
+
28
+ .close-button {
29
+ background: none;
30
+ border: none;
31
+ cursor: pointer;
32
+ padding: 0;
33
+ color: #666;
34
+
35
+ &:hover {
36
+ color: #333;
37
+ }
38
+ }
39
+ }
40
+
41
+ form {
42
+ display: grid;
43
+ grid-template-columns: 1fr 2fr;
44
+ gap: 15px;
45
+ align-items: center;
46
+ width: 100%;
47
+ box-sizing: border-box;
48
+
49
+ label {
50
+ display: contents;
51
+
52
+ span.title {
53
+ font-weight: 500;
54
+ color: #444;
55
+ text-align: right;
56
+ padding-right: 10px;
57
+ }
58
+
59
+ input {
60
+ max-width: 100%;
61
+ }
62
+
63
+ input[type="text"],
64
+ input[type="number"] {
65
+ padding: 8px 12px;
66
+ border: 1px solid #ccc;
67
+ border-radius: 5px;
68
+ font-size: 1rem;
69
+ color: #333;
70
+ background-color: #f9f9f9;
71
+ transition: border-color 0.2s;
72
+
73
+ &:focus {
74
+ border-color: #007bff;
75
+ outline: none;
76
+ background-color: #fff;
77
+ }
78
+
79
+ &:disabled {
80
+ background-color: #e9ecef;
81
+ color: #666;
82
+ }
83
+ }
84
+
85
+ input[type="checkbox"] {
86
+ width: 20px;
87
+ height: 20px;
88
+ margin: 0;
89
+ cursor: pointer;
90
+ }
91
+ }
92
+
93
+ .button-group {
94
+ grid-column: span 2;
95
+ display: flex;
96
+ justify-content: flex-end;
97
+ gap: 10px;
98
+ margin-top: 20px;
99
+
100
+ button {
101
+ padding: 10px 20px;
102
+ border: none;
103
+ border-radius: 5px;
104
+ font-size: 1rem;
105
+ cursor: pointer;
106
+ transition: background-color 0.2s;
107
+
108
+ &:first-child {
109
+ background-color: #007bff;
110
+ color: white;
111
+
112
+ &:hover {
113
+ background-color: #0056b3;
114
+ }
115
+ }
116
+
117
+ &:nth-child(2) {
118
+ background-color: #28a745;
119
+ color: white;
120
+
121
+ &:hover {
122
+ background-color: #218838;
123
+ }
124
+ }
125
+
126
+ &:last-child {
127
+ background-color: #6c757d;
128
+ color: white;
129
+
130
+ &:hover {
131
+ background-color: #5a6268;
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
src/components/pages/qePrompts/QePromptModal.tsx ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { fetchDefaultQePrompt, fetchQePromptById } from '@/api/qePrompts/qePromptApi';
2
+ import { QePrompt } from '@/api/qePrompts/types';
3
+ import { useQuery } from '@tanstack/react-query';
4
+ import React from 'react';
5
+ import { GoX } from 'react-icons/go';
6
+ import Modal from 'react-modal';
7
+ import './QePromptModal.scss';
8
+
9
+ interface QePromptModalProps {
10
+ isOpen: boolean;
11
+ onRequestClose: () => void;
12
+ prompt: QePrompt | null;
13
+ onSave: (prompt: QePrompt | Omit<QePrompt, 'id' | 'created_at'>) => Promise<void>;
14
+ isEditMode: boolean;
15
+ }
16
+
17
+ const customStyles = {
18
+ content: {
19
+ top: '40%',
20
+ left: '50%',
21
+ right: 'auto',
22
+ bottom: 'auto',
23
+ transform: 'translate(-50%, -50%)',
24
+ borderRadius: '15px',
25
+ maxWidth: '100%',
26
+ overflow: 'hidden',
27
+ },
28
+ };
29
+
30
+ const QePromptModal: React.FC<QePromptModalProps> = ({
31
+ isOpen,
32
+ onRequestClose,
33
+ prompt,
34
+ onSave,
35
+ isEditMode,
36
+ }) => {
37
+ const [formData, setFormData] = React.useState<QePrompt | Omit<QePrompt, 'id' | 'created_at'>>(() => ({
38
+ is_default: false,
39
+ name: 'Промпт ' + Date.now().toLocaleString(),
40
+ text: "",
41
+ type: 'query_expansion'
42
+ }));
43
+
44
+ // Запрос промпта для редактирования
45
+ const { data: serverPrompt, isLoading: isPromptLoading } = useQuery({
46
+ queryKey: ['qePrompt', prompt?.id],
47
+ queryFn: () => fetchQePromptById(prompt!.id),
48
+ enabled: isOpen && isEditMode && !!prompt?.id,
49
+ });
50
+
51
+ // Запрос дефолтного промпта для создания
52
+ const { data: defaultPrompt, isLoading: isDefaultLoading } = useQuery({
53
+ queryKey: ['defaultQePrompt'],
54
+ queryFn: fetchDefaultQePrompt,
55
+ enabled: isOpen && !isEditMode, // Запрашиваем только при создании
56
+ });
57
+
58
+ React.useEffect(() => {
59
+ if (isOpen) {
60
+ if (isEditMode && serverPrompt) {
61
+ setFormData(serverPrompt); // Данные с сервера для редактирования
62
+ } else if (!isEditMode && defaultPrompt) {
63
+ setFormData(defaultPrompt); // Дефолтная запись с сервера для создания
64
+ } else if (!isEditMode && !defaultPrompt && !isDefaultLoading) {
65
+ setFormData({ // Если дефолтной записи нет и загрузка завершена
66
+ is_default: true,
67
+ name: 'Промпт ' + Date.now().toLocaleString(),
68
+ text: "",
69
+ type: 'query_expansion'
70
+ });
71
+ }
72
+ }
73
+ }, [isOpen, isEditMode, serverPrompt, defaultPrompt, isDefaultLoading]);
74
+
75
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
76
+ const { name, value } = e.target;
77
+ setFormData(prev => ({
78
+ ...prev,
79
+ [name]:
80
+ name === 'is_default' ? (e.target as HTMLInputElement).checked :
81
+ name === 'text' || name === 'name' ? value :
82
+ parseFloat(value) || 0,
83
+ }));
84
+ };
85
+
86
+ const handleSubmit = async (e: React.FormEvent) => {
87
+ e.preventDefault();
88
+ try {
89
+ await onSave(formData);
90
+ onRequestClose();
91
+ } catch (err) {
92
+ console.error('Save failed:', err);
93
+ }
94
+ };
95
+
96
+ if (isPromptLoading && isEditMode) {
97
+ return <div>Loading prompt...</div>;
98
+ }
99
+ if (isDefaultLoading && !isEditMode) {
100
+ return <div>Loading default prompt...</div>;
101
+ }
102
+
103
+ return (
104
+ <Modal
105
+ isOpen={isOpen}
106
+ onRequestClose={onRequestClose}
107
+ style={customStyles}
108
+ overlayClassName="modal-overlay"
109
+ >
110
+ <div className="modal-content">
111
+ <div className="label">
112
+ <h3 className="name">{isEditMode ? 'Редактирование промпта' : 'Новый промпт'}</h3>
113
+ <button className="close-button" onClick={onRequestClose}>
114
+ <GoX style={{ height: '25px', width: '25px' }} />
115
+ </button>
116
+ </div>
117
+ <form onSubmit={handleSubmit}>
118
+ {isEditMode && 'id' in formData && (
119
+ <label>
120
+ <span className='title'>ID:</span>
121
+ <input type="text" value={formData.id} disabled />
122
+ </label>
123
+ )}
124
+ <label>
125
+ <span className='title'>Задать по-умолчанию:</span>
126
+ <input
127
+ type="checkbox"
128
+ name="is_default"
129
+ checked={formData.is_default}
130
+ onChange={handleChange}
131
+ />
132
+ </label>
133
+ <label>
134
+ <span className='title'>Название:</span>
135
+ <input
136
+ type="text"
137
+ name="name"
138
+ value={formData.name}
139
+ onChange={handleChange}
140
+ />
141
+ </label>
142
+ <label>
143
+ <span className='title'>Текст:</span>
144
+ <textarea
145
+ name="text"
146
+ value={formData.text || ''}
147
+ onChange={handleChange}
148
+ />
149
+ </label>
150
+ <div className="button-group">
151
+ <button type="submit">{isEditMode ? 'Сохранит��' : 'Создать'}</button>
152
+ </div>
153
+ </form>
154
+ </div>
155
+ </Modal>
156
+ );
157
+ };
158
+
159
+ export default QePromptModal;
src/components/views/documents/docsList/DocsList.tsx CHANGED
@@ -21,9 +21,9 @@ export const DocsList: FC<DocsListProps> = ({ datasetId, handleDeleteFile }) =>
21
  const [searchInput, setSearchInput] = useState<string | undefined>(undefined);
22
  const [search, setSearch] = useState<string | undefined>(undefined);
23
  const [sort, setSort] = useState<undefined | { field: string; direction: SortDirections }[]>(undefined);
24
-
25
  const { data: datasetData, isFetching } = useGetDataset(datasetId ?? -1, { page, page_size: pageSize, search, sort });
26
-
27
  const toggleSort = (field: string) => {
28
  setSort((prevSort) => {
29
  if (prevSort?.length && prevSort?.length > 0) {
 
21
  const [searchInput, setSearchInput] = useState<string | undefined>(undefined);
22
  const [search, setSearch] = useState<string | undefined>(undefined);
23
  const [sort, setSort] = useState<undefined | { field: string; direction: SortDirections }[]>(undefined);
24
+
25
  const { data: datasetData, isFetching } = useGetDataset(datasetId ?? -1, { page, page_size: pageSize, search, sort });
26
+
27
  const toggleSort = (field: string) => {
28
  setSort((prevSort) => {
29
  if (prevSort?.length && prevSort?.length > 0) {