From c1b3a1ca4508dd375cfa4b891c8918763ba190a7 Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 10 Sep 2025 20:12:28 +0300 Subject: [PATCH 1/3] Initial commit with task details for issue #208 Adding CLAUDE.md with task information for AI processing. This file will be removed when the task is complete. Issue: https://github.com/deep-assistant/GPTutor/issues/208 --- CLAUDE.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..32385cf1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +Issue to solve: https://github.com/deep-assistant/GPTutor/issues/208 +Your prepared branch: issue-208-2b458636 +Your prepared working directory: /tmp/gh-issue-solver-1757524327577 + +Proceed. \ No newline at end of file From 6a41575cef8caa94a6beed6e02d9c35b1c0004b4 Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 10 Sep 2025 20:12:44 +0300 Subject: [PATCH 2/3] Remove CLAUDE.md - PR created successfully --- CLAUDE.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 32385cf1..00000000 --- a/CLAUDE.md +++ /dev/null @@ -1,5 +0,0 @@ -Issue to solve: https://github.com/deep-assistant/GPTutor/issues/208 -Your prepared branch: issue-208-2b458636 -Your prepared working directory: /tmp/gh-issue-solver-1757524327577 - -Proceed. \ No newline at end of file From c58c34c571f3ebdac1d24b6c3bd0a465c8cef000 Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 10 Sep 2025 20:17:40 +0300 Subject: [PATCH 3/3] #208 : Implement global refactoring improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Backend: Replace @Autowired field injection with constructor injection in controllers - Backend: Remove redundant Spring Boot dependency version declarations - Backend: Improve type safety and error handling - Python Models: Add proper error handling, request validation, and logging - RAG Service: Enhance TypeScript type safety and error handling - Frontend: Organize imports in App.tsx with logical grouping and comments 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- GPTutor-Backend/pom.xml | 20 --- .../controllers/ConversationController.java | 11 +- .../controllers/HistoryController.java | 11 +- .../chatgpt/controllers/ImagesController.java | 17 ++- GPTutor-Frontend/src/App.tsx | 74 +++++---- GPTutor-Models/app.py | 89 +++++++---- GPTutor-Rag/index.ts | 140 ++++++++++++------ 7 files changed, 222 insertions(+), 140 deletions(-) diff --git a/GPTutor-Backend/pom.xml b/GPTutor-Backend/pom.xml index 9bcaa2f5..a6832357 100644 --- a/GPTutor-Backend/pom.xml +++ b/GPTutor-Backend/pom.xml @@ -22,11 +22,6 @@ aws-java-sdk-s3 1.12.546 - - org.springframework - spring-websocket - 6.0.10 - org.springframework.boot spring-boot-starter-websocket @@ -36,21 +31,6 @@ spring-boot-starter-test test - - org.springframework.boot - spring-boot-autoconfigure - 3.0.5 - - - org.springframework - spring-web - 6.0.6 - - - org.springframework.boot - spring-boot - 3.0.5 - org.springframework.boot spring-boot-starter-web diff --git a/GPTutor-Backend/src/main/java/com/chatgpt/controllers/ConversationController.java b/GPTutor-Backend/src/main/java/com/chatgpt/controllers/ConversationController.java index b7ceb7a7..56e38a27 100644 --- a/GPTutor-Backend/src/main/java/com/chatgpt/controllers/ConversationController.java +++ b/GPTutor-Backend/src/main/java/com/chatgpt/controllers/ConversationController.java @@ -14,8 +14,11 @@ @RestController public class ConversationController { - @Autowired - ConversationsService conversationsService; + private final ConversationsService conversationsService; + + public ConversationController(ConversationsService conversationsService) { + this.conversationsService = conversationsService; + } @PostMapping(path = "/vk-doc/conversation") public String getConversationVkDoc(@RequestBody QuestionRequest questionRequest) { @@ -23,8 +26,8 @@ public String getConversationVkDoc(@RequestBody QuestionRequest questionRequest) } @PostMapping(path = "/conversation", consumes = MediaType.APPLICATION_JSON_VALUE) - public T getConversation(@RequestBody ConversationRequest conversationRequest, HttpServletRequest request) throws IOException { - return (T) conversationsService.getConversation(conversationRequest, (String) request.getAttribute("vkUserId")); + public Object getConversation(@RequestBody ConversationRequest conversationRequest, HttpServletRequest request) throws IOException { + return conversationsService.getConversation(conversationRequest, (String) request.getAttribute("vkUserId")); } } diff --git a/GPTutor-Backend/src/main/java/com/chatgpt/controllers/HistoryController.java b/GPTutor-Backend/src/main/java/com/chatgpt/controllers/HistoryController.java index 6562d4a3..a9a45622 100644 --- a/GPTutor-Backend/src/main/java/com/chatgpt/controllers/HistoryController.java +++ b/GPTutor-Backend/src/main/java/com/chatgpt/controllers/HistoryController.java @@ -18,12 +18,13 @@ @RestController public class HistoryController { + private final HistoryService historyService; + private final MessageRepository messageRepository; - @Autowired - HistoryService historyService; - - @Autowired - MessageRepository messageRepository; + public HistoryController(HistoryService historyService, MessageRepository messageRepository) { + this.historyService = historyService; + this.messageRepository = messageRepository; + } @PostMapping(path = "/history") @RateLimiter(name = "historyLimit", fallbackMethod = "fallbackMethod") diff --git a/GPTutor-Backend/src/main/java/com/chatgpt/controllers/ImagesController.java b/GPTutor-Backend/src/main/java/com/chatgpt/controllers/ImagesController.java index 619faabd..4ab22210 100644 --- a/GPTutor-Backend/src/main/java/com/chatgpt/controllers/ImagesController.java +++ b/GPTutor-Backend/src/main/java/com/chatgpt/controllers/ImagesController.java @@ -19,14 +19,15 @@ @RestController public class ImagesController { - @Autowired - ImagesService imagesService; - - @Autowired - ComplaintsService complaintsService; - - @Autowired - ImageLikeService imageLikeService; + private final ImagesService imagesService; + private final ComplaintsService complaintsService; + private final ImageLikeService imageLikeService; + + public ImagesController(ImagesService imagesService, ComplaintsService complaintsService, ImageLikeService imageLikeService) { + this.imagesService = imagesService; + this.complaintsService = complaintsService; + this.imageLikeService = imageLikeService; + } @PostMapping(path = "/image") List generateImage(@RequestBody GenerateImageRequest prompt, HttpServletRequest request) { diff --git a/GPTutor-Frontend/src/App.tsx b/GPTutor-Frontend/src/App.tsx index f7bc2896..1b8ca59b 100644 --- a/GPTutor-Frontend/src/App.tsx +++ b/GPTutor-Frontend/src/App.tsx @@ -1,4 +1,11 @@ import React, { useEffect } from "react"; +import { retrieveLaunchParams } from "@telegram-apps/sdk"; +import { useLocation } from "@happysanta/router"; +import { + useAdaptivity, + useAppearance, + useInsets, +} from "@vkontakte/vk-bridge-react"; import { AdaptivityProvider, AppRoot, @@ -11,70 +18,83 @@ import bridge, { AppearanceType, parseURLSearchParamsForGetLaunchParams, } from "@vkontakte/vk-bridge"; -import { useLocation } from "@happysanta/router"; -import { - useAdaptivity, - useAppearance, - useInsets, -} from "@vkontakte/vk-bridge-react"; -import "markdown-it-latex/dist/index.css"; +// Styles +import "markdown-it-latex/dist/index.css"; import "@vkontakte/vkui/dist/vkui.css"; import "./index.css"; +// Core entities and services import { vkUserModel } from "./entity/user"; import { online } from "./api/online"; +import { appService } from "$/services/AppService"; +import { transformVKBridgeAdaptivity } from "$/utility/strings"; +// Theme components import { OneDark } from "./OneDark"; import { OneLight } from "./OneLight"; + +// Routing import { Modals, Panels, Views } from "./entity/routing"; +// Navigation and utilities +import { useNavigationContext } from "./NavigationContext"; +import { SnackbarNotifier } from "./components/SnackbarNotifier"; +import UtilBlock from "./UtilBlock"; + +// Main panels import { Home } from "./panels/Home"; import { Chapters } from "./panels/Chapters"; import { History } from "./panels/History"; import { Modes } from "./panels/Modes"; - -import { useNavigationContext } from "./NavigationContext"; -import { SnackbarNotifier } from "./components/SnackbarNotifier"; import { ChatSettings } from "./panels/ChatSettings"; -import { ApplicationInfo } from "./modals/ApplicationInfo"; +import { LoadingPanel } from "$/panels/LoadingPanel"; + +// Chat panels import { ChatFree } from "./panels/ChatFree"; import { ChatLesson } from "./panels/ChatLesson"; import { ChatInterview } from "./panels/ChatInterview"; -import { InterviewQuestions } from "./modals/InterviewQuestions"; -import { LeetcodeProblems } from "./panels/LeetCodeProblems"; import { ChatLeetCode } from "./panels/ChatLeetCode"; +import { ChatTrainer } from "./panels/ChatTrainer"; + +// LeetCode panels +import { LeetcodeProblems } from "./panels/LeetCodeProblems"; import { ProblemDetail } from "./panels/ProblemDetail"; -import { AppAlert } from "./modals/AppAlert"; + +// Code editor import { CodeEditor } from "./panels/CodeEditor"; -import { ChatTrainer } from "./panels/ChatTrainer"; -import { ImageGenerationResult } from "./panels/ImageGenerationResult"; -import UtilBlock from "./UtilBlock"; -import { appService } from "$/services/AppService"; -import { LoadingPanel } from "$/panels/LoadingPanel"; +// Image generation panels import { ImageGeneration } from "$/panels/ImageGeneration"; +import { ImageGenerationResult } from "./panels/ImageGenerationResult"; import { ImageGenerationExamples } from "$/panels/ImageGenerationExamples"; import Gallery from "$/panels/Gallery"; import ImageCreatePrompts from "$/panels/ImageCreatePrompts"; -import Profile from "$/panels/Profile"; -import ApplicationInfoStableArt from "./modals/ApplicationInfoStableArt/ApplicationInfoStableArt"; import { PublishingImages } from "$/panels/PublishingImages"; -import { Agreement } from "$/modals/Agreement"; -import { DetailImage } from "$/modals/DetailImage"; -import { WeakRequestModal } from "$/modals/WeakRequestModal"; + +// Profile panels +import Profile from "$/panels/Profile"; import { GPTutorProfile } from "$/panels/GPTutorProfile"; -import { transformVKBridgeAdaptivity } from "$/utility/strings"; + +// Other feature panels import { MermaidPage } from "$/panels/MermaidPage"; import { AdditionalRequests } from "$/panels/AdditionalRequests"; import { AnecdoteMain } from "$/panels/AnecdoteMain"; import AnecdoteGeneration from "./panels/AnecdoteGeneration/AnecdoteGeneration"; import { AnecdoteNews } from "$/panels/AnecdoteNews"; -import ApplicationInfoHumor from "./modals/ApplicationInfoHumor/ApplicationInfoHumor"; import { BingPanel } from "$/panels/BingPanel"; import VKDocQuestionPanel from "./panels/VKDocQuestionPanel/VKDocQestionPanel"; import { VkDocQuestionRequest } from "$/panels/VkDocQuestionRequest"; -import { retrieveLaunchParams } from "@telegram-apps/sdk"; + +// Modals +import { ApplicationInfo } from "./modals/ApplicationInfo"; +import ApplicationInfoStableArt from "./modals/ApplicationInfoStableArt/ApplicationInfoStableArt"; +import ApplicationInfoHumor from "./modals/ApplicationInfoHumor/ApplicationInfoHumor"; +import { InterviewQuestions } from "./modals/InterviewQuestions"; +import { Agreement } from "$/modals/Agreement"; +import { DetailImage } from "$/modals/DetailImage"; +import { WeakRequestModal } from "$/modals/WeakRequestModal"; +import { AppAlert } from "./modals/AppAlert"; const App = () => { const location = useLocation(); diff --git a/GPTutor-Models/app.py b/GPTutor-Models/app.py index 2be60706..92274960 100644 --- a/GPTutor-Models/app.py +++ b/GPTutor-Models/app.py @@ -1,49 +1,38 @@ -from flask import Flask, request +from flask import Flask, request, jsonify +import logging +import traceback from images.dalle3 import generate_dalle from images.prodia import txt2img from vk_docs.index import create_question_vk_doc app = Flask(__name__) +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) @app.post('/llm') def llm_post(): - return None + return jsonify({"message": "LLM POST endpoint not implemented"}), 501 @app.get('/llm') def llm_get(): - return [] + return jsonify([]) @app.post("/image") def image(): - return txt2img( - prompt=request.json["prompt"], - model=request.json["modelId"], - negative_prompt=request.json["negativePrompt"], - scheduler=request.json["scheduler"], - guidance_scale=request.json["guidanceScale"], - seed=request.json["seed"], - steps=request.json["numInferenceSteps"], - ) - - -@app.post("/vk-doc-question") -def vk_doc_question(): - return create_question_vk_doc( - question=request.json["question"], - source=request.json["source"] - ) - - -@app.post("/dalle") -def dalle(): - print(request.json) - try: - return txt2img( + if not request.json: + return jsonify({"error": "JSON payload required"}), 400 + + required_fields = ["prompt", "modelId", "negativePrompt", "scheduler", "guidanceScale", "seed", "numInferenceSteps"] + for field in required_fields: + if field not in request.json: + return jsonify({"error": f"Missing required field: {field}"}), 400 + + result = txt2img( prompt=request.json["prompt"], model=request.json["modelId"], negative_prompt=request.json["negativePrompt"], @@ -52,10 +41,47 @@ def dalle(): seed=request.json["seed"], steps=request.json["numInferenceSteps"], ) + return jsonify(result) + except Exception as e: + logger.error(f"Error in image generation: {str(e)}") + logger.error(traceback.format_exc()) + return jsonify({"error": "Image generation failed"}), 500 + +@app.post("/vk-doc-question") +def vk_doc_question(): + try: + if not request.json: + return jsonify({"error": "JSON payload required"}), 400 + + if "question" not in request.json or "source" not in request.json: + return jsonify({"error": "Missing required fields: question, source"}), 400 + + result = create_question_vk_doc( + question=request.json["question"], + source=request.json["source"] + ) + return jsonify(result) except Exception as e: - print(e) - return txt2img( + logger.error(f"Error in VK doc question: {str(e)}") + logger.error(traceback.format_exc()) + return jsonify({"error": "VK doc question processing failed"}), 500 + + +@app.post("/dalle") +def dalle(): + try: + if not request.json: + return jsonify({"error": "JSON payload required"}), 400 + + logger.info(f"DALLE request: {request.json}") + + required_fields = ["prompt", "modelId", "negativePrompt", "scheduler", "guidanceScale", "seed", "numInferenceSteps"] + for field in required_fields: + if field not in request.json: + return jsonify({"error": f"Missing required field: {field}"}), 400 + + result = txt2img( prompt=request.json["prompt"], model=request.json["modelId"], negative_prompt=request.json["negativePrompt"], @@ -64,6 +90,11 @@ def dalle(): seed=request.json["seed"], steps=request.json["numInferenceSteps"], ) + return jsonify(result) + except Exception as e: + logger.error(f"Error in DALLE generation: {str(e)}") + logger.error(traceback.format_exc()) + return jsonify({"error": "DALLE generation failed"}), 500 def run_flask(): diff --git a/GPTutor-Rag/index.ts b/GPTutor-Rag/index.ts index 50bc22bc..5b442da3 100644 --- a/GPTutor-Rag/index.ts +++ b/GPTutor-Rag/index.ts @@ -1,5 +1,4 @@ -import express from "express"; - +import express, { Request, Response } from "express"; import { GigaChatEmbeddings } from "./GigaChatSupport/GigaChatEmbeddings"; import { FaissStore } from "@langchain/community/vectorstores/faiss"; import * as bodyParser from "body-parser"; @@ -10,60 +9,107 @@ import dotenv from "dotenv"; dotenv.config(); const app = express(); +const PORT = process.env.PORT || 5000; app.use(bodyParser.json()); +interface DocQuestionRequest { + question: string; + source: string; +} + +type SupportedSources = "all" | "vk_api_docs" | "vk_ui" | "videos"; + +function isValidSource(source: string): source is SupportedSources { + return ["all", "vk_api_docs", "vk_ui", "videos"].includes(source); +} + (async () => { - const vectorStoreVKUIDoc = await FaissStore.loadFromPython( - "./faiss_vk_ui_docs_index", - new GigaChatEmbeddings({ clientSecretKey: process.env.CLIENT_SECRET_KEY }) - ); - - const vectorStoreVKDoc = await FaissStore.load( - "./faiss_vk_docs_index_js", - new GigaChatEmbeddings({ clientSecretKey: process.env.CLIENT_SECRET_KEY }) - ); - - const vectorStoreVideos = await FaissStore.loadFromPython( - "./faiss_vk_videos_index", - new GigaChatEmbeddings({ clientSecretKey: process.env.CLIENT_SECRET_KEY }) - ); - - function createRetriever(source: string) { - if (source === "all") { - return new EnsembleRetriever({ - retrievers: [ - vectorStoreVKUIDoc.asRetriever({ k: 2 }), - vectorStoreVKDoc.asRetriever({ k: 2 }), - vectorStoreVideos.asRetriever({ k: 2 }), - ], - weights: [0.33, 0.33, 0.33], - }); + try { + console.log("Loading vector stores..."); + + if (!process.env.CLIENT_SECRET_KEY) { + throw new Error("CLIENT_SECRET_KEY environment variable is required"); } - if (source == "vk_api_docs") { - return new EnsembleRetriever({ - retrievers: [vectorStoreVKDoc.asRetriever({ k: 3 })], - }); - } + const embeddings = new GigaChatEmbeddings({ clientSecretKey: process.env.CLIENT_SECRET_KEY }); - if (source == "vk_ui") { - return vectorStoreVKUIDoc.asRetriever({ k: 3 }); - } + const vectorStoreVKUIDoc = await FaissStore.loadFromPython( + "./faiss_vk_ui_docs_index", + embeddings + ); - return vectorStoreVideos.asRetriever({ k: 3 }); - } + const vectorStoreVKDoc = await FaissStore.load( + "./faiss_vk_docs_index_js", + embeddings + ); + + const vectorStoreVideos = await FaissStore.loadFromPython( + "./faiss_vk_videos_index", + embeddings + ); - app.post("/doc-question", async (req, res) => { - try { - const { question, source } = req.body; - res.send(await createWorkflow(question, createRetriever(source))); - } catch (e) { - res.send("Что-то пошло не так"); + console.log("Vector stores loaded successfully"); + + function createRetriever(source: SupportedSources) { + switch (source) { + case "all": + return new EnsembleRetriever({ + retrievers: [ + vectorStoreVKUIDoc.asRetriever({ k: 2 }), + vectorStoreVKDoc.asRetriever({ k: 2 }), + vectorStoreVideos.asRetriever({ k: 2 }), + ], + weights: [0.33, 0.33, 0.33], + }); + case "vk_api_docs": + return new EnsembleRetriever({ + retrievers: [vectorStoreVKDoc.asRetriever({ k: 3 })], + }); + case "vk_ui": + return vectorStoreVKUIDoc.asRetriever({ k: 3 }); + case "videos": + return vectorStoreVideos.asRetriever({ k: 3 }); + default: + throw new Error(`Unsupported source: ${source}`); + } } - }); - app.listen(5000, () => { - console.log(`Server running on port ${5000}`); - }); + app.post("/doc-question", async (req: Request<{}, any, DocQuestionRequest>, res: Response) => { + try { + const { question, source } = req.body; + + if (!question || !source) { + return res.status(400).json({ + error: "Missing required fields: question and source are required" + }); + } + + if (!isValidSource(source)) { + return res.status(400).json({ + error: `Invalid source. Supported sources: ${["all", "vk_api_docs", "vk_ui", "videos"].join(", ")}` + }); + } + + const result = await createWorkflow(question, createRetriever(source)); + res.json({ result }); + } catch (error) { + console.error("Error processing doc question:", error); + res.status(500).json({ + error: "Internal server error occurred while processing your question" + }); + } + }); + + app.get("/health", (req: Request, res: Response) => { + res.json({ status: "healthy", timestamp: new Date().toISOString() }); + }); + + app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); + }); + } catch (error) { + console.error("Failed to initialize server:", error); + process.exit(1); + } })();