브라우저 컨트롤러 및 UI 개선: ED 모드에 따른 이미지 프로세서 초기화 로직을 추가하고, 썸네일 및 상세페이지 수정 처리 로직을 개선하여 사용자 경험을 향상시켰습니다. 이미지 태그 제거 기능을 추가하여 등록모드에서의 텍스트 보존을 지원하며, 관련 로그 메시지를 개선하여 디버깅 용이성을 높였습니다. 데이터베이스 파일이 업데이트되었습니다.
This commit is contained in:
parent
20d3aace50
commit
5d64b4f4e2
|
|
@ -0,0 +1,380 @@
|
|||
; AutoPercenty3 Inno Setup Script
|
||||
; 이 스크립트는 cx_Freeze로 빌드된 결과물이 있는 "build\exe.win-amd64-3.11" 폴더를 기반으로 인스톨러를 제작합니다.
|
||||
; 20251106_104442에 생성됨
|
||||
|
||||
#define AppId "autopercenty"
|
||||
#define MyAppName "Edit_PartTimer"
|
||||
#define MyAppVersion "3.12.10"
|
||||
#define MyAppPublisher "WhenRideMyCar"
|
||||
#define MyAppProgramName "편집알바생"
|
||||
#define MyAppDescription "편집알바생"
|
||||
#define MyAppCopyright "Copyright 2024"
|
||||
#define MyAppExeName "Edit_PartTimer3"
|
||||
#define MySetupName "Edit_PartTimer Setup"
|
||||
#define MySetupIcon "src/Edit_PartTimer3.ico"
|
||||
#define MySetupOutputDir "dist/installer"
|
||||
|
||||
[Setup]
|
||||
; 기본 설정
|
||||
AppId={#AppId}
|
||||
AppName={#MyAppProgramName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
DefaultDirName={autopf}\{#MyAppName}
|
||||
DefaultGroupName={#MyAppPublisher}
|
||||
OutputDir={#MySetupOutputDir}
|
||||
OutputBaseFilename={#MySetupName}
|
||||
SetupIconFile={#MySetupIcon}
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
|
||||
; 업데이트 관련 설정 - 권한 최적화
|
||||
PrivilegesRequired=admin
|
||||
PrivilegesRequiredOverridesAllowed=dialog
|
||||
UpdateUninstallLogAppName=yes
|
||||
AppMutex={#MyAppName}
|
||||
CloseApplications=yes
|
||||
RestartApplications=no
|
||||
|
||||
; 보안 및 호환성 설정
|
||||
ArchitecturesAllowed=x64
|
||||
ArchitecturesInstallIn64BitMode=x64
|
||||
AllowNoIcons=yes
|
||||
|
||||
; 버전 정보
|
||||
VersionInfoVersion={#MyAppVersion}
|
||||
VersionInfoCompany={#MyAppPublisher}
|
||||
VersionInfoDescription={#MyAppDescription}
|
||||
VersionInfoCopyright={#MyAppCopyright}
|
||||
VersionInfoProductName={#MyAppProgramName}
|
||||
VersionInfoProductVersion={#MyAppVersion}
|
||||
|
||||
[Languages]
|
||||
Name: "korean"; MessagesFile: "compiler:Languages\Korean.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"
|
||||
|
||||
[Dirs]
|
||||
; 설치 시 {app}\logs 폴더를 생성하고,
|
||||
; Users 그룹에 'modify' 권한(=쓰기 가능)을 부여
|
||||
Name: "{app}\logs"; Permissions: users-modify
|
||||
; 설치 시 {app}\user_data 폴더를 생성하고,
|
||||
; Users 그룹에 'modify' 권한(=쓰기 가능)을 부여
|
||||
Name: "{app}\user_data"; Permissions: users-modify
|
||||
; Playwright 브라우저 폴더를 Program Files 내부에 생성
|
||||
Name: "{app}\lib\src\browsers\chromium-1155"; Permissions: users-modify
|
||||
; Playwright 브라우저 사용자폴더를 Program Files 내부에 생성
|
||||
Name: "{app}\lib\src\browsers\user_data"; Permissions: users-modify
|
||||
|
||||
[Files]
|
||||
; 프로그램 파일만 설치 (항상 덮어쓰기)
|
||||
Source: "build\exe.win-amd64-3.11\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
; updater.exe 파일 포함 (업데이트 관리자)
|
||||
Source: "updater.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
; VC++ 재배포 패키지 파일을 임시 폴더({tmp})에 복사
|
||||
Source: "VC_redist.x64.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall
|
||||
|
||||
[Registry]
|
||||
; Playwright 브라우저 경로를 Program Files 내부로 설정
|
||||
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "PLAYWRIGHT_BROWSERS_PATH"; ValueData: "{app}\lib\src\browsers"; Flags: preservestringtype
|
||||
|
||||
[Icons]
|
||||
; 시작 메뉴 바로가기
|
||||
Name: "{group}\{#MyAppProgramName}"; Filename: "{app}\{#MyAppExeName}.exe"
|
||||
; 바탕화면 바로가기
|
||||
Name: "{autodesktop}\{#MyAppProgramName}"; Filename: "{app}\{#MyAppExeName}.exe"; Tasks: desktopicon
|
||||
; 프로그램 제거 바로가기
|
||||
Name: "{group}\{#MyAppProgramName} 제거"; Filename: "{uninstallexe}"
|
||||
|
||||
[Run]
|
||||
; VC++ 재배포 패키지 설치 (필요할 경우)
|
||||
Filename: "{tmp}\VC_redist.x64.exe"; Parameters: "/install /passive /norestart"; StatusMsg: "VC++ 재배포 패키지 설치 중..."; Check: NeedsVCredist
|
||||
; 설치 후 프로그램 실행 (원할 경우)
|
||||
Filename: "{app}\{#MyAppExeName}.exe"; Description: "{cm:LaunchProgram,{#MyAppProgramName}}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[Code]
|
||||
function CompareVersion(V1, V2: string): Integer;
|
||||
var
|
||||
P1, P2, N1, N2: Integer;
|
||||
begin
|
||||
P1 := 1;
|
||||
P2 := 1;
|
||||
Result := 0;
|
||||
while (Result = 0) and ((P1 <= Length(V1)) or (P2 <= Length(V2))) do begin
|
||||
while (P1 <= Length(V1)) and (V1[P1] = '.') do Inc(P1);
|
||||
while (P2 <= Length(V2)) and (V2[P2] = '.') do Inc(P2);
|
||||
if (P1 <= Length(V1)) and (P2 <= Length(V2)) then begin
|
||||
N1 := 0; while (P1 <= Length(V1)) and (V1[P1] >= '0') and (V1[P1] <= '9') do begin N1 := N1 * 10 + Ord(V1[P1]) - Ord('0'); Inc(P1); end;
|
||||
N2 := 0; while (P2 <= Length(V2)) and (V2[P2] >= '0') and (V2[P2] <= '9') do begin N2 := N2 * 10 + Ord(V2[P2]) - Ord('0'); Inc(P2); end;
|
||||
if N1 < N2 then Result := -1 else if N1 > N2 then Result := 1;
|
||||
end else begin
|
||||
if P1 <= Length(V1) then Result := 1 else if P2 <= Length(V2) then Result := -1;
|
||||
end;
|
||||
while (P1 <= Length(V1)) and (V1[P1] <> '.') do Inc(P1);
|
||||
while (P2 <= Length(V2)) and (V2[P2] <> '.') do Inc(P2);
|
||||
end;
|
||||
end;
|
||||
|
||||
// 파일 또는 폴더 복사 함수
|
||||
procedure CopyDir(const SourcePath, DestPath: string);
|
||||
var
|
||||
FindRec: TFindRec;
|
||||
SourceFilePath: string;
|
||||
DestFilePath: string;
|
||||
begin
|
||||
ForceDirectories(DestPath);
|
||||
|
||||
if FindFirst(SourcePath + '\*', FindRec) then
|
||||
begin
|
||||
try
|
||||
repeat
|
||||
if (FindRec.Name <> '.') and (FindRec.Name <> '..') then
|
||||
begin
|
||||
SourceFilePath := SourcePath + '\' + FindRec.Name;
|
||||
DestFilePath := DestPath + '\' + FindRec.Name;
|
||||
|
||||
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0 then
|
||||
begin
|
||||
if FileCopy(SourceFilePath, DestFilePath, False) then
|
||||
Log('파일 복사 성공: ' + SourceFilePath + ' -> ' + DestFilePath)
|
||||
else
|
||||
Log('파일 복사 실패: ' + SourceFilePath);
|
||||
end
|
||||
else
|
||||
CopyDir(SourceFilePath, DestFilePath);
|
||||
end;
|
||||
until not FindNext(FindRec);
|
||||
finally
|
||||
FindClose(FindRec);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
// 디렉토리 삭제 함수
|
||||
procedure DeleteDir(const DirPath: string);
|
||||
var
|
||||
FindRec: TFindRec;
|
||||
FilePath: string;
|
||||
begin
|
||||
if not DirExists(DirPath) then Exit;
|
||||
|
||||
if FindFirst(DirPath + '\*', FindRec) then
|
||||
begin
|
||||
try
|
||||
repeat
|
||||
if (FindRec.Name <> '.') and (FindRec.Name <> '..') then
|
||||
begin
|
||||
FilePath := DirPath + '\' + FindRec.Name;
|
||||
|
||||
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0 then
|
||||
begin
|
||||
if DeleteFile(FilePath) then
|
||||
Log('파일 삭제 성공: ' + FilePath)
|
||||
else
|
||||
Log('파일 삭제 실패: ' + FilePath);
|
||||
end
|
||||
else
|
||||
DeleteDir(FilePath);
|
||||
end;
|
||||
until not FindNext(FindRec);
|
||||
finally
|
||||
FindClose(FindRec);
|
||||
end;
|
||||
end;
|
||||
|
||||
if RemoveDir(DirPath) then
|
||||
Log('디렉토리 삭제 성공: ' + DirPath)
|
||||
else
|
||||
Log('디렉토리 삭제 실패: ' + DirPath);
|
||||
end;
|
||||
|
||||
// 프로그램 실행 여부 확인
|
||||
function IsAppRunning(const FileName: string): Boolean;
|
||||
var
|
||||
Handle: THandle;
|
||||
begin
|
||||
Handle := FindWindowByWindowName('{#MyAppProgramName}'); // 프로그램의 윈도우 타이틀로 찾기
|
||||
Result := (Handle <> 0);
|
||||
end;
|
||||
|
||||
// 프로그램 종료
|
||||
procedure CloseApplication(const FileName: string);
|
||||
var
|
||||
Handle: THandle;
|
||||
begin
|
||||
Handle := FindWindowByWindowName('{#MyAppProgramName}');
|
||||
if Handle <> 0 then
|
||||
begin
|
||||
PostMessage(Handle, 18, 0, 0); // WM_QUIT
|
||||
Sleep(1000); // 종료 대기
|
||||
end;
|
||||
end;
|
||||
|
||||
// VC++ 재배포 패키지 필요 여부 확인
|
||||
function NeedsVCredist: Boolean;
|
||||
begin
|
||||
if RegKeyExists(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64') then
|
||||
Result := False // 이미 설치됨
|
||||
else
|
||||
Result := True; // 미설치 -> 설치 필요
|
||||
end;
|
||||
|
||||
// 설치 완료 후 실행 여부 확인
|
||||
function InitializeFinish(): Boolean;
|
||||
var
|
||||
ResultCode: Integer;
|
||||
begin
|
||||
Result := True;
|
||||
if MsgBox('설치가 완료되었습니다. 프로그램을 실행하시겠습니까?' + #13#10 +
|
||||
'(실행 시 서버와 동기화하여 설정이 업데이트됩니다)',
|
||||
mbConfirmation, MB_YESNO) = IDYES then
|
||||
begin
|
||||
Exec(ExpandConstant('{app}\{#MyAppExeName}.exe'), '', '', SW_SHOW, ewNoWait, ResultCode);
|
||||
end;
|
||||
end;
|
||||
|
||||
function InitializeSetup(): Boolean;
|
||||
var
|
||||
OldVersion: String;
|
||||
NewVersion: String;
|
||||
OldAppPath: String;
|
||||
UserDataSourcePath, UserDataBackupPath: String;
|
||||
LibSrcPath: String;
|
||||
ResultCode: Integer;
|
||||
begin
|
||||
Result := True;
|
||||
NewVersion := '{#MyAppVersion}';
|
||||
UserDataBackupPath := ExpandConstant('{tmp}\user_data_backup');
|
||||
|
||||
// 현재 프로그램 버전 확인
|
||||
if RegQueryStringValue(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#MyAppName}_is1',
|
||||
'DisplayVersion', OldVersion) then
|
||||
begin
|
||||
// 같은 버전이거나 더 높은 버전이 설치되어 있는 경우
|
||||
if CompareVersion(OldVersion, NewVersion) >= 0 then
|
||||
begin
|
||||
MsgBox('현재 설치된 버전(' + OldVersion + ')이 이 설치 프로그램의 버전(' +
|
||||
NewVersion + ')과 같거나 더 높습니다.' + #13#10 +
|
||||
'설치를 계속할 수 없습니다.', mbInformation, MB_OK);
|
||||
Result := False;
|
||||
exit;
|
||||
end;
|
||||
|
||||
// 이전 버전이 설치되어 있는 경우 업데이트 진행
|
||||
if CompareVersion(OldVersion, NewVersion) < 0 then
|
||||
begin
|
||||
Log('업데이트 설치 진행: ' + OldVersion + ' -> ' + NewVersion);
|
||||
|
||||
// 프로그램이 실행 중인지 확인하고 종료 요청
|
||||
if IsAppRunning('{#MyAppExeName}.exe') then
|
||||
begin
|
||||
if MsgBox('프로그램을 업데이트하기 위해 실행 중인 프로그램을 종료해야 합니다.' + #13#10 +
|
||||
'계속하시겠습니까?', mbConfirmation, MB_YESNO) = IDNO then
|
||||
begin
|
||||
Result := False;
|
||||
exit;
|
||||
end;
|
||||
CloseApplication('{#MyAppExeName}.exe');
|
||||
Sleep(2000); // 프로세스 종료 대기
|
||||
|
||||
// 프로세스가 여전히 실행 중이면 강제 종료
|
||||
Log('프로세스 강제 종료 시도: {#MyAppExeName}.exe');
|
||||
Exec('taskkill', '/f /im {#MyAppExeName}.exe /t', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||
Sleep(3000); // 강제 종료 후 추가 대기
|
||||
end;
|
||||
|
||||
// 레지스트리에서 설치 경로 확인
|
||||
if RegQueryStringValue(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#MyAppName}_is1',
|
||||
'InstallLocation', OldAppPath) then
|
||||
begin
|
||||
Log('기존 설치 경로: ' + OldAppPath);
|
||||
|
||||
// lib/src/user_data 폴더 백업
|
||||
UserDataSourcePath := OldAppPath + '\lib\src\user_data';
|
||||
if DirExists(UserDataSourcePath) then
|
||||
begin
|
||||
Log('사용자 데이터 백업 중: ' + UserDataSourcePath + ' -> ' + UserDataBackupPath);
|
||||
ForceDirectories(UserDataBackupPath);
|
||||
CopyDir(UserDataSourcePath, UserDataBackupPath);
|
||||
end
|
||||
else
|
||||
begin
|
||||
Log('사용자 데이터 폴더가 존재하지 않음: ' + UserDataSourcePath);
|
||||
end;
|
||||
|
||||
// 기존 설치 폴더 완전 삭제 (사용자 데이터는 이미 백업됨)
|
||||
if DirExists(OldAppPath) then
|
||||
begin
|
||||
Log('기존 설치 폴더 삭제 중: ' + OldAppPath);
|
||||
|
||||
// 1차 시도: Inno Setup 내장 함수
|
||||
DeleteDir(OldAppPath);
|
||||
|
||||
// 삭제 확인 및 재시도
|
||||
Sleep(2000); // 2초 대기
|
||||
if DirExists(OldAppPath) then
|
||||
begin
|
||||
Log('설치 폴더 삭제 재시도 (Windows 명령어 사용): ' + OldAppPath);
|
||||
// Windows rmdir 명령어로 강제 삭제 시도
|
||||
Exec('cmd.exe', '/c rmdir /s /q "' + OldAppPath + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||
Sleep(2000); // 추가 대기
|
||||
end;
|
||||
|
||||
// 3차 시도: PowerShell로 강제 삭제
|
||||
if DirExists(OldAppPath) then
|
||||
begin
|
||||
Log('설치 폴더 삭제 3차 시도 (PowerShell 사용): ' + OldAppPath);
|
||||
Exec('powershell.exe', '-Command "Remove-Item -Path ''' + OldAppPath + ''' -Recurse -Force -ErrorAction SilentlyContinue"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||
Sleep(2000);
|
||||
end;
|
||||
|
||||
// 여전히 존재하면 사용자에게 알림
|
||||
if DirExists(OldAppPath) then
|
||||
begin
|
||||
Log('경고: 설치 폴더 삭제 실패 - 일부 파일이 사용 중이거나 권한 문제일 수 있음: ' + OldAppPath);
|
||||
if MsgBox('기존 설치 폴더를 완전히 삭제하지 못했습니다.' + #13#10 +
|
||||
'이전 버전의 파일들이 남아있어 새 버전과 충돌할 수 있습니다.' + #13#10 + #13#10 +
|
||||
'폴더: ' + OldAppPath + #13#10 + #13#10 +
|
||||
'설치를 계속하시겠습니까?' + #13#10 +
|
||||
'(아니오를 선택하면 수동으로 폴더를 삭제한 후 다시 설치하세요)',
|
||||
mbConfirmation, MB_YESNO) = IDNO then
|
||||
begin
|
||||
Result := False;
|
||||
exit;
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
Log('기존 설치 폴더 삭제 완료: ' + OldAppPath);
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
Log('삭제할 설치 폴더가 존재하지 않음: ' + OldAppPath);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
var
|
||||
UserDataBackupPath, UserDataDestPath: String;
|
||||
begin
|
||||
// 설치 완료 후
|
||||
if CurStep = ssPostInstall then
|
||||
begin
|
||||
UserDataBackupPath := ExpandConstant('{tmp}\user_data_backup');
|
||||
UserDataDestPath := ExpandConstant('{app}\lib\src\user_data');
|
||||
|
||||
// 백업한 사용자 데이터 폴더가 있으면 복원
|
||||
if DirExists(UserDataBackupPath) then
|
||||
begin
|
||||
Log('사용자 데이터 복원 중: ' + UserDataBackupPath + ' -> ' + UserDataDestPath);
|
||||
ForceDirectories(UserDataDestPath);
|
||||
CopyDir(UserDataBackupPath, UserDataDestPath);
|
||||
Log('사용자 데이터 복원 완료');
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
Binary file not shown.
|
|
@ -550,14 +550,27 @@ class BrowserController(QThread):
|
|||
|
||||
if logger:
|
||||
logger.log(f"{extension_id} 확장프로그램의 file:// 접근 권한을 자동 허용으로 설정했습니다.", level=20)
|
||||
|
||||
|
||||
async def start_browser_async(self):
|
||||
"""비동기 Playwright 초기화 및 로그인 수행"""
|
||||
try:
|
||||
if not self.toggle_states.get('ed_mode', False):
|
||||
self.logger.log(f"일반모드로 이미지 프로세서 초기화 중...", level=logging.DEBUG)
|
||||
# ED_MODE에서도 이미지 번역이 필요한 경우 이미지 프로세서 초기화
|
||||
is_ed_mode = self.toggle_states.get('ed_mode', False)
|
||||
detail_IMGTrans = self.toggle_states.get('detail_IMGTrans', False)
|
||||
optionIMGTrans = self.toggle_states.get('optionIMGTrans', False)
|
||||
thumb = self.toggle_states.get('thumb', False)
|
||||
|
||||
# 일반 모드이거나, ED_MODE에서 이미지 번역이 필요한 경우 초기화
|
||||
needs_image_processor = not is_ed_mode or detail_IMGTrans or optionIMGTrans or thumb
|
||||
|
||||
if needs_image_processor:
|
||||
if is_ed_mode:
|
||||
self.logger.log(f"ED_MODE에서 이미지 번역이 필요하여 이미지 프로세서 초기화 중...", level=logging.DEBUG)
|
||||
else:
|
||||
self.logger.log(f"일반모드로 이미지 프로세서 초기화 중...", level=logging.DEBUG)
|
||||
self.is_image_processor_init = self.init_image_processor()
|
||||
|
||||
|
||||
if not self.is_image_processor_init:
|
||||
self.logger.log(f"이미지 프로세서를 사용하지 않습니다.", level=logging.INFO)
|
||||
self.image_processor_error.emit("이미지 프로세서 초기화 오류로 이미지번역이 실행되지 않습니다.")
|
||||
|
|
@ -581,7 +594,7 @@ class BrowserController(QThread):
|
|||
thumb_status = self.toggle_states.get('thumb', False)
|
||||
debug_mode = self.toggle_states.get('debug_mode', False)
|
||||
|
||||
id_ed_mode = self.toggle_states.get('ed_mode', False)
|
||||
id_ed_mode = is_ed_mode
|
||||
if id_ed_mode:
|
||||
debug_mode = True
|
||||
self.logger.log(f"id_ed_mode: {id_ed_mode}", level=logging.DEBUG)
|
||||
|
|
@ -4925,6 +4938,14 @@ class BrowserController(QThread):
|
|||
|
||||
|
||||
self.gpt_client.update_gpt_model(self.toggle_states['gpt_model'])
|
||||
|
||||
# 이미지워커의 toggle_states 업데이트
|
||||
if hasattr(self, 'image_processor') and self.image_processor:
|
||||
try:
|
||||
await self.image_processor.update_toggle_states(self.toggle_states)
|
||||
self.logger.log("이미지워커 toggle_states 업데이트 완료", level=logging.DEBUG)
|
||||
except Exception as e:
|
||||
self.logger.log(f"이미지워커 toggle_states 업데이트 중 오류: {e}", level=logging.WARNING)
|
||||
|
||||
# if not is_ed_mode:
|
||||
# # 서버 URL 상태 체크 및 필요시 업데이트
|
||||
|
|
@ -5133,9 +5154,20 @@ class BrowserController(QThread):
|
|||
self.start_stage_signal.emit(3)
|
||||
thumb = self.toggle_states.get('thumb')
|
||||
thumb_nukki = self.toggle_states.get('thumb_nukki')
|
||||
self.logger.log(f"썸네일수정 : {thumb} ", level=logging.DEBUG)
|
||||
thumb_represent = self.toggle_states.get('thumb_represent', False)
|
||||
|
||||
# ED_MODE에서는 thumb_represent가 True일 때만, 일반 모드에서는 thumb 또는 thumb_nukki가 True일 때 실행
|
||||
should_process_thumb = False
|
||||
if is_ed_mode:
|
||||
should_process_thumb = thumb_represent
|
||||
if should_process_thumb:
|
||||
self.logger.log(f"썸네일수정 (ED_MODE): thumb_represent={thumb_represent}", level=logging.DEBUG)
|
||||
else:
|
||||
should_process_thumb = thumb or thumb_nukki
|
||||
if should_process_thumb:
|
||||
self.logger.log(f"썸네일수정 : thumb={thumb}, thumb_nukki={thumb_nukki}", level=logging.DEBUG)
|
||||
|
||||
if thumb or thumb_nukki:
|
||||
if should_process_thumb:
|
||||
await self.random_human_behavior(self.page)
|
||||
self.check_pause() # 일시중지 상태 확인
|
||||
thumb_result = await self.edit_thumb(title_infos)
|
||||
|
|
@ -5169,10 +5201,22 @@ class BrowserController(QThread):
|
|||
detail_IMGTrans = self.toggle_states.get('detail_IMGTrans')
|
||||
detail_IMGTrans_type = self.toggle_states.get('detail_IMGTrans_type')
|
||||
|
||||
# ED_MODE에서는 detail_IMGTrans만 허용 (detail_Option은 제외)
|
||||
# 일반 모드에서는 detail_Option과 detail_IMGTrans 모두 허용
|
||||
should_process_detail = False
|
||||
if is_ed_mode:
|
||||
# ED_MODE: 상세페이지 이미지 번역만 허용
|
||||
should_process_detail = detail_IMGTrans
|
||||
else:
|
||||
# 일반 모드: 옵션과 이미지 번역 모두 허용
|
||||
should_process_detail = detail_Option or detail_IMGTrans
|
||||
|
||||
if (detail_Option or detail_IMGTrans) and not is_ed_mode:
|
||||
if should_process_detail:
|
||||
self.check_pause() # 일시중지 상태 확인
|
||||
self.logger.log(f"상세페이지 수정 : {detail_Option} + {detail_IMGTrans} + {detail_IMGTrans_type}", level=logging.DEBUG)
|
||||
if is_ed_mode:
|
||||
self.logger.log(f"상세페이지 수정 (ED_MODE): detail_IMGTrans={detail_IMGTrans}, type={detail_IMGTrans_type}", level=logging.DEBUG)
|
||||
else:
|
||||
self.logger.log(f"상세페이지 수정 : {detail_Option} + {detail_IMGTrans} + {detail_IMGTrans_type}", level=logging.DEBUG)
|
||||
|
||||
# 상세페이지 수정
|
||||
await self.random_human_behavior(self.page)
|
||||
|
|
|
|||
164
mainUI_SP.py
164
mainUI_SP.py
|
|
@ -1484,6 +1484,7 @@ class MAIN_GUI(QMainWindow):
|
|||
'tag_by_product_name': True,
|
||||
'delete_all_tags': False,
|
||||
'thumb': False,
|
||||
'thumb_represent': False,
|
||||
'thumb_trans_type': None,
|
||||
'thumb_nukki': False,
|
||||
'remove_background_white': True,
|
||||
|
|
@ -2060,7 +2061,14 @@ class MAIN_GUI(QMainWindow):
|
|||
selected_value = "alphabetic_upper" # 기본값
|
||||
self.logger.log(f"[옵션 넘버링 Debug] currentData가 비어있음, 기본값 사용: {selected_value}", level=logging.WARNING)
|
||||
else:
|
||||
selected_value = selected_text
|
||||
# type: data 인 경우 currentData() 사용
|
||||
config_type = config.get("type", "text")
|
||||
if config_type == "data":
|
||||
selected_value = widget.currentData()
|
||||
if selected_value is None:
|
||||
selected_value = selected_text
|
||||
else:
|
||||
selected_value = selected_text
|
||||
|
||||
# 설정 및 상태 저장
|
||||
self.settings_manager.save_value(settings_key, selected_value)
|
||||
|
|
@ -2071,11 +2079,9 @@ class MAIN_GUI(QMainWindow):
|
|||
level=logging.DEBUG
|
||||
)
|
||||
|
||||
# 의존성 있는 위젯 활성화/비활성화 처리 - GPT 모델의 경우 실제 데이터 값 전달
|
||||
if widget_name == "gpt_model":
|
||||
self.handle_toggle_state(widget, selected_value)
|
||||
else:
|
||||
self.handle_toggle_state(widget, selected_text)
|
||||
# 의존성 있는 위젯 활성화/비활성화 처리 & 중복 저장(handle_toggle_state 내부)
|
||||
# 모든 위젯에 대해 올바른 selected_value(데이터값) 전달
|
||||
self.handle_toggle_state(widget, selected_value)
|
||||
|
||||
|
||||
def handle_toggle_state(self, widget, value=None):
|
||||
|
|
@ -2585,31 +2591,48 @@ class MAIN_GUI(QMainWindow):
|
|||
def _load_toggle_states_from_settings(self):
|
||||
"""SettingsManager에서 저장된 설정을 toggle_states에 로드합니다."""
|
||||
try:
|
||||
for key in self.toggle_states.keys():
|
||||
# bool 타입 토글들
|
||||
if isinstance(self.toggle_states[key], bool):
|
||||
saved_value = self.settings_manager.get_value(f"toggle/{key}")
|
||||
if saved_value is not None:
|
||||
# QSettings에서 불러온 값을 bool로 변환
|
||||
if isinstance(saved_value, str):
|
||||
self.toggle_states[key] = saved_value.lower() in ['true', '1', 'yes']
|
||||
else:
|
||||
self.toggle_states[key] = bool(saved_value)
|
||||
# 위젯맵을 통해 각 위젯의 저장된 값을 toggle_states에 로드
|
||||
for widget_name, config in self.widget_map.items():
|
||||
state_key = config.get("state_key")
|
||||
if not state_key:
|
||||
continue # state_key가 없는 위젯은 처리하지 않음
|
||||
|
||||
# 문자열 타입들
|
||||
elif isinstance(self.toggle_states[key], str):
|
||||
saved_value = self.settings_manager.get_value(f"input/{key}")
|
||||
if saved_value is not None:
|
||||
self.toggle_states[key] = str(saved_value)
|
||||
# state_key가 toggle_states에 없는 경우 스킵
|
||||
if state_key not in self.toggle_states:
|
||||
continue
|
||||
|
||||
# 숫자 타입들
|
||||
elif isinstance(self.toggle_states[key], (int, float)):
|
||||
saved_value = self.settings_manager.get_value(f"spinbox/{key}")
|
||||
if saved_value is not None:
|
||||
if isinstance(self.toggle_states[key], int):
|
||||
self.toggle_states[key] = int(saved_value)
|
||||
else:
|
||||
self.toggle_states[key] = float(saved_value)
|
||||
# 위젯맵의 key를 사용하여 저장된 값 가져오기
|
||||
settings_key = config.get("key")
|
||||
if not settings_key:
|
||||
continue
|
||||
|
||||
saved_value = self.settings_manager.get_value(settings_key)
|
||||
if saved_value is None:
|
||||
continue # 저장된 값이 없으면 스킵
|
||||
|
||||
# 타입에 맞게 변환하여 toggle_states 업데이트
|
||||
current_value = self.toggle_states[state_key]
|
||||
|
||||
if isinstance(current_value, bool):
|
||||
# bool 타입 변환
|
||||
if isinstance(saved_value, str):
|
||||
self.toggle_states[state_key] = saved_value.lower() in ['true', '1', 'yes']
|
||||
else:
|
||||
self.toggle_states[state_key] = bool(saved_value)
|
||||
elif isinstance(current_value, str):
|
||||
# 문자열 타입
|
||||
self.toggle_states[state_key] = str(saved_value)
|
||||
elif isinstance(current_value, (int, float)):
|
||||
# 숫자 타입
|
||||
if isinstance(current_value, int):
|
||||
self.toggle_states[state_key] = int(saved_value)
|
||||
else:
|
||||
self.toggle_states[state_key] = float(saved_value)
|
||||
|
||||
self.logger.log(
|
||||
f"[toggle_states 로드] {widget_name} (state_key: {state_key}): {self.toggle_states[state_key]}",
|
||||
level=logging.DEBUG
|
||||
)
|
||||
|
||||
self.logger.log("[SettingsManager] toggle_states 업데이트 완료", level=logging.DEBUG)
|
||||
|
||||
|
|
@ -3448,7 +3471,9 @@ class MAIN_GUI(QMainWindow):
|
|||
# 기본 경로 사용 (내부에서 bizinfo.db 생성)
|
||||
self.biz_dbManager = BizDBManager()
|
||||
|
||||
dlg = BizDialog(biz_db_manager=self.biz_dbManager, max_biz_count=3, parent=self, browser_controller=self.browser_controller)
|
||||
self.max_biz_count = 20
|
||||
|
||||
dlg = BizDialog(biz_db_manager=self.biz_dbManager, max_biz_count=self.max_biz_count, parent=self, browser_controller=self.browser_controller)
|
||||
dlg.exec()
|
||||
|
||||
# 버튼 라벨 갱신 (선택마켓 탭 사용으로 이름 표시는 유지)
|
||||
|
|
@ -4299,7 +4324,7 @@ class MAIN_GUI(QMainWindow):
|
|||
show_etc = False
|
||||
show_upload_conditions = True # 등록모드에서는 항상 표시
|
||||
if level == 'vip':
|
||||
show_name, show_option, show_thumb, show_price, show_detail = True, False, True, True, False
|
||||
show_name, show_option, show_thumb, show_price, show_detail = True, False, True, True, True
|
||||
elif level == 'premium':
|
||||
show_name, show_option, show_thumb, show_price, show_detail = True, False, False, True, False
|
||||
else: # 기타 미분류는 basic로 취급
|
||||
|
|
@ -4581,22 +4606,35 @@ class MAIN_GUI(QMainWindow):
|
|||
self.logger.log(f"_hide_widgets_in_layout_by_names 오류: {e}", level=logging.WARNING)
|
||||
|
||||
def _update_vip_detail_edit_visibility(self):
|
||||
"""VIP 상세페이지 수정 토글의 가시성을 업데이트합니다 (등록모드 + VIP 사용자만)"""
|
||||
"""VIP 상세페이지 관련 토글의 가시성을 업데이트합니다 (등록모드 + VIP 사용자만)"""
|
||||
try:
|
||||
if hasattr(self, 'vip_detail_edit_widget'):
|
||||
# 등록모드이고 VIP 사용자인 경우에만 표시
|
||||
is_vip = (self.user_membership_level or '').lower() == 'vip'
|
||||
show_vip_toggle = self.is_register_product_mode and is_vip
|
||||
# 등록모드이고 VIP 사용자인 경우에만 표시
|
||||
is_vip = (self.user_membership_level or '').lower() == 'vip'
|
||||
show_vip_toggle = self.is_register_product_mode and is_vip
|
||||
|
||||
# # VIP 상세페이지 수정 토글
|
||||
# if hasattr(self, 'vip_detail_edit_widget'):
|
||||
# self.vip_detail_edit_widget.setVisible(show_vip_toggle)
|
||||
|
||||
self.vip_detail_edit_widget.setVisible(show_vip_toggle)
|
||||
# if show_vip_toggle:
|
||||
# self.logger.log("VIP 상세페이지 수정 토글 표시 (등록모드 + VIP 사용자)", level=logging.DEBUG)
|
||||
# else:
|
||||
# if not self.is_register_product_mode:
|
||||
# self.logger.log("VIP 상세페이지 수정 토글 숨김 (기본모드)", level=logging.DEBUG)
|
||||
# elif not is_vip:
|
||||
# self.logger.log("VIP 상세페이지 수정 토글 숨김 (VIP 등급 아님)", level=logging.DEBUG)
|
||||
|
||||
# 상페 이미지 번역 토글
|
||||
if hasattr(self, 'detail_IMGTrans_widget'):
|
||||
self.detail_IMGTrans_widget.setVisible(show_vip_toggle)
|
||||
|
||||
if show_vip_toggle:
|
||||
self.logger.log("VIP 상세페이지 수정 토글 표시 (등록모드 + VIP 사용자)", level=logging.DEBUG)
|
||||
self.logger.log("상페 이미지 번역 토글 표시 (등록모드 + VIP 사용자)", level=logging.DEBUG)
|
||||
else:
|
||||
if not self.is_register_product_mode:
|
||||
self.logger.log("VIP 상세페이지 수정 토글 숨김 (기본모드)", level=logging.DEBUG)
|
||||
self.logger.log("상페 이미지 번역 토글 숨김 (기본모드)", level=logging.DEBUG)
|
||||
elif not is_vip:
|
||||
self.logger.log("VIP 상세페이지 수정 토글 숨김 (VIP 등급 아님)", level=logging.DEBUG)
|
||||
self.logger.log("상페 이미지 번역 토글 숨김 (VIP 등급 아님)", level=logging.DEBUG)
|
||||
except Exception as e:
|
||||
self.logger.log(f"_update_vip_detail_edit_visibility 오류: {e}", level=logging.WARNING)
|
||||
|
||||
|
|
@ -6672,31 +6710,31 @@ class MAIN_GUI(QMainWindow):
|
|||
|
||||
self.detail_toggle_layout.addWidget(self.detail_text_button_widget)
|
||||
|
||||
# VIP용 등록모드 상세페이지 수정 토글
|
||||
self.vip_detail_edit_widget = QWidget()
|
||||
self.vip_detail_edit_toggle_layout = QHBoxLayout(self.vip_detail_edit_widget)
|
||||
self.vip_detail_edit_toggle_label = QLabel("상페수정(등록모드)", self)
|
||||
self.vip_detail_edit_toggle = ToggleSwitch(self)
|
||||
self.vip_detail_edit_toggle.setEnabled(False) # 개선때 까지 비활성화
|
||||
self.vip_detail_edit_toggle.setChecked(False) # 개선때 까지 비활성화
|
||||
self.vip_detail_edit_toggle.setObjectName("vip_detail_edit_toggle")
|
||||
self.vip_detail_edit_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.vip_detail_edit_toggle, checked))
|
||||
self.vip_detail_edit_widget.enterEvent = lambda e: self.show_manual_html(
|
||||
self.detail_manual_group,
|
||||
"상세페이지 수정 (등록모드 전용)",
|
||||
self.detail_manual_label,
|
||||
"""
|
||||
<h4>등록모드 상세페이지 수정</h4>
|
||||
<p>등록모드에서 상세페이지를 수정할 수 있습니다.</p>
|
||||
<p>활성화 시 선택마켓에서 지정된 스마트스토어의 <strong>사업자 이름 + 랜덤 홍보문구</strong>가 첫 줄에 자동 추가됩니다.</p>
|
||||
"""
|
||||
)
|
||||
self.vip_detail_edit_widget.leaveEvent = lambda e: self.reset_manual(self.detail_manual_group, self.detail_manual_label)
|
||||
# # VIP용 등록모드 상세페이지 수정 토글
|
||||
# self.vip_detail_edit_widget = QWidget()
|
||||
# self.vip_detail_edit_toggle_layout = QHBoxLayout(self.vip_detail_edit_widget)
|
||||
# self.vip_detail_edit_toggle_label = QLabel("상페수정(등록모드)", self)
|
||||
# self.vip_detail_edit_toggle = ToggleSwitch(self)
|
||||
# self.vip_detail_edit_toggle.setEnabled(False) # 개선때 까지 비활성화
|
||||
# self.vip_detail_edit_toggle.setChecked(False) # 개선때 까지 비활성화
|
||||
# self.vip_detail_edit_toggle.setObjectName("vip_detail_edit_toggle")
|
||||
# self.vip_detail_edit_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.vip_detail_edit_toggle, checked))
|
||||
# self.vip_detail_edit_widget.enterEvent = lambda e: self.show_manual_html(
|
||||
# self.detail_manual_group,
|
||||
# "상세페이지 수정 (등록모드 전용)",
|
||||
# self.detail_manual_label,
|
||||
# """
|
||||
# <h4>등록모드 상세페이지 수정</h4>
|
||||
# <p>등록모드에서 상세페이지를 수정할 수 있습니다.</p>
|
||||
# <p>활성화 시 선택마켓에서 지정된 스마트스토어의 <strong>사업자 이름 + 랜덤 홍보문구</strong>가 첫 줄에 자동 추가됩니다.</p>
|
||||
# """
|
||||
# )
|
||||
# self.vip_detail_edit_widget.leaveEvent = lambda e: self.reset_manual(self.detail_manual_group, self.detail_manual_label)
|
||||
|
||||
self.vip_detail_edit_toggle_layout.addWidget(self.vip_detail_edit_toggle_label)
|
||||
self.vip_detail_edit_toggle_layout.addWidget(self.vip_detail_edit_toggle)
|
||||
# self.vip_detail_edit_toggle_layout.addWidget(self.vip_detail_edit_toggle_label)
|
||||
# self.vip_detail_edit_toggle_layout.addWidget(self.vip_detail_edit_toggle)
|
||||
|
||||
self.detail_toggle_layout.addWidget(self.vip_detail_edit_widget)
|
||||
# self.detail_toggle_layout.addWidget(self.vip_detail_edit_widget)
|
||||
|
||||
# 상페 이미지 번역 토글
|
||||
self.detail_IMGTrans_widget = QWidget()
|
||||
|
|
|
|||
|
|
@ -137,6 +137,69 @@ class DetailHandler:
|
|||
self.logger.log(f"CKEditor 데이터 지우기 실패: {e}", level=logging.ERROR)
|
||||
return False
|
||||
|
||||
def remove_image_tags_from_html(self, html_content: str) -> str:
|
||||
"""HTML에서 이미지 태그만 제거하고 텍스트는 보존합니다.
|
||||
|
||||
Args:
|
||||
html_content: 원본 HTML 문자열
|
||||
|
||||
Returns:
|
||||
이미지 태그가 제거된 HTML 문자열
|
||||
"""
|
||||
try:
|
||||
soup = BeautifulSoup(html_content, "html.parser")
|
||||
|
||||
# 모든 <img> 태그 찾아서 제거
|
||||
for img in soup.find_all("img"):
|
||||
img.decompose() # 태그와 내용 모두 제거
|
||||
|
||||
# <figure> 태그가 비어있으면 제거 (이미지와 함께 사용되는 경우)
|
||||
for figure in soup.find_all("figure"):
|
||||
if not figure.contents or len(figure.get_text(strip=True)) == 0:
|
||||
figure.decompose()
|
||||
|
||||
cleaned_html = str(soup)
|
||||
self.logger.log("이미지 태그 제거 완료 (텍스트 보존)", level=logging.DEBUG)
|
||||
return cleaned_html
|
||||
except Exception as e:
|
||||
self.logger.log(f"이미지 태그 제거 중 오류: {e}", level=logging.ERROR)
|
||||
return html_content # 오류 시 원본 반환
|
||||
|
||||
async def set_ckeditor_data(self, html_content: str):
|
||||
"""CKEditor에 HTML 데이터를 설정합니다.
|
||||
|
||||
Args:
|
||||
html_content: 설정할 HTML 문자열
|
||||
|
||||
Returns:
|
||||
bool: 설정 성공 여부
|
||||
"""
|
||||
try:
|
||||
# 소스 모드로 전환
|
||||
await self.page.click(self.source_button_locator)
|
||||
self.logger.log("소스 버튼 클릭 완료.", level=logging.DEBUG)
|
||||
|
||||
# 소스 편집 영역이 나타날 때까지 대기 (더 구체적인 선택자 사용)
|
||||
await self.page.wait_for_selector(".ck-source-editing-area", timeout=5000)
|
||||
|
||||
# 소스 편집 영역만 선택 (strict mode violation 방지)
|
||||
textarea_locator = self.page.locator(".ck-source-editing-area").first
|
||||
await textarea_locator.wait_for(state="visible", timeout=5000)
|
||||
|
||||
# data-value 속성 설정
|
||||
await textarea_locator.evaluate(f'(element) => {{ element.setAttribute("data-value", {repr(html_content)}); }}')
|
||||
|
||||
self.logger.log("CKEditor 데이터 설정 완료.", level=logging.DEBUG)
|
||||
|
||||
# 다시 에디터 모드로 전환
|
||||
await self.page.click(self.source_button_locator)
|
||||
self.logger.log("소스 버튼 재클릭 완료.", level=logging.DEBUG)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.log(f"CKEditor 데이터 설정 실패: {e}", level=logging.ERROR)
|
||||
return False
|
||||
|
||||
|
||||
def is_valid_html(self, html_data: str) -> bool:
|
||||
"""
|
||||
|
|
@ -410,6 +473,7 @@ class DetailHandler:
|
|||
self.detail_IMGTrans = toggle_states.get('detail_IMGTrans', False)
|
||||
self.detail_IMGTrans_type = toggle_states.get('detail_IMGTrans_type', True)
|
||||
self.unwanted_words = toggle_states.get('unwanted_words', False)
|
||||
self.ed_mode = toggle_states.get('ed_mode', False) # 등록모드 확인
|
||||
|
||||
self.logger.log(f"self.toggle_states : {self.toggle_states}", level=logging.DEBUG)
|
||||
|
||||
|
|
@ -437,7 +501,8 @@ class DetailHandler:
|
|||
# 상세페이지 옵션에 따라 처리
|
||||
self.logger.log(f"self.detail_Option : {self.detail_Option}", level=logging.DEBUG)
|
||||
self.logger.log(f"self.detail_IMGTrans : {self.detail_IMGTrans}", level=logging.DEBUG)
|
||||
|
||||
self.logger.log(f"image_processor 존재 여부: {self.browser_controller.image_processor is not None}", level=logging.DEBUG)
|
||||
|
||||
if (self.detail_Option or self.detail_IMGTrans) and (self.browser_controller.image_processor is not None):
|
||||
|
||||
# if self.detail_Option:
|
||||
|
|
@ -453,15 +518,23 @@ class DetailHandler:
|
|||
# if (self.detail_IMGTrans_type in ('CPU', '자체서버')) and (self.imageProcessor is not None):
|
||||
if self.detail_IMGTrans_type and (self.browser_controller.image_processor is not None):
|
||||
|
||||
# CKEditor 내용 지우기
|
||||
await self.clear_ckeditor_data()
|
||||
# 등록모드(ed_mode) 처리 분기
|
||||
if self.ed_mode:
|
||||
# 등록모드: 기존 텍스트 보존, 이미지 태그만 제거
|
||||
self.logger.log("등록모드: 이미지 태그만 제거하고 텍스트 보존", level=logging.INFO)
|
||||
cleaned_html = self.remove_image_tags_from_html(current_html)
|
||||
await self.set_ckeditor_data(cleaned_html)
|
||||
else:
|
||||
# 기본모드: CKEditor 내용 완전히 지우기
|
||||
await self.clear_ckeditor_data()
|
||||
|
||||
# 텍스트 입력 필드 선택
|
||||
# 텍스트 입력 필드 선택 (strict mode violation 방지)
|
||||
input_field = self.page.locator(self.cke_text_editing_area_locator).first
|
||||
await input_field.click()
|
||||
self.logger.log("입력 필드 선택", level=logging.DEBUG)
|
||||
|
||||
# 소개글과 옵션데이터 입력
|
||||
if self.detail_Option:
|
||||
# 소개글과 옵션데이터 입력 (기본모드일 때만)
|
||||
if self.detail_Option and not self.ed_mode:
|
||||
await self.input_detail_text(optionHandler, input_field)
|
||||
|
||||
# 이미지 처리 통계
|
||||
|
|
@ -481,9 +554,13 @@ class DetailHandler:
|
|||
|
||||
# 번역 파라미터 준비
|
||||
font_type = self.browser_controller.toggle_states.get('font_type', '')
|
||||
self.logger.log(f"font_type : {font_type}", level=logging.DEBUG)
|
||||
unwanted_texts = list(self.browser_controller.unwanted_words.keys()) if hasattr(self.browser_controller, 'unwanted_words') else []
|
||||
self.logger.log(f"unwanted_texts : {unwanted_texts}", level=logging.DEBUG)
|
||||
is_member_valid = self.browser_controller.user_membership_level == "premium"
|
||||
self.logger.log(f"is_member_valid : {is_member_valid}", level=logging.DEBUG)
|
||||
authenticated_by_admin = self.browser_controller.authenticated_by_admin
|
||||
self.logger.log(f"authenticated_by_admin : {authenticated_by_admin}", level=logging.DEBUG)
|
||||
|
||||
for idx, image_url in enumerate(image_urls):
|
||||
try:
|
||||
|
|
@ -674,7 +751,10 @@ class DetailHandler:
|
|||
|
||||
else:
|
||||
# 상세페이지 옵션이 비활성화된 경우
|
||||
self.logger.log("상세페이지 옵션이 비활성화되어 있습니다.", level=logging.INFO)
|
||||
if self.browser_controller.image_processor is None:
|
||||
self.logger.log("상세페이지 옵션이 활성화되어 있지만 image_processor가 초기화되지 않았습니다.", level=logging.WARNING)
|
||||
else:
|
||||
self.logger.log("상세페이지 옵션이 비활성화되어 있습니다.", level=logging.INFO)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import os
|
|||
import logging
|
||||
import random
|
||||
import re
|
||||
import unicodedata
|
||||
import math
|
||||
import glob
|
||||
|
||||
|
|
@ -570,9 +571,6 @@ class OptionHandler:
|
|||
비활성화: 마케팅 노이즈 제거, 오역방지 치환, Jieba 기반 사전체환, 색상 팔레트 통일
|
||||
(요청사항: 색상/단위/크기·무게·전류치 등 스펙만 유지)
|
||||
"""
|
||||
import re
|
||||
import unicodedata
|
||||
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
return text
|
||||
|
||||
|
|
@ -3359,7 +3357,6 @@ class OptionHandler:
|
|||
|
||||
def _extract_common_tokens(self, translations: list[str]) -> set:
|
||||
"""여러 옵션명에서 공통으로 등장하는 토큰 집합을 추출"""
|
||||
import re
|
||||
if not translations:
|
||||
return set()
|
||||
token_sets = [set(re.split(r"\s+", txt)) for txt in translations if txt]
|
||||
|
|
@ -3367,7 +3364,6 @@ class OptionHandler:
|
|||
|
||||
def _simplify_translation(self, text: str, common_tokens: set, max_length: int = 22) -> str:
|
||||
"""공통어 제거, 중복 토큰 제거 후 지정 길이 내로 축약"""
|
||||
import re
|
||||
tokens = [tok for tok in re.split(r"\s+", text) if tok and tok not in common_tokens]
|
||||
# 순서 보존하며 중복 제거
|
||||
seen = set()
|
||||
|
|
@ -3453,7 +3449,6 @@ class OptionHandler:
|
|||
current_value = item_data['current_value']
|
||||
|
||||
# 기존 넘버링 패턴 제거 (정규식으로 다양한 넘버링 패턴 제거)
|
||||
import re
|
||||
# 다양한 넘버링 패턴을 제거하는 정규식
|
||||
patterns = [
|
||||
r'^[A-Za-z]+\.\s*', # A., B., AA., etc.
|
||||
|
|
@ -3519,7 +3514,6 @@ class OptionHandler:
|
|||
if match_mode == 'partial':
|
||||
if forbidden_word.lower() in filtered_text.lower():
|
||||
# 대소문자 무관하게 제거
|
||||
import re
|
||||
pattern = re.escape(forbidden_word)
|
||||
filtered_text = re.sub(pattern, '', filtered_text, flags=re.IGNORECASE)
|
||||
removed_words.append(forbidden_word)
|
||||
|
|
@ -3532,7 +3526,6 @@ class OptionHandler:
|
|||
removed_words.append(forbidden_word)
|
||||
|
||||
# 연속된 공백이나 특수문자 정리
|
||||
import re
|
||||
filtered_text = re.sub(r'\s+', ' ', filtered_text) # 연속 공백을 하나로
|
||||
filtered_text = re.sub(r'[+\-\(\)]+\s*[+\-\(\)]+', '+', filtered_text) # 연속 구분자 정리
|
||||
filtered_text = filtered_text.strip()
|
||||
|
|
@ -3550,7 +3543,6 @@ class OptionHandler:
|
|||
"""
|
||||
텍스트에 한자가 포함되어 있는지 확인합니다.
|
||||
"""
|
||||
import re
|
||||
if not isinstance(text, str):
|
||||
return False
|
||||
return bool(re.search(r'[\u4e00-\u9fff]', text))
|
||||
|
|
@ -3559,7 +3551,6 @@ class OptionHandler:
|
|||
"""
|
||||
중국어 용어를 한국어로 번역하기 전에 일반적인 용어로 변환합니다.
|
||||
"""
|
||||
import re
|
||||
if not isinstance(text, str):
|
||||
return text
|
||||
self.logger.log(f"최소 중국어 전처리 시작(라인 보존): {text}", level=logging.DEBUG)
|
||||
|
|
|
|||
|
|
@ -114,10 +114,10 @@ class TitleGenerator:
|
|||
self.parsing_page = parsing_page
|
||||
self.logger.log(f"paparsing_pagege객체 업데이트 : {parsing_page}", level=logging.DEBUG)
|
||||
|
||||
def translate_product_name_from_google(self, original_name: str) -> str:
|
||||
async def translate_product_name_from_google(self, original_name: str) -> str:
|
||||
"""텍스트를 한국어로 번역하는 메서드"""
|
||||
try:
|
||||
translated_name = self.papago_translator.translate(original_name, 'ko')
|
||||
translated_name = await self.papago_translator.translate(original_name, 'ko')
|
||||
self.title_infos["translated_name"] = translated_name
|
||||
return translated_name
|
||||
except Exception as e:
|
||||
|
|
@ -289,12 +289,12 @@ class TitleGenerator:
|
|||
prefix += "/"
|
||||
return prefix + title
|
||||
|
||||
def generate_product_title(self, trans_type: bool, original_name: str, keyword_name: str, search_result: dict, product_category: str, max_length: int = 35) -> str:
|
||||
async def generate_product_title(self, trans_type: bool, original_name: str, keyword_name: str, search_result: dict, product_category: str, max_length: int = 35) -> str:
|
||||
"""상품명을 생성하는 메서드"""
|
||||
# 1. 원본 상품명 번역
|
||||
if trans_type:
|
||||
self.logger.log(f'trans_type : {trans_type}, 구글 번역 사용', level=logging.INFO)
|
||||
translated_name = self.translate_product_name_from_google(original_name)
|
||||
translated_name = await self.translate_product_name_from_google(original_name)
|
||||
else:
|
||||
self.logger.log(f'trans_type : {trans_type}, GPT 번역 사용', level=logging.INFO)
|
||||
translated_name = self.translate_product_name(original_name)
|
||||
|
|
@ -873,11 +873,12 @@ class TitleGenerator:
|
|||
# 6. 상품명 생성
|
||||
|
||||
title_shuffle = self.toggle_states["title_shuffle"]
|
||||
self.logger.log(f'상품명 셔플 여부 : {title_shuffle}', level=logging.DEBUG)
|
||||
|
||||
if title_shuffle:
|
||||
product_title = self.process_title_shuffle(keyword_name=self.title_infos["keyword_name"], max_duplicates=1, max_length=self.toggle_states["title_length_limit"])
|
||||
else:
|
||||
generated_product_title = self.generate_product_title(trans_type=self.toggle_states["title_trans_type"], original_name=self.title_infos["original_name"], keyword_name=self.title_infos["keyword_name"], search_result=self.title_infos["search_result"], product_category=self.title_infos["category"])
|
||||
generated_product_title = await self.generate_product_title(trans_type=self.toggle_states["title_trans_type"], original_name=self.title_infos["original_name"], keyword_name=self.title_infos["keyword_name"], search_result=self.title_infos["search_result"], product_category=self.title_infos["category"])
|
||||
product_title = self.limit_title_length(generated_product_title, max_length=self.toggle_states["title_length_limit"])
|
||||
if product_title == "관련성이 없는 상품 - 체크필요":
|
||||
return
|
||||
|
|
|
|||
|
|
@ -983,7 +983,7 @@ class BizDialog(QDialog):
|
|||
self.selected_biz_id = 1
|
||||
self.setWindowTitle("사업자 정보 입력")
|
||||
# self.setGeometry(50, 50, 700, 600)
|
||||
self.setMinimumSize(700, 600)
|
||||
self.setMinimumSize(700, 700)
|
||||
self.setMaximumSize(1000, 900)
|
||||
self.max_biz_count = max_biz_count
|
||||
self.current_biz_count = 0
|
||||
|
|
|
|||
|
|
@ -247,6 +247,40 @@ class ImageWorkerClient:
|
|||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
async def update_toggle_states(self, toggle_states: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
이미지워커 서버의 toggle_states를 업데이트합니다.
|
||||
|
||||
Args:
|
||||
toggle_states: 업데이트할 toggle_states 딕셔너리
|
||||
|
||||
Returns:
|
||||
bool: 업데이트 성공 여부
|
||||
"""
|
||||
try:
|
||||
payload = {
|
||||
"toggle_states": toggle_states
|
||||
}
|
||||
r = requests.post(
|
||||
f"{self.api}/v1/worker/update-toggle-states",
|
||||
json=payload,
|
||||
timeout=self.timeout
|
||||
)
|
||||
r.raise_for_status()
|
||||
result = r.json()
|
||||
if result.get("ok", False):
|
||||
self.logger.log(f"이미지워커 toggle_states 업데이트 완료", level=logging.DEBUG)
|
||||
return True
|
||||
else:
|
||||
self.logger.log(f"이미지워커 toggle_states 업데이트 실패: {result}", level=logging.WARNING)
|
||||
return False
|
||||
except requests.exceptions.RequestException as e:
|
||||
self.logger.log(f"이미지워커 toggle_states 업데이트 중 네트워크 오류: {e}", level=logging.ERROR)
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.log(f"이미지워커 toggle_states 업데이트 중 오류: {e}", level=logging.ERROR, exc_info=True)
|
||||
return False
|
||||
|
||||
# ---------------------- 제출/대기 ----------------------
|
||||
async def submit_process_image(self, file_path: str, index: int, file_prefix: str,
|
||||
font_type: str, unwanted_texts: List[str],
|
||||
|
|
|
|||
|
|
@ -130,7 +130,17 @@ class SettingsManager:
|
|||
|
||||
:param widget_obj: 실제 위젯 객체(self 등)
|
||||
"""
|
||||
for widget_name, key in self.widget_map.items():
|
||||
# widget_obj에 widget_map이 있으면 그것을 사용, 없으면 self.widget_map 사용
|
||||
widget_map = getattr(widget_obj, 'widget_map', self.widget_map)
|
||||
|
||||
for widget_name, config in widget_map.items():
|
||||
# config가 딕셔너리인 경우 (mainUI_SP.py 형식)
|
||||
if isinstance(config, dict):
|
||||
key = config.get("key", widget_name)
|
||||
else:
|
||||
# config가 문자열인 경우 (이전 형식)
|
||||
key = config
|
||||
|
||||
widget = getattr(widget_obj, widget_name, None)
|
||||
if widget is None:
|
||||
self._log(f"[SettingsManager] '{widget_name}' 위젯을 찾을 수 없습니다.", logging.WARNING)
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -7,7 +7,7 @@ import logging
|
|||
""" 프로그램 기본 정보 """
|
||||
__title__ = "Edit_PartTimer"
|
||||
__description__ = "편집알바생"
|
||||
__version__ = "3.12.9"
|
||||
__version__ = "3.12.11"
|
||||
__build__ = "1" # 빌드 번호
|
||||
__author__ = "WhenRideMyCar"
|
||||
__author_email__ = "abc@gmail.com"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,21 @@
|
|||
- 비밀번호 변경 추가
|
||||
|
||||
|
||||
# 3.12.11 패치 업데이트 로그
|
||||
|
||||
### 패치 11 오류수정
|
||||
- 상품명 AI생성이 적용되지 않던 문제 적용
|
||||
- 등록모드에서 대표썸네일 수정 선택사항이 반영되지 않던 문제 수정
|
||||
|
||||
### 패치 11 기능변경
|
||||
- 등록모드에서도 상세페이지 번역기능 추가(VIP)
|
||||
|
||||
# 3.12.10 패치 업데이트 로그
|
||||
|
||||
### 패치 10 오류수정
|
||||
- 옵션 유니코드 처리 오류 수정
|
||||
|
||||
|
||||
# 3.12.9 패치 업데이트 로그
|
||||
### 패치 9 오류수정
|
||||
- 옵션자동선택을 꺼도 옵션이미지번역시 다시 동작하는 문제 수정
|
||||
|
|
|
|||
Loading…
Reference in New Issue