diff --git a/.changeset/nine-moments-rule.md b/.changeset/nine-moments-rule.md new file mode 100644 index 00000000000..e80630a3665 --- /dev/null +++ b/.changeset/nine-moments-rule.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Add a button to view the latest release notes to the extension diff --git a/.kilocode/rules/reactRules.md b/.kilocode/rules/reactRules.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/storybook/stories/ReleaseNotesModal.stories.tsx b/apps/storybook/stories/ReleaseNotesModal.stories.tsx new file mode 100644 index 00000000000..0f0029d862b --- /dev/null +++ b/apps/storybook/stories/ReleaseNotesModal.stories.tsx @@ -0,0 +1,60 @@ +// kilocode_change - new file: Storybook story for Release Notes Modal component +import type { Meta, StoryObj } from "@storybook/react-vite" +import { ReleaseNotesModal } from "@/components/release-notes/ReleaseNotesModal" +import { sampleReleaseNotes } from "./sampleReleaseNotes" + +const meta = { + title: "Components/ReleaseNotesModal", + component: ReleaseNotesModal, + argTypes: { + isOpen: { + control: { type: "boolean" }, + }, + loading: { + control: { type: "boolean" }, + }, + currentVersion: { + control: { type: "text" }, + }, + }, + parameters: { + disableChromaticDualThemeSnapshot: true, + }, + args: { + isOpen: true, + currentVersion: "4.106.0", + releases: sampleReleaseNotes, + loading: false, + onClose: () => console.log("Modal closed"), + onMarkAsViewed: (version: string) => console.log("Version marked as viewed:", version), + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + releases: sampleReleaseNotes, + }, +} + +export const SingleRelease: Story = { + args: { + releases: [sampleReleaseNotes[0]], + }, +} + +export const Loading: Story = { + args: { + releases: [], + loading: true, + }, +} + +export const EmptyReleases: Story = { + args: { + releases: [], + loading: false, + }, +} diff --git a/apps/storybook/stories/ShowReleaseNotesButton.stories.tsx b/apps/storybook/stories/ShowReleaseNotesButton.stories.tsx new file mode 100644 index 00000000000..88b2f3a6414 --- /dev/null +++ b/apps/storybook/stories/ShowReleaseNotesButton.stories.tsx @@ -0,0 +1,51 @@ +// kilocode_change - new file: Storybook story for Show Release Notes Button Inner component +import type { Meta, StoryObj } from "@storybook/react-vite" +import { fn } from "storybook/test" +import { ShowReleaseNotesButtonInner } from "@/components/release-notes/ShowReleaseNotesButtonInner" +import { Bell } from "lucide-react" + +const meta = { + title: "Components/ShowReleaseNotesButton", + component: ShowReleaseNotesButtonInner, + parameters: { + layout: "centered", + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: { + buttonText: { + control: { type: "text" }, + }, + showBadge: { + control: { type: "boolean" }, + }, + className: { + control: { type: "text" }, + }, + }, + args: { + onClick: fn(), + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + buttonText: "View Release Notes", + showBadge: false, + }, +} + +export const WithBadge: Story = { + args: { + buttonText: "View Release Notes", + showBadge: true, + }, +} diff --git a/apps/storybook/stories/sampleReleaseNotes.tsx b/apps/storybook/stories/sampleReleaseNotes.tsx new file mode 100644 index 00000000000..ae12b7fb60d --- /dev/null +++ b/apps/storybook/stories/sampleReleaseNotes.tsx @@ -0,0 +1,39 @@ +// kilocode_change - new file: Sample release notes data for Storybook testing +import { ReleaseNote } from "@roo-code/types" + +export const sampleReleaseNotes: ReleaseNote[] = [ + { + version: "4.106.0", + changes: [ + { + description: "Preliminary support for native tool calling (a.k.a native function calling) was added", + prNumber: 2833, + commitHash: "0b8ef46", + author: "mcowger", + }, + { + description: "CMD-I now invokes the agent so you can give it more complex prompts", + prNumber: 3050, + commitHash: "357d438", + author: "markijbema", + }, + ], + }, + { + version: "4.105.0", + changes: [ + { + description: "Improve the edit chat area to allow context and file drag and drop when editing messages", + prNumber: 3005, + commitHash: "b87ae9c", + author: "kevinvandijk", + }, + { + description: "A warning is now shown when the webview memory usage crosses 90% of the limit", + prNumber: 3046, + commitHash: "1bd934f", + author: "chrarnoldus", + }, + ], + }, +] diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 5a8ef907312..545ba68967a 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -175,6 +175,7 @@ export const globalSettingsSchema = z.object({ customSupportPrompts: customSupportPromptsSchema.optional(), enhancementApiConfigId: z.string().optional(), dismissedNotificationIds: z.string().array().optional(), // kilocode_change + lastViewedReleaseVersion: z.string().optional(), // kilocode_change commitMessageApiConfigId: z.string().optional(), // kilocode_change terminalCommandApiConfigId: z.string().optional(), // kilocode_change ghostServiceSettings: ghostServiceSettingsSchema, // kilocode_change diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index c0997ad5ae5..218767ba956 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -25,6 +25,7 @@ export * from "./type-fu.js" export * from "./vscode.js" export * from "./kilocode/kilocode.js" export * from "./kilocode/native-function-calling.js" +export * from "./release-notes.js" // kilocode_change export * from "./usage-tracker.js" // kilocode_change export * from "./providers/index.js" diff --git a/packages/types/src/release-notes.ts b/packages/types/src/release-notes.ts new file mode 100644 index 00000000000..4d9b54bf98b --- /dev/null +++ b/packages/types/src/release-notes.ts @@ -0,0 +1,17 @@ +// kilocode_change - new file +export interface ReleaseItem { + description: string + prNumber?: number + commitHash?: string + author?: string +} + +export interface ReleaseNote { + version: string + changes: ReleaseItem[] +} + +export interface ReleaseData { + currentVersion: string + releases: ReleaseNote[] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7513e98fd50..31cffcd82f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2216,9 +2216,6 @@ importers: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@4.1.6) - unist-util-visit: - specifier: ^5.0.0 - version: 5.0.0 use-sound: specifier: ^5.0.0 version: 5.0.0(react@18.3.1) @@ -2304,6 +2301,12 @@ importers: jsdom: specifier: ^26.0.0 version: 26.1.0 + remark: + specifier: ^15.0.1 + version: 15.0.1 + remark-parse: + specifier: ^11.0.0 + version: 11.0.0 storybook: specifier: ^8.4.7 version: 8.6.14(prettier@3.5.3) @@ -2313,6 +2316,9 @@ importers: typescript: specifier: 5.8.3 version: 5.8.3 + unist-util-visit: + specifier: ^5.0.0 + version: 5.0.0 vite: specifier: 6.3.6 version: 6.3.6(@types/node@20.17.57)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.19.4)(yaml@2.8.0) @@ -16589,6 +16595,9 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + remark@15.0.1: + resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} + remove-bom-buffer@3.0.0: resolution: {integrity: sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==} engines: {node: '>=0.10.0'} @@ -37541,6 +37550,15 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + remark@15.0.1: + dependencies: + '@types/mdast': 4.0.4 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + remove-bom-buffer@3.0.0: dependencies: is-buffer: 1.1.6 diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index a127eae5e0e..45d0c950f10 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -360,6 +360,7 @@ export type ExtensionState = Pick< | "commitMessageApiConfigId" // kilocode_change | "terminalCommandApiConfigId" // kilocode_change | "dismissedNotificationIds" // kilocode_change + | "lastViewedReleaseVersion" // kilocode_change | "ghostServiceSettings" // kilocode_change | "autoPurgeEnabled" // kilocode_change | "autoPurgeDefaultRetentionDays" // kilocode_change diff --git a/webview-ui/.gitignore b/webview-ui/.gitignore index 1f81cba3f58..a85ce9a6ece 100644 --- a/webview-ui/.gitignore +++ b/webview-ui/.gitignore @@ -11,6 +11,9 @@ # production /build +# kilocode_change - Generated release notes JSON (but keep .d.ts files) +/src/generated/**/*.json + # misc .DS_Store .env.local diff --git a/webview-ui/package.json b/webview-ui/package.json index ad2498e7ca4..b1a53d089f9 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -32,9 +32,9 @@ "@roo-code/types": "workspace:^", "@tailwindcss/vite": "^4.0.0", "@tanstack/react-query": "^5.68.0", + "@types/qrcode": "^1.5.5", "@types/seedrandom": "^3.0.8", "@use-gesture/react": "^10.3.1", - "@types/qrcode": "^1.5.5", "@vscode/codicons": "^0.0.36", "@vscode/webview-ui-toolkit": "^1.4.0", "axios": "^1.12.0", @@ -80,7 +80,6 @@ "tailwind-merge": "^3.0.0", "tailwindcss": "^4.0.0", "tailwindcss-animate": "^1.0.7", - "unist-util-visit": "^5.0.0", "use-sound": "^5.0.0", "vscode-material-icons": "^0.1.1", "vscrui": "^0.2.2", @@ -111,9 +110,12 @@ "jest-environment-jsdom": "^29.7.0", "jest-simple-dot-reporter": "^1.0.5", "jsdom": "^26.0.0", + "remark": "^15.0.1", + "remark-parse": "^11.0.0", "storybook": "^8.4.7", "ts-jest": "^29.2.5", "typescript": "5.8.3", + "unist-util-visit": "^5.0.0", "vite": "6.3.6", "vitest": "^3.2.3" } diff --git a/webview-ui/scripts/generate-release-notes.mjs b/webview-ui/scripts/generate-release-notes.mjs new file mode 100644 index 00000000000..ca5050ecfb4 --- /dev/null +++ b/webview-ui/scripts/generate-release-notes.mjs @@ -0,0 +1,138 @@ +#!/usr/bin/env node +// kilocode_change - new file: Build-time script to parse CHANGELOG.md and generate releases.json + +import fs from 'fs' +import path from 'path' +import process from 'process' +import { fileURLToPath } from 'url' +import { remark } from 'remark' +import { visit } from 'unist-util-visit' + +// Limit to the last 10 releases to keep build size manageable +const MAX_RELEASES = 10 + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +const changelogPath = path.resolve(__dirname, '../../CHANGELOG.md') +const outputDir = path.resolve(__dirname, '../src/generated/releases') + +console.log('🚀 Starting changelog parsing...') +console.log('📖 Reading changelog from:', changelogPath) +console.log('📁 Output directory:', outputDir) + +// Check if changelog exists +if (!fs.existsSync(changelogPath)) { + console.error('❌ Changelog not found at:', changelogPath) + process.exit(1) +} + +// Read and parse changelog +const changelogContent = fs.readFileSync(changelogPath, 'utf-8') +console.log('📄 Changelog loaded, length:', changelogContent.length, 'characters') + +const tree = remark().parse(changelogContent) +const releases = [] +let currentRelease = null + +// Traverse the AST to extract releases +visit(tree, (node) => { + // Look for version headers (## [vX.X.X]) + if (node.type === 'heading' && node.depth === 2) { + const text = node.children?.[0]?.value + const versionMatch = text?.match(/^\[v(\d+\.\d+\.\d+)\]$/) + + if (versionMatch) { + if (currentRelease && currentRelease.changes.length > 0) { + releases.push(currentRelease) + } + currentRelease = { version: versionMatch[1], changes: [] } + } + } + + // Look for list items (changes) + if (node.type === 'listItem' && currentRelease) { + const change = parseChangeFromListItem(node) + if (change) { + currentRelease.changes.push(change) + } + } +}) + +// Don't forget the last release +if (currentRelease && currentRelease.changes.length > 0) { + releases.push(currentRelease) +} + +function parseChangeFromListItem(node) { + let description = '' + let prNumber = null + let commitHash = null + let author = null + + // Extract text and links from the list item + visit(node, (child) => { + if (child.type === 'text') { + description += child.value + } else if (child.type === 'link') { + const url = child.url + const text = child.children?.[0]?.value + + // Extract PR number + if (url?.includes('/pull/')) { + prNumber = parseInt(text?.replace('#', '')) + } + // Extract commit hash + else if (url?.includes('/commit/')) { + commitHash = text?.replace(/`/g, '') + } + // Extract author + else if (url?.includes('github.com/') && text?.startsWith('@')) { + author = text.replace('@', '') + } + + description += text + } + }) + + // Extract the actual description after "! - " + const descMatch = description.match(/! - (.+)$/) + if (descMatch) { + description = descMatch[1].trim() + } + + // Only return changes that have a PR number (main changes, not patch notes) + if (!prNumber) { + return null + } + + return { + description: description.trim(), + ...(prNumber && { prNumber }), + ...(commitHash && { commitHash }), + ...(author && { author }) + } +} + +const limitedReleases = releases.slice(0, MAX_RELEASES) +console.log(`✅ Found ${releases.length} releases`) +console.log(`🔢 Limiting to ${limitedReleases.length} most recent releases (from ${releases.length} total)`) + +// Create output directory +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }) + console.log('📁 Created output directory') +} + +// Generate single releases.json file with current version and all releases +const releaseData = { + currentVersion: limitedReleases[0]?.version || "0.0.0", + releases: limitedReleases +} + +const releasesPath = path.join(outputDir, 'releases.json') +fs.writeFileSync(releasesPath, JSON.stringify(releaseData, null, 2)) + +console.log(`💾 Generated releases.json with ${limitedReleases.length} releases`) +console.log(`📋 Current version: ${releaseData.currentVersion}`) +console.log('🎉 Changelog parsing completed successfully!') diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index ad65b8310d1..d7902f4d129 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -32,6 +32,7 @@ import { TooltipProvider } from "./components/ui/tooltip" import { STANDARD_TOOLTIP_DELAY } from "./components/ui/standard-tooltip" import { useKiloIdentity } from "./utils/kilocode/useKiloIdentity" import { MemoryWarningBanner } from "./kilocode/MemoryWarningBanner" +import { AutoReleaseNotesChecker } from "./components/release-notes" // kilocode_change type Tab = "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account" | "cloud" | "profile" // kilocode_change: add "profile" @@ -282,6 +283,10 @@ const App = () => { <> {/* kilocode_change: add MemoryWarningBanner */} + + {/* kilocode_change: Auto Release Notes Checker - only show if user has API configured */} + {apiConfiguration && } + {tab === "modes" && switchTab("chat")} />} {tab === "mcp" && switchTab("chat")} />} {tab === "history" && switchTab("chat")} />} diff --git a/webview-ui/src/__mocks__/releases.json b/webview-ui/src/__mocks__/releases.json new file mode 100644 index 00000000000..9c6fab10a21 --- /dev/null +++ b/webview-ui/src/__mocks__/releases.json @@ -0,0 +1,4 @@ +{ + "currentVersion": "0.0.0", + "releases": [] +} diff --git a/webview-ui/src/components/ErrorBoundary.tsx b/webview-ui/src/components/ErrorBoundary.tsx index 8a009e06bac..fdacd17e59c 100644 --- a/webview-ui/src/components/ErrorBoundary.tsx +++ b/webview-ui/src/components/ErrorBoundary.tsx @@ -2,6 +2,7 @@ import React, { Component } from "react" import { telemetryClient } from "@src/utils/TelemetryClient" import { withTranslation, WithTranslation } from "react-i18next" import { enhanceErrorWithSourceMaps } from "@src/utils/sourceMapUtils" +import { REPOSITORY_URL } from "@/utils/kilocode/repository" type ErrorProps = { children: React.ReactNode @@ -71,7 +72,7 @@ class ErrorBoundary extends Component {

{t("errorBoundary.reportText")}{" "} - + {t("errorBoundary.githubText")}

diff --git a/webview-ui/src/components/marketplace/IssueFooter.tsx b/webview-ui/src/components/marketplace/IssueFooter.tsx index b90402062f4..564b3d3f0be 100644 --- a/webview-ui/src/components/marketplace/IssueFooter.tsx +++ b/webview-ui/src/components/marketplace/IssueFooter.tsx @@ -1,13 +1,14 @@ import React from "react" import { Trans } from "react-i18next" import { VSCodeLink } from "@vscode/webview-ui-toolkit/react" +import { REPOSITORY_URL } from "@/utils/kilocode/repository" export const IssueFooter: React.FC = () => { return (
Open a GitHub issue diff --git a/webview-ui/src/components/release-notes/AutoReleaseNotesChecker.tsx b/webview-ui/src/components/release-notes/AutoReleaseNotesChecker.tsx new file mode 100644 index 00000000000..521093426cd --- /dev/null +++ b/webview-ui/src/components/release-notes/AutoReleaseNotesChecker.tsx @@ -0,0 +1,49 @@ +// kilocode_change - new file: Auto-checks for unviewed releases and shows modal when needed +import React, { useState, useEffect } from "react" +import { ReleaseNotesModal } from "./ReleaseNotesModal" +import { useReleaseNotes } from "../../hooks/useReleaseNotes" + +const SHOULD_AUTO_OPEN = false // Disable auto-opening for the first version +const AUTO_OPEN_DELAY_MS = 2000 // 2 second delay before showing modal + +export const AutoReleaseNotesChecker: React.FC = () => { + const [showModal, setShowModal] = useState(false) + const { releases, hasUnviewedReleases, loadReleases, markAsViewed, currentVersion, loading } = useReleaseNotes() + + useEffect(() => { + if (!SHOULD_AUTO_OPEN) return + + let mounted = true + const checkAndAutoOpen = async () => { + try { + await loadReleases() + const hasUnviewed = await hasUnviewedReleases() + if (!mounted || !hasUnviewed) return + + setTimeout(() => { + if (mounted) { + setShowModal(true) + } + }, AUTO_OPEN_DELAY_MS) + } catch (error) { + console.warn("Failed to check for unviewed releases:", error) + } + } + checkAndAutoOpen() + + return () => { + mounted = false + } + }, [hasUnviewedReleases, loadReleases, currentVersion]) + + return showModal ? ( + setShowModal(false)} + releases={releases} + loading={loading} + currentVersion={currentVersion} + onMarkAsViewed={markAsViewed} + /> + ) : null +} diff --git a/webview-ui/src/components/release-notes/ReleaseItem.tsx b/webview-ui/src/components/release-notes/ReleaseItem.tsx new file mode 100644 index 00000000000..acd443cce14 --- /dev/null +++ b/webview-ui/src/components/release-notes/ReleaseItem.tsx @@ -0,0 +1,51 @@ +// kilocode_change - new file: Component to display individual release note items +import React from "react" +import { ReleaseItem } from "@roo-code/types" +import { REPOSITORY_URL } from "@/utils/kilocode/repository" +import { useAppTranslation } from "@/i18n/TranslationContext" + +interface ReleaseItemProps { + item: ReleaseItem +} + +export const ReleaseItemComponent: React.FC = ({ item }) => { + const { t } = useAppTranslation() + + return ( +
+
+ • {item.description} + {item.prNumber && ( + + #{item.prNumber} + + )} + {item.commitHash && ( + + {item.commitHash} + + )} + {item.author && ( + + {t("kilocode:releaseNotes.byAuthor")}{" "} + + @{item.author} + + + )} +
+
+ ) +} diff --git a/webview-ui/src/components/release-notes/ReleaseNoteDisplay.tsx b/webview-ui/src/components/release-notes/ReleaseNoteDisplay.tsx new file mode 100644 index 00000000000..048be325685 --- /dev/null +++ b/webview-ui/src/components/release-notes/ReleaseNoteDisplay.tsx @@ -0,0 +1,37 @@ +// kilocode_change - new file: Component to display a complete release note +import React from "react" +import { ReleaseNote } from "@roo-code/types" +import { ReleaseSection } from "./ReleaseSection" +import { REPOSITORY_URL } from "@/utils/kilocode/repository" +import { useAppTranslation } from "@/i18n/TranslationContext" + +interface ReleaseNoteDisplayProps { + release: ReleaseNote + isLatest?: boolean +} + +export const ReleaseNoteDisplay: React.FC = ({ release, isLatest }) => { + const { t } = useAppTranslation() + const { version, changes = [] } = release + + return ( +
+
+ + v{version} + + {isLatest && ( + + {t("kilocode:releaseNotes.latestBadge")} + + )} +
+ + +
+ ) +} diff --git a/webview-ui/src/components/release-notes/ReleaseNotesModal.tsx b/webview-ui/src/components/release-notes/ReleaseNotesModal.tsx new file mode 100644 index 00000000000..0d4af649aa3 --- /dev/null +++ b/webview-ui/src/components/release-notes/ReleaseNotesModal.tsx @@ -0,0 +1,59 @@ +// kilocode_change - refactored: Modal component that accepts releases as props +import React, { useEffect } from "react" +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog" +import { ReleaseNoteDisplay } from "./ReleaseNoteDisplay" +import { ReleaseNote } from "@roo-code/types" +import { useAppTranslation } from "@/i18n/TranslationContext" + +interface ReleaseNotesModalProps { + isOpen: boolean + onClose: () => void + releases: ReleaseNote[] + loading?: boolean + currentVersion?: string + onMarkAsViewed?: (version: string) => void +} + +export const ReleaseNotesModal: React.FC = ({ + isOpen, + onClose, + releases, + loading = false, + currentVersion, + onMarkAsViewed, +}) => { + const { t } = useAppTranslation() + + useEffect(() => { + if (isOpen && currentVersion && onMarkAsViewed) { + onMarkAsViewed(currentVersion) + } + }, [isOpen, currentVersion, onMarkAsViewed]) + + return ( + + + + + {t("kilocode:releaseNotes.modalTitle")} + + +
+ {loading ? ( +
+ {t("kilocode:releaseNotes.loading")} +
+ ) : releases.length === 0 ? ( +
+ {t("kilocode:releaseNotes.noReleases")} +
+ ) : ( + releases.map((release, index) => ( + + )) + )} +
+
+
+ ) +} diff --git a/webview-ui/src/components/release-notes/ReleaseSection.tsx b/webview-ui/src/components/release-notes/ReleaseSection.tsx new file mode 100644 index 00000000000..8c47ec5981b --- /dev/null +++ b/webview-ui/src/components/release-notes/ReleaseSection.tsx @@ -0,0 +1,24 @@ +// kilocode_change - new file: Component to display a section of release notes +import React from "react" +import { ReleaseItem } from "@roo-code/types" +import { ReleaseItemComponent } from "./ReleaseItem" + +interface ReleaseSectionProps { + items: ReleaseItem[] + icon?: React.ReactNode +} + +export const ReleaseSection: React.FC = ({ items, icon }) => { + if (items.length === 0) return null + + return ( +
+

{icon}

+
+ {items.map((item, index) => ( + + ))} +
+
+ ) +} diff --git a/webview-ui/src/components/release-notes/ShowReleaseNotesButton.tsx b/webview-ui/src/components/release-notes/ShowReleaseNotesButton.tsx new file mode 100644 index 00000000000..c3473d343d6 --- /dev/null +++ b/webview-ui/src/components/release-notes/ShowReleaseNotesButton.tsx @@ -0,0 +1,52 @@ +// kilocode_change - new file: Container component that handles release notes button logic +import React, { useState } from "react" +import { ShowReleaseNotesButtonInner } from "./ShowReleaseNotesButtonInner" +import { ReleaseNotesModal } from "./ReleaseNotesModal" +import { useAppTranslation } from "@/i18n/TranslationContext" +import { useReleaseNotes } from "../../hooks/useReleaseNotes" +import { useExtensionState } from "../../context/ExtensionStateContext" + +interface ShowReleaseNotesButtonProps { + buttonText?: string + className?: string +} + +export const ShowReleaseNotesButton: React.FC = ({ buttonText, className = "w-40" }) => { + const { t } = useAppTranslation() + const [showModal, setShowModal] = useState(false) + const { releases, loadReleases, loading, markAsViewed, currentVersion } = useReleaseNotes() + const { lastViewedReleaseVersion } = useExtensionState() + const displayText = buttonText || t("kilocode:releaseNotes.viewReleaseNotes") + + // Badge shows when there's a version mismatch + const showBadge = lastViewedReleaseVersion !== currentVersion && currentVersion !== "0.0.0" + + const handleOpenModal = () => { + loadReleases() + setShowModal(true) + // Mark as viewed when modal opens + if (currentVersion) { + markAsViewed(currentVersion) + } + } + + return ( + <> + + + setShowModal(false)} + releases={releases} + loading={loading} + currentVersion={currentVersion} + onMarkAsViewed={markAsViewed} + /> + + ) +} diff --git a/webview-ui/src/components/release-notes/ShowReleaseNotesButtonInner.tsx b/webview-ui/src/components/release-notes/ShowReleaseNotesButtonInner.tsx new file mode 100644 index 00000000000..b0537cc605f --- /dev/null +++ b/webview-ui/src/components/release-notes/ShowReleaseNotesButtonInner.tsx @@ -0,0 +1,30 @@ +// kilocode_change - new file: Static presentation component for showing release notes button +import React from "react" +import { FileText, Bell } from "lucide-react" +import { Button } from "../ui" + +interface ShowReleaseNotesButtonInnerProps { + buttonText: string + className?: string + showBadge: boolean + onClick: () => void +} + +export const ShowReleaseNotesButtonInner: React.FC = ({ + buttonText, + className, + showBadge, + onClick, +}) => { + return ( + + ) +} diff --git a/webview-ui/src/components/release-notes/index.ts b/webview-ui/src/components/release-notes/index.ts new file mode 100644 index 00000000000..90bad1643cc --- /dev/null +++ b/webview-ui/src/components/release-notes/index.ts @@ -0,0 +1,7 @@ +// kilocode_change - Release notes component exports (hook-based approach) +export { ReleaseNotesModal } from "./ReleaseNotesModal" +export { AutoReleaseNotesChecker } from "./AutoReleaseNotesChecker" +export { ShowReleaseNotesButton as ManualReleaseNotesButton } from "./ShowReleaseNotesButton" +export { ReleaseNoteDisplay } from "./ReleaseNoteDisplay" +export { ReleaseSection } from "./ReleaseSection" +export { ReleaseItemComponent } from "./ReleaseItem" diff --git a/webview-ui/src/components/settings/About.tsx b/webview-ui/src/components/settings/About.tsx index fbba3e2d58a..ce5b1462c60 100644 --- a/webview-ui/src/components/settings/About.tsx +++ b/webview-ui/src/components/settings/About.tsx @@ -13,7 +13,9 @@ import { Package } from "@roo/package" import { vscode } from "@/utils/vscode" import { cn } from "@/lib/utils" +import { REPOSITORY_URL } from "@/utils/kilocode/repository" import { Button } from "@/components/ui" +import { ManualReleaseNotesButton } from "@/components/release-notes" // kilocode_change import { SectionHeader } from "./SectionHeader" import { Section } from "./Section" @@ -67,7 +69,7 @@ export const About = ({ telemetrySetting, setTelemetrySetting, className, ...pro , + githubLink: , redditLink: , discordLink: , }} @@ -84,7 +86,6 @@ export const About = ({ telemetrySetting, setTelemetrySetting, className, ...pro />
{/* kilocode_change end */} -
+ {/* kilocode_change start */} +
+ +
+ {/* kilocode_change end */} + { // kilocode_change start process.env.NODE_ENV === "development" && ( diff --git a/webview-ui/src/components/settings/SettingsFooter.tsx b/webview-ui/src/components/settings/SettingsFooter.tsx index e3f805af501..36ca4242d7c 100644 --- a/webview-ui/src/components/settings/SettingsFooter.tsx +++ b/webview-ui/src/components/settings/SettingsFooter.tsx @@ -4,6 +4,7 @@ import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react" import { vscode } from "@/utils/vscode" import { cn } from "@/lib/utils" +import { REPOSITORY_URL } from "@/utils/kilocode/repository" type SettingsFooterProps = HTMLAttributes & { version: string @@ -13,7 +14,7 @@ export const SettingsFooter = ({ version, className, ...props }: SettingsFooterP

If you have any questions or feedback, feel free to open an issue at{" "} - + github.com/Kilo-Org/kilocode {" "} or join{" "} diff --git a/webview-ui/src/components/ui/dialog.tsx b/webview-ui/src/components/ui/dialog.tsx index ae394f95ce4..1cfbfb47f16 100644 --- a/webview-ui/src/components/ui/dialog.tsx +++ b/webview-ui/src/components/ui/dialog.tsx @@ -45,7 +45,7 @@ function DialogContent({ className, children, ...props }: React.ComponentProps {children} - + Close diff --git a/webview-ui/src/generated/releases/releases.json.d.ts b/webview-ui/src/generated/releases/releases.json.d.ts new file mode 100644 index 00000000000..8675858fcad --- /dev/null +++ b/webview-ui/src/generated/releases/releases.json.d.ts @@ -0,0 +1,7 @@ +// Type declaration for generated release notes JSON +// This file tells TypeScript that the JSON module exists without requiring the actual file +import type { ReleaseData } from "@roo-code/types" + +declare const releases: ReleaseData + +export default releases diff --git a/webview-ui/src/hooks/useReleaseNotes.ts b/webview-ui/src/hooks/useReleaseNotes.ts new file mode 100644 index 00000000000..cf34e5e0969 --- /dev/null +++ b/webview-ui/src/hooks/useReleaseNotes.ts @@ -0,0 +1,65 @@ +// kilocode_change start - new file: Simple hook for release notes loading pre-generated JSON +import { useState } from "react" +import { ReleaseNote } from "@roo-code/types" +import { useExtensionState } from "../context/ExtensionStateContext" +import { vscode } from "../utils/vscode" + +// Global cache +let releasesCache: ReleaseData | null = null + +const NULL_VERSION = "0.0.0" + +interface ReleaseData { + currentVersion: string + releases: ReleaseNote[] +} + +export const useReleaseNotes = () => { + const [loading, setLoading] = useState(false) + const { lastViewedReleaseVersion } = useExtensionState() + + const loadReleases = async (): Promise => { + if (releasesCache) { + return releasesCache + } + + setLoading(true) + try { + // Load pre-generated JSON from build + const data = await import("../generated/releases/releases.json") + releasesCache = data.default as ReleaseData + return releasesCache + } catch (error) { + console.error("Failed to load release notes:", error) + releasesCache = { currentVersion: NULL_VERSION, releases: [] } + return releasesCache + } finally { + setLoading(false) + } + } + // kilocode_change end + + const hasUnviewedReleases = async (): Promise => { + const data = await loadReleases() + // If no last viewed version, or current version is different, there are unviewed releases + return !lastViewedReleaseVersion || lastViewedReleaseVersion !== data.currentVersion + } + + const markAsViewed = async (version: string): Promise => { + // Use the generic updateGlobalState message to persist the last viewed version + vscode.postMessage({ + type: "updateGlobalState", + stateKey: "lastViewedReleaseVersion", + stateValue: version, + }) + } + + return { + releases: releasesCache?.releases || [], + currentVersion: releasesCache?.currentVersion || NULL_VERSION, + loading, + loadReleases, + hasUnviewedReleases, + markAsViewed, + } +} diff --git a/webview-ui/src/i18n/locales/ar/kilocode.json b/webview-ui/src/i18n/locales/ar/kilocode.json index d9dd44b6596..afbfa9728b3 100644 --- a/webview-ui/src/i18n/locales/ar/kilocode.json +++ b/webview-ui/src/i18n/locales/ar/kilocode.json @@ -281,5 +281,13 @@ }, "modes": { "shareModesNewBanner": "جديد: مشاركة الأوضاع عن طريق إنشاء منظمة" + }, + "releaseNotes": { + "viewReleaseNotes": "عرض ملاحظات الإصدار", + "noReleases": "لا تتوفر ملاحظات الإصدار", + "loading": "جاري تحميل ملاحظات الإصدار...", + "latestBadge": "الأحدث", + "modalTitle": "ما الجديد في كيلو كود", + "byAuthor": "بواسطة" } } diff --git a/webview-ui/src/i18n/locales/ca/kilocode.json b/webview-ui/src/i18n/locales/ca/kilocode.json index 7fa9c12fc8b..e65658e4551 100644 --- a/webview-ui/src/i18n/locales/ca/kilocode.json +++ b/webview-ui/src/i18n/locales/ca/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "Nou: Comparteix modes creant una organització" + }, + "releaseNotes": { + "viewReleaseNotes": "Veure notes de la versió", + "modalTitle": "Novetats a Kilo Code", + "loading": "S'estan carregant les notes de la versió...", + "latestBadge": "Últim", + "noReleases": "No hi ha notes de llançament disponibles", + "byAuthor": "per" } } diff --git a/webview-ui/src/i18n/locales/cs/kilocode.json b/webview-ui/src/i18n/locales/cs/kilocode.json index ec34d350d51..df8ad572a8f 100644 --- a/webview-ui/src/i18n/locales/cs/kilocode.json +++ b/webview-ui/src/i18n/locales/cs/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "Novinka: Sdílejte režimy vytvořením organizace" + }, + "releaseNotes": { + "viewReleaseNotes": "Zobrazit poznámky k vydání", + "loading": "Načítání poznámek k vydání...", + "noReleases": "Poznámky k vydání nejsou k dispozici", + "latestBadge": "Nejnovější", + "modalTitle": "Co je nového v Kilo Code", + "byAuthor": "od" } } diff --git a/webview-ui/src/i18n/locales/de/kilocode.json b/webview-ui/src/i18n/locales/de/kilocode.json index 8934984e311..2c0741e13d5 100644 --- a/webview-ui/src/i18n/locales/de/kilocode.json +++ b/webview-ui/src/i18n/locales/de/kilocode.json @@ -281,5 +281,13 @@ }, "modes": { "shareModesNewBanner": "Neu: Modi teilen durch Erstellen einer Organisation" + }, + "releaseNotes": { + "viewReleaseNotes": "Release Notes ansehen", + "modalTitle": "Was gibt es Neues in Kilo Code", + "loading": "Versionshinweise werden geladen...", + "noReleases": "Keine Release Notes verfügbar", + "latestBadge": "Neueste", + "byAuthor": "von" } } diff --git a/webview-ui/src/i18n/locales/en/kilocode.json b/webview-ui/src/i18n/locales/en/kilocode.json index 74d57e18ff7..1b51f9d665e 100644 --- a/webview-ui/src/i18n/locales/en/kilocode.json +++ b/webview-ui/src/i18n/locales/en/kilocode.json @@ -280,5 +280,13 @@ }, "modes": { "shareModesNewBanner": "New: Share modes by creating an organization" + }, + "releaseNotes": { + "viewReleaseNotes": "View Release Notes", + "modalTitle": "What's New in Kilo Code", + "loading": "Loading release notes...", + "noReleases": "No release notes available", + "latestBadge": "Latest", + "byAuthor": "by" } } diff --git a/webview-ui/src/i18n/locales/es/kilocode.json b/webview-ui/src/i18n/locales/es/kilocode.json index c912c172633..43ee0fd846c 100644 --- a/webview-ui/src/i18n/locales/es/kilocode.json +++ b/webview-ui/src/i18n/locales/es/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "Nuevo: Comparte modos creando una organización" + }, + "releaseNotes": { + "viewReleaseNotes": "Ver notas de la versión", + "modalTitle": "Novedades en Kilo Code", + "loading": "Cargando notas de la versión...", + "noReleases": "No hay notas de lanzamiento disponibles", + "byAuthor": "por", + "latestBadge": "Último" } } diff --git a/webview-ui/src/i18n/locales/fr/kilocode.json b/webview-ui/src/i18n/locales/fr/kilocode.json index f9c935ae658..a4c640e0b02 100644 --- a/webview-ui/src/i18n/locales/fr/kilocode.json +++ b/webview-ui/src/i18n/locales/fr/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "Nouveau : Partagez des modes en créant une organisation" + }, + "releaseNotes": { + "viewReleaseNotes": "Voir les notes de version", + "loading": "Chargement des notes de version...", + "modalTitle": "Quoi de neuf dans Kilo Code", + "noReleases": "Aucune note de version disponible", + "byAuthor": "par", + "latestBadge": "Plus récent" } } diff --git a/webview-ui/src/i18n/locales/hi/kilocode.json b/webview-ui/src/i18n/locales/hi/kilocode.json index 578f1a683e5..7c765c5ec3a 100644 --- a/webview-ui/src/i18n/locales/hi/kilocode.json +++ b/webview-ui/src/i18n/locales/hi/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "नया: एक संगठन बनाकर मोड साझा करें" + }, + "releaseNotes": { + "viewReleaseNotes": "रिलीज़ नोट्स देखें", + "loading": "रिलीज़ नोट्स लोड हो रहे हैं...", + "modalTitle": "किलो कोड में नया क्या है", + "latestBadge": "नवीनतम", + "noReleases": "कोई रिलीज़ नोट्स उपलब्ध नहीं", + "byAuthor": "द्वारा" } } diff --git a/webview-ui/src/i18n/locales/id/kilocode.json b/webview-ui/src/i18n/locales/id/kilocode.json index 6b3b0d0730b..9403bb4849f 100644 --- a/webview-ui/src/i18n/locales/id/kilocode.json +++ b/webview-ui/src/i18n/locales/id/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "Baru: Bagikan mode dengan membuat organisasi" + }, + "releaseNotes": { + "viewReleaseNotes": "Lihat Catatan Rilis", + "modalTitle": "Apa yang Baru di Kilo Code", + "loading": "Memuat catatan rilis...", + "noReleases": "Tidak ada catatan rilis yang tersedia", + "byAuthor": "oleh", + "latestBadge": "Terbaru" } } diff --git a/webview-ui/src/i18n/locales/it/kilocode.json b/webview-ui/src/i18n/locales/it/kilocode.json index d7d5928b5d4..79e24b1a57b 100644 --- a/webview-ui/src/i18n/locales/it/kilocode.json +++ b/webview-ui/src/i18n/locales/it/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "Novità: Condividi le modalità creando un'organizzazione" + }, + "releaseNotes": { + "viewReleaseNotes": "Visualizza note di rilascio", + "modalTitle": "Novità in Kilo Code", + "loading": "Caricamento delle note di rilascio...", + "noReleases": "Nessuna nota di rilascio disponibile", + "latestBadge": "Più recenti", + "byAuthor": "di" } } diff --git a/webview-ui/src/i18n/locales/ja/kilocode.json b/webview-ui/src/i18n/locales/ja/kilocode.json index bc84648557a..9f36224306f 100644 --- a/webview-ui/src/i18n/locales/ja/kilocode.json +++ b/webview-ui/src/i18n/locales/ja/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "新機能: 組織を作成してモードを共有" + }, + "releaseNotes": { + "viewReleaseNotes": "リリースノートを表示", + "modalTitle": "Kilo Codeの新機能", + "loading": "リリースノートを読み込んでいます...", + "latestBadge": "最新", + "noReleases": "リリースノートはありません", + "byAuthor": "によって" } } diff --git a/webview-ui/src/i18n/locales/ko/kilocode.json b/webview-ui/src/i18n/locales/ko/kilocode.json index 1e6be0b68fc..4cb2a9f8661 100644 --- a/webview-ui/src/i18n/locales/ko/kilocode.json +++ b/webview-ui/src/i18n/locales/ko/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "신규: 조직을 생성하여 모드 공유하기" + }, + "releaseNotes": { + "viewReleaseNotes": "릴리스 노트 보기", + "loading": "릴리스 노트 로딩 중...", + "noReleases": "릴리스 노트 없음", + "modalTitle": "Kilo Code의 새로운 기능", + "latestBadge": "최신", + "byAuthor": "작성자:" } } diff --git a/webview-ui/src/i18n/locales/nl/kilocode.json b/webview-ui/src/i18n/locales/nl/kilocode.json index d4d78f53678..dcc2116a266 100644 --- a/webview-ui/src/i18n/locales/nl/kilocode.json +++ b/webview-ui/src/i18n/locales/nl/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "Nieuw: Deel modi door een organisatie aan te maken" + }, + "releaseNotes": { + "viewReleaseNotes": "Bekijk Release Notes", + "modalTitle": "Wat is er nieuw in Kilo Code", + "loading": "Releaseopmerkingen laden...", + "byAuthor": "door", + "noReleases": "Geen release notes beschikbaar", + "latestBadge": "Nieuwste" } } diff --git a/webview-ui/src/i18n/locales/pl/kilocode.json b/webview-ui/src/i18n/locales/pl/kilocode.json index 5ed229ee5ba..8d85a7d998e 100644 --- a/webview-ui/src/i18n/locales/pl/kilocode.json +++ b/webview-ui/src/i18n/locales/pl/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "Nowość: Udostępniaj tryby tworząc organizację" + }, + "releaseNotes": { + "viewReleaseNotes": "Zobacz informacje o wydaniu", + "modalTitle": "Co nowego w Kilo Code", + "loading": "Ładowanie informacji o wydaniu...", + "latestBadge": "Najnowsze", + "noReleases": "Brak dostępnych informacji o wydaniu", + "byAuthor": "przez" } } diff --git a/webview-ui/src/i18n/locales/pt-BR/kilocode.json b/webview-ui/src/i18n/locales/pt-BR/kilocode.json index 4bb9c545ada..2c3e163ea2d 100644 --- a/webview-ui/src/i18n/locales/pt-BR/kilocode.json +++ b/webview-ui/src/i18n/locales/pt-BR/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "Novo: Compartilhe modos criando uma organização" + }, + "releaseNotes": { + "viewReleaseNotes": "Ver Notas da Versão", + "loading": "Carregando notas de atualização...", + "modalTitle": "O Que Há de Novo no Kilo Code", + "noReleases": "Sem notas de lançamento disponíveis", + "latestBadge": "Mais recentes", + "byAuthor": "por" } } diff --git a/webview-ui/src/i18n/locales/ru/kilocode.json b/webview-ui/src/i18n/locales/ru/kilocode.json index 9a6c30d14c1..af7f38a8577 100644 --- a/webview-ui/src/i18n/locales/ru/kilocode.json +++ b/webview-ui/src/i18n/locales/ru/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "Новое: Делитесь режимами путем создания организации" + }, + "releaseNotes": { + "viewReleaseNotes": "Посмотреть примечания к выпуску", + "loading": "Загрузка примечаний к выпуску...", + "latestBadge": "Последние", + "noReleases": "Примечания к выпуску отсутствуют", + "modalTitle": "Что нового в Kilo Code", + "byAuthor": "от" } } diff --git a/webview-ui/src/i18n/locales/th/kilocode.json b/webview-ui/src/i18n/locales/th/kilocode.json index 263be7a3641..32f9d6813aa 100644 --- a/webview-ui/src/i18n/locales/th/kilocode.json +++ b/webview-ui/src/i18n/locales/th/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "ใหม่: แชร์โหมดโดยการสร้างองค์กร" + }, + "releaseNotes": { + "viewReleaseNotes": "ดูบันทึกการเปิดตัว", + "latestBadge": "ล่าสุด", + "modalTitle": "มีอะไรใหม่ใน Kilo Code", + "loading": "กำลังโหลดบันทึกการอัปเดต...", + "noReleases": "ไม่มีบันทึกประจำรุ่นที่เผยแพร่", + "byAuthor": "โดย" } } diff --git a/webview-ui/src/i18n/locales/tr/kilocode.json b/webview-ui/src/i18n/locales/tr/kilocode.json index e1d9a261998..75392cf77ec 100644 --- a/webview-ui/src/i18n/locales/tr/kilocode.json +++ b/webview-ui/src/i18n/locales/tr/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "Yeni: Bir organizasyon oluşturarak modları paylaşın" + }, + "releaseNotes": { + "viewReleaseNotes": "Sürüm Notlarını Görüntüle", + "modalTitle": "Kilo Code'da Yeni Neler Var", + "noReleases": "Hiçbir sürüm notu bulunmuyor", + "loading": "Sürüm notları yükleniyor...", + "byAuthor": "tarafından", + "latestBadge": "En Son" } } diff --git a/webview-ui/src/i18n/locales/uk/kilocode.json b/webview-ui/src/i18n/locales/uk/kilocode.json index 16004a9fb10..faa96140deb 100644 --- a/webview-ui/src/i18n/locales/uk/kilocode.json +++ b/webview-ui/src/i18n/locales/uk/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "Нове: Діліться режимами, створивши організацію" + }, + "releaseNotes": { + "viewReleaseNotes": "Переглянути примітки до випуску", + "loading": "Завантаження приміток до випуску...", + "modalTitle": "Що нового у Kilo Code", + "latestBadge": "Останні", + "noReleases": "Примітки до випуску відсутні", + "byAuthor": "від" } } diff --git a/webview-ui/src/i18n/locales/vi/kilocode.json b/webview-ui/src/i18n/locales/vi/kilocode.json index 6c1b0f30f37..521f9dddd91 100644 --- a/webview-ui/src/i18n/locales/vi/kilocode.json +++ b/webview-ui/src/i18n/locales/vi/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "Mới: Chia sẻ chế độ bằng cách tạo một tổ chức" + }, + "releaseNotes": { + "viewReleaseNotes": "Xem Ghi Chú Phát Hành", + "modalTitle": "Có gì mới trong Kilo Code", + "latestBadge": "Mới nhất", + "loading": "Đang tải ghi chú phát hành...", + "noReleases": "Không có ghi chú phát hành", + "byAuthor": "bởi" } } diff --git a/webview-ui/src/i18n/locales/zh-CN/kilocode.json b/webview-ui/src/i18n/locales/zh-CN/kilocode.json index 7929764bf34..5fa102a4611 100644 --- a/webview-ui/src/i18n/locales/zh-CN/kilocode.json +++ b/webview-ui/src/i18n/locales/zh-CN/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "新功能:通过创建组织来共享模式" + }, + "releaseNotes": { + "viewReleaseNotes": "查看发布说明", + "noReleases": "没有可用的发布说明", + "modalTitle": "Kilo Code 的新特性", + "latestBadge": "最新", + "byAuthor": "由", + "loading": "加载发行说明..." } } diff --git a/webview-ui/src/i18n/locales/zh-TW/kilocode.json b/webview-ui/src/i18n/locales/zh-TW/kilocode.json index 7610c4428fa..daf6a8300e9 100644 --- a/webview-ui/src/i18n/locales/zh-TW/kilocode.json +++ b/webview-ui/src/i18n/locales/zh-TW/kilocode.json @@ -282,5 +282,13 @@ }, "modes": { "shareModesNewBanner": "新功能:通过创建组织共享模式" + }, + "releaseNotes": { + "viewReleaseNotes": "查看发布说明", + "modalTitle": "Kilo Code 新功能一览", + "loading": "正在加载发行说明...", + "noReleases": "没有可用的发布说明", + "byAuthor": "由", + "latestBadge": "最新" } } diff --git a/webview-ui/src/utils/kilocode/repository.ts b/webview-ui/src/utils/kilocode/repository.ts new file mode 100644 index 00000000000..8a42ebc5da5 --- /dev/null +++ b/webview-ui/src/utils/kilocode/repository.ts @@ -0,0 +1,2 @@ +// kilocode_change - Repository URL constant +export const REPOSITORY_URL = "https://github.com/Kilo-Org/kilocode" diff --git a/webview-ui/src/vite-plugins/sourcemapPlugin.ts b/webview-ui/src/vite-plugins/sourcemapPlugin.ts index 1449c888f2d..964224c819e 100644 --- a/webview-ui/src/vite-plugins/sourcemapPlugin.ts +++ b/webview-ui/src/vite-plugins/sourcemapPlugin.ts @@ -92,7 +92,11 @@ export function sourcemapPlugin(): Plugin { fs.writeFileSync(mapPath, JSON.stringify(mapContent, null, 2)) console.log(`Updated source map for ${jsFile}`) } catch (error) { - console.error(`Error processing source map for ${jsFile}:`, error) + console.error( + `Skipping malformed source map for ${jsFile}:`, + error instanceof Error ? error.message : error, + ) + fs.unlinkSync(mapPath) // Delete the malformed source map to prevent build errors } } else { console.log(`No source map found for ${jsFile}`) diff --git a/webview-ui/vite.config.ts b/webview-ui/vite.config.ts index e8b267ba238..9301ab4ccca 100644 --- a/webview-ui/vite.config.ts +++ b/webview-ui/vite.config.ts @@ -51,6 +51,21 @@ const persistPortPlugin = (): Plugin => ({ }, }) +// kilocode_change start: Generate release notes at build time +const releaseNotesPlugin = (): Plugin => ({ + name: "generate-release-notes", + buildStart() { + console.log("[Release Notes Plugin] Generating release notes...") + try { + execSync("node scripts/generate-release-notes.mjs", { cwd: __dirname, stdio: "inherit" }) + console.log("[Release Notes Plugin] Release notes generated successfully") + } catch (error) { + console.error("[Release Notes Plugin] Failed to generate release notes:", error) + } + }, +}) +// kilocode_change end + // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { let outDir = "../src/webview-ui/build" @@ -91,7 +106,14 @@ export default defineConfig(({ mode }) => { define["process.env.PKG_OUTPUT_CHANNEL"] = JSON.stringify("Kilo-Code-Nightly") } - const plugins: PluginOption[] = [react(), tailwindcss(), persistPortPlugin(), wasmPlugin(), sourcemapPlugin()] + const plugins: PluginOption[] = [ + react(), + tailwindcss(), + persistPortPlugin(), + wasmPlugin(), + sourcemapPlugin(), + releaseNotesPlugin(), // kilocode_change: Generate release notes at build time + ] return { plugins, diff --git a/webview-ui/vitest.config.ts b/webview-ui/vitest.config.ts index 595c51348d5..d3a21cf1bae 100644 --- a/webview-ui/vitest.config.ts +++ b/webview-ui/vitest.config.ts @@ -23,6 +23,9 @@ export default defineConfig({ // Mock the vscode module for tests since it's not available outside // VS Code extension context. vscode: path.resolve(__dirname, "./src/__mocks__/vscode.ts"), + "../generated/releases/releases.json": path.resolve(__dirname, "./src/__mocks__/releases.json"), }, }, + // Only include specific JSON files we actually need as assets + assetsInclude: ["src/i18n/locales/**/*.json", "src/generated/**/*.json"], }) diff --git a/webview-ui/vitest.setup.ts b/webview-ui/vitest.setup.ts index 57f2b1614d9..f23265a2cb2 100644 --- a/webview-ui/vitest.setup.ts +++ b/webview-ui/vitest.setup.ts @@ -50,3 +50,23 @@ Object.defineProperty(window, "matchMedia", { // Mock scrollIntoView which is not available in jsdom Element.prototype.scrollIntoView = vi.fn() + +// kilocode_change start +// Mock i18n setup to prevent eager loading of locale files during tests +// This prevents Vite from trying to parse all JSON files at import time +vi.mock("./src/i18n/setup", () => { + const mockI18next = { + language: "en", + use: vi.fn().mockReturnThis(), + init: vi.fn().mockReturnThis(), + t: vi.fn((key: string) => key), + changeLanguage: vi.fn(), + addResourceBundle: vi.fn(), + } + + return { + default: mockI18next, + loadTranslations: vi.fn(), + } +}) +// kilocode_change end