From 6ede1ca15fb54d27cfc3922c9cbcfe2e940fad95 Mon Sep 17 00:00:00 2001 From: Mikan <72257910+Mikan-DS@users.noreply.github.com> Date: Mon, 2 Jun 2025 16:43:01 +0300 Subject: [PATCH] =?UTF-8?q?=D1=84=D0=B0=D0=BA=D0=B8=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReactDev/__init__.py | 16 +- ReactDev/core/ai_interface.py | 116 +++++++++++-- ReactDev/core/project_map.py | 14 +- StaticHelper/__init__.py | 6 + StaticHelper/ai_interface.py | 197 +++++++++++++++++++++++ StaticHelper/config.py | 4 + StaticHelper/source/docs.md | 241 ++++++++++++++++++++++++++++ StaticHelper/source/header.md | 14 ++ StaticHelper/source/old_input.md | 0 StaticHelper/source/working_file.md | 95 +++++++++++ StaticHelper/utils/file_utils.py | 21 +++ StaticHelper/utils/logger.py | 13 ++ StaticHelper/utils/prompt_utils.py | 0 13 files changed, 720 insertions(+), 17 deletions(-) create mode 100644 StaticHelper/__init__.py create mode 100644 StaticHelper/ai_interface.py create mode 100644 StaticHelper/config.py create mode 100644 StaticHelper/source/docs.md create mode 100644 StaticHelper/source/header.md create mode 100644 StaticHelper/source/old_input.md create mode 100644 StaticHelper/source/working_file.md create mode 100644 StaticHelper/utils/file_utils.py create mode 100644 StaticHelper/utils/logger.py create mode 100644 StaticHelper/utils/prompt_utils.py diff --git a/ReactDev/__init__.py b/ReactDev/__init__.py index 64368c6..f28d010 100644 --- a/ReactDev/__init__.py +++ b/ReactDev/__init__.py @@ -99,13 +99,17 @@ def run_workflow(): ai_response = ai_interface.analyze_project(user_prompt, full_context) log(f"AI response: {ai_response}") - # Шаг 5: Генерация плана модификации - command_generator = CommandGenerator() - modifications = ai_response.get("modifications", []) - modification_plan = command_generator.generate_commands(modifications) + ai_response = ai_interface.get_modifications(user_prompt, full_context) + log(f"AI response: {ai_response}") + + + # # Шаг 5: Генерация плана модификации + # command_generator = CommandGenerator() + # modifications = ai_response.get("modifications", []) + # modification_plan = command_generator.generate_commands(modifications) with open("modification_plan.md", "w", encoding="utf-8") as f: - f.write(modification_plan) + f.write(ai_response["response"]) - log(f"Generated modification plan:\n{modification_plan}") + # log(f"Generated modification plan:\n{modification_plan}") log("ReactDev workflow completed.") diff --git a/ReactDev/core/ai_interface.py b/ReactDev/core/ai_interface.py index e1f6e13..b042ca9 100644 --- a/ReactDev/core/ai_interface.py +++ b/ReactDev/core/ai_interface.py @@ -2,6 +2,7 @@ from openai import OpenAI from ..config import OPENAI_API_KEY, OPENAI_BASE_URL, DEFAULT_MODEL from ..utils.logger import log + class AIInterface: def __init__(self): self.client = OpenAI( @@ -9,19 +10,24 @@ class AIInterface: base_url=OPENAI_BASE_URL, ) - def send_request(self, prompt: str, context: dict = None) -> dict: + def send_request(self, prompt: str, context: dict = None, system_prompt: str = "") -> dict: """ Отправляет запрос на эндпоинт ИИ через OpenAI и возвращает ответ. """ try: log(f"Sending request to AI endpoint with prompt: {prompt}") + messages = [] + + if system_prompt: + messages.append({"role": "system", "content": system_prompt}) + # Подготовка сообщений для чат-комплитшена - messages = [{"role": "user", "content": prompt}] - # if context: - # # Преобразуем контекст в строку - # context_str = self._format_context(context) - # messages.append({"role": "system", "content": context_str}) + messages.append({"role": "user", "content": prompt}) + if context: + # Преобразуем контекст в строку + context_str = self._format_context(context) + messages.append({"role": "system", "content": context_str}) # Создание запроса к OpenAI response = self.client.chat.completions.create( @@ -46,17 +52,107 @@ class AIInterface: formatted_context.append(f"{key}: {value}") return "\n".join(formatted_context) - - def analyze_project(self, project_structure: str, prompt: str) -> dict: + def analyze_project(self, prompt: str, project_structure: dict) -> dict: """ Анализирует структуру проекта и запрос пользователя через ИИ. """ context = {"project_structure": project_structure} - return self.send_request(prompt, context) + answer = self.send_request(prompt, context, ANALYZE_PROMPT) + + max_attempts = 10 + while "Достаточно информации" not in answer.get("response", "Достаточно информации"): + max_attempts -= 1 + if max_attempts < 0: + log(f"Error communicating with AI endpoint.", level="error") + raise Exception("Error communicating with AI endpoint.") + + for command in answer.get("response", "").split("\n"): + try: + command_type, details = command.split(":", 1) + if command_type.strip().lower() == "запрос содержимого файла": + project_structure['files_info'] = project_structure.get('files_info', {}) + try: + with open(details.strip(), "r", encoding="utf-8") as f: + content = f.read() + project_structure['files_info'][details.strip()] = content + except FileNotFoundError: + project_structure['files_info'][details.strip()] = "Файл не найден" + elif command_type.strip().lower() == "запрос содержимого функции": + raise NotImplementedError("Запрос содержимого функции не реализован") + elif command_type.strip().lower() == "уточнение запроса": + answer = self.request_additional_info(details.strip()) + project_structure['QA_info'] = project_structure.get('QA_info', {}) + project_structure['QA_info'][details.strip()] = answer + else: + raise ValueError(f"Unknown command type: {command_type}") + except Exception as e: + log(f"Error parsing command: {command}", level="error") + answer = self.send_request(prompt, context, ANALYZE_PROMPT) + + return answer + + def request_additional_info(self, message: str) -> str: """ Запрашивает дополнительную информацию у пользователя. """ log("Requesting additional information from user.") - return input(f"{message}\n> ") \ No newline at end of file + return input(f"{message}\n> ") + + def get_modifications(self, prompt: str, project_structure: dict) -> dict: + """ + Создает модификации проекта на основе команды пользователя. + """ + context = {"project_structure": project_structure} + answer = self.send_request(prompt, context, COMMANDS_PROMPT) + + + return answer + + +ANALYZE_PROMPT = """ +Ты - эксперт по анализу структуры проектов. +Ты должен проанализировать структуру проекта и запрос пользователя. + +Если данных которые тебе передали достаточно - ответь "Достаточно информации". +Если данных недостаточно - запроси содержимое файла или функции которые тебе интересны и/или запроси у пользователя дополнительную информацию. +Если не получается найти файл или функцию - учитывай что его возможно нужно будет создать и ты можешь пропустить этот шаг и продолжить работу дальше. Пути должны соответствовать структуре проекта. + +ОТВЕЧАЙ ТОЛЬКО КОМАНДАМИ! + +### Запрос содержимого файла +Содержимое файла: <имя файла> +### Запрос содержимого функции +Содержимое функции: <имя файла> <имя функции> +### Запрос у пользователя +Уточнение запроса: <текст вопроса к пользователю> + +Например, вот ответ с двумя командами, первый просить содержимое файла, второй - уточнить что нужно создать.: +Содержимое файла: app/models/user.ts +Уточнение запроса: Нужно ли создать интерфейсы в types/users.ts? + + +НЕ ОТВЕЧАЙ НИЧЕМ ЧТО НЕ СООТВЕТСТВУЕТ ПРАВИЛАМ ВЫШЕ, ТОЛЬКО КОМАНДЫ! +ПОСТАРАЙСЯ В ОДНОМ ОТВЕТЕ НАПИСАТЬ ВСЕ НЕОБХОДИМЫЕ КОМАНДЫ! +""".strip() + + +COMMANDS_PROMPT = """ +Ты - senior developer. Ты должен прочитать запрос пользователя и на его основе сформировать список команд для выполнения этого запроса. +Ты должен ознакомиться с текущей структурой проекта и отдельными файлами и функциями. + +Итого ты должен сформировать список команд для выполнения этого запроса. +Команды в таком формате через перенос строки: +`FILE <Команда>` `<имя и путь к файлу>` + +`FUNCTION <Команда>` `<имя функции>` +```ts +<код функции> +``` + +Команды: CREATE, UPDATE, DELETE + +НЕ ОТВЕЧАЙ НИЧЕМ ЧТО НЕ СООТВЕТСТВУЕТ ПРАВИЛАМ ВЫШЕ, ТОЛЬКО КОМАНДЫ! +ПОСТАРАЙСЯ В ОДНОМ ОТВЕТЕ НАПИСАТЬ ВСЕ НЕОБХОДИМЫЕ КОМАНДЫ! +""".strip() \ No newline at end of file diff --git a/ReactDev/core/project_map.py b/ReactDev/core/project_map.py index 5cc4b7a..c5ae949 100644 --- a/ReactDev/core/project_map.py +++ b/ReactDev/core/project_map.py @@ -5,16 +5,19 @@ from pathspec import PathSpec from pathspec.patterns import GitWildMatchPattern from ..utils.logger import log +from .file_analyzer import FileAnalyzer class ProjectMap: def __init__(self, project_path: str): self.project_path = project_path self.gitignore_rules = self._load_gitignore() + self.file_analyzer = FileAnalyzer() def generate_map(self) -> str: """ Создает карту структуры проекта, игнорируя файлы из .gitignore и директорию .git/. + Добавляет информацию о функциях в файлы .ts, .tsx, .js, .jsx. """ log("Generating project structure map...") structure = self._traverse_directory(self.project_path) @@ -44,6 +47,15 @@ class ProjectMap: result += self._traverse_directory(full_path, level + 1) else: result += f"{indent}- {entry}\n" + + # Если это файл TypeScript/JavaScript, добавляем информацию о функциях + if entry.endswith((".ts", ".tsx", ".js", ".jsx")): + functions_info = self.file_analyzer.analyze_file(full_path) + if functions_info: + for func_name, details in functions_info.items(): + params = ", ".join(details["params"]) + return_type = details.get("return_type", "unknown") + result += f"{indent} FUNCTION {func_name}({params}): {return_type}\n" return result def save_map(self, output_file: str = "react_app_map.txt"): @@ -78,4 +90,4 @@ class ProjectMap: spec = PathSpec.from_lines(GitWildMatchPattern, self.gitignore_rules) # Проверяем, соответствует ли путь правилам - return spec.match_file(path) + return spec.match_file(path) \ No newline at end of file diff --git a/StaticHelper/__init__.py b/StaticHelper/__init__.py new file mode 100644 index 0000000..8603169 --- /dev/null +++ b/StaticHelper/__init__.py @@ -0,0 +1,6 @@ +from StaticHelper.ai_interface import AIInterface + +if __name__ == '__main__': + ai = AIInterface() + ai.work("") + diff --git a/StaticHelper/ai_interface.py b/StaticHelper/ai_interface.py new file mode 100644 index 0000000..f2de064 --- /dev/null +++ b/StaticHelper/ai_interface.py @@ -0,0 +1,197 @@ +from openai import OpenAI +from .config import OPENAI_API_KEY, OPENAI_BASE_URL, DEFAULT_MODEL +from .utils.logger import log + + +class AIInterface: + def __init__(self): + self.client = OpenAI( + api_key=OPENAI_API_KEY, + base_url=OPENAI_BASE_URL, + ) + + def work(self, prompt): + with open("source/header.md", 'r', encoding='utf-8') as file: + header = file.read() + with open("source/docs.md", 'r', encoding='utf-8') as file: + docs = file.read() + with open("source/working_file.md", 'r', encoding='utf-8') as file: + wf = file.read() + + + try: + log(f"Sending request to AI endpoint with prompt") + + messages = [ + {"role": "system", "content": header}, + {"role": "user", "content": docs}, + {"role": "user", "content": prompt+"\n\n"+wf} + ] + + + # Создание запроса к OpenAI + response = self.client.chat.completions.create( + model=DEFAULT_MODEL, + messages=messages, + stream=False, + ) + + with open("source/old_input.md", 'w', encoding='utf-8') as file: + file.write(wf) + with open("source/working_file.md", 'w', encoding='utf-8') as file: + file.write(response.choices[0].message.content) + + log(f"AI response received: {response.choices[0].message.content}") + # Обработка ответа + return {"response": response.choices[0].message.content} + + except Exception as e: + log(f"Error communicating with AI endpoint: {e}", level="error") + raise + + def send_request(self, prompt: str, context: dict = None, system_prompt: str = "") -> dict: + """ + Отправляет запрос на эндпоинт ИИ через OpenAI и возвращает ответ. + """ + try: + log(f"Sending request to AI endpoint with prompt: {prompt}") + + messages = [] + + if system_prompt: + messages.append({"role": "system", "content": system_prompt}) + + # Подготовка сообщений для чат-комплитшена + messages.append({"role": "user", "content": prompt}) + if context: + # Преобразуем контекст в строку + context_str = self._format_context(context) + messages.append({"role": "system", "content": context_str}) + + # Создание запроса к OpenAI + response = self.client.chat.completions.create( + model=DEFAULT_MODEL, + messages=messages, + stream=False, # Пока отключаем стриминг для простоты + ) + + # Обработка ответа + return {"response": response.choices[0].message.content} + + except Exception as e: + log(f"Error communicating with AI endpoint: {e}", level="error") + raise + + def _format_context(self, context: dict) -> str: + """ + Преобразует контекст в строку для передачи в OpenAI API. + """ + formatted_context = [] + for key, value in context.items(): + formatted_context.append(f"{key}: {value}") + return "\n".join(formatted_context) + + def analyze_project(self, prompt: str, project_structure: dict) -> dict: + """ + Анализирует структуру проекта и запрос пользователя через ИИ. + """ + context = {"project_structure": project_structure} + answer = self.send_request(prompt, context, ANALYZE_PROMPT) + + max_attempts = 10 + while "Достаточно информации" not in answer.get("response", "Достаточно информации"): + max_attempts -= 1 + if max_attempts < 0: + log(f"Error communicating with AI endpoint.", level="error") + raise Exception("Error communicating with AI endpoint.") + + for command in answer.get("response", "").split("\n"): + try: + command_type, details = command.split(":", 1) + if command_type.strip().lower() == "запрос содержимого файла": + project_structure['files_info'] = project_structure.get('files_info', {}) + try: + with open(details.strip(), "r", encoding="utf-8") as f: + content = f.read() + project_structure['files_info'][details.strip()] = content + except FileNotFoundError: + project_structure['files_info'][details.strip()] = "Файл не найден" + elif command_type.strip().lower() == "запрос содержимого функции": + raise NotImplementedError("Запрос содержимого функции не реализован") + elif command_type.strip().lower() == "уточнение запроса": + answer = self.request_additional_info(details.strip()) + project_structure['QA_info'] = project_structure.get('QA_info', {}) + project_structure['QA_info'][details.strip()] = answer + else: + raise ValueError(f"Unknown command type: {command_type}") + except Exception as e: + log(f"Error parsing command: {command}", level="error") + answer = self.send_request(prompt, context, ANALYZE_PROMPT) + + return answer + + + + def request_additional_info(self, message: str) -> str: + """ + Запрашивает дополнительную информацию у пользователя. + """ + log("Requesting additional information from user.") + return input(f"{message}\n> ") + + def get_modifications(self, prompt: str, project_structure: dict) -> dict: + """ + Создает модификации проекта на основе команды пользователя. + """ + context = {"project_structure": project_structure} + answer = self.send_request(prompt, context, COMMANDS_PROMPT) + + + return answer + + +ANALYZE_PROMPT = """ +Ты - эксперт по анализу структуры проектов. +Ты должен проанализировать структуру проекта и запрос пользователя. + +Если данных которые тебе передали достаточно - ответь "Достаточно информации". +Если данных недостаточно - запроси содержимое файла или функции которые тебе интересны и/или запроси у пользователя дополнительную информацию. +Если не получается найти файл или функцию - учитывай что его возможно нужно будет создать и ты можешь пропустить этот шаг и продолжить работу дальше. Пути должны соответствовать структуре проекта. + +ОТВЕЧАЙ ТОЛЬКО КОМАНДАМИ! + +### Запрос содержимого файла +Содержимое файла: <имя файла> +### Запрос содержимого функции +Содержимое функции: <имя файла> <имя функции> +### Запрос у пользователя +Уточнение запроса: <текст вопроса к пользователю> + +Например, вот ответ с двумя командами, первый просить содержимое файла, второй - уточнить что нужно создать.: +Содержимое файла: app/models/user.ts +Уточнение запроса: Нужно ли создать интерфейсы в types/users.ts? + + +НЕ ОТВЕЧАЙ НИЧЕМ ЧТО НЕ СООТВЕТСТВУЕТ ПРАВИЛАМ ВЫШЕ, ТОЛЬКО КОМАНДЫ! +ПОСТАРАЙСЯ В ОДНОМ ОТВЕТЕ НАПИСАТЬ ВСЕ НЕОБХОДИМЫЕ КОМАНДЫ! +""".strip() + + +COMMANDS_PROMPT = """ +Ты - senior developer. Ты должен прочитать запрос пользователя и на его основе сформировать список команд для выполнения этого запроса. +Ты должен ознакомиться с текущей структурой проекта и отдельными файлами и функциями. + +Итого ты должен сформировать список команд для выполнения этого запроса. +Команды в таком формате через перенос строки: +`FILE <Команда>` `<имя и путь к файлу>` + +`FUNCTION <Команда>` `<имя функции>` +```ts +<код функции> +``` + +Команды: CREATE, UPDATE, DELETE + +НЕ ОТВЕЧАЙ НИЧЕМ ЧТО НЕ СООТВЕТСТВУЕТ ПРАВИЛАМ ВЫШЕ, ТОЛЬКО КОМАНДЫ! +ПОСТАРАЙСЯ В ОДНОМ ОТВЕТЕ НАПИСАТЬ ВСЕ НЕОБХОДИМЫЕ КОМАНДЫ! +""".strip() \ No newline at end of file diff --git a/StaticHelper/config.py b/StaticHelper/config.py new file mode 100644 index 0000000..e9da3ac --- /dev/null +++ b/StaticHelper/config.py @@ -0,0 +1,4 @@ +REACT_PROJECT_PATH = r'C:\Users\Mikan\PycharmProjects\tisbiProFi\frontend\react-source' +OPENAI_API_KEY = "secret" # Замените на ваш API-ключ +OPENAI_BASE_URL = "http://localhost:51337/v1" # Базовый URL для OpenAI +DEFAULT_MODEL = "gpt-4o-mini" # Модель по умолчанию diff --git a/StaticHelper/source/docs.md b/StaticHelper/source/docs.md new file mode 100644 index 0000000..976ab7c --- /dev/null +++ b/StaticHelper/source/docs.md @@ -0,0 +1,241 @@ +# Требования к коду + +Используется библиотека `react-router-dom` и `react=markdown`. + +Все компоненты должны быть функциональными. +props должны быть сразу распакованы. +export без default + +Пример: + +```tsx +export function ProfessionPage() { + const {professionId} = useParams<{ professionId: string }>(); +} +``` + +Используйте современные практики React (например, хуки). +При создании новых компонентов добавьте комментарии. + +Если для задачи необходимы новые типы, интерфейсы или вспомогательные компоненты - создавай их в соответвующих файлах. + +# Документария + +## Существующие типы + +`types/api.ts` +```ts +export interface ApiResponse { + body?: object, + success: boolean +} +export interface PageResponse{ + count: number; + next?: string; + previous?: string; + results: T[] +} +``` + +`types/common.ts` +```ts +export interface UniqueItem{ + id: number; +} + +export interface PageControl{ + items: T[] | null; + hasNext: boolean; + hasPrevious: boolean; + nextPage(): void; + previousPage(): void; +} +``` +`types/survey.ts` +```ts +import {UniqueItem} from "./common.ts"; + +export interface Survey extends UniqueItem{ + title: string; + description: string; +} +interface Statement extends UniqueItem{ + text: string; + score_variable: number; +} +export enum QuestionType{ + QuestionPair, + AgreementQuestion +} +export interface Question extends UniqueItem{ + questionType: QuestionType +} +export interface QuestionPair extends Question{ + statements: Statement[] +} +export interface AgreementQuestion extends Question{ + question: string; + score_variable: number; +} +export interface ScoreVariable { + id: string; + name: string; + title: string; + description: string; + survey: number; +} +export interface Profession extends UniqueItem { + name: string; + description: string; +} +export interface Specialty extends UniqueItem { + name: string; + institution: number; + link?: string; +} +export interface Institution extends UniqueItem { + name: string; +} +``` +`types/users.ts` +```ts +import {UniqueItem} from "./common.ts"; + +export type UserContextType = { + user: Account | undefined; + login: (credentials: TokenObtainPair) => Promise; + logout: () => void; +}; +export interface ContactData{ + first_name: string; + last_name?: string; + middle_name?: string; + phone?: string; + email?:string; +} +interface Role extends UniqueItem{ + title: string; +} +export interface Account extends UniqueItem, ContactData{ + login: string; + role?: Role; + is_staff: boolean; + is_active: boolean; +} +export interface TokenObtainPair { + login: string; + password: string; +} +export interface TokenRefresh { + refresh: string; +} +export interface TokenVerify { + token: string; +} +export interface JwtTokenResponse { + access: string; + refresh: string; +} +``` + +## Способы запросов + +Вместо fetch запросы должны проходить при помощи специальных функций/функции + +`API/common.ts` +```ts +async function getFetch(path: string) // Get method +async function postFetch( + path: string, + body: object | FormData +) // Post method +async function patchFetch( + path: string, + body: object | FormData +) // Patch method +async function deleteFetch(path: string) // Delete method +``` + +## Хуки + +### Для загрузки данных + +Для упрощения работы с api созданы следующие хуки + +`API/hooks.ts` +```ts +function useDataPage(application:string, endpoint: string, params:{[key:string]:string|number}={}, needLoad:boolean=true):PageControl +function useData(application:string, endpoint: string, id: string|number, needLoad:boolean=true): T|null|undefined +function useCachedData(application: string, endpoint: string): (id: string | number, needLoad: boolean = true)=> T|undefined|null +``` +- application - Название модуля +- endpoint - Название объекта над которым идёт работа +- id - id искомого объекта +- params - дополнительные параметры (например фильтрация) +- needLoad - указывает нужно ли загружать список/объект прямо сейчас + + +- null - загрузка еще не происходила +- undefined - не найдено +- T (объект) + +### Для ленивой загрузки элементов + +`components/LoadingData.tsx` +```tsx +function LoadingData({data, children}: {data: object|null|undefined, children: (ReactElement | null | undefined | boolean)[]}) +``` + +Для загрузки отдельного элемента. +Если data +- null - Возвращает
Идёт загрузка...
+- undefined - 404 НЕ НАЙДЕНО +- иначе возвращает children + +`components/LoadingList.tsx` +```tsx +function LoadingList({data, listElement}: {data: UniqueItem[]|null, listElement(e:UniqueItem, i:number):unknown}) +``` + +Для загрузки списка. Если data не null - то возвращает элементы результаты `listElement` + +### Примеры + +```tsx +export function ProfessionPage() { + const { professionId } = useParams<{ professionId: string }>(); + const profession = useData('surveys', 'professions', professionId || 0); + const specialties = useDataPage('surveys', 'specialties', { profession: professionId || 0 }, !!profession); + + const institutions = useCachedData("surveys", "institutions"); + + return ( +
+ +

{profession?.name}

+
+ {profession?.description || ''} +
+
+ {specialties.items != null ?

Где можно обучиться

:null} + ( + + )} + /> +
+
+
+ ); +} +``` + + +### Авторизация + +Везде доступен хук `useUser():UserContextType | undefined ` \ No newline at end of file diff --git a/StaticHelper/source/header.md b/StaticHelper/source/header.md new file mode 100644 index 0000000..dba9390 --- /dev/null +++ b/StaticHelper/source/header.md @@ -0,0 +1,14 @@ +Ты Senior разработчик React. + +Твоя задача - писать код под задачи (отвечай ТОЛЬКО кодом). + +Я могу кидать тебе просто код или информацию. Если я не прошу писать код - отвечай только "Понял". +Код, по возможности, только тот который необходимо внести в проект. Может быть работа сразу над несколькими файлами + +Шаблон ответа кодом: + +`Путь и название файла` +```ts +// Код который должен быть в этом файле +``` + diff --git a/StaticHelper/source/old_input.md b/StaticHelper/source/old_input.md new file mode 100644 index 0000000..e69de29 diff --git a/StaticHelper/source/working_file.md b/StaticHelper/source/working_file.md new file mode 100644 index 0000000..c3c9c3d --- /dev/null +++ b/StaticHelper/source/working_file.md @@ -0,0 +1,95 @@ +`API/users.ts` +```ts + +export async function login(data: TokenObtainPair): Promise + +export async function refreshToken(data: TokenRefresh): Promise + +export async function verifyToken(data: TokenVerify): Promise +``` + +`utils/users/UserProvider.tsx` +```tsx +export function UserProvider({ children }: { children: ReactNode }) { + const [user, setUser] = useState(() => { + const storedUser = localStorage.getItem('user'); + return storedUser ? JSON.parse(storedUser) : undefined; + }); + const [tokens, setTokens] = useState(() => { + const storedTokens = localStorage.getItem('tokens'); + return storedTokens ? JSON.parse(storedTokens) : undefined; + }); + + useEffect(() => { + if (tokens?.access) { + localStorage.setItem('tokens', JSON.stringify(tokens)); + } else { + localStorage.removeItem('tokens'); + } + }, [tokens]); + + useEffect(() => { + if (user) { + localStorage.setItem('user', JSON.stringify(user)); + } else { + localStorage.removeItem('user'); + } + }, [user]); + + const handleLogin = async (credentials: TokenObtainPair) => { + const tokens = await apiLogin(credentials); + if (tokens) { + setTokens(tokens); + // Здесь можно добавить запрос для получения данных пользователя + setUser({ id: 1, login: credentials.login, is_staff: false, is_active: true } as Account); // TODO + } + }; + + const handleLogout = () => { + setUser(undefined); + setTokens(undefined); + }; + + const validateToken = async () => { + if (tokens?.access) { + const isValid = await verifyToken({ token: tokens.access }); + if (!isValid && tokens.refresh) { + const newTokens = await refreshToken({ refresh: tokens.refresh }); + if (newTokens) { + setTokens(newTokens); + } else { + handleLogout(); + } + } else if (!isValid) { + handleLogout(); + } + } + }; + + useEffect(() => { + validateToken().then(); + }, []); + + return ( + + {children} + + ); +} +``` + + +В частности обрати внимание на эту функцию +```ts +const handleLogin = async (credentials: TokenObtainPair) => { + const tokens = await apiLogin(credentials); + if (tokens) { + setTokens(tokens); + // Здесь можно добавить запрос для получения данных пользователя + setUser({ id: 1, login: credentials.login, is_staff: false, is_active: true } as Account); // TODO + } +}; +``` + +Воспользуйся эндпоинтами /users/auth/users/me/ для получения id текущего пользователя + diff --git a/StaticHelper/utils/file_utils.py b/StaticHelper/utils/file_utils.py new file mode 100644 index 0000000..f680898 --- /dev/null +++ b/StaticHelper/utils/file_utils.py @@ -0,0 +1,21 @@ +import os + +def read_file(file_path: str) -> str: + """ + Читает содержимое файла. + """ + with open(file_path, "r", encoding="utf-8") as f: + return f.read() + +def write_file(file_path: str, content: str): + """ + Записывает содержимое в файл. + """ + with open(file_path, "w", encoding="utf-8") as f: + f.write(content) + +def create_directory(path: str): + """ + Создает директорию, если она не существует. + """ + os.makedirs(path, exist_ok=True) \ No newline at end of file diff --git a/StaticHelper/utils/logger.py b/StaticHelper/utils/logger.py new file mode 100644 index 0000000..36ff2cd --- /dev/null +++ b/StaticHelper/utils/logger.py @@ -0,0 +1,13 @@ +import sys + +def log(message: str, level: str = "info"): + """ + Логирует сообщения в консоль. + """ + levels = { + "info": "[INFO]", + "warning": "[WARNING]", + "error": "[ERROR]", + } + prefix = levels.get(level, "[UNKNOWN]") + print(f"{prefix} {message}", file=sys.stderr if level == "error" else sys.stdout) \ No newline at end of file diff --git a/StaticHelper/utils/prompt_utils.py b/StaticHelper/utils/prompt_utils.py new file mode 100644 index 0000000..e69de29