keep versioning, revert mistral (will review separately)

This commit is contained in:
Andrew Pareles 2025-03-21 15:18:29 -07:00
parent 6d8f843b12
commit 22a16974f9
9 changed files with 115 additions and 221 deletions

8
package-lock.json generated
View file

@ -15,7 +15,7 @@
"@google/generative-ai": "^0.22.0",
"@microsoft/1ds-core-js": "^3.2.13",
"@microsoft/1ds-post-js": "^3.2.13",
"@mistralai/mistralai": "^1.5.1",
"@mistralai/mistralai": "^1.5.0",
"@parcel/watcher": "2.5.1",
"@types/semver": "^7.5.8",
"@vscode/deviceid": "^0.1.1",
@ -2540,9 +2540,9 @@
"integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ=="
},
"node_modules/@mistralai/mistralai": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.5.1.tgz",
"integrity": "sha512-Ie0EH4dAO11MEXR5N2kS2cgr+ycTWvqN/yP9bKrtmUEqjdcF4i7DLxtrFMUw5l2dOPhrkX93G4SziFiATPWu2w==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.5.0.tgz",
"integrity": "sha512-AIn8pwAwA/fDvEUvmkt+40zH1ZmfaG3Q7oUWl17GUEC1tU7ZPwYz8Cv9P59lyS1SisHdDSu81oknO7f1ywkz8Q==",
"dependencies": {
"zod-to-json-schema": "^3.24.1"
},

View file

@ -68,18 +68,15 @@
"extensions-ci": "node ./node_modules/gulp/bin/gulp.js extensions-ci",
"extensions-ci-pr": "node ./node_modules/gulp/bin/gulp.js extensions-ci-pr",
"perf": "node scripts/code-perf.js",
"update-build-ts-version": "npm install typescript@next && tsc -p ./build/tsconfig.build.json",
"void-version-patch": "node scripts/update-version.js patch && git add product.json && git commit -m \"Bump: voidVersion\" && git tag v$(node -e \"console.log(require('./product.json').voidVersion)\") && git push && git push --tags",
"void-version-minor": "node scripts/update-version.js minor && git add product.json && git commit -m \"Bump: voidVersion minor\" && git tag v$(node -e \"console.log(require('./product.json').voidVersion)\") && git push && git push --tags",
"void-version-major": "node scripts/update-version.js major && git add product.json && git commit -m \"Bump: voidVersion major\" && git tag v$(node -e \"console.log(require('./product.json').voidVersion)\") && git push && git push --tags"
},
"update-build-ts-version": "npm install typescript@next && tsc -p ./build/tsconfig.build.json"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
"@floating-ui/react": "^0.27.5",
"@google/generative-ai": "^0.22.0",
"@microsoft/1ds-core-js": "^3.2.13",
"@microsoft/1ds-post-js": "^3.2.13",
"@mistralai/mistralai": "^1.5.1",
"@mistralai/mistralai": "^1.5.0",
"@parcel/watcher": "2.5.1",
"@types/semver": "^7.5.8",
"@vscode/deviceid": "^0.1.1",

View file

@ -1,41 +1,38 @@
{
"nameShort": "Void",
"nameLong": "Void",
"voidVersion": "1.0.3",
"commit": "18d2d9f5ee4cb290bd392035680b9c6ea60eeb6b",
"date": "2025-03-17",
"applicationName": "void",
"dataFolderName": ".void-editor",
"win32MutexName": "voideditor",
"licenseName": "MIT",
"licenseUrl": "https://github.com/voideditor/void/blob/main/LICENSE.txt",
"serverLicenseUrl": "https://github.com/voideditor/void/blob/main/LICENSE.txt",
"serverGreeting": [],
"serverLicense": [],
"serverLicensePrompt": "",
"serverApplicationName": "void-server",
"serverDataFolderName": ".void-server",
"tunnelApplicationName": "void-tunnel",
"win32DirName": "Void",
"win32NameVersion": "Void",
"win32RegValueName": "VoidEditor",
"win32x64AppId": "{{9D394D01-1728-45A7-B997-A6C82C5452C3}",
"win32arm64AppId": "{{0668DD58-2BDE-4101-8CDA-40252DF8875D}",
"win32x64UserAppId": "{{8BED5DC1-6C55-46E6-9FE6-18F7E6F7C7F1}",
"win32arm64UserAppId": "{{F6C87466-BC82-4A8F-B0FF-18CA366BA4D8}",
"win32AppUserModelId": "Void.Editor",
"win32ShellNameShort": "V&oid",
"win32TunnelServiceMutex": "void-tunnelservice",
"win32TunnelMutex": "void-tunnel",
"darwinBundleIdentifier": "com.voideditor.code",
"linuxIconName": "void-editor",
"licenseFileName": "LICENSE.txt",
"reportIssueUrl": "https://github.com/voideditor/void/issues/new",
"nodejsRepository": "https://nodejs.org",
"urlProtocol": "void-editor",
"extensionsGallery": {
"serviceUrl": "https://open-vsx.org/vscode/gallery",
"itemUrl": "https://open-vsx.org/vscode/item"
},
"builtInExtensions": []
}
"nameShort": "Void",
"nameLong": "Void",
"applicationName": "void",
"dataFolderName": ".void-editor",
"win32MutexName": "voideditor",
"licenseName": "MIT",
"licenseUrl": "https://github.com/voideditor/void/blob/main/LICENSE.txt",
"serverLicenseUrl": "https://github.com/voideditor/void/blob/main/LICENSE.txt",
"serverGreeting": [],
"serverLicense": [],
"serverLicensePrompt": "",
"serverApplicationName": "void-server",
"serverDataFolderName": ".void-server",
"tunnelApplicationName": "void-tunnel",
"win32DirName": "Void",
"win32NameVersion": "Void",
"win32RegValueName": "VoidEditor",
"win32x64AppId": "{{9D394D01-1728-45A7-B997-A6C82C5452C3}",
"win32arm64AppId": "{{0668DD58-2BDE-4101-8CDA-40252DF8875D}",
"win32x64UserAppId": "{{8BED5DC1-6C55-46E6-9FE6-18F7E6F7C7F1}",
"win32arm64UserAppId": "{{F6C87466-BC82-4A8F-B0FF-18CA366BA4D8}",
"win32AppUserModelId": "Void.Editor",
"win32ShellNameShort": "V&oid",
"win32TunnelServiceMutex": "void-tunnelservice",
"win32TunnelMutex": "void-tunnel",
"darwinBundleIdentifier": "com.voideditor.code",
"linuxIconName": "void-editor",
"licenseFileName": "LICENSE.txt",
"reportIssueUrl": "https://github.com/voideditor/void/issues/new",
"nodejsRepository": "https://nodejs.org",
"urlProtocol": "void",
"extensionsGallery": {
"serviceUrl": "https://marketplace.visualstudio.com/_apis/public/gallery",
"itemUrl": "https://marketplace.visualstudio.com/items"
},
"builtInExtensions": []
}

View file

@ -1,27 +0,0 @@
const fs = require('fs');
const { execSync } = require('child_process');
const semver = require('semver');
function updateProductJson(type = 'patch') {
// Read product.json
const productJsonPath = './product.json';
const product = JSON.parse(fs.readFileSync(productJsonPath, 'utf8'));
// Update the version
product.voidVersion = semver.inc(product.voidVersion, type);
// Update the commit hash
product.commit = execSync('git rev-parse HEAD').toString().trim();
// Update the date
product.date = new Date().toISOString().split('T')[0];
// Write the modifications
fs.writeFileSync(productJsonPath, JSON.stringify(product, null, 2));
return product.voidVersion;
}
// Execute the update
const newVersion = updateProductJson(process.argv[2] || 'patch');
console.log(`Updated version: ${newVersion}`);

View file

@ -56,7 +56,7 @@ export type ExtensionVirtualWorkspaceSupport = {
export interface IProductConfiguration {
readonly version: string;
readonly voidVersion: string;
readonly voidVersion?: string; // Void added this
readonly date?: string;
readonly quality?: string;
readonly commit?: string;

View file

@ -51,14 +51,13 @@ export const defaultModelsOfProvider = {
'llama-3.1-8b-instant',
// 'qwen-2.5-coder-32b', // preview mode (experimental)
],
mistral: [ // https://docs.mistral.ai/getting-started/models/models_overview/
'codestral-latest',
'open-codestral-mamba',
'mistral-small-latest',
'mistral-large-latest',
'ministral-3b-latest',
'ministral-8b-latest',
],
// not supporting mistral right now- it's last on Void usage, and a huge pain to set up since it's nonstandard (it supports codestral FIM but it's on v1/fim/completions, etc)
// mistral: [ // https://docs.mistral.ai/getting-started/models/models_overview/
// 'codestral-latest',
// 'mistral-large-latest',
// 'ministral-3b-latest',
// 'ministral-8b-latest',
// ],
openAICompatible: [], // fallback
} as const satisfies Record<ProviderName, string[]>
@ -122,32 +121,6 @@ const modelOptionsDefaults: ModelOptions = {
supportsReasoning: false,
}
const mistralModelOptions = {
'codestral-latest': {
contextWindow: 32_000,
maxOutputTokens: 4_096,
cost: { input: 0.00, output: 0.00 },
supportsFIM: true,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
supportsReasoning: false,
},
'mistral-large-latest': {
contextWindow: 32_000,
maxOutputTokens: 4_096,
cost: { input: 0.00, output: 0.00 },
supportsFIM: false,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
supportsReasoning: false,
}
} as const satisfies { [s: string]: ModelOptions }
const mistralSettings: ProviderSettings = {
...mistralModelOptions,
modelOptions: {},
modelOptionsFallback: (modelName) => extensiveModelFallback(modelName),
}
const openSourceModelOptions_assumingOAICompat = {
'deepseekR1': {
@ -162,6 +135,12 @@ const openSourceModelOptions_assumingOAICompat = {
supportsTools: false,
supportsReasoning: false,
},
'codestral': {
supportsFIM: true,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
supportsReasoning: false,
},
// llama
'llama3': {
supportsFIM: false,
@ -213,9 +192,11 @@ const openSourceModelOptions_assumingOAICompat = {
supportsTools: false,
supportsReasoning: false,
},
...mistralModelOptions,
} as const satisfies { [s: string]: Partial<ModelOptions> }
const extensiveModelFallback: ProviderSettings['modelOptionsFallback'] = (modelName) => {
const toFallback = (opts: Omit<ModelOptions, 'cost'>): ModelOptions & { modelName: string } => {
return {
@ -233,12 +214,16 @@ const extensiveModelFallback: ProviderSettings['modelOptionsFallback'] = (modelN
if (modelName.includes('deepseek')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.deepseekCoderV2, contextWindow: 32_000, maxOutputTokens: 4_096, })
if (modelName.includes('llama3')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.llama3, contextWindow: 32_000, maxOutputTokens: 4_096, })
if (modelName.includes('qwen') && modelName.includes('2.5') && modelName.includes('coder')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['qwen2.5coder'], contextWindow: 32_000, maxOutputTokens: 4_096, })
if (modelName.includes('mistral')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['mistral-large-latest'] })
if (modelName.includes('codestral')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['codestral-latest'] })
if (modelName.includes('codestral')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.codestral, contextWindow: 32_000, maxOutputTokens: 4_096, })
if (/\bo1\b/.test(modelName) || /\bo3\b/.test(modelName)) return toFallback(openAIModelOptions['o1'])
return toFallback(modelOptionsDefaults)
}
// ---------------- ANTHROPIC ----------------
const anthropicModelOptions = {
'claude-3-7-sonnet-20250219': { // https://docs.anthropic.com/en/docs/about-claude/models/all-models#model-comparison-table
@ -369,13 +354,6 @@ const openAISettings: ProviderSettings = {
}
}
// ---------------- XAI ----------------
const xAIModelOptions = {
'grok-2': {
@ -577,20 +555,13 @@ const openRouterModelOptions_assumingOpenAICompat = {
supportsReasoning: false,
},
'mistralai/codestral-2501': {
...openSourceModelOptions_assumingOAICompat['codestral-latest'],
...openSourceModelOptions_assumingOAICompat.codestral,
contextWindow: 256_000,
maxOutputTokens: null,
cost: { input: 0.3, output: 0.9 },
supportsTools: 'openai-style',
supportsReasoning: false,
},
'mistralai/mistral-large-latest': {
...openSourceModelOptions_assumingOAICompat['mistral-large-latest'],
contextWindow: 256_000,
maxOutputTokens: null,
cost: { input: 0.3, output: 0.9 },
},
'qwen/qwen-2.5-coder-32b-instruct': {
...openSourceModelOptions_assumingOAICompat['qwen2.5coder'],
contextWindow: 33_000,
@ -619,6 +590,8 @@ const openRouterSettings: ProviderSettings = {
}
// ---------------- model settings of everything above ----------------
const modelSettingsOfProvider: { [providerName in ProviderName]: ProviderSettings } = {
@ -626,6 +599,7 @@ const modelSettingsOfProvider: { [providerName in ProviderName]: ProviderSetting
anthropic: anthropicSettings,
xAI: xAISettings,
gemini: geminiSettings,
// open source models
deepseek: deepseekSettings,
groq: groqSettings,
@ -635,7 +609,7 @@ const modelSettingsOfProvider: { [providerName in ProviderName]: ProviderSetting
vLLM: vLLMSettings,
ollama: ollamaSettings,
openAICompatible: openaiCompatible,
mistral: mistralSettings,
// googleVertex: {},
// microsoftAzure: {},
} as const

View file

@ -43,9 +43,6 @@ export const defaultProviderSettings = {
xAI: {
apiKey: ''
},
mistral: {
apiKey: ''
},
} as const
@ -147,11 +144,6 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn
title: 'Grok (xAI)',
}
}
else if (providerName === 'mistral') {
return {
title: 'Mistral.ai API',
}
}
throw new Error(`descOfProviderName: Unknown provider name: "${providerName}"`)
@ -178,8 +170,7 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
providerName === 'groq' ? 'gsk_key...' :
providerName === 'openAICompatible' ? 'sk-key...' :
providerName === 'xAI' ? 'xai-key...' :
providerName === 'mistral' ? 'key...' :
'',
'',
subTextMd: providerName === 'anthropic' ? 'Get your [API Key here](https://console.anthropic.com/settings/keys).' :
providerName === 'openAI' ? 'Get your [API Key here](https://platform.openai.com/api-keys).' :
@ -188,9 +179,8 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
providerName === 'gemini' ? 'Get your [API Key here](https://aistudio.google.com/apikey).' :
providerName === 'groq' ? 'Get your [API Key here](https://console.groq.com/keys).' :
providerName === 'xAI' ? 'Get your [API Key here](https://console.x.ai).' :
providerName === 'mistral' ? 'Get your [API Key here](https://console.mistral.ai/api-keys).' :
providerName === 'openAICompatible' ? undefined :
'',
providerName === 'openAICompatible' ? undefined :
'',
isPasswordField: true,
}
}
@ -298,12 +288,6 @@ export const defaultSettingsOfProvider: SettingsOfProvider = {
...modelInfoOfDefaultModelNames(defaultModelsOfProvider.openAICompatible),
_didFillInProviderSettings: undefined,
},
mistral: { // aggregator
...defaultCustomSettings,
...defaultProviderSettings.mistral,
...modelInfoOfDefaultModelNames(defaultModelsOfProvider.mistral),
_didFillInProviderSettings: undefined,
},
ollama: { // aggregator
...defaultCustomSettings,
...defaultProviderSettings.ollama,

View file

@ -6,13 +6,8 @@
import Anthropic from '@anthropic-ai/sdk';
import { Ollama } from 'ollama';
import OpenAI, { ClientOptions } from 'openai';
import { Model as OpenAIModel } from 'openai/resources/models.js';
// Mistral FIM
import { MistralCore } from "@mistralai/mistralai/core.js";
import { fimComplete } from "@mistralai/mistralai/funcs/fimComplete.js";
//
import { extractReasoningOnFinalMessage, extractReasoningOnTextWrapper } from '../../common/helpers/extractCodeFromResult.js';
import { LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText } from '../../common/sendLLMMessageTypes.js';
import { defaultProviderSettings, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
@ -117,16 +112,12 @@ const newOpenAICompatibleSDK = ({ settingsOfProvider, providerName, includeInPay
const thisConfig = settingsOfProvider[providerName]
return new OpenAI({ baseURL: 'https://api.x.ai/v1', apiKey: thisConfig.apiKey, ...commonPayloadOpts })
}
else if (providerName === 'mistral') {
const thisConfig = settingsOfProvider[providerName]
return new OpenAI({ baseURL: 'https://api.mistral.ai/v1', apiKey: thisConfig.apiKey, ...commonPayloadOpts })
}
else throw new Error(`Void providerName was invalid: ${providerName}.`)
}
const _sendOpenAICompatibleFIM = ({ messages: messages_, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, aiInstructions, modelSelectionOptions, }: SendFIMParams_Internal) => {
const _sendOpenAICompatibleFIM = ({ messages: messages_, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, aiInstructions, }: SendFIMParams_Internal) => {
const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_)
if (!supportsFIM) {
if (modelName === modelName_)
@ -148,7 +139,6 @@ const _sendOpenAICompatibleFIM = ({ messages: messages_, onFinalMessage, onError
max_tokens: messages.maxTokens,
})
.then(async response => {
const fullText = response.choices[0]?.text
onFinalMessage({ fullText, fullReasoning: '', anthropicReasoning: null });
})
@ -158,7 +148,10 @@ const _sendOpenAICompatibleFIM = ({ messages: messages_, onFinalMessage, onError
})
}
const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, aiInstructions, modelSelectionOptions, tools: tools_ }: SendChatParams_Internal) => {
const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, aiInstructions, tools: tools_ }: SendChatParams_Internal) => {
const {
modelName,
supportsReasoning,
@ -471,56 +464,6 @@ const sendOllamaFIM = ({ messages: messages_, onFinalMessage, onError, settingsO
})
}
//////// MISTRAL ////////
const _sendMistralChat = ({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, aiInstructions, modelSelectionOptions }: SendChatParams_Internal) => {
_sendOpenAICompatibleChat({
messages: messages_,
onText,
onFinalMessage,
onError,
settingsOfProvider,
modelName: modelName_,
_setAborter,
providerName,
aiInstructions,
modelSelectionOptions
});
}
const _sendMistralFIM = ({ messages: messages_, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, aiInstructions, modelSelectionOptions }: SendFIMParams_Internal) => {
const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_)
if (!supportsFIM) {
if (modelName === modelName_)
onError({ message: `Model ${modelName} does not support FIM.`, fullError: null })
else
onError({ message: `Model ${modelName_} (${modelName}) does not support FIM.`, fullError: null })
return
}
prepareFIMMessage({ messages: messages_, aiInstructions })
const mistral = new MistralCore({ apiKey: settingsOfProvider.mistral.apiKey })
fimComplete(
mistral, {
model: modelName,
prompt: messages_.prefix,
suffix: messages_.suffix,
stream: false,
topP: 1,
stop: messages_.stopTokens
},
)
.then(async response => {
let content = response?.ok ? response.value.choices?.[0]?.message?.content : '';
const fullText = typeof content === 'string' ? content :
Array.isArray(content) ? content.map(chunk => chunk.type === 'text' ? chunk.text : '').join('') : '';
onFinalMessage({ fullText, fullReasoning: '', anthropicReasoning: null });
})
.catch(error => {
onError({ message: error + '', fullError: error });
})
}
type CallFnOfProvider = {
@ -552,6 +495,11 @@ export const sendLLMMessageToProviderImplementation = {
sendFIM: null,
list: null,
},
// mistral: {
// sendChat: , // TODO
// sendFIM: , // TODO // https://docs.mistral.ai/api/#tag/fim
// list: null,
// },
ollama: {
sendChat: (params) => _sendOpenAICompatibleChat(params),
sendFIM: sendOllamaFIM,
@ -582,9 +530,30 @@ export const sendLLMMessageToProviderImplementation = {
sendFIM: null,
list: null,
},
mistral: {
sendChat: (params) => _sendMistralChat(params),
sendFIM: (params) => _sendMistralFIM(params),
list: null,
},
} satisfies CallFnOfProvider
/*
FIM info (this may be useful in the future with vLLM, but in most cases the only way to use FIM is if the provider explicitly supports it):
qwen2.5-coder https://ollama.com/library/qwen2.5-coder/blobs/e94a8ecb9327
<|fim_prefix|>{{ .Prompt }}<|fim_suffix|>{{ .Suffix }}<|fim_middle|>
codestral https://ollama.com/library/codestral/blobs/51707752a87c
[SUFFIX]{{ .Suffix }}[PREFIX] {{ .Prompt }}
deepseek-coder-v2 https://ollama.com/library/deepseek-coder-v2/blobs/22091531faf0
<fimbegin>{{ .Prompt }}<fimhole>{{ .Suffix }}<fimend>
starcoder2 https://ollama.com/library/starcoder2/blobs/3b190e68fefe
<file_sep>
<fim_prefix>
{{ .Prompt }}<fim_suffix>{{ .Suffix }}<fim_middle>
<|end_of_text|>
codegemma https://ollama.com/library/codegemma:2b/blobs/48d9a8140749
<|fim_prefix|>{{ .Prompt }}<|fim_suffix|>{{ .Suffix }}<|fim_middle|>
*/

View file

@ -71,7 +71,7 @@ export class NativeDialogHandler extends AbstractDialogHandler {
async about(): Promise<void> {
let version = this.productService.version;
let voidVersion = this.productService.voidVersion || 'Unknown';
let voidVersion = this.productService.voidVersion;
if (this.productService.target) {
version = `${version} (${this.productService.target} setup)`;
} else if (this.productService.darwinUniversalAssetId) {
@ -82,8 +82,8 @@ export class NativeDialogHandler extends AbstractDialogHandler {
const detailString = (useAgo: boolean): string => {
return localize({ key: 'aboutDetail', comment: ['Electron, Chromium, Node.js and V8 are product names that need no translation'] },
"Void : {0}\nVSCode Version: {1}\nCommit: {2}\nDate: {3}\nElectron: {4}\nElectronBuildId: {5}\nChromium: {6}\nNode.js: {7}\nV8: {8}\nOS: {9}",
voidVersion,
"Void Version: {0}\nVSCode Version: {1}\nCommit: {2}\nDate: {3}\nElectron: {4}\nElectronBuildId: {5}\nChromium: {6}\nNode.js: {7}\nV8: {8}\nOS: {9}",
voidVersion || 'Unknown',
version,
this.productService.commit || 'Unknown',
this.productService.date ? `${this.productService.date}${useAgo ? ' (' + fromNow(new Date(this.productService.date), true) + ')' : ''}` : 'Unknown',