deploy: current vibn theia state
Made-with: Cursor
This commit is contained in:
10
packages/ai-history/.eslintrc.js
Normal file
10
packages/ai-history/.eslintrc.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
extends: [
|
||||
'../../configs/build.eslintrc.json'
|
||||
],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: 'tsconfig.json'
|
||||
}
|
||||
};
|
||||
32
packages/ai-history/README.md
Normal file
32
packages/ai-history/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
<div align='center'>
|
||||
|
||||
<br />
|
||||
|
||||
<img src='https://raw.githubusercontent.com/eclipse-theia/theia/master/logo/theia.svg?sanitize=true' alt='theia-ext-logo' width='100px' />
|
||||
|
||||
<h2>ECLIPSE THEIA - AI HISTORY EXTENSION</h2>
|
||||
|
||||
<hr />
|
||||
|
||||
</div>
|
||||
|
||||
## Description
|
||||
|
||||
The `@theia/ai-history` extension offers a framework for agents to record their requests and responses.
|
||||
It also offers a view to inspect the history.
|
||||
|
||||
## Additional Information
|
||||
|
||||
- [API documentation for `@theia/ai-history`](https://eclipse-theia.github.io/theia/docs/next/modules/_theia_ai-history.html)
|
||||
- [Theia - GitHub](https://github.com/eclipse-theia/theia)
|
||||
- [Theia - Website](https://theia-ide.org/)
|
||||
|
||||
## License
|
||||
|
||||
- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
|
||||
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
|
||||
|
||||
## Trademark
|
||||
|
||||
"Theia" is a trademark of the Eclipse Foundation
|
||||
<https://www.eclipse.org/theia>
|
||||
54
packages/ai-history/package.json
Normal file
54
packages/ai-history/package.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "@theia/ai-history",
|
||||
"version": "1.68.0",
|
||||
"description": "Theia - AI communication history",
|
||||
"dependencies": {
|
||||
"@theia/ai-core": "1.68.0",
|
||||
"@theia/ai-chat-ui": "1.68.0",
|
||||
"@theia/core": "1.68.0",
|
||||
"@theia/filesystem": "1.68.0",
|
||||
"@theia/output": "1.68.0",
|
||||
"@theia/workspace": "1.68.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"main": "lib/common",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"theiaExtensions": [
|
||||
{
|
||||
"frontend": "lib/browser/ai-history-frontend-module"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"theia-extension"
|
||||
],
|
||||
"license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/eclipse-theia/theia.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/eclipse-theia/theia/issues"
|
||||
},
|
||||
"homepage": "https://github.com/eclipse-theia/theia",
|
||||
"files": [
|
||||
"lib",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "theiaext build",
|
||||
"clean": "theiaext clean",
|
||||
"compile": "theiaext compile",
|
||||
"lint": "theiaext lint",
|
||||
"test": "theiaext test",
|
||||
"watch": "theiaext watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@theia/ext-scripts": "1.68.0"
|
||||
},
|
||||
"nyc": {
|
||||
"extends": "../../configs/nyc.json"
|
||||
},
|
||||
"gitHead": "21358137e41342742707f660b8e222f940a27652"
|
||||
}
|
||||
240
packages/ai-history/src/browser/ai-history-contribution.ts
Normal file
240
packages/ai-history/src/browser/ai-history-contribution.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource GmbH.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
import { FrontendApplication, codicon } from '@theia/core/lib/browser';
|
||||
import { AIViewContribution } from '@theia/ai-core/lib/browser';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { AIHistoryView } from './ai-history-widget';
|
||||
import { Command, CommandRegistry, Emitter, nls } from '@theia/core';
|
||||
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { LanguageModelService } from '@theia/ai-core';
|
||||
import { ChatViewWidget } from '@theia/ai-chat-ui/lib/browser/chat-view-widget';
|
||||
|
||||
export const AI_HISTORY_TOGGLE_COMMAND_ID = 'aiHistory:toggle';
|
||||
export const OPEN_AI_HISTORY_VIEW = Command.toLocalizedCommand({
|
||||
id: 'aiHistory:open',
|
||||
label: 'Open AI History view',
|
||||
});
|
||||
|
||||
export const AI_HISTORY_VIEW_SORT_CHRONOLOGICALLY = Command.toLocalizedCommand({
|
||||
id: 'aiHistory:sortChronologically',
|
||||
label: 'AI History: Sort chronologically',
|
||||
iconClass: codicon('arrow-down')
|
||||
});
|
||||
|
||||
export const AI_HISTORY_VIEW_SORT_REVERSE_CHRONOLOGICALLY = Command.toLocalizedCommand({
|
||||
id: 'aiHistory:sortReverseChronologically',
|
||||
label: 'AI History: Sort reverse chronologically',
|
||||
iconClass: codicon('arrow-up')
|
||||
});
|
||||
|
||||
export const AI_HISTORY_VIEW_TOGGLE_COMPACT = Command.toLocalizedCommand({
|
||||
id: 'aiHistory:toggleCompact',
|
||||
label: 'AI History: Toggle compact view',
|
||||
iconClass: codicon('list-flat')
|
||||
});
|
||||
|
||||
export const AI_HISTORY_VIEW_TOGGLE_RAW = Command.toLocalizedCommand({
|
||||
id: 'aiHistory:toggleRaw',
|
||||
label: 'AI History: Toggle raw view',
|
||||
iconClass: codicon('list-tree')
|
||||
});
|
||||
|
||||
export const AI_HISTORY_VIEW_TOGGLE_RENDER_NEWLINES = Command.toLocalizedCommand({
|
||||
id: 'aiHistory:toggleRenderNewlines',
|
||||
label: 'AI History: Interpret newlines',
|
||||
iconClass: codicon('newline')
|
||||
});
|
||||
|
||||
export const AI_HISTORY_VIEW_TOGGLE_HIDE_NEWLINES = Command.toLocalizedCommand({
|
||||
id: 'aiHistory:toggleHideNewlines',
|
||||
label: 'AI History: Stop interpreting newlines',
|
||||
iconClass: codicon('no-newline')
|
||||
});
|
||||
|
||||
export const AI_HISTORY_VIEW_CLEAR = Command.toLocalizedCommand({
|
||||
id: 'aiHistory:clear',
|
||||
label: 'AI History: Clear History',
|
||||
iconClass: codicon('clear-all')
|
||||
});
|
||||
|
||||
@injectable()
|
||||
export class AIHistoryViewContribution extends AIViewContribution<AIHistoryView> implements TabBarToolbarContribution {
|
||||
@inject(LanguageModelService) private languageModelService: LanguageModelService;
|
||||
|
||||
protected readonly chronologicalChangedEmitter = new Emitter<void>();
|
||||
protected readonly chronologicalStateChanged = this.chronologicalChangedEmitter.event;
|
||||
|
||||
protected readonly compactViewChangedEmitter = new Emitter<void>();
|
||||
protected readonly compactViewStateChanged = this.compactViewChangedEmitter.event;
|
||||
|
||||
protected readonly renderNewlinesChangedEmitter = new Emitter<void>();
|
||||
protected readonly renderNewlinesStateChanged = this.renderNewlinesChangedEmitter.event;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
widgetId: AIHistoryView.ID,
|
||||
widgetName: AIHistoryView.LABEL,
|
||||
defaultWidgetOptions: {
|
||||
area: 'bottom',
|
||||
rank: 100
|
||||
},
|
||||
toggleCommandId: AI_HISTORY_TOGGLE_COMMAND_ID,
|
||||
});
|
||||
}
|
||||
|
||||
async initializeLayout(_app: FrontendApplication): Promise<void> {
|
||||
await this.openView();
|
||||
}
|
||||
|
||||
override registerCommands(registry: CommandRegistry): void {
|
||||
super.registerCommands(registry);
|
||||
registry.registerCommand(OPEN_AI_HISTORY_VIEW, {
|
||||
execute: () => this.openView({ activate: true }),
|
||||
});
|
||||
registry.registerCommand(AI_HISTORY_VIEW_SORT_CHRONOLOGICALLY, {
|
||||
isEnabled: widget => this.withHistoryWidget(widget, historyView => !historyView.isChronological),
|
||||
isVisible: widget => this.withHistoryWidget(widget, historyView => !historyView.isChronological),
|
||||
execute: widget => this.withHistoryWidget(widget, historyView => {
|
||||
historyView.sortHistory(true);
|
||||
this.chronologicalChangedEmitter.fire();
|
||||
return true;
|
||||
})
|
||||
});
|
||||
registry.registerCommand(AI_HISTORY_VIEW_SORT_REVERSE_CHRONOLOGICALLY, {
|
||||
isEnabled: widget => this.withHistoryWidget(widget, historyView => historyView.isChronological),
|
||||
isVisible: widget => this.withHistoryWidget(widget, historyView => historyView.isChronological),
|
||||
execute: widget => this.withHistoryWidget(widget, historyView => {
|
||||
historyView.sortHistory(false);
|
||||
this.chronologicalChangedEmitter.fire();
|
||||
return true;
|
||||
})
|
||||
});
|
||||
registry.registerCommand(AI_HISTORY_VIEW_TOGGLE_COMPACT, {
|
||||
isEnabled: widget => this.withHistoryWidget(widget),
|
||||
isVisible: widget => this.withHistoryWidget(widget, historyView => !historyView.isCompactView),
|
||||
execute: widget => this.withHistoryWidget(widget, historyView => {
|
||||
historyView.toggleCompactView();
|
||||
this.compactViewChangedEmitter.fire();
|
||||
return true;
|
||||
})
|
||||
});
|
||||
registry.registerCommand(AI_HISTORY_VIEW_TOGGLE_RAW, {
|
||||
isEnabled: widget => this.withHistoryWidget(widget),
|
||||
isVisible: widget => this.withHistoryWidget(widget, historyView => historyView.isCompactView),
|
||||
execute: widget => this.withHistoryWidget(widget, historyView => {
|
||||
historyView.toggleCompactView();
|
||||
this.compactViewChangedEmitter.fire();
|
||||
return true;
|
||||
})
|
||||
});
|
||||
registry.registerCommand(AI_HISTORY_VIEW_TOGGLE_RENDER_NEWLINES, {
|
||||
isEnabled: widget => this.withHistoryWidget(widget),
|
||||
isVisible: widget => this.withHistoryWidget(widget, historyView => !historyView.isRenderNewlines),
|
||||
execute: widget => this.withHistoryWidget(widget, historyView => {
|
||||
historyView.toggleRenderNewlines();
|
||||
this.renderNewlinesChangedEmitter.fire();
|
||||
return true;
|
||||
})
|
||||
});
|
||||
registry.registerCommand(AI_HISTORY_VIEW_TOGGLE_HIDE_NEWLINES, {
|
||||
isEnabled: widget => this.withHistoryWidget(widget),
|
||||
isVisible: widget => this.withHistoryWidget(widget, historyView => historyView.isRenderNewlines),
|
||||
execute: widget => this.withHistoryWidget(widget, historyView => {
|
||||
historyView.toggleRenderNewlines();
|
||||
this.renderNewlinesChangedEmitter.fire();
|
||||
return true;
|
||||
})
|
||||
});
|
||||
registry.registerCommand(AI_HISTORY_VIEW_CLEAR, {
|
||||
isEnabled: widget => this.withHistoryWidget(widget),
|
||||
isVisible: widget => this.withHistoryWidget(widget),
|
||||
execute: widget => this.withHistoryWidget(widget, () => {
|
||||
this.clearHistory();
|
||||
return true;
|
||||
})
|
||||
});
|
||||
}
|
||||
public clearHistory(): void {
|
||||
this.languageModelService.sessions = [];
|
||||
}
|
||||
|
||||
protected withHistoryWidget(
|
||||
widget: unknown = this.tryGetWidget(),
|
||||
predicate: (output: AIHistoryView) => boolean = () => true
|
||||
): boolean | false {
|
||||
return widget instanceof AIHistoryView ? predicate(widget) : false;
|
||||
}
|
||||
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||
registry.registerItem({
|
||||
id: AI_HISTORY_VIEW_SORT_CHRONOLOGICALLY.id,
|
||||
command: AI_HISTORY_VIEW_SORT_CHRONOLOGICALLY.id,
|
||||
tooltip: nls.localize('theia/ai/history/sortChronologically/tooltip', 'Sort chronologically'),
|
||||
isVisible: widget => this.withHistoryWidget(widget),
|
||||
onDidChange: this.chronologicalStateChanged
|
||||
});
|
||||
registry.registerItem({
|
||||
id: AI_HISTORY_VIEW_SORT_REVERSE_CHRONOLOGICALLY.id,
|
||||
command: AI_HISTORY_VIEW_SORT_REVERSE_CHRONOLOGICALLY.id,
|
||||
tooltip: nls.localize('theia/ai/history/sortReverseChronologically/tooltip', 'Sort reverse chronologically'),
|
||||
isVisible: widget => this.withHistoryWidget(widget),
|
||||
onDidChange: this.chronologicalStateChanged
|
||||
});
|
||||
registry.registerItem({
|
||||
id: AI_HISTORY_VIEW_TOGGLE_COMPACT.id,
|
||||
command: AI_HISTORY_VIEW_TOGGLE_COMPACT.id,
|
||||
tooltip: nls.localize('theia/ai/history/toggleCompact/tooltip', 'Show compact view'),
|
||||
isVisible: widget => this.withHistoryWidget(widget, historyView => !historyView.isCompactView),
|
||||
onDidChange: this.compactViewStateChanged
|
||||
});
|
||||
registry.registerItem({
|
||||
id: AI_HISTORY_VIEW_TOGGLE_RAW.id,
|
||||
command: AI_HISTORY_VIEW_TOGGLE_RAW.id,
|
||||
tooltip: nls.localize('theia/ai/history/toggleRaw/tooltip', 'Show raw view'),
|
||||
isVisible: widget => this.withHistoryWidget(widget, historyView => historyView.isCompactView),
|
||||
onDidChange: this.compactViewStateChanged
|
||||
});
|
||||
registry.registerItem({
|
||||
id: AI_HISTORY_VIEW_TOGGLE_RENDER_NEWLINES.id,
|
||||
command: AI_HISTORY_VIEW_TOGGLE_RENDER_NEWLINES.id,
|
||||
tooltip: nls.localize('theia/ai/history/toggleRenderNewlines/tooltip', 'Interpret newlines'),
|
||||
isVisible: widget => this.withHistoryWidget(widget, historyView => !historyView.isRenderNewlines),
|
||||
onDidChange: this.renderNewlinesStateChanged
|
||||
});
|
||||
registry.registerItem({
|
||||
id: AI_HISTORY_VIEW_TOGGLE_HIDE_NEWLINES.id,
|
||||
command: AI_HISTORY_VIEW_TOGGLE_HIDE_NEWLINES.id,
|
||||
tooltip: nls.localize('theia/ai/history/toggleHideNewlines/tooltip', 'Stop interpreting newlines'),
|
||||
isVisible: widget => this.withHistoryWidget(widget, historyView => historyView.isRenderNewlines),
|
||||
onDidChange: this.renderNewlinesStateChanged
|
||||
});
|
||||
registry.registerItem({
|
||||
id: AI_HISTORY_VIEW_CLEAR.id,
|
||||
command: AI_HISTORY_VIEW_CLEAR.id,
|
||||
tooltip: nls.localize('theia/ai/history/clear/tooltip', 'Clear History of all agents'),
|
||||
isVisible: widget => this.withHistoryWidget(widget)
|
||||
});
|
||||
// Register the AI History view command for the chat view
|
||||
registry.registerItem({
|
||||
id: 'chat-view.' + OPEN_AI_HISTORY_VIEW.id,
|
||||
command: OPEN_AI_HISTORY_VIEW.id,
|
||||
tooltip: nls.localize('theia/ai/history/open-history-tooltip', 'Open AI history...'),
|
||||
group: 'ai-settings',
|
||||
priority: 1,
|
||||
isVisible: widget => this.activationService.isActive && widget instanceof ChatViewWidget
|
||||
});
|
||||
}
|
||||
}
|
||||
253
packages/ai-history/src/browser/ai-history-exchange-card.tsx
Normal file
253
packages/ai-history/src/browser/ai-history-exchange-card.tsx
Normal file
@@ -0,0 +1,253 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 STMicroelectronics and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
import {
|
||||
LanguageModelExchangeRequest,
|
||||
LanguageModelExchange,
|
||||
LanguageModelMonitoredStreamResponse,
|
||||
LanguageModelExchangeRequestResponse
|
||||
} from '@theia/ai-core/lib/common/language-model-interaction-model';
|
||||
import { nls } from '@theia/core';
|
||||
import * as React from '@theia/core/shared/react';
|
||||
|
||||
const getTextFromResponse = (response: LanguageModelExchangeRequestResponse): string => {
|
||||
// Handle monitored stream response
|
||||
if ('parts' in response) {
|
||||
let result = '';
|
||||
for (const chunk of response.parts) {
|
||||
if ('content' in chunk && chunk.content) {
|
||||
result += chunk.content;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Handle text response
|
||||
if ('text' in response) {
|
||||
return response.text;
|
||||
}
|
||||
|
||||
// Handle parsed response
|
||||
if ('content' in response) {
|
||||
return response.content;
|
||||
}
|
||||
|
||||
return JSON.stringify(response);
|
||||
};
|
||||
|
||||
const renderTextWithNewlines = (text: string): React.ReactNode => text.split(/\\n|\n/).map((line, i) => (
|
||||
<React.Fragment key={i}>
|
||||
{i > 0 && <br />}
|
||||
{line}
|
||||
</React.Fragment>
|
||||
));
|
||||
|
||||
const formatJson = (data: unknown): string => {
|
||||
try {
|
||||
return JSON.stringify(data, undefined, 2);
|
||||
} catch (error) {
|
||||
console.error('Error formatting JSON:', error);
|
||||
return 'Error formatting data';
|
||||
}
|
||||
};
|
||||
|
||||
const formatTimestamp = (timestamp: number | undefined): string =>
|
||||
timestamp ? new Date(timestamp).toLocaleString(undefined, {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
}) : 'N/A';
|
||||
|
||||
export interface ExchangeCardProps {
|
||||
exchange: LanguageModelExchange;
|
||||
selectedAgentId?: string;
|
||||
compactView?: boolean;
|
||||
renderNewlines?: boolean;
|
||||
}
|
||||
|
||||
export const ExchangeCard: React.FC<ExchangeCardProps> = ({ exchange, selectedAgentId, compactView = true, renderNewlines = false }) => {
|
||||
|
||||
const earliestTimestamp = exchange.requests.reduce((earliest, req) => {
|
||||
const timestamp = req.metadata.timestamp as number || 0;
|
||||
return timestamp && (!earliest || timestamp < earliest) ? timestamp : earliest;
|
||||
}, 0);
|
||||
|
||||
return (
|
||||
<div className="theia-card exchange-card"
|
||||
role="article"
|
||||
aria-label={`Exchange ${exchange.id}`}>
|
||||
<div className='theia-card-meta'>
|
||||
<span className='theia-card-request-id'>
|
||||
{nls.localizeByDefault('ID')}: {exchange.id}
|
||||
</span>
|
||||
{exchange.metadata.agent && (
|
||||
<span className='theia-card-agent-id'>
|
||||
{nls.localize('theia/ai/history/exchange-card/agentId', 'Agent')}: {exchange.metadata.agent}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className='theia-card-content'>
|
||||
<div className='requests-container'>
|
||||
{exchange.requests.map((request, index) => (
|
||||
<RequestCard
|
||||
key={request.id}
|
||||
request={request}
|
||||
index={index}
|
||||
totalRequests={exchange.requests.length}
|
||||
selectedAgentId={selectedAgentId}
|
||||
compactView={compactView}
|
||||
renderNewlines={renderNewlines}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className='theia-card-meta'>
|
||||
{earliestTimestamp > 0 && (
|
||||
<span className='theia-card-timestamp'>
|
||||
{nls.localize('theia/ai/history/exchange-card/timestamp', 'Started')}: {formatTimestamp(earliestTimestamp)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface RequestCardProps {
|
||||
request: LanguageModelExchangeRequest;
|
||||
index: number;
|
||||
totalRequests: number;
|
||||
selectedAgentId?: string;
|
||||
compactView?: boolean;
|
||||
renderNewlines?: boolean;
|
||||
}
|
||||
|
||||
const RequestCard: React.FC<RequestCardProps> = ({ request, index, totalRequests, selectedAgentId, compactView = true, renderNewlines = false }) => {
|
||||
const isFromDifferentAgent = selectedAgentId &&
|
||||
request.metadata.agent &&
|
||||
request.metadata.agent !== selectedAgentId;
|
||||
|
||||
const isStreamResponse = 'parts' in request.response;
|
||||
|
||||
const getRequestContent = () => {
|
||||
if (compactView) {
|
||||
const content = formatJson(request.request.messages);
|
||||
return (
|
||||
<div className="compact-response">
|
||||
<pre className={`formatted-json ${renderNewlines ? 'render-newlines' : ''}`}>
|
||||
{renderNewlines ? renderTextWithNewlines(content) : content}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const content = formatJson(request.request);
|
||||
return (
|
||||
<pre className={`formatted-json ${renderNewlines ? 'render-newlines' : ''}`}>
|
||||
{renderNewlines ? renderTextWithNewlines(content) : content}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getResponseContent = () => {
|
||||
if (compactView) {
|
||||
const content = getTextFromResponse(request.response);
|
||||
return (
|
||||
<div className="compact-response">
|
||||
<pre className={`formatted-json ${renderNewlines ? 'render-newlines' : ''}`}>
|
||||
{renderNewlines ? renderTextWithNewlines(content) : content}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
} else if (isStreamResponse) {
|
||||
const streamResponse = request.response as LanguageModelMonitoredStreamResponse;
|
||||
return streamResponse.parts.map((part, i) => (
|
||||
<div key={`part-${i}`} className="stream-part">
|
||||
<pre className={`formatted-json ${renderNewlines ? 'render-newlines' : ''}`}>
|
||||
{renderNewlines ? renderTextWithNewlines(JSON.stringify(part, undefined, 2)) : JSON.stringify(part, undefined, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
));
|
||||
} else {
|
||||
const content = formatJson(request.response);
|
||||
return (
|
||||
<pre className={`formatted-json ${renderNewlines ? 'render-newlines' : ''}`}>
|
||||
{renderNewlines ? renderTextWithNewlines(content) : content}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`request-card ${isFromDifferentAgent ? 'different-agent-opacity' : ''}`}>
|
||||
<div className='request-header'>
|
||||
{totalRequests > 1 && (
|
||||
<h3>{nls.localize('theia/ai/history/request-card/title', 'Request')} {index + 1}</h3>
|
||||
)}
|
||||
<div className='request-info'>
|
||||
<span className='request-id'>ID: {request.id}</span>
|
||||
{request.metadata.agent && (
|
||||
<span className={`request-agent ${isFromDifferentAgent ? 'different-agent-name' : ''}`}>
|
||||
{nls.localize('theia/ai/history/request-card/agent', 'Agent')}: {request.metadata.agent}
|
||||
</span>
|
||||
)}
|
||||
<span className='request-model'>
|
||||
{nls.localize('theia/ai/history/request-card/model', 'Model')}: {request.languageModel}
|
||||
</span>
|
||||
{!!request.metadata.promptVariantId && (
|
||||
<span className={`request-prompt-variant ${request.metadata.isPromptVariantCustomized ? 'customized' : ''}`}>
|
||||
{!!request.metadata.isPromptVariantCustomized && (
|
||||
<span className='customized-prefix'>
|
||||
[{nls.localize('theia/ai/history/edited', 'edited')}]{' '}
|
||||
</span>
|
||||
)}
|
||||
{nls.localize('theia/ai/history/request-card/promptVariant', 'Prompt Variant')}: {request.metadata.promptVariantId as string}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='request-content-container'>
|
||||
<details>
|
||||
<summary>
|
||||
{nls.localize('theia/ai/history/request-card/request', 'Request')}
|
||||
</summary>
|
||||
<div className='request-content'>
|
||||
{getRequestContent()}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
{nls.localize('theia/ai/history/request-card/response', 'Response')}
|
||||
</summary>
|
||||
<div className='response-content'>
|
||||
{getResponseContent()}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div className='request-meta'>
|
||||
{request.metadata.timestamp && (
|
||||
<span className='request-timestamp'>
|
||||
{nls.localize('theia/ai/history/request-card/timestamp', 'Timestamp')}: {formatTimestamp(request.metadata.timestamp as number)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource GmbH.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
import { ContainerModule } from '@theia/core/shared/inversify';
|
||||
import { bindViewContribution, WidgetFactory } from '@theia/core/lib/browser';
|
||||
import { AIHistoryViewContribution } from './ai-history-contribution';
|
||||
import { AIHistoryView } from './ai-history-widget';
|
||||
import '../../src/browser/style/ai-history.css';
|
||||
import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
|
||||
export default new ContainerModule(bind => {
|
||||
bindViewContribution(bind, AIHistoryViewContribution);
|
||||
|
||||
bind(AIHistoryView).toSelf();
|
||||
bind(WidgetFactory).toDynamicValue(context => ({
|
||||
id: AIHistoryView.ID,
|
||||
createWidget: () => context.container.get<AIHistoryView>(AIHistoryView)
|
||||
})).inSingletonScope();
|
||||
bind(TabBarToolbarContribution).toService(AIHistoryViewContribution);
|
||||
});
|
||||
194
packages/ai-history/src/browser/ai-history-widget.tsx
Normal file
194
packages/ai-history/src/browser/ai-history-widget.tsx
Normal file
@@ -0,0 +1,194 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource GmbH.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
import { Agent, AgentService, LanguageModelService, SessionEvent } from '@theia/ai-core';
|
||||
import { LanguageModelExchange } from '@theia/ai-core/lib/common/language-model-interaction-model';
|
||||
import { codicon, ReactWidget, StatefulWidget } from '@theia/core/lib/browser';
|
||||
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import { ExchangeCard } from './ai-history-exchange-card';
|
||||
import { SelectComponent, SelectOption } from '@theia/core/lib/browser/widgets/select-component';
|
||||
import { deepClone, nls } from '@theia/core';
|
||||
|
||||
namespace AIHistoryView {
|
||||
export interface State {
|
||||
chronological: boolean;
|
||||
compactView: boolean;
|
||||
renderNewlines: boolean;
|
||||
selectedAgentId?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class AIHistoryView extends ReactWidget implements StatefulWidget {
|
||||
@inject(LanguageModelService)
|
||||
protected languageModelService: LanguageModelService;
|
||||
@inject(AgentService)
|
||||
protected readonly agentService: AgentService;
|
||||
|
||||
public static ID = 'ai-history-widget';
|
||||
static LABEL = nls.localize('theia/ai/history/view/label', 'AI Agent History [Beta]');
|
||||
|
||||
protected _state: AIHistoryView.State = { chronological: false, compactView: true, renderNewlines: true };
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.id = AIHistoryView.ID;
|
||||
this.title.label = AIHistoryView.LABEL;
|
||||
this.title.caption = AIHistoryView.LABEL;
|
||||
this.title.closable = true;
|
||||
this.title.iconClass = codicon('history');
|
||||
}
|
||||
|
||||
protected get state(): AIHistoryView.State {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
protected set state(state: AIHistoryView.State) {
|
||||
this._state = state;
|
||||
this.update();
|
||||
}
|
||||
|
||||
storeState(): object {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
restoreState(oldState: object & Partial<AIHistoryView.State>): void {
|
||||
const copy = deepClone(this.state);
|
||||
if (oldState.chronological !== undefined) {
|
||||
copy.chronological = oldState.chronological;
|
||||
}
|
||||
if (oldState.compactView !== undefined) {
|
||||
copy.compactView = oldState.compactView;
|
||||
}
|
||||
if (oldState.renderNewlines !== undefined) {
|
||||
copy.renderNewlines = oldState.renderNewlines;
|
||||
}
|
||||
this.state = copy;
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.update();
|
||||
this.toDispose.push(this.languageModelService.onSessionChanged((event: SessionEvent) => this.historyContentUpdated(event)));
|
||||
this.selectAgent(this.agentService.getAllAgents()[0]);
|
||||
}
|
||||
|
||||
protected selectAgent(agent: Agent | undefined): void {
|
||||
this.state = { ...this.state, selectedAgentId: agent?.id };
|
||||
}
|
||||
|
||||
protected historyContentUpdated(event: SessionEvent): void {
|
||||
this.update();
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
const selectionChange = (value: SelectOption) => {
|
||||
this.selectAgent(this.agentService.getAllAgents().find(agent => agent.id === value.value));
|
||||
};
|
||||
const agents = this.agentService.getAllAgents();
|
||||
if (agents.length === 0) {
|
||||
return (
|
||||
<div className='agent-history-widget'>
|
||||
<div className='theia-card no-content'>{nls.localize('theia/ai/history/view/noAgent', 'No agent available.')}</div>
|
||||
</div >);
|
||||
}
|
||||
return (
|
||||
<div className='agent-history-widget'>
|
||||
<SelectComponent
|
||||
options={agents.map(agent => ({
|
||||
value: agent.id,
|
||||
label: agent.name,
|
||||
description: agent.description || ''
|
||||
}))}
|
||||
onChange={selectionChange}
|
||||
defaultValue={this.state.selectedAgentId} />
|
||||
<div className='agent-history'>
|
||||
{this.renderHistory()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderHistory(): React.ReactNode {
|
||||
if (!this.state.selectedAgentId) {
|
||||
return <div className='theia-card no-content'>{nls.localize('theia/ai/history/view/noAgentSelected', 'No agent selected.')}</div>;
|
||||
}
|
||||
|
||||
const exchanges = this.getExchangesByAgent(this.state.selectedAgentId);
|
||||
|
||||
if (exchanges.length === 0) {
|
||||
const selectedAgent = this.agentService.getAllAgents().find(agent => agent.id === this.state.selectedAgentId);
|
||||
return <div className='theia-card no-content'>
|
||||
{nls.localize('theia/ai/history/view/noHistoryForAgent', 'No history available for the selected agent \'{0}\'', selectedAgent?.name || this.state.selectedAgentId)}
|
||||
</div>;
|
||||
}
|
||||
|
||||
// Sort exchanges by timestamp (using the first sub-request's timestamp)
|
||||
const sortedExchanges = [...exchanges].sort((a, b) => {
|
||||
const aTimestamp = a.requests[0]?.metadata.timestamp as number || 0;
|
||||
const bTimestamp = b.requests[0]?.metadata.timestamp as number || 0;
|
||||
return this.state.chronological ? aTimestamp - bTimestamp : bTimestamp - aTimestamp;
|
||||
});
|
||||
|
||||
return sortedExchanges.map(exchange => (
|
||||
<ExchangeCard
|
||||
key={exchange.id}
|
||||
exchange={exchange}
|
||||
selectedAgentId={this.state.selectedAgentId}
|
||||
compactView={this.state.compactView}
|
||||
renderNewlines={this.state.renderNewlines}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all exchanges for a specific agent.
|
||||
* Includes all exchanges in which the agent is involved, either as the main exchange or as a sub-request.
|
||||
* @param agentId The agent ID to filter by
|
||||
*/
|
||||
protected getExchangesByAgent(agentId: string): LanguageModelExchange[] {
|
||||
return this.languageModelService.sessions.flatMap(session =>
|
||||
session.exchanges.filter(exchange =>
|
||||
exchange.metadata.agent === agentId ||
|
||||
exchange.requests.some(request => request.metadata.agent === agentId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public sortHistory(chronological: boolean): void {
|
||||
this.state = { ...deepClone(this.state), chronological: chronological };
|
||||
}
|
||||
|
||||
public toggleCompactView(): void {
|
||||
this.state = { ...deepClone(this.state), compactView: !this.state.compactView };
|
||||
}
|
||||
|
||||
public toggleRenderNewlines(): void {
|
||||
this.state = { ...deepClone(this.state), renderNewlines: !this.state.renderNewlines };
|
||||
}
|
||||
|
||||
get isChronological(): boolean {
|
||||
return this.state.chronological === true;
|
||||
}
|
||||
|
||||
get isCompactView(): boolean {
|
||||
return this.state.compactView === true;
|
||||
}
|
||||
|
||||
get isRenderNewlines(): boolean {
|
||||
return this.state.renderNewlines === true;
|
||||
}
|
||||
}
|
||||
237
packages/ai-history/src/browser/style/ai-history.css
Normal file
237
packages/ai-history/src/browser/style/ai-history.css
Normal file
@@ -0,0 +1,237 @@
|
||||
.agent-history-widget {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.agent-history-widget .theia-select-component {
|
||||
margin: 10px 0;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.agent-history {
|
||||
width: calc(80% + 16px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.theia-card {
|
||||
background-color: var(--theia-sideBar-background);
|
||||
border: 1px solid var(--theia-sideBarSectionHeader-border);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.theia-card-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.9em;
|
||||
padding: var(--theia-ui-padding) 0;
|
||||
}
|
||||
|
||||
.theia-card-content {
|
||||
color: var(--theia-font-color);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.theia-card-content h3 {
|
||||
font-size: var(--theia-ui-font-size1);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.theia-card-content pre {
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--theia-sideBarSectionHeader-border);
|
||||
background-color: var(--theia-terminal-background);
|
||||
overflow: visible;
|
||||
padding: 10px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.theia-card-request-id,
|
||||
.theia-card-timestamp {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.exchange-card {
|
||||
border-radius: 6px;
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.requests-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Request Card Styles */
|
||||
.request-card {
|
||||
background-color: var(--theia-editor-background);
|
||||
border: 1px solid var(--theia-sideBarSectionHeader-border);
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin-bottom: 15px;
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.request-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--theia-sideBarSectionHeader-border);
|
||||
}
|
||||
|
||||
.request-header h3 {
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.request-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.request-id,
|
||||
.request-agent,
|
||||
.request-model,
|
||||
.request-prompt-variant {
|
||||
font-size: 0.9em;
|
||||
color: var(--theia-descriptionForeground);
|
||||
white-space: nowrap;
|
||||
padding: 2px 6px;
|
||||
background-color: var(--theia-editor-inactiveSelectionBackground);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.request-prompt-variant.customized {
|
||||
color: var(--theia-editorWarning-foreground);
|
||||
background-color: var(--theia-inputValidation-warningBackground);
|
||||
border: 1px solid var(--theia-editorWarning-foreground);
|
||||
}
|
||||
|
||||
.request-content-container {
|
||||
width: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.request-content-container details {
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid var(--theia-editorWidget-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.request-content-container summary {
|
||||
padding: 8px 12px;
|
||||
background-color: var(--theia-editorWidget-background);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
display: block; /* Make the entire summary clickable */
|
||||
}
|
||||
|
||||
.request-content-container summary:hover {
|
||||
background-color: var(--theia-list-hoverBackground);
|
||||
}
|
||||
|
||||
.request-content,
|
||||
.response-content {
|
||||
padding: 10px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.request-content-container details {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.request-content-container summary {
|
||||
padding: 10px 15px;
|
||||
font-size: 1em;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.stream-part {
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px dashed var(--theia-sideBarSectionHeader-border);
|
||||
}
|
||||
|
||||
.stream-part:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.compact-response {
|
||||
padding: 8px;
|
||||
background-color: var(--theia-editor-background);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.formatted-json {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-family: var(--theia-ui-font-family-monospace);
|
||||
font-size: var(--theia-code-font-size);
|
||||
line-height: var(--theia-code-line-height);
|
||||
color: var(--theia-editor-foreground);
|
||||
}
|
||||
|
||||
.formatted-json.render-newlines {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.request-content-container summary::before {
|
||||
content: '▶';
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
transition: transform 0.2s;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.request-content-container details[open] summary::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.theia-card-agent-id {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.request-card.different-agent-opacity {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.request-agent.different-agent-name {
|
||||
font-style: italic;
|
||||
color: var(--theia-notificationsWarningIcon-foreground);
|
||||
}
|
||||
|
||||
/* Request meta information */
|
||||
.request-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.85em;
|
||||
color: var(--theia-descriptionForeground);
|
||||
margin-top: 10px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--theia-sideBarSectionHeader-border);
|
||||
}
|
||||
|
||||
.no-content {
|
||||
padding: 15px;
|
||||
color: var(--theia-descriptionForeground);
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.request-timestamp {
|
||||
font-size: 0.85em;
|
||||
color: var(--theia-descriptionForeground);
|
||||
}
|
||||
16
packages/ai-history/src/common/index.ts
Normal file
16
packages/ai-history/src/common/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2024 EclipseSource GmbH.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
28
packages/ai-history/src/package.spec.ts
Normal file
28
packages/ai-history/src/package.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 EclipseSource GmbH and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
/* note: this bogus test file is required so that
|
||||
we are able to run mocha unit tests on this
|
||||
package, without having any actual unit tests in it.
|
||||
This way a coverage report will be generated,
|
||||
showing 0% coverage, instead of no report.
|
||||
This file can be removed once we have real unit
|
||||
tests in place. */
|
||||
|
||||
describe('ai-history package', () => {
|
||||
|
||||
it('support code coverage statistics', () => true);
|
||||
});
|
||||
31
packages/ai-history/tsconfig.json
Normal file
31
packages/ai-history/tsconfig.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"extends": "../../configs/base.tsconfig",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../ai-chat-ui"
|
||||
},
|
||||
{
|
||||
"path": "../ai-core"
|
||||
},
|
||||
{
|
||||
"path": "../core"
|
||||
},
|
||||
{
|
||||
"path": "../filesystem"
|
||||
},
|
||||
{
|
||||
"path": "../output"
|
||||
},
|
||||
{
|
||||
"path": "../workspace"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user