Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat][New Feature]: Added a new instruction selector dropdown menu #54

Merged
merged 6 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions extensions/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

# Changelog of the extensions

## feat: Instruction Selector - 12/2/2024 - @lumpinif

### New Feature Implementation

- Added instruction selector feature for enhanced user interaction
- Implemented quick instruction selection capability
- Improved text insertion reliability
- Restructured initialization logic for better feature scoping

### Version Update

- Bumped version to 3.2.0 to reflect new feature addition

## feat: CSS Architecture - 12/1/2024 - @lumpinif

### Modular CSS Architecture Implementation
Expand Down
4 changes: 3 additions & 1 deletion extensions/chrome/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "thinking-claude",
"version": "3.1.5",
"version": "3.2.0",
"description": "Chrome extension for letting Claude think like a real human",
"type": "module",
"scripts": {
Expand All @@ -21,7 +21,9 @@
"dependencies": {
"@radix-ui/react-accordion": "^1.2.1",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-scroll-area": "^1.2.1",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.4",
"class-variance-authority": "^0.7.0",
Expand Down
2 changes: 1 addition & 1 deletion extensions/chrome/public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Thinking Claude",
"version": "3.1.5",
"version": "3.2.0",
"description": "Chrome extension for letting Claude think like a real human",
"background": {
"service_worker": "background.js"
Expand Down
168 changes: 168 additions & 0 deletions extensions/chrome/src/components/instruction-selector/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import * as React from "react"

import { formatStarCount } from "@/utils/format"
import { insertTextIntoClaudeInput } from "@/utils/insert-text"
import { GitHubLogoIcon, StarFilledIcon } from "@radix-ui/react-icons"

import { cn } from "@/lib/utils"

import { useModelInstructions } from "@/hooks/use-model-instructions"

import { Badge } from "@/components/ui/badge"
import {
Select,
SelectContent,
SelectGroup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"

import { useContentSync } from "../../hooks/use-content-sync"
import { InstructionDescription } from "./instruction-description"
import { InstructionItem } from "./instruction-item"

// Types
export interface ModelInstruction {
value: string
label: string
description?: string
content: string
}

const LoadingState = ({ isLoading }: { isLoading: boolean }) => (
<div
className={cn(
"tc-min-w-24 text-text-500 tc-text-xs tc-bg-clip-text tc-text-transparent tc-bg-[length:200%_100%]",
isLoading &&
"tc-animate-shimmer tc-bg-gradient-to-r tc-from-gray-400/70 tc-via-gray-300 tc-to-gray-400/70"
)}
>
Loading model instructions...
</div>
)

export function InstructionSelect() {
const [value, setValue] = React.useState("")
const [key, setKey] = React.useState(Date.now())
const [hoveredInstruction, setHoveredInstruction] =
React.useState<ModelInstruction | null>(null)
const {
instructions,
isLoading,
starsCount,
handleInstructionSelect,
error,
} = useModelInstructions()

useContentSync({
instructions,
onValueChange: setValue,
onKeyChange: () => setKey(Date.now()),
})

const handleClear = async (e: React.MouseEvent) => {
e.stopPropagation()
setValue("")
setKey(Date.now())
await insertTextIntoClaudeInput("")
}

const handleInstructionClick = async (instruction: ModelInstruction) => {
if (instruction.content) {
setValue(instruction.value)
await handleInstructionSelect(instruction)
}
}

const selectedInstruction = instructions.find((inst) => inst.value === value)
const displayedInstruction = hoveredInstruction || selectedInstruction

if (isLoading) {
return <LoadingState isLoading={isLoading} />
}

return (
<div className="tc-min-w-24">
<Select
key={key}
value={value}
onValueChange={(value) => {
handleInstructionClick(
instructions.find((inst) => inst.value === value)!
)
}}
>
<SelectTrigger className="inline-flex items-center justify-center relative shrink-0 ring-offset-2 ring-offset-bg-300 ring-accent-main-100 focus-visible:outline-none focus-visible:ring-1 tc-shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none disabled:drop-shadow-none max-w-full min-w-0 pl-1.5 pr-1 h-7 ml-0.5 mr-1 hover:bg-bg-200 hover:border-border-400 border-0.5 text-sm rounded-md border-transparent transition text-text-500 hover:text-text-200 font-tiempos !tc-font-normal tc-gap-x-1">
<SelectValue placeholder="Let Claude think" />
</SelectTrigger>
<SelectContent className="z-50 bg-bg-200 backdrop-blur-xl border-0.5 border-border-300 rounded-xl min-w-[12rem] overflow-hidden p-1 text-text-200 shadow-[0_0_0_0.5px_rgba(0,0,0,0.1),0_0_20px_rgba(0,0,0,0.05),0_1px_5px_rgba(0,0,0,0.1)] w-64 sm:w-[28rem] md:tc-w-[32rem] !z-30">
<div className="sm:flex justify-between items-center flex-1 text-xs font-medium text-text-300 px-1.5 pt-1 pb-1.5 min-h-5">
<div className="translate-y-[0.5px]">
Which model instruction should Claude use?
</div>
<a
href="https://github.com/richards199999/Thinking-Claude"
target="_blank"
rel="noopener noreferrer"
>
<Badge
variant="default"
className="border-0.5 border-border-300 tc-flex tc-items-center tc-gap-2 tc-cursor-pointer hover:!bg-accent-main-100 hover:!text-oncolor-100 hover:!border-transparent transition tc-font-normal tc-text-xs tc-flex-nowrap"
>
<span
title="Open-souced on GitHub"
className="tc-flex tc-items-center tc-justify-center"
>
<GitHubLogoIcon className="tc-size-3" />
</span>
<span className="tc-flex tc-items-center tc-justify-center">
{starsCount && (
<span
className="tc-text-xs"
title={`${starsCount.toLocaleString()} stars`}
>
{formatStarCount(starsCount)}
</span>
)}
<StarFilledIcon className="tc-size-3" />
</span>
</Badge>
</a>
</div>
<div className="grid sm:grid-cols-2 tc-gap-2 mt-0.5 pb-1 px-1">
<div className="min-h-0">
<div className="overflow-x-visible overflow-y-auto scroll-pb-6 min-h-[0px] [scrollbar-color:hsl(var(--text-500))] scroll-smooth overscroll-contain [-webkit-overflow-scrolling:touch] [&::-webkit-scrollbar]:mt-4 [&::-webkit-scrollbar]:w-[0.25rem] [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-track]:my-1 [&::-webkit-scrollbar-thumb]:rounded-[1em] [&::-webkit-scrollbar-thumb]:border-[0.25rem] [&::-webkit-scrollbar-thumb]:border-transparent [&::-webkit-scrollbar-thumb]:bg-clip-padding [&::-webkit-scrollbar-thumb]:bg-text-500/80 [&::-webkit-scrollbar-thumb:hover]:bg-text-500 sm:mr-1 min-h-40 max-h-64">
<SelectGroup>
{instructions.map((instruction) => (
<div
key={instruction.value}
onMouseEnter={() => setHoveredInstruction(instruction)}
onMouseLeave={() => setHoveredInstruction(null)}
>
<InstructionItem
value={instruction.value}
label={instruction.label}
/>
</div>
))}
<button
onClick={handleClear}
className="py-1 px-2 rounded-md cursor-pointer whitespace-nowrap overflow-hidden text-ellipsis grid grid-cols-[minmax(0,_1fr)_auto] gap-2 items-center outline-none select-none [&[data-highlighted]]:bg-bg-300 [&[data-highlighted]]:text-text-000 bg-transparent border-0.5 border-border-300 hover:!bg-accent-main-100 hover:!text-oncolor-100 hover:!border-transparent transition mb-1 mt-4 !rounded-lg text-center text-sm font-medium w-full"
>
Clear selection
</button>
</SelectGroup>
</div>
</div>
<div className="flex flex-col">
<InstructionDescription
error={error}
selectedInstruction={displayedInstruction}
/>
</div>
</div>
</SelectContent>
</Select>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { cn } from "@/lib/utils"

import type { ModelInstruction } from "."

interface InstructionDescriptionProps {
error: string | null
selectedInstruction?: ModelInstruction
}

const convertMarkdownToHtml = (markdown: string): string => {
return markdown
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>") // Bold
.replace(/\*(.*?)\*/g, "<em>$1</em>") // Italic
.replace(/\n/g, "<br />") // New lines
}

export const InstructionDescription = ({
error,
selectedInstruction,
}: InstructionDescriptionProps) => (
<div className="flex-1 mr-1 mb-1 text-wrap py-3 px-3.5 gap-2.5 rounded-lg border-0.5 transition max-sm:hidden bg-bg-100/40 border-border-300 text-text-200 tc-font-light tc-text-balance">
<div
className={cn(
"font-tiempos text-[0.9375rem] leading-snug",
!error && "tc-prose tc-prose-sm tc-prose-neutral dark:tc-prose-invert",
!selectedInstruction?.description &&
"tc-flex tc-items-center tc-justify-center tc-size-full tc-text-center"
)}
>
{error ? (
<span className="text-danger-100">{error}</span>
) : (
<div
key={selectedInstruction?.value} // Force re-render on instruction change
className="tc-animate-fade-in"
dangerouslySetInnerHTML={{
__html: convertMarkdownToHtml(
selectedInstruction?.description ||
"Select a model instruction version to let Claude think"
),
}}
/>
)}
</div>
</div>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { SelectItem } from "@/components/ui/select"

// Constants
const ITEM_STYLES =
"py-1 px-2 rounded-md cursor-pointer whitespace-nowrap overflow-hidden text-ellipsis grid grid-cols-[minmax(0,_1fr)_auto] gap-2 items-center outline-none select-none [&[data-highlighted]]:bg-bg-300 [&[data-highlighted]]:text-text-000 pr-0 mb-0.5 line-clamp-2 leading-tight tc-text-base tc-w-full tc-pr-6"

export const InstructionItem = ({
value,
label,
}: {
value: string
label: string
}) => (
<SelectItem value={value} className={ITEM_STYLES}>
<div className="flex items-center justify-between">
<div
className="flex-1 tc-text-nowrap tc-overflow-hidden tc-text-ellipsis"
title={label}
>
{label}
</div>
</div>
</SelectItem>
)
9 changes: 0 additions & 9 deletions extensions/chrome/src/components/sample-ui.tsx

This file was deleted.

37 changes: 37 additions & 0 deletions extensions/chrome/src/components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as React from "react"

import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)

export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}

export { Badge, badgeVariants }
Loading