mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
build(vscode-ide-companion): 添加 SCSS 支持
- 在 esbuild.js 中添加 SCSS 文件处理逻辑 - 在 package.json 中添加 sass 依赖 - 新增代码使用 sass 编译 SCSS 文件,并将其注入到页面中
This commit is contained in:
@@ -37,6 +37,7 @@ const esbuildProblemMatcherPlugin = {
|
||||
const cssInjectPlugin = {
|
||||
name: 'css-inject',
|
||||
setup(build) {
|
||||
// Handle CSS files
|
||||
build.onLoad({ filter: /\.css$/ }, async (args) => {
|
||||
const fs = await import('fs');
|
||||
const css = await fs.promises.readFile(args.path, 'utf8');
|
||||
@@ -49,6 +50,23 @@ const cssInjectPlugin = {
|
||||
loader: 'js',
|
||||
};
|
||||
});
|
||||
|
||||
// Handle SCSS files
|
||||
build.onLoad({ filter: /\.scss$/ }, async (args) => {
|
||||
const sass = await import('sass');
|
||||
const result = sass.compile(args.path, {
|
||||
loadPaths: [args.path.substring(0, args.path.lastIndexOf('/'))],
|
||||
});
|
||||
const css = result.css;
|
||||
return {
|
||||
contents: `
|
||||
const style = document.createElement('style');
|
||||
style.textContent = ${JSON.stringify(css)};
|
||||
document.head.appendChild(style);
|
||||
`,
|
||||
loader: 'js',
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"esbuild": "^0.25.3",
|
||||
"eslint": "^9.25.1",
|
||||
"npm-run-all2": "^8.0.2",
|
||||
"sass": "^1.94.1",
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
|
||||
@@ -298,6 +298,22 @@ button {
|
||||
padding: var(--app-spacing-medium);
|
||||
}
|
||||
|
||||
.thinking-message {
|
||||
background-color: var(--app-list-hover-background, rgba(100, 100, 255, 0.1));
|
||||
opacity: 0.8;
|
||||
font-style: italic;
|
||||
position: relative;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.thinking-message::before {
|
||||
content: "💭";
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.thinking-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
@@ -314,6 +330,70 @@ button {
|
||||
color: rgba(200, 200, 255, 0.9);
|
||||
}
|
||||
|
||||
/* Waiting message styles - similar to Claude Code thinking state */
|
||||
.message.waiting-message {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.waiting-message .message-content {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: 8px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Typing indicator for loading messages */
|
||||
.typing-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-right: 0;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.typing-dot, .thinking-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: var(--app-secondary-foreground);
|
||||
border-radius: 50%;
|
||||
margin-right: 0;
|
||||
opacity: 0.6;
|
||||
animation: typingPulse 1.4s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.typing-dot:nth-child(1),
|
||||
.thinking-dot:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.typing-dot:nth-child(2),
|
||||
.thinking-dot:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.typing-dot:nth-child(3),
|
||||
.thinking-dot:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes typingPulse {
|
||||
0%, 60%, 100% {
|
||||
transform: scale(0.7);
|
||||
opacity: 0.6;
|
||||
}
|
||||
30% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
opacity: 0.7;
|
||||
font-style: italic;
|
||||
color: var(--app-secondary-foreground);
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Scrollbar Styling
|
||||
=========================== */
|
||||
@@ -190,13 +190,15 @@ export const App: React.FC = () => {
|
||||
const [messages, setMessages] = useState<TextMessage[]>([]);
|
||||
const [inputText, setInputText] = useState('');
|
||||
const [isStreaming, setIsStreaming] = useState(false);
|
||||
const [_isWaitingForResponse, setIsWaitingForResponse] = useState(false);
|
||||
const [_loadingMessage, setLoadingMessage] = useState('');
|
||||
const [isWaitingForResponse, setIsWaitingForResponse] = useState(false);
|
||||
const [loadingMessage, setLoadingMessage] = useState('');
|
||||
const [currentStreamContent, setCurrentStreamContent] = useState('');
|
||||
const [qwenSessions, setQwenSessions] = useState<
|
||||
Array<Record<string, unknown>>
|
||||
>([]);
|
||||
const [currentSessionId, setCurrentSessionId] = useState<string | null>(null);
|
||||
const [currentSessionTitle, setCurrentSessionTitle] =
|
||||
useState<string>('Past Conversations');
|
||||
const [showSessionSelector, setShowSessionSelector] = useState(false);
|
||||
const [sessionSearchQuery, setSessionSearchQuery] = useState('');
|
||||
const [permissionRequest, setPermissionRequest] = useState<{
|
||||
@@ -336,6 +338,18 @@ export const App: React.FC = () => {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'thoughtChunk': {
|
||||
const chunkData = message.data;
|
||||
// Handle thought chunks for AI thinking display
|
||||
const thinkingMessage: TextMessage = {
|
||||
role: 'thinking',
|
||||
content: chunkData.content || chunkData.chunk || '',
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
setMessages((prev) => [...prev, thinkingMessage]);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'streamEnd':
|
||||
// Finalize the streamed message
|
||||
if (currentStreamContentRef.current) {
|
||||
@@ -347,12 +361,14 @@ export const App: React.FC = () => {
|
||||
setMessages((prev) => [...prev, assistantMessage]);
|
||||
}
|
||||
setIsStreaming(false);
|
||||
setIsWaitingForResponse(false); // Clear waiting state
|
||||
setCurrentStreamContent('');
|
||||
currentStreamContentRef.current = '';
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
setIsStreaming(false);
|
||||
setIsWaitingForResponse(false);
|
||||
break;
|
||||
|
||||
case 'permissionRequest':
|
||||
@@ -371,10 +387,30 @@ export const App: React.FC = () => {
|
||||
setQwenSessions(sessions);
|
||||
// If no current session is selected and there are sessions, select the first one
|
||||
if (!currentSessionId && sessions.length > 0) {
|
||||
const firstSession = sessions[0];
|
||||
const firstSessionId =
|
||||
(sessions[0].id as string) || (sessions[0].sessionId as string);
|
||||
(firstSession.id as string) || (firstSession.sessionId as string);
|
||||
const firstSessionTitle =
|
||||
(firstSession.title as string) ||
|
||||
(firstSession.name as string) ||
|
||||
'Past Conversations';
|
||||
if (firstSessionId) {
|
||||
setCurrentSessionId(firstSessionId);
|
||||
setCurrentSessionTitle(firstSessionTitle);
|
||||
}
|
||||
} else if (currentSessionId && sessions.length > 0) {
|
||||
// Update title for the current session if it exists in the list
|
||||
const currentSession = sessions.find(
|
||||
(s: Record<string, unknown>) =>
|
||||
(s.id as string) === currentSessionId ||
|
||||
(s.sessionId as string) === currentSessionId,
|
||||
);
|
||||
if (currentSession) {
|
||||
const title =
|
||||
(currentSession.title as string) ||
|
||||
(currentSession.name as string) ||
|
||||
'Past Conversations';
|
||||
setCurrentSessionTitle(title);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -391,6 +427,14 @@ export const App: React.FC = () => {
|
||||
message.data.sessionId,
|
||||
);
|
||||
}
|
||||
// Update current session title
|
||||
if (message.data.title || message.data.name) {
|
||||
const title =
|
||||
(message.data.title as string) ||
|
||||
(message.data.name as string) ||
|
||||
'Past Conversations';
|
||||
setCurrentSessionTitle(title);
|
||||
}
|
||||
// Load messages from the session
|
||||
if (message.data.messages) {
|
||||
console.log(
|
||||
@@ -463,6 +507,7 @@ export const App: React.FC = () => {
|
||||
vscode.postMessage({ type: 'newQwenSession', data: {} });
|
||||
setShowSessionSelector(false);
|
||||
setCurrentSessionId(null);
|
||||
setCurrentSessionTitle('Past Conversations'); // Reset title to default
|
||||
// Clear messages in UI
|
||||
setMessages([]);
|
||||
setCurrentStreamContent('');
|
||||
@@ -694,7 +739,7 @@ export const App: React.FC = () => {
|
||||
title="Past conversations"
|
||||
>
|
||||
<span className="button-content">
|
||||
<span className="button-text">Past Conversations</span>
|
||||
<span className="button-text">{currentSessionTitle}</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -734,14 +779,31 @@ export const App: React.FC = () => {
|
||||
<EmptyState />
|
||||
) : (
|
||||
<>
|
||||
{messages.map((msg, index) => (
|
||||
<div key={index} className={`message ${msg.role}`}>
|
||||
<div className="message-content">{msg.content}</div>
|
||||
<div className="message-timestamp">
|
||||
{new Date(msg.timestamp).toLocaleTimeString()}
|
||||
{messages.map((msg, index) => {
|
||||
// Special styling for thinking messages (Claude Code style)
|
||||
const messageClass =
|
||||
msg.role === 'thinking'
|
||||
? 'message assistant thinking-message'
|
||||
: `message ${msg.role}`;
|
||||
|
||||
return (
|
||||
<div key={index} className={messageClass}>
|
||||
<div className="message-content">
|
||||
{msg.role === 'thinking' && (
|
||||
<span className="thinking-indicator">
|
||||
<span className="thinking-dot"></span>
|
||||
<span className="thinking-dot"></span>
|
||||
<span className="thinking-dot"></span>
|
||||
</span>
|
||||
)}
|
||||
{msg.content}
|
||||
</div>
|
||||
<div className="message-timestamp">
|
||||
{new Date(msg.timestamp).toLocaleTimeString()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Tool Calls */}
|
||||
{Array.from(toolCalls.values()).map((toolCall) => (
|
||||
@@ -757,6 +819,20 @@ export const App: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Loading/Waiting Message - in message list */}
|
||||
{isWaitingForResponse && loadingMessage && (
|
||||
<div className="message assistant waiting-message">
|
||||
<div className="message-content">
|
||||
<span className="typing-indicator">
|
||||
<span className="typing-dot"></span>
|
||||
<span className="typing-dot"></span>
|
||||
<span className="typing-dot"></span>
|
||||
</span>
|
||||
<span className="loading-text">{loadingMessage}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isStreaming && currentStreamContent && (
|
||||
<div className="message assistant streaming">
|
||||
<div className="message-content">{currentStreamContent}</div>
|
||||
@@ -830,7 +906,7 @@ export const App: React.FC = () => {
|
||||
role="textbox"
|
||||
aria-label="Message input"
|
||||
aria-multiline="true"
|
||||
data-placeholder="Ask qwen to edit…"
|
||||
data-placeholder="Ask Qwen Code …"
|
||||
onInput={(e) => {
|
||||
const target = e.target as HTMLDivElement;
|
||||
setInputText(target.textContent || '');
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { App } from './App.js';
|
||||
import './App.css';
|
||||
import './App.scss';
|
||||
import './ClaudeCodeStyles.css';
|
||||
|
||||
const container = document.getElementById('root');
|
||||
|
||||
Reference in New Issue
Block a user