Skip to content

Commit

Permalink
[feat]: add new instruction selector dropdown menu
Browse files Browse the repository at this point in the history
[feat][New Feature]: Added a new instruction selector dropdown menu
  • Loading branch information
richards199999 authored Dec 2, 2024
2 parents cd696f8 + 5bfc494 commit 6c4f798
Show file tree
Hide file tree
Showing 28 changed files with 1,120 additions and 34 deletions.
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

0 comments on commit 6c4f798

Please sign in to comment.