Updates
This commit is contained in:
+2
-3
@@ -333,9 +333,8 @@ export class AgentExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. API Request Setup
|
||||
const { ollamaUrl, defaultModel: configDefaultModel, timeout } = getConfig();
|
||||
const actualModel = modelName || configDefaultModel;
|
||||
// 3. API Request Setup (라인 229에서 이미 추출한 ollamaUrl, configDefaultModel 재사용)
|
||||
const actualModel = (modelName && modelName.trim()) || configDefaultModel;
|
||||
const reqMessages = this.buildRequestHistory(this.chatHistory);
|
||||
|
||||
// Handle Vision Content Injection
|
||||
|
||||
+94
-15
@@ -74,6 +74,16 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
localResourceRoots: [this._extensionUri]
|
||||
};
|
||||
|
||||
// [State Persistence Fix] 사이드바가 다시 보여질 때 세팅값 자동 복원
|
||||
webviewView.onDidChangeVisibility(() => {
|
||||
if (webviewView.visible) {
|
||||
logInfo('Sidebar became visible, restoring state...');
|
||||
void this._sendModels();
|
||||
void this._sendBrainProfiles();
|
||||
void this._sendAgentsList();
|
||||
}
|
||||
});
|
||||
|
||||
webviewView.webview.html = this._getHtml(webviewView.webview);
|
||||
this._agent.setWebview(webviewView.webview);
|
||||
|
||||
@@ -204,6 +214,10 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
case 'deleteAgent':
|
||||
await this._deleteAgent(data.path);
|
||||
break;
|
||||
case 'saveAgentSelection':
|
||||
await this._context.globalState.update(SidebarChatProvider.lastAgentStateKey, data.path || 'none');
|
||||
logInfo(`Agent selection saved: ${data.path}`);
|
||||
break;
|
||||
case 'refreshModels':
|
||||
await this._sendModels();
|
||||
break;
|
||||
@@ -1824,10 +1838,75 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
|
||||
const designerContext = designerGuard !== false ? this._buildDesignerGuardContext() : undefined;
|
||||
|
||||
// [File Processing v2] 파일 타입별 분류 처리
|
||||
let processedPrompt = value || '';
|
||||
let imageFiles: any[] | undefined = undefined;
|
||||
|
||||
if (files && Array.isArray(files) && files.length > 0) {
|
||||
const textContents: string[] = [];
|
||||
const images: any[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
const name = file.name?.toLowerCase() || '';
|
||||
const type = file.type || '';
|
||||
|
||||
if (name.endsWith('.pdf') || type === 'application/pdf') {
|
||||
// PDF: 서버사이드 텍스트 추출 (pdf-parse v2 API)
|
||||
try {
|
||||
const { PDFParse } = require('pdf-parse');
|
||||
const rawBuffer = Buffer.from(file.data, 'base64');
|
||||
const uint8 = new Uint8Array(rawBuffer.buffer, rawBuffer.byteOffset, rawBuffer.byteLength);
|
||||
const parser = new PDFParse(uint8);
|
||||
await parser.load();
|
||||
const textResult = await parser.getText();
|
||||
// pdf-parse v2: getText() returns {pages: [{text, num}], text: string, total: number}
|
||||
const extracted = (typeof textResult === 'string' ? textResult : (textResult?.text || '')).trim();
|
||||
// 페이지 구분 마커 제거하여 깔끔한 텍스트 추출
|
||||
const cleanText = extracted.replace(/\n*-- \d+ of \d+ --\n*/g, '\n').trim();
|
||||
if (cleanText && cleanText.length > 10) {
|
||||
textContents.push(`\n[PDF: ${file.name}]\n${cleanText}`);
|
||||
logInfo(`PDF text extracted successfully.`, { fileName: file.name, chars: cleanText.length });
|
||||
} else {
|
||||
textContents.push(`\n[PDF: ${file.name}]\n(텍스트 추출 결과 없음 - 이미지 기반 PDF일 수 있습니다. 텍스트 레이어가 없는 스캔 문서는 OCR 변환 후 재시도하세요.)`);
|
||||
logInfo(`PDF text extraction returned empty/minimal result.`, { fileName: file.name, rawLength: extracted.length });
|
||||
}
|
||||
} catch (pdfError: any) {
|
||||
logError(`PDF parsing failed.`, { fileName: file.name, error: pdfError?.message || String(pdfError) });
|
||||
textContents.push(`\n[PDF: ${file.name}]\n(PDF 파싱 오류: ${pdfError?.message || '알 수 없는 오류'})`);
|
||||
}
|
||||
} else if (
|
||||
type.startsWith('text/') ||
|
||||
type === 'application/json' ||
|
||||
/\.(txt|md|csv|json|js|ts|jsx|tsx|html|css|py|java|rs|go|yaml|yml|xml|toml|sql|sh)$/i.test(name)
|
||||
) {
|
||||
// 텍스트 파일: base64 디코딩
|
||||
try {
|
||||
const decoded = Buffer.from(file.data, 'base64').toString('utf-8');
|
||||
textContents.push(`\n[FILE: ${file.name}]\n\`\`\`\n${decoded}\n\`\`\``);
|
||||
} catch (decodeError: any) {
|
||||
logError(`Text file decode failed.`, { fileName: file.name, error: decodeError?.message });
|
||||
textContents.push(`\n[FILE: ${file.name}]\n(디코딩 오류)`);
|
||||
}
|
||||
} else if (type.startsWith('image/')) {
|
||||
// 이미지: 기존 vision 방식 유지
|
||||
images.push(file);
|
||||
} else {
|
||||
// 미지원 타입: 파일명만 기록
|
||||
textContents.push(`\n[ATTACHMENT: ${file.name}]\n(지원하지 않는 파일 형식: ${type || 'unknown'})`);
|
||||
}
|
||||
}
|
||||
|
||||
// 추출된 텍스트를 프롬프트에 주입
|
||||
if (textContents.length > 0) {
|
||||
processedPrompt = `${processedPrompt}\n\n--- 첨부 파일 내용 ---${textContents.join('\n')}`;
|
||||
}
|
||||
imageFiles = images.length > 0 ? images : undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
await this._agent.handlePrompt(value, model, {
|
||||
await this._agent.handlePrompt(processedPrompt, model, {
|
||||
internetEnabled: internet,
|
||||
visionContent: files,
|
||||
visionContent: imageFiles,
|
||||
agentSkillContext,
|
||||
negativePrompt,
|
||||
designerContext,
|
||||
@@ -1894,19 +1973,15 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
await vscode.workspace.getConfiguration('g1nation').update('defaultModel', defaultModel, vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
|
||||
if (models.length > 0 && (!defaultModel || !models.includes(defaultModel))) {
|
||||
// [State Persistence Fix] 저장된 모델이 목록에 없을 때:
|
||||
// 즉시 강제 리셋하는 대신, 현재 모델 목록의 첫 번째를 '폴백 후보'로만 사용.
|
||||
// 단, defaultModel이 완전히 없는 경우에만 실제로 저장함.
|
||||
if (!defaultModel) {
|
||||
defaultModel = models[0];
|
||||
await vscode.workspace.getConfiguration('g1nation').update('defaultModel', defaultModel, vscode.ConfigurationTarget.Global);
|
||||
} else {
|
||||
// 저장된 모델명은 유지하고, UI에는 첫 번째 모델을 보여주되
|
||||
// 설정은 건드리지 않아 다음 번에 같은 모델이 다시 로드될 경우 복원 가능하도록 함
|
||||
logInfo('Saved model not found in current list, using first available as fallback.', { saved: defaultModel, fallback: models[0] });
|
||||
defaultModel = models[0];
|
||||
}
|
||||
if (models.length > 0 && !defaultModel) {
|
||||
// [State Persistence Fix v2] defaultModel이 완전히 비어있을 때만 첫 번째 모델로 설정
|
||||
defaultModel = models[0];
|
||||
await vscode.workspace.getConfiguration('g1nation').update('defaultModel', defaultModel, vscode.ConfigurationTarget.Global);
|
||||
} else if (models.length > 0 && defaultModel && !models.includes(defaultModel)) {
|
||||
// [State Persistence Fix v2] 저장된 모델이 로컬 엔진 목록에 없는 경우:
|
||||
// 강제 리셋하지 않고, 저장된 모델을 목록 선두에 추가하여 사용자 선택을 보존
|
||||
logInfo('Saved model not in local engine list. Preserving user selection.', { saved: defaultModel, localModels: models.slice(0, 3) });
|
||||
models.unshift(defaultModel);
|
||||
}
|
||||
|
||||
const defaultIdx = models.indexOf(defaultModel);
|
||||
@@ -3309,6 +3384,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
agentSel.onchange = () => {
|
||||
if (agentSel.value !== 'none') {
|
||||
vscode.postMessage({ type: 'getAgentContent', path: agentSel.value });
|
||||
// [State Persistence Fix] 에이전트 선택값을 즉시 백엔드에 저장
|
||||
vscode.postMessage({ type: 'saveAgentSelection', path: agentSel.value });
|
||||
if (editMode) agentConfigPanel.style.display = 'flex';
|
||||
} else {
|
||||
agentConfigPanel.style.display = 'none';
|
||||
@@ -3316,6 +3393,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
editAgentBtn.classList.remove('active');
|
||||
agentPrompt.value = '';
|
||||
negativePrompt.value = '';
|
||||
// [State Persistence Fix] 에이전트 해제도 즉시 저장
|
||||
vscode.postMessage({ type: 'saveAgentSelection', path: 'none' });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+6
-3
@@ -154,7 +154,7 @@ Never say "upload the source code", "provide the files", or "파일 내용을
|
||||
If access fails after trying, explain the failure and only then ask for an upload.
|
||||
|
||||
[STRICT GLOBAL RULES]
|
||||
1. [NO EMOJIS] Never use emojis, icons, or pictorial symbols. Professional text-only output. Exception: the 💡 icon in the suggestion header only.
|
||||
1. [NO EMOJIS - ABSOLUTE RULE] NEVER use ANY emojis, emoticons, Unicode pictorial symbols (including but not limited to emoji, kaomoji, Unicode icons), or decorative symbols anywhere in your response. NO EXCEPTIONS. Use plain text dashes (-) or asterisks (*) for bullets. Use plain markdown ## for headers. This rule overrides ALL other formatting instructions.
|
||||
2. [UNIQUE HEADINGS] Every markdown heading must be unique and appear exactly once.
|
||||
3. [NO INTERNAL LOGS] Never output <details>, "2nd Brain Trace", or "Debug JSON" blocks.
|
||||
4. [NO SECTION LEAKAGE] Never output sections named "요청 요약", "사용자 의도 추론", "프로젝트 기록 대상 확인", "핵심 확인 질문", or "근거 파일 경로".
|
||||
@@ -170,7 +170,7 @@ For conversational replies, quick facts, or simple updates — answer directly w
|
||||
- Root cause of the problem.
|
||||
- Concrete step-by-step instructions: what to change, which files to edit, which commands to run.
|
||||
|
||||
## 💡 제안 ← Optional. Only include if a meaningfully better alternative exists. Omit otherwise.
|
||||
## 제안 ← Optional. Only include if a meaningfully better alternative exists. Omit otherwise.
|
||||
|
||||
[FOLLOW-UP QUESTION RULES]
|
||||
A follow-up question is a precision tool, not a ritual.
|
||||
@@ -228,7 +228,10 @@ If neither condition is met, give a definitive answer and stop.
|
||||
3. When the user says "분석해줘", "봐줘", "확인해줘", "리뷰해줘" with a path — that IS the confirmation. Access the path immediately and run the full analysis in one continuous response. Do not pause to ask "진행할까요?" or "시작할까요?".`;
|
||||
|
||||
export function getSystemPrompt(): string {
|
||||
return BASE_SYSTEM_PROMPT;
|
||||
const now = new Date();
|
||||
const dateTimeStr = now.toLocaleString('ko-KR', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit', weekday: 'long', hour: '2-digit', minute: '2-digit' });
|
||||
const isoDate = now.toISOString().split('T')[0];
|
||||
return `${BASE_SYSTEM_PROMPT}\n\n[CURRENT DATE/TIME]\nToday: ${isoDate} (${dateTimeStr})\nUse this date as the absolute reference for any date-related calculations (e.g., "this week", "today", "yesterday").`;
|
||||
}
|
||||
|
||||
export const SYSTEM_PROMPT = BASE_SYSTEM_PROMPT;
|
||||
|
||||
Reference in New Issue
Block a user