yaz/commit

interaction

authorMichiel Hildebrand
Wed Feb 9 16:33:04 2011 +0100
committerMichiel Hildebrand
Wed Feb 9 16:33:04 2011 +0100
commit70082945a0bc6981806955e9995d5e55fbed053e
treefdde90a7ef601d5a9c74a160e829f4075ef57b8c
parent8b8a70cef7ffa879969bec9565ed100a42686ef7
Diff style: patch stat
diff --git a/api/reconcile.pl b/api/reconcile.pl
index 126984f..7214021 100644
--- a/api/reconcile.pl
+++ b/api/reconcile.pl
@@ -1,5 +1,6 @@
 :- module(reconcile,
-	  [reconcile/5    % +Query, +Max, +Type, +Properties, -Concepts
+	  [reconcile/3,   % +Query, +Max, -Hits
+	   reconcile/5    % +Query, +Max, +Type, +Properties, -Hits
 	  ]).
 
 :- use_module(library(http/http_dispatch)).
@@ -24,6 +25,11 @@
 :- http_handler(yaz(reconcile), http_reconcile, []).
 :- http_handler(yaz(savereconcile), http_save_reconcile, []).
 
+:- dynamic
+	reconciled/2.
+
+
+
 %%	http_reconcile(+Request)
 %
 %	Handler to find reconciliations.
@@ -96,10 +102,27 @@ reconcile_list([Key=json([query=Query])|Ts], Max, Type, Properties, [Key=json([r
 %
 %	Reconcile a QueryString to a set of candidate concepts
 
+reconcile(Query, Max, Hits) :-
+	reconcile(Query, Max, _, [], Hits).
+
+reconcile(Query, Max, Type, Properties, Hits) :-
+	reconciled(Query, Hits0),
+	reconcile_filter(Hits0, Max, Type, Properties, Hits),
+	!.
 reconcile(Query, Max, Type, Properties, Hits) :-
  	label_list(LabelList),
 	find_resource_by_name(Query, Hits0, [attributes(LabelList),match(stem),distance(true)]),
-	filter_hits(Hits0, Max, Type, Properties, Hits).
+	assert(reconciled(Query, Hits0)),
+	reconcile_filter(Hits0, Max, Type, Properties, Hits).
+
+reconcile_filter(Hits, Max, Type, Properties, Filtered) :-
+	(   var(Type),
+	    (	Properties = []
+	    ;	var(Properties)
+	    )
+	->  list_limit(Hits, Max, Filtered, _)
+	),
+	filter_hits(Hits, Max, Type, Properties, Filtered).
 
 label_list(LabelList) :-
 	rdf_equal(rdfs:label, Label),
diff --git a/applications/yaz_cgarden.pl b/applications/yaz_cgarden.pl
index e18e573..2ce388d 100644
--- a/applications/yaz_cgarden.pl
+++ b/applications/yaz_cgarden.pl
@@ -73,9 +73,24 @@ http_yaz_cgarden(Request) :-
 					 ], _Process),
 	video_annotations(Video, As0, Options),
 	sort_by_arg(As0, 2, As),
+
 	tag_matches(As, User, Process, Interval, Annotations),
  	html_video_page(Video, CurrentUser, Annotations, 0, Options).
 
+%%	link_to_concept(+Annotations, -Annotations1)
+%
+%	Add candidate concepts.
+
+link_to_concepts([A0|As], [A|Rest]) :-
+	A0 = annotation(Value,Start,End,Entries),
+	A = annotation(Value,Start,End,Entries,Concepts),
+	reconcile(Value, 5, _, [], Hits),
+	maplist(hit_concept, Hits, Concepts),
+	link_to_concepts(As, Rest).
+
+hit_concept(hit(_,C,_,_), C).
+
+
 tag_matches([], _, _, _, []).
 tag_matches([A0|As], User, Process, Interval, [A|Rest]) :-
 	A0 = annotation(Value,Start,End,Entries),
diff --git a/applications/yaz_game.pl b/applications/yaz_game.pl
index e5ab5da..8a50fd1 100644
--- a/applications/yaz_game.pl
+++ b/applications/yaz_game.pl
@@ -32,15 +32,15 @@
 
 :- setting(match_interval, integer, 10000,
 	   'Interval in which tags can be matched (in miliseconds)').
-:- setting(max_player_count, integer, 8,
+:- setting(max_player_count, integer, 2,
 	   'Maximum number of players before auto starting a game').
 :- setting(request_interval, integer, 2000,
 	   'Interval between requests to the server (in miliseconds)').
 :- setting(video_buffer_time, integer, 1,
 	   'Expect delay in load of page and start of Video (in seconds)').
-:- setting(gamestart, oneof([all,creator,none,false]), creator,
+:- setting(gamestart, oneof([all,creator,none,false]), none,
 	   'Determines who can start the game').
-:- setting(garden, oneof([player,sgarden,mgarden,cgarden,false]), cgarden,
+:- setting(garden, oneof([player,sgarden,mgarden,cgarden,false]), mgarden,
 	   'Redirect to gardening when video is finished').
 
 :- dynamic
diff --git a/applications/yaz_mgarden.pl b/applications/yaz_mgarden.pl
index 60f4477..40cc997 100644
--- a/applications/yaz_mgarden.pl
+++ b/applications/yaz_mgarden.pl
@@ -22,6 +22,7 @@
 :- use_module(library(video_annotation)).
 :- use_module(library(rdf_history)).
 :- use_module(library(tag_match)).
+:- use_module(api(reconcile)).
 
 :- use_module(components(label)).
 :- use_module(components(yaz_page)).
@@ -44,7 +45,8 @@ http_yaz_mgarden(Request) :-
 	user_property(User0, url(CurrentUser)),
 	http_parameters(Request,
 			[ video(Video,
-				[description('Current video')]),
+				[optional(true),
+				 description('Current video')]),
 			  process(Process,
 			       [optional(true), desription('When set only annotations within this process are shown')]),
 			  user(User,
@@ -54,49 +56,55 @@ http_yaz_mgarden(Request) :-
 	Options = [process(Process),
 		   user(User)
   		  ],
+	(   var(Video),
+	    var(Process)
+	->  fail % proper error
+	;   var(Video)
+	->  rdf(Process,opmv:used,Video)
+	;   true
+	),
 	create_user_process(CurrentUser, [rdf:type=pprime:'mgarden',
 				   opmv:used=Video
-				  ], _Process),
+				  ], _GardenProcess),
 	video_annotations(Video, As0, Options),
 	sort_by_arg(As0, 2, As),
-	tag_matches(As, User, Process, 10000, Annotations),
- 	html_video_page(Video, CurrentUser, Annotations, 0, Options).
+ 	tag_matches(As, User, Process, 10000, Annotations),
+ 	html_video_page(Process, Video, CurrentUser, Annotations, 0, Options).
+
 
 tag_matches([], _, _, _, []).
 tag_matches([A0|As], User, Process, Interval, [A|Rest]) :-
 	A0 = annotation(Value,Start,End,Entries),
-	A =  annotation(Value,Start,End,Entries,Match),
-	(   existing_match(Entries, Match)
-	->  true
-	;   tag_match(Value, Start, User, Process, Interval, Match)
-	->  true
-	;   Match = @false
-	),
-	tag_matches(As, User, Process, Interval, Rest).
+	A =  annotation(Value,Start,End,Entries,Score,Matches),
+	tag_score(Entries, Score),
+	findall(M,tag_match(Value, Start, User, Process, Interval, M),Ms),
+	sort(Ms, Matches),
+ 	tag_matches(As, User, Process, Interval, Rest).
 
-existing_match(Entries, Match) :-
+tag_score(Entries, Score) :-
 	findall(S, (member(i(E,_), Entries),
 		    rdf(E, pprime:score, literal(S))
 		   ),
 		Ss),
-	max_list(Ss, Score),
-	Match = match(exact, Score).
+	sumlist(Ss, Score).
 
 tag_match(literal(Tag), Time, User, Game, Interval, Match) :-
 	Match = match(Type, 0, E, literal(Tag1)),
-	snowball(english, Tag, Stem0),
+	snowball(dutch, Tag, Stem0),
 	downcase_atom(Stem0, Stem),
-	findall(Concept, find_tag_concept(Tag, Concept, _), Cs),
- 	tagentry_in_interval(Game, User, Time, Interval, E),
-	(   rdf(E, rdf:value, literal(Tag1)),
-	    snowball(english, Tag1, Stem1),
-	    downcase_atom(Stem1, Stem)
-	->  Type = stem
-	;   rdf(E, rdf:value, literal(Tag1)),
-	    member(C, Cs),
-	    find_tag_concept(Tag1, C1, _),
-	    tag_concept_match(C, C1, Type)
- 	).
+	reconcile(Tag, 5, Hits),
+  	tagentry_in_interval(Game, User, Tag, Time, Interval, E, Tag1),
+	match(Stem, Hits, Tag1, Type).
+
+match(Stem, _, Tag1, stem) :-
+	snowball(dutch, Tag1, Stem1),
+	downcase_atom(Stem1, Stem).
+match(_Stem, Hits, Tag1, Type) :-
+	Hits = [_|_],
+	reconcile(Tag1, 5, Hits1),
+	member(hit(_,C,_,_), Hits),
+	member(hit(_,C1,_,_), Hits1),
+	tag_concept_match(C, C1, Type).
 
 tag_concept_match(R, R, synonym) :- !.
 tag_concept_match(R1, R2, specific) :-
@@ -105,21 +113,34 @@ tag_concept_match(R1, R2, specific) :-
 tag_concept_match(R1, R2, generic) :-
  	rdf_reachable(R2, skos:broader, R1),
 	!.
+tag_concept_match(R1, R2, sibling) :-
+ 	rdf_reachable(R1, skos:broader, R, 2, _),
+	rdf_reachable(R2, skos:broader, R, 2, _),
+	!.
+tag_concept_match(R1, R2, related) :-
+ 	(   rdf(R2, skos:related, R1)
+	;   rdf(R1, skos:related, R2)
+	),
+	!.
 
-tagentry_in_interval(Game, User, Time, Interval, E) :-
+tagentry_in_interval(Game, User, Tag, Time, Interval, E, Tag1) :-
 	Start is Time-Interval,
 	End is Time+Interval,
   	rdf(E, pprime:videoPlayhead, literal(between(Start,End), _), Game),
-	\+ rdf(E, pprime:creator, User).
+	\+ rdf(E, pprime:creator, User),
+	rdf(E, rdf:value, literal(Tag1)),
+	\+ Tag == Tag1.
 
 
 
-%%	html_video_page(+Video, +User, +Annotations, +StartTime,
+
+
+%%	html_video_page(+Game, +Video, +User, +Annotations, +StartTime,
 %%	+Options)
 %
 %	Emit an HTML page with a video player and a tag carousel.
 
-html_video_page(Video, User, Annotations, StartTime, Options) :-
+html_video_page(Game, Video, User, Annotations, StartTime, Options) :-
 	reply_html_page(yaz,
 			[ title(['YAZ - ', Video])
 			],
@@ -127,7 +148,7 @@ html_video_page(Video, User, Annotations, StartTime, Options) :-
  			  div(class('video-results'),
 			      \html_video_page_containers(Video, Options)),
 			 script(type('text/javascript'),
-				\html_video_page_yui(Video, User, Annotations, StartTime, Options))
+				\html_video_page_yui(Game, Video, User, Annotations, StartTime, Options))
 			]).
 
 html_video_page_containers(Video, _Options) -->
@@ -140,7 +161,7 @@ html_video_page_containers(Video, _Options) -->
 		   ])
     	     ]).
 
-html_video_page_yui(Video, User, Annotations, StartTime, _Options) -->
+html_video_page_yui(Game, Video, User, Annotations, StartTime, _Options) -->
 	{ video_source(Video, Src),
  	  http_absolute_location(js('videoplayer/'), FilePath, []),
 	  http_absolute_location(js('videoplayer/videoplayer.js'), VideoPlayer, []),
@@ -170,24 +191,26 @@ html_video_page_yui(Video, User, Annotations, StartTime, _Options) -->
 			  'Y.mazzle.TagCarousel'({tags:JSONTags,
 						  height:480,
 						  width:200,
-						  confirm:true
+						  confirm:symbol(true),
+						  topIndent:symbol(false)
  						 })),
     		  'var oldTime;\n',
  		  \js_call('videoPlayer.render'('#videoplayer')),
 		  \js_call('tagCarousel.render'('#tagplayer')),
  		  \js_yui3_on(tagCarousel, itemSelect, \js_tag_select),
- 		  \js_yui3_on(tagCarousel, itemConfirm, \js_confirm(User)),
-		  \js_support_functions(User),
+ 		  \js_yui3_on(tagCarousel, itemConfirm, \js_confirm(Game, User)),
+		  \js_support_functions(Game, User),
 		  \js_call('Y.later'(RequestInterval, symbol('Y'),
 				     symbol(fetchData), symbol({}), symbol(true)))
    		]).
 
-js_support_functions(User) -->
+js_support_functions(Game, User) -->
 	{ http_location_by_id(http_yaz_api_mgarden_data, DataServer)
 	},
-	js_function_decl(fetchData, [],
+	js_function_decl(fetchData, [e],
 			 \[
-'   var data = {user:"',User,'"};
+'   var data = {user:"',User,'",
+	        game:"',Game,'"};
     Y.io("',DataServer,'", {data: data,
 			    on: {success:handleResponse}
 			   });\n'
@@ -203,27 +226,29 @@ js_tag_select -->
 		    \[
 '    if(e.tag.startTime)
      { var time = (e.tag.startTime/1000)-2;
-       videoPlayer.setTime(time, false);
+       videoPlayer.setTime(time, true);
      }\n'
 		    ]).
 
-js_confirm(User) -->
+js_confirm(Game, User) -->
 	{ http_location_by_id(http_yaz_api_confirm_match, ConfirmServer)
 	},
 	js_function([e],
 		    [
 '    var i = e.index,
-	 source =  e.annotation.annotations[0].uri,
-	 target = e.annotation.match.uri,
-	 match = e.annotation.match.type;\n',
+	 m = e.matchIndex,
+ 	 data = {source:e.annotation.annotations[0].uri,
+		 target:e.annotation.match[m].uri,
+		 action:e.action,
+		 match:e.annotation.match[m].type,
+		 game:"',Game,'",
+		 user:"',User,'"',
+		'};\n',
      \js_call('Y.io'(ConfirmServer, {
-				       data:{user:User,
-					     source:symbol(source),
-					     target:symbol(target),
-					     match:symbol(match)},
+				       data:symbol(data),
 				       on:{success:symbol('function(id, o)
 				             {var r = Y.JSON.parse(o.responseText);
-					      tagCarousel.scoreIndex(i,r.score,"confirm",r.confirm)
+					      tagCarousel.setConfirm(i,m,r.confirm,r.action)
 					     }')},
 				       context:symbol(tagCarousel)
 				      }))
@@ -239,6 +264,10 @@ http_yaz_api_confirm_match(Request) :-
  	http_parameters(Request,
 			[ user(User,
 			       [description('URL of the user')]),
+			  game(Game,
+			       [description('URL of the game')]),
+			  action(Action,
+				 [description('accept or reject')]),
 			  source(Source,
 				   [description('URL of source')]),
 			  target(Target,
@@ -248,53 +277,49 @@ http_yaz_api_confirm_match(Request) :-
 			]),
  	debug(yaz(update), 'confirm ~w match between ~w and ~w',
 	      [Match, Source, Target]),
-
-	(   confirmed(Match, Source, Target, User, Confirm)
-	->  match_score(Match, Score)
-	;   Score = 0,
-	    rdf_bnode(Confirm),
-	    rdfh_transaction((rdfh_assert(Confirm, pprime:match, literal(Match)),
-			      rdfh_assert(Confirm, pprime:creator, User),
-			      rdfh_assert(Confirm, pprime:matchSource, Source),
-			      rdfh_assert(Confirm, pprime:matchTarget, Target)))
+ 	(   confirmed(Match, Game, Action, Source, Target, Id),
+	    rdf(Id, pprime:creator, User)
+	->  true
+	;   rdf_bnode(Id),
+	    rdfh_transaction((rdfh_assert(Id, pprime:action, literal(Action)),
+			      rdfh_assert(Id, pprime:match, literal(Match)),
+			      rdfh_assert(Id, pprime:process, Game),
+			      rdfh_assert(Id, pprime:creator, User),
+			      rdfh_assert(Id, pprime:matchSource, Source),
+			      rdfh_assert(Id, pprime:matchTarget, Target)))
 	),
-
-  	reply_json(json([confirm=Confirm,
-			 score=Score
+  	reply_json(json([confirm=Id,
+ 			 action=Action
  			])).
 
 
-confirmed(specific, Source, Target, User, Confirm) :-
-	rdf(Confirm, pprime:match, literal(Match)),
-	\+ rdf(Confirm, pprime:creator, User),
-	(   Match = specific
-	->  confirmed_(Confirm, Source, Target)
-	;   Match = generic
-	->  confirmed_(Confirm, Target, Source)
+confirmed(specific, Game, Action, Source, Target, Id) :-
+	!,
+	(   confirmed_(generic, Game, Action, Source, Target, Id)
+	;   confirmed_(specific, Game, Action, Target, Source, Id)
 	).
-confirmed(generic, Source, Target, User, Confirm) :-
-	rdf(Confirm, pprime:match, literal(Match)),
-	\+ rdf(Confirm, pprime:creator, User),
-	(   Match = generic
-	->  confirmed_(Confirm, Source, Target)
-	;   Match = specific
-	->  confirmed_(Confirm, Target, Source)
+confirmed(generic, Game, Action, Source, Target, Id) :-
+	!,
+	(   confirmed_(generic, Game, Action, Source, Target, Id)
+	;   confirmed_(specific, Game, Action, Target, Source, Id)
 	).
-confirmed(Match, Source, Target, User, Confirm) :-
-	rdf(Confirm, pprime:match, literal(Match)),
-	\+ rdf(Confirm, pprime:creator, User),
-	(   confirmed_(Confirm, Source, Target)
-	;   confirmed_(Confirm, Target, Source)
+confirmed(Match, Game, Action, Source, Target, Id) :-
+ 	(   confirmed_(Match, Game, Action, Source, Target, Id)
+	;   confirmed_(Match, Game, Action, Target, Source, Id)
 	).
 
-confirmed_(Confirm, Source, Target) :-
-	rdf(Confirm, pprime:matchSource, Source),
-	rdf(Confirm, pprime:matchTarget, Target).
+confirmed_(Match, Game, Action, Source, Target, Id) :-
+	rdf(Id, pprime:process, Game),
+	rdf(Id, pprime:matchSource, Source),
+	rdf(Id, pprime:action, literal(Action)),
+	rdf(Id, pprime:match, literal(Match)),
+	rdf(Id, pprime:matchTarget, Target).
 
 match_score(stem, 75).
 match_score(synonym, 100).
 match_score(specific, 150).
 match_score(generic, 125).
+match_score(related, 75).
 
 
 %%	http_yaz_api_mgarden_data(+Request)
@@ -304,19 +329,18 @@ match_score(generic, 125).
 http_yaz_api_mgarden_data(Request) :-
 	http_parameters(Request,
 			[ user(User,
-			       [description('URL of the user')])
+			       [description('URL of the user')]),
+			  game(Game,
+			       [description('URL of the game')])
 			]),
 	current_user_process(Process),
-	Obj = json([confirm=Confirm, score=Score]),
-	findall(Obj, user_confirmed(User, Process, Confirm, Score), Confirmed),
+	Obj = json([confirm=Id, score=Score]),
+	findall(Obj, user_confirmed(User, Process, Game, Id, Score), Confirmed),
   	reply_json(Confirmed).
 
-user_confirmed(User, Process, Confirm, Score) :-
-	rdf(Confirm, pprime:creator, User, Process),
-	rdf(Confirm, pprime:match, literal(Match)),
-	rdf(Confirm, pprime:matchSource, Source),
-	rdf(Confirm, pprime:matchTarget, Target),
-	(   confirmed(Match, Source, Target, User, _)
-	->  match_score(Match, Score)
-	;   Score = 0
-	).
+user_confirmed(User, Process, Game, Id, Score) :-
+	rdf(Id, pprime:creator, User, Process),
+	confirmed_(Match, Game, Action, Source, Target, Id),
+	confirmed(Match, Game, Action, Source, Target, Id1),
+	\+ rdf(Id1, pprime:creator, User),
+	match_score(Match, Score).
diff --git a/applications/yaz_new_user.pl b/applications/yaz_new_user.pl
index 5d4e633..cd24a48 100644
--- a/applications/yaz_new_user.pl
+++ b/applications/yaz_new_user.pl
@@ -37,7 +37,8 @@ http_yaz_start(Request) :-
 		       message('name already exists')])
 	;   video(1, Video),
 	    http_link_to_id(http_yaz_game, [url(Video)], Game),
-	    user_add(User, [realname(User),allow([read(_,_),write(_,_)])]),
+	    password_hash(presto, PW),
+	    user_add(User, [realname(User),password(PW),allow([read(_,_),write(_,_)])]),
 	    login(User),
 	    throw(http_reply(moved_temporary(Game)))
 	).
diff --git a/lib/yaz_util.pl b/lib/yaz_util.pl
index 640840d..180aa8e 100644
--- a/lib/yaz_util.pl
+++ b/lib/yaz_util.pl
@@ -335,7 +335,7 @@ http:convert_parameter(jsonresource, Atom, Term) :-
     annotation(tag:_, count:number),
     annotation(tag:_, tags:list, count:number),
     annotation(tag:_, startTime:number, endTime:number, annotations:list),
-    annotation(tag:_, startTime:number, endTime:number, annotations:list, match:_),
+    annotation(tag:_, startTime:number, endTime:number, annotations:list, score:number, match:_),
     match(type:atom, score:_),
     match(type:atom, score:_, uri:_, value:_).
 
diff --git a/web/css/player.css b/web/css/player.css
index 2b64048..a401d41 100644
--- a/web/css/player.css
+++ b/web/css/player.css
@@ -31,8 +31,8 @@
 #tagplayer .yui3-tag-carousel {
 	background: transparent;
 	overflow: auto;
-}
-
+	border: 1px solid #CCC;
+}	
 /* tag carousel */
 .yui3-tag-carousel ul {
 	margin: 0;
@@ -45,15 +45,12 @@
 	padding: 4px 8px;
 	/*border-top: 1px solid #f2f2f2;*/
 }
+.yui3-tag-carousel li:nth-child(even) {
+	background-color: #EEE;
+}
 .yui3-tag-carousel li.focus .label {
     font-size: 125%;
 }
-.yui3-tag-carousel li.scored {
-    background-color: #EEE;
-	border: 2px solid #00DD00;
-	-moz-border-radius: 4px;
-	border-radius: 4px;
-}
 .yui3-tag-carousel li .hidden {
 	display: none;
 }
@@ -68,28 +65,57 @@
 
 .yui3-tag-carousel li .edit,
 .yui3-tag-carousel li .remove,
-.yui3-tag-carousel li .confirm,
 .yui3-tag-carousel li .score {
 	float: right;
 	color: #222;
-	background-color: #CCC;
-	padding: 1px 6px;
+	padding: 1px 3px 1px 2px;
+	border: 2px solid #999;
 	-moz-border-radius: 6px;
 	border-radius: 6px;
 	font-weight: bold;
+	font-size: 90%;
 }
 .yui3-tag-carousel li .edit a,
-.yui3-tag-carousel li .remove a,
-.yui3-tag-carousel li .confirm a {
+.yui3-tag-carousel li .remove a {
 	color: #222;
 }
 .yui3-tag-carousel li .edit a:hover,
-.yui3-tag-carousel li .remove a:hover,
-.yui3-tag-carousel li .confirm a:hover {
+.yui3-tag-carousel li .remove a:hover {
 	color: red;
 	text-decoration: none;
 }
-
+.yui3-tag-carousel li .match {
+	line-height: 16px;
+	clear: both;
+	overflow: auto;
+	color: #999;
+}
+.yui3-tag-carousel li .match .label {
+	padding: 0 0 2px 8px;
+}
+.yui3-tag-carousel li .match.accept {
+    color: green;
+}
+.yui3-tag-carousel li .match.reject {
+	color: red;
+}
+.yui3-tag-carousel li .match.reject .score {
+	border-color: red;
+}
+.yui3-tag-carousel li .match.accept .score {
+	border-color: green;
+}
+.yui3-tag-carousel li .confirm {
+	float: right;
+}
+.yui3-tag-carousel li .confirm .accept {
+	padding-left: 16px;
+	background-image: url('../icons/accept.png');
+}
+.yui3-tag-carousel li .confirm .reject {
+	padding-left: 16px;
+	background-image: url('../icons/cancel.png');
+}
 
 .yui3-tag-carousel li .label input {
 	width: 120px;
diff --git a/web/js/tagcarousel/tagcarousel.js b/web/js/tagcarousel/tagcarousel.js
index b60ddf3..7d447a7 100644
--- a/web/js/tagcarousel/tagcarousel.js
+++ b/web/js/tagcarousel/tagcarousel.js
@@ -104,11 +104,19 @@ YUI.add('tag-carousel', function(Y) {
 		bindUI : function() {
 			this.after("tagsChange", this.syncUI);
 			Y.delegate("click", this._itemSelect, this.listNode, "li .label", this);
-			Y.delegate("click", this._itemEdit, this.listNode, "li .edit", this);
-			Y.delegate("click", this._itemRemove, this.listNode, "li .remove", this);
-			Y.delegate("click", this._itemConfirm, this.listNode, "li .confirm", this);
-			Y.delegate("click", this._reconcileSelect, this.listNode, "li .recon-select", this);
-			Y.delegate("click", this._reconcileInfo, this.listNode, "li .recon-concept", this);
+			if(this.get("edit")) {
+				Y.delegate("click", this._itemEdit, this.listNode, "li .edit", this);
+			}
+			if(this.get("remove")) {
+				Y.delegate("click", this._itemRemove, this.listNode, "li .remove", this);
+			}
+			if(this.get("confirm")) {
+				Y.delegate("click", this._itemConfirm, this.listNode, "li .confirm", this);
+			}
+			if(this.get("reconcile")) {
+				Y.delegate("click", this._reconcileSelect, this.listNode, "li .recon-select", this);
+				Y.delegate("click", this._reconcileInfo, this.listNode, "li .recon-concept", this);
+			}
 			
 			this._scrollAnim = new Y.Anim({
 			    node: this.get("boundingBox"),
@@ -139,26 +147,17 @@ YUI.add('tag-carousel', function(Y) {
 		formatItem : function(item) { 
 			var tag = item.tag,
 				label = tag.label ? tag.label : tag.value,
-				match = item.match,
-				score = (match&&match.score) ? match.score : '',
-				html = "";
-
-			if(this.get("confirm")&&(match&&!match.score)) {
-				var sep = match.type=='specific' ? '<' : (match.type=='generic' ? '>' : '=');
-				html += '<div class="label match">'+label+" "+sep+" "+match.value.value+'</div>';
-				html += '<div class="confirm"><a href="javascript:{}">?</a></div>';
-			}
-			else {
-				html += '<div class="label">'+label+'</div>';				
-			}
+				score = item.score,
+				match = item.match;
 
+			html = '<div class="label">'+label+'</div>';				
 			if(item.count) {
 				html += '<div class="count">'+item.count+'</div>';
 			}
-			if(this.get("edit")&&!(match&&match.score)) {
+			if(this.get("edit")&&!score) {
 				html += '<div class="edit"><a href="javascript:{}">e</a></div>';
 			}
-			if(this.get("remove")) {
+			if(this.get("remove")&&!score) {
 				html += '<div class="remove"><a href="javascript:{}">x</a></div>';
 			}
 			if(score) {
@@ -166,6 +165,19 @@ YUI.add('tag-carousel', function(Y) {
 			} else {
 				html += '<div class="score hidden"></div>';
 			}
+			if(this.get("confirm")&&(match&&Lang.isArray(match))) {
+				for (var i=0; i < match.length; i++) {
+					var matchItem = match[i];
+					html += '<div class="match">'
+						+'<div class="label" title="'+matchItem.type+'">'+matchItem.value.value+'</div>'
+						+'<div class="score hidden"></div>'
+						+'<div class="confirm" title="'+i+'">'
+						+'<a class="accept" href="javascript:{}"></a>'
+						+'<a class="reject" href="javascript:{}"></a>'
+						+'</div></div>';
+				}
+			}			
+			
 			if(item.uri) {
 				return '<a href="javascript:{}">'+html+'</a>';
 			} else {
@@ -242,14 +254,18 @@ YUI.add('tag-carousel', function(Y) {
 		},
 		
 		_itemConfirm : function(e) {
-			var node = e.currentTarget.get("parentNode"),
+			var action = e.target.hasClass("accept") ? "accept" : "reject",
+				node = e.currentTarget.get("parentNode").get("parentNode"),
+				matchIndex = e.currentTarget.get("title"),
 				index = e.container.all("li").indexOf(node),
 				item = this.get("tags")[index],
 				arg = {
+					action:action,
 					index:index,
+					matchIndex:matchIndex,
 					annotation:item
 				};
-			Y.log('confirm match '+item.tag.value+' at index '+index);
+			Y.log(action+' match '+item.tag.value+' at index '+index);
         	this.fire("itemConfirm", arg);
 		},
 
@@ -319,7 +335,7 @@ YUI.add('tag-carousel', function(Y) {
 			}
 		},
 		
-		scoreIndex : function(index, score, type, id) {
+		scoreIndex : function(index, score, id, action) {
 			var item = this.listNode.all("li").item(index),
 				el;
 			if(id) {
@@ -331,7 +347,7 @@ YUI.add('tag-carousel', function(Y) {
 				el = '.confirm';
 			}
 			if(el) {
-				item.addClass('scored');
+				item.addClass('scored '+action);
 				item.one(el).addClass("hidden");
 				if(score>0) {
 					item.one('.score')
@@ -385,13 +401,24 @@ YUI.add('tag-carousel', function(Y) {
 			}
 		},
 		
+		setConfirm : function(index, matchIndex, id, action) {
+			var match = this.listNode.all("li").item(index)
+					.all('.match').item(matchIndex);
+			
+			this.scores[id] = match;	
+			match.addClass(action);
+			match.one('.confirm').addClass("hidden");
+		},
+		
 		updateMatch : function(matches) {
 			for(var i=0; i < matches.length; i++) {
 				var match = matches[i];
 				if(match.score) {
 					var node = this.scores[match.confirm];
 					if(node) {
-						node.one('.score').setContent(match.score);
+						node.one('.score')
+							.setContent(match.score)
+							.removeClass("hidden");
 					}
 				}
 			}