Last active
April 27, 2025 09:49
-
-
Save lunamoth/27717e92ded97d0f5e820d8897ade053 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Google AI Studio - 사이드바 자동 숨김 (v2.3 - 사용자 설정) | |
// @namespace http://tampermonkey.net/ | |
// @version 2.3 | |
// @description Google AI Studio 자동 숨김. 사용자가 직접 열었을 때 유지 시간 설정 가능. | |
// @author Gemini | |
// @match https://aistudio.google.com/* | |
// @grant none | |
// @run-at document-idle | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
// ======================================================================== | |
// ★★★ 사용자 설정 영역 시작 ★★★ | |
// ======================================================================== | |
// 오른쪽 패널을 직접 열었을 때 자동으로 닫히지 않고 유지되는 시간 (밀리초 단위) | |
// 예: 5초 = 5000, 10초 = 10000, 30초 = 30000, 1분 = 60000 | |
// 스크립트 편집기에서 이 숫자 값을 원하는 시간으로 변경하세요. | |
const USER_ACTION_GRACE_PERIOD_MS = 10000; // <-- 여기 숫자를 수정하세요 (현재 10초로 설정됨) | |
// (고급 설정) 핵심 확인 주기 (밀리초). 낮을수록 반응이 빠르지만 CPU 사용량이 늘 수 있습니다. (기본값: 250) | |
const CHECK_INTERVAL_MS = 250; | |
// (고급 설정) 초기 로딩 시 확인 최대 지속 시간 (밀리초). (기본값: 5000) | |
const INITIAL_CHECK_MAX_DURATION_MS = 5000; | |
// (고급 설정) 페이지 이동(네비게이션) 후 닫기 시도까지 지연 시간 (밀리초). (기본값: 50) | |
const NAV_CLOSE_DELAY_MS = 50; | |
// ======================================================================== | |
// ★★★ 사용자 설정 영역 끝 ★★★ | |
// ======================================================================== | |
// --- 상태 변수 --- | |
let lastUserOpenTimestamp = 0; // 사용자가 마지막으로 열기 버튼 누른 시간 (Timestamp) | |
let checkIntervalId = null; | |
let isInitialCheckPhase = true; // 초기 확인 단계인지 여부 | |
let initialCheckStartTime = Date.now(); | |
let currentHref = document.location.href; | |
let isClosingRightPanel = false; // 스크립트가 닫기 진행 중인지 플래그 | |
// --- 선택자 정의 --- | |
const LEFT_NAVBAR_SELECTOR = 'ms-navbar .layout-navbar'; | |
const LEFT_COLLAPSE_BUTTON_SELECTOR = 'ms-navbar button[aria-label="Expand or collapse navigation menu"], ms-navbar button[aria-label*="탐색 메뉴"]'; | |
const RIGHT_PANEL_SELECTOR = 'ms-right-side-panel'; | |
const RIGHT_PANEL_CONTENT_SELECTOR = '.content-container'; // RIGHT_PANEL_SELECTOR 하위에서 검색 | |
const RIGHT_PANEL_CLOSE_BUTTON_SELECTOR = 'ms-run-settings button[aria-label="Close run settings panel"], ms-run-settings button[aria-label*="닫기"]'; // RIGHT_PANEL_SELECTOR 하위에서 검색 | |
const RIGHT_PANEL_OPEN_BUTTON_SELECTOR = 'button[aria-label="Run settings"], button[aria-label*="실행 설정"]'; // RIGHT_PANEL_SELECTOR 하위에서 검색 | |
console.log(`AI Studio 자동 숨김 (v2.3 - 사용자 설정): 스크립트 시작됨. 유지 시간: ${USER_ACTION_GRACE_PERIOD_MS / 1000}초`); | |
// --- 디바운스 함수 --- | |
function debounce(func, wait) { | |
let timeout; | |
return function executedFunction(...args) { | |
const later = () => { | |
clearTimeout(timeout); | |
func.apply(this, args); | |
}; | |
clearTimeout(timeout); | |
timeout = setTimeout(later, wait); | |
}; | |
} | |
// --- 오른쪽 패널 열기 버튼 리스너 --- | |
function attachOpenButtonListener() { | |
try { | |
const rightPanelElement = document.querySelector(RIGHT_PANEL_SELECTOR); | |
if (!rightPanelElement) return false; | |
const openButton = rightPanelElement.querySelector(RIGHT_PANEL_OPEN_BUTTON_SELECTOR); | |
if (openButton && openButton.getAttribute('data-listener-attached-v2.3') !== 'true') { | |
openButton.removeEventListener('click', handleOpenButtonClick); // 이전 리스너 제거 | |
openButton.addEventListener('click', handleOpenButtonClick); // 새 리스너 추가 | |
openButton.setAttribute('data-listener-attached-v2.3', 'true'); | |
return true; | |
} | |
} catch (error) { | |
console.error('AI Studio 자동 숨김: attachOpenButtonListener 오류:', error); | |
} | |
return false; | |
} | |
// 열기 버튼 클릭 핸들러 | |
function handleOpenButtonClick(event) { | |
if (event && event.isTrusted) { | |
console.log('%cAI Studio 자동 숨김: [사용자 액션] 오른쪽 패널 열기 버튼 클릭 감지 (타임스탬프 설정).', 'color: blue; font-weight: bold;'); | |
lastUserOpenTimestamp = Date.now(); // 타임스탬프 기록 | |
} | |
} | |
// --- 왼쪽 패널 닫기 --- | |
function closeLeftPanel() { | |
try { | |
const leftNavbar = document.querySelector(LEFT_NAVBAR_SELECTOR); | |
const leftCollapseButton = document.querySelector(LEFT_COLLAPSE_BUTTON_SELECTOR); | |
if (leftNavbar && leftCollapseButton && leftNavbar.classList.contains('expanded')) { | |
leftCollapseButton.click(); | |
return true; | |
} | |
} catch (error) { | |
console.error('AI Studio 자동 숨김: closeLeftPanel 오류:', error); | |
} | |
return false; | |
} | |
// --- 오른쪽 패널 닫기 (타임스탬프 확인 후) --- | |
function closeRightPanelIfNeeded(reason = "Check") { | |
if (isClosingRightPanel) return false; // 닫기 진행 중이면 건너뜀 | |
const timeSinceLastUserOpen = Date.now() - lastUserOpenTimestamp; | |
// 사용자가 직접 열었고, 설정된 유예 시간 이내라면 닫지 않음 | |
if (lastUserOpenTimestamp > 0 && timeSinceLastUserOpen < USER_ACTION_GRACE_PERIOD_MS) { | |
// 유예 시간이 0이 아니고, 마지막 사용자 액션이 있었고, 그 시간이 유예 시간 내일 때만 로그 출력 | |
if (USER_ACTION_GRACE_PERIOD_MS > 0) { | |
// console.log(`AI Studio 자동 숨김: 오른쪽 닫기 건너뜀 (사용자 유예 기간 ${Math.round((USER_ACTION_GRACE_PERIOD_MS - timeSinceLastUserOpen)/1000)}초 남음).`); | |
} | |
return false; | |
} | |
try { | |
const rightPanelElement = document.querySelector(RIGHT_PANEL_SELECTOR); | |
if (!rightPanelElement) return false; | |
const content = rightPanelElement.querySelector(RIGHT_PANEL_CONTENT_SELECTOR); | |
const closeButton = rightPanelElement.querySelector(RIGHT_PANEL_CLOSE_BUTTON_SELECTOR); | |
if (content && closeButton) { // 열려 있는 경우 | |
isClosingRightPanel = true; | |
// console.log(`%cAI Studio 자동 숨김: 오른쪽 패널 자동 닫기 실행됨. (사유: ${reason})`, 'color: red;'); // 로그 간소화 | |
try { | |
if (document.body.contains(closeButton)) { | |
closeButton.click(); | |
} else { | |
console.warn('AI Studio 자동 숨김: 닫기 버튼 클릭 시점에 버튼이 사라짐.'); | |
} | |
} catch (clickError) { | |
console.error('AI Studio 자동 숨김: 닫기 버튼 클릭 오류:', clickError); | |
} finally { | |
setTimeout(() => { isClosingRightPanel = false; }, 100); // 닫기 시도 후 플래그 해제 | |
} | |
return true; | |
} | |
} catch (error) { | |
console.error('AI Studio 자동 숨김: closeRightPanelIfNeeded 오류:', error); | |
isClosingRightPanel = false; | |
} | |
return false; | |
} | |
// --- 핵심 주기적 확인 함수 --- | |
function persistentCheck() { | |
try { | |
closeLeftPanel(); | |
closeRightPanelIfNeeded("Persistent Check"); | |
attachOpenButtonListener(); | |
if (isInitialCheckPhase) { | |
const leftOk = !document.querySelector(LEFT_NAVBAR_SELECTOR + '.expanded'); | |
const rightOk = !document.querySelector(RIGHT_PANEL_SELECTOR + ' ' + RIGHT_PANEL_CONTENT_SELECTOR); | |
const elapsedTime = Date.now() - initialCheckStartTime; | |
if ((leftOk && rightOk && elapsedTime > 500) || (elapsedTime > INITIAL_CHECK_MAX_DURATION_MS)) { | |
isInitialCheckPhase = false; | |
console.log(`%cAI Studio 자동 숨김: === 초기 확인 단계 종료 === (경과: ${elapsedTime}ms). 상태: Left ${leftOk}, Right ${rightOk}`, 'color: green; font-weight: bold;'); | |
} | |
} | |
} catch (error) { | |
console.error('AI Studio 자동 숨김: persistentCheck 오류:', error); | |
} | |
} | |
// --- URL 변경 처리 함수 --- | |
function handleUrlChange() { | |
console.log('%cAI Studio 자동 숨김: [URL 변경 감지] 즉시 닫기 시도.', 'background-color: yellow;'); | |
currentHref = document.location.href; | |
lastUserOpenTimestamp = 0; // 타임스탬프 초기화 | |
isClosingRightPanel = false; // 닫기 플래그 초기화 | |
setTimeout(() => { | |
// console.log('AI Studio 자동 숨김: [Nav] 왼쪽 즉시 닫기 시도.'); // 로그 간소화 | |
closeLeftPanel(); | |
// console.log('AI Studio 자동 숨김: [Nav] 오른쪽 즉시 닫기 시도 (강제).'); // 로그 간소화 | |
try { | |
const rightPanelElement = document.querySelector(RIGHT_PANEL_SELECTOR); | |
const content = rightPanelElement?.querySelector(RIGHT_PANEL_CONTENT_SELECTOR); | |
const closeButton = rightPanelElement?.querySelector(RIGHT_PANEL_CLOSE_BUTTON_SELECTOR); | |
if (content && closeButton) { | |
// console.log('%cAI Studio 자동 숨김: [Nav] 오른쪽 강제 닫기 실행.', 'color: magenta;'); // 로그 간소화 | |
closeButton.click(); | |
} | |
} catch (error) { | |
console.error('AI Studio 자동 숨김: [Nav] 오른쪽 강제 닫기 오류:', error); | |
} | |
attachOpenButtonListener(); // 리스너 재부착 | |
isInitialCheckPhase = true; // 초기화 단계 재시작 | |
initialCheckStartTime = Date.now(); | |
}, NAV_CLOSE_DELAY_MS); | |
} | |
const debouncedHandleUrlChange = debounce(handleUrlChange, 150); | |
// --- URL 변경 리스너 설정 --- | |
window.addEventListener('popstate', () => { | |
if (document.location.href !== currentHref) debouncedHandleUrlChange(); | |
}); | |
const originalPushState = history.pushState; | |
history.pushState = function(...args) { | |
const previousHref = currentHref; | |
originalPushState.apply(history, args); | |
if (document.location.href !== previousHref) debouncedHandleUrlChange(); | |
}; | |
const originalReplaceState = history.replaceState; | |
history.replaceState = function(...args) { | |
const previousHref = currentHref; | |
originalReplaceState.apply(history, args); | |
if (document.location.href !== previousHref) debouncedHandleUrlChange(); | |
}; | |
// --- 스크립트 실행 시작 --- | |
function initialize() { | |
console.log('AI Studio 자동 숨김 (v2.3 - 사용자 설정): 초기화 시작!'); | |
initialCheckStartTime = Date.now(); | |
isInitialCheckPhase = true; | |
lastUserOpenTimestamp = 0; | |
isClosingRightPanel = false; | |
if (checkIntervalId) clearInterval(checkIntervalId); // 기존 인터벌 정리 | |
checkIntervalId = setInterval(persistentCheck, CHECK_INTERVAL_MS); | |
console.log(`AI Studio 자동 숨김: 핵심 확인 인터벌 (간격: ${CHECK_INTERVAL_MS}ms) 시작됨.`); | |
setTimeout(persistentCheck, 50); // 첫 확인은 더 빠르게 | |
} | |
// DOM 로드 상태 확인 후 초기화 시작 | |
if (document.readyState === 'complete') { | |
setTimeout(initialize, 200); | |
} else { | |
document.addEventListener('readystatechange', () => { | |
if (document.readyState === 'complete' && !checkIntervalId) { | |
setTimeout(initialize, 200); | |
} | |
}); | |
setTimeout(() => { // 안전 장치 | |
if (!checkIntervalId && document.readyState === 'complete') { | |
console.warn('AI Studio 자동 숨김: 강제 초기화.'); | |
initialize(); | |
} | |
}, 1500); | |
} | |
// 페이지 언로드 시 인터벌 정리 | |
window.addEventListener('beforeunload', () => { | |
// console.log('AI Studio 자동 숨김: 페이지 언로드. 인터벌 정리.'); // 로그 간소화 | |
if (checkIntervalId) clearInterval(checkIntervalId); | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment